第16章 Spring数据访问之扩展篇(一)

第16章 Spring数据访问之扩展篇

本章内容

  • 活用模板方法模式及Callback
  • 数据访问中的多数据源
  • Spring 3.0展望

16.1 活用模板方法模式及Callback

纵观第13章到第15章的内容,我们会发现不管是Spring对JDBC API的抽象,还是对Hibernate、iBATIS等ORM的集成,全部都采用了一种理念或者处理方式,那就是模板方法模式与相应的Callback接口相结合。

那么,为什么要在这里使用模板方法与Callback相结合的问题处理方式呢?最基本的原因是:不管是JDBC还是Hibemate或者其他ORM实现,在资源管理上有一个共性,那就是需要在资源使用之后可以安全地释放这些资源。与Bitter Java所提出的理念相同,为了确保尽可能地将资源的获取和资源的释放操作放在一起,Spring在数据访问层处理资源的问题上,采用了模板方法模式

这样,以一种统一而集中的方式来处理资源的获取和释放,避免了将这种容易出现问题的操作分散到代码中的各个地方,进而也就避免了由此产生的“资源泄漏”一类比较严重的问题。

推而广之,我们可以以相同的模式来处理类似的问题,而也会发现,这样的处理与我们之前的处理或者封装方式是如此的不同,如此的简洁明了。

16.1.1 FTPClientTemplate

之前我们说过,通常的FX系统会从相应的新闻提供商那里定期获取外汇交易相关新闻。最常见的方式就是,通过FTP协议到指定的FTP服务器去定期下载相应的新闻文件,所以,FX系统的应用程序需要提供相应的实现类来进行FTP操作。而程序中的FTP操作应该是比较通用的,无非就是上传下载文件。

为了程序能有一个良好的结构,我们通常会将这些FTP操作逻辑封装为一个工具类。而下面我们将看到的,就是两种截然不同的工具类实现方式。

我们不需要为最为底层的FTP操作“重新发明轮子”,Jakarta Commons Net类库提供了基本的FTP支持,不过,直接使用CommonsNet的API就与直接使用JDBC API一样让人尴尬,下方代码清单的代码演示了直接使用Commons Net API进行FTP操作的一般情况。

boolean error = false;
try {
    
    
  int reply;
  ftp.connect("ftp.foobar.com");
  System.out.println("Connected to " + server + ".");
  System.out.print(ftp.getReplyString());
  
  // After connection attempt. you should check the reply code to verify success
  reply = ftp.getReplyCode();
  
  if (!FTPReply.isPositiveCompletion(reply)) {
    
    
    ftp.disconnect();
    System.err.println("FTP server refused connection.");
    System.exit(1);
  }
  ... // transfer files
  ftp.logout();
} catch(IOException e) {
    
    
  error = true;
  e.printStackTrace();
} finnaly {
    
    
  if (ftp.isConnected()) {
    
    
    try {
    
    
      ftp.disconnect();
    } catch (IOException ioe) {
    
    
      // do nothing
    }
  }
  System.exit(error ? 1 : 0);
}

我得承认,FTPClient类提供的这段代码只是一段示例,不能在实际的生产环境下使用,所以,我们尝试对其进行封装。

对于使用FTPClient类实现的FTP操作来说,无非就是登录FTP服务器,传输文件,然后退出服务器三步。下方代码清单所示的FTP操作工具类是最为常见的实现方式。

class Phase1FtpUtility {
    
    
	public boolean login(...) {
    
    
		//登录代码
	}
	public void doTransfer(...) {
    
    
		//文件传输逻辑实现.
	}
	public boolean logout() {
    
    
		//退出登录
	}
}

相对于示例中的代码来说,通过PhaselFtpUtility类的封装,现在进行FTP操作看起来要简洁多了。不过,这样的封装方式并没有起到多少实际效果。

Phase1FtpUtility对FTPClientAPI的封装力度不够,与直接使用FTPClient的API相比,调用方只是少写几行代码而已,如下所示:

Phase1Ftputility ftpUtility = ...;

if (ftpUtility.login(...)) {
    
    
  ftpUtility.doTransfer(...);
}

ftpUtility.logout();

而且,这样的代码把资源的管理留给了每处调用Phase1FtpUtility进行FTP操作的调用代码。与数据库连接一样,你要怎样保证相应的资源在每处都能成功地获得释放呢?就现有的API封装方式,我们只能加强开发人员的约束力来达到正确使用API的目的了。

通常,Phase1FtpUtility的doTransfer(..)方法用来实现具体的FTP操作逻辑,但现在的phase1FtpUtility只能提供固定的FTP操作逻辑。如果其他调用方需要不同的FTP操作,那么,或许就得子类化Phase1FtpUtility并覆写doTransfer(..)方法了。不过,这样好像偏离了我们要将phaselFtpUtility作为单一工具类来使用的初衷。

鉴于这些限制,我们需要另一种FTPClientAPI的封装方式。而你也看出来了,就如Phase1FtpUtility所展示的那样,所有的使用FTPClient进行FTP操作的步骤几乎是一样的,唯一的不同就是每次进行FTP操作的细节。在经过JdbcTemplate、HibernateTemplate以及SqlMapClientTemplate等熏陶之后,自然而然就应该想到,我们可以对FTPClientAPI进行同样的处理,从而下方代码清单所给出的FTPClientTemplate就是我们的最终需要。

public class FTPClientTemplate {
    
    
    private static final Log logger = LogFactory.getLog(FTPClientTemplate.class);
    private FTPClientConfig ftpClientConfig; //可选属性

    private String server; //必须指定
    private String username; //必须指定
    private String password; //必须指定
    private int port = 21; //必须指定

    public FTPClientTemplate(String host, String username, String password) {
    
    
        this.server = host;
        this.username = username;
        this.password = password;
    }

    public void execute(FTPClientCallback callback) throws IOException {
    
    
        FTPClient ftp = new FTPClient();
        try {
    
    
            if (this.getFtpClientConfig() != null) {
    
    
                ftp.configure(this.getFtpClientConfigl));
            }
            ftp.connect(server, getPort());
            // 检查到服务器的连接是否正确
            int reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
    
    
                throw new IOException("failed to connect to the FTP Server:" + server);
            }
            // 登录
            boolean isLoginSuc = ftp.login(this.getUsername(), this.getPassword());
            if (!isLoginSuc) {
    
    
                throw newIOException("wrong username or password,please try to login again.");
            }
            // 通过回调方法实现特定的FTP操作
            callback.processFTPRequest(ftp);
            // 退出登录
            ftp.logout();
        } finally {
    
    
            if (ftp.isConnected()) {
    
    
                ftp.disconnect();
            }
        }
    }

    public String[] listFileNames(final String remoteDir, final String fileNamePattern) throws IOException {
    
    
        final List<String[]> container = new ArrayList<String[]>();
        execute(new FTPClientCallback() {
    
    
            public void processFTPRequest(FTPClient ftp) throws IOException {
    
    
                ftp.enterLocalPassiveMode();
                changeWorkingDir(ftp, remoteDir);

                if (logger.isDebugEnabled())
                    logger.đebug("working dir:" + ftp.printWorkingDirectory());
            }
        });
        return container.get(0);
    }

    protected void changeWorkingDir(FTPClient ftp, String remoteDir) throws IOException {
    
    
        Validate.notEmpty(remoteDir);
        ftp.changeWorkingDirectory(remoteDir);
    }
    
    //setter和getter方法定义
}

我们通过execute(FTPClientCallback)方法对整个的基于FTPClient的API使用流程进行了封装,而将我们真正关心的每次具体的FTP操作交给了FTPClientCallback,该接口定义如下所示:

public interface FTPClientCallback {
    
    
  public void processFTPRequest(FTPClientftpClient) throws IOException;
}

现在要做的,就是根据每次FTP操作请求细节提供相应的FTPClientCallback实现,然后交给FTPClientTemplate执行,如下所示:

FTPClientTemplate ftpTemplate = new FTPClientTemplate(host, user, pwd);
FTPClientCallback callback = new FTPClientCallback() {
    
    
	public void processFTPRequest(FTPClientftpClient) throws IOException {
    
    
		...// 特定的FTP操作实现逻辑    
  }
};
ftpTemplate.excute(callback);

FTPClientTemplate一旦构建完成,其他任何调用方都可以共享使用它,调用方每次提供自己的FTPClientCallback实现即可。

现在的FTPClientTemplate看起来可能过于单薄。某些常用的FTP操作,如文件上传、文件下载、文件列表读取等,我们可以在FTPClientTemplate内直接提供,而没有必要让调用方每次实现几乎相同的代码。

listFileNames(remoteDir, fileNamePattern)方法就是这样的方法实现,无非就是提供了相应的FTPClientCallback实现,然后最终委托execute()方法执行而已。

作为工具类,我们可以直接将这些常用的FTP操作方法定义到FTPClientTemplate中。如果愿意,也可以设计一个FTPOperation之类的接口,里面定义一系列的FTP操作方法,然后让FTPClientTemplate来实现该接口。

16.1.2 HttpClientTemplate

现在基于REST方式的Web服务好像比原来SOAP的方式更加受人欢迎一些,世界上许多券商或者银行通常也会以REST的方式发送一些外汇牌价之类的信息,甚至,FX系统的某些外汇新闻提供商也通过HTTP协议采用类似于REST的方式来发送新闻。那么,与基于FTP协议的信息交换类似,对于这种方式的信息交换,我们也需要在应用程序中采用适当的API进行处理。

Apache Commons HttpClient是一个提供HTTP协议支持的Java类库,许多应用包括稍后我们将提到的Spring Remoting都是采用该类库实现的。我们同样可以使用该类库进行基于HTTP协议的信息交换,或者说得更“时髦”一点儿,进行REST方式的Web服务开发。

如果你是初次接触HttpClient,那么应该先看一下HtpClient网站提供的Tutorial文档,里面给出了类似下方代码清单给出的使用代码示例。

public class HttpClientTutorial {
    
    
    private static String url = "http://www.apache.org/";

    public static void main(String[] args) {
    
    
        // Create an instance of HttpClient.
        HttpClient client = new HttpClient();

        // Create a method instance.
        GetMethod method = new GetMethod(url);
        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams, RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(3, false));

        try {
    
    
            // Execute the method.
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStacus.SC_OK) {
    
    
                System.err.println("Method failed: " + methoa.getStatusLine());
            }

            // Read the response body.
            byte[] responseBody = method.getResponseBody();

            // Deal with the response.
            // Use caution: ensure correct character encoding and is not binary data
            System.out.println(new String(responseBody));
        } catch (HttpException e) {
    
    
            System.err.println("Fatal protocol violation: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
    
    
            System.err.println("Fatal transport error: " + e.getMessage());
            e.printStackTrace();
        } finally {
    
    
            // Release the connection.
            method.releaseConnection();
        }
    }
}

又是获取资源、操作、释放资源、处理异常等,而且这样的代码无法用于生产环境也是肯定的了,那该怎么处理,我想你已经心里有数了吧?余下的部分,还是留给你来实现吧。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/124891943
今日推荐