話回正轉(zhuǎn),這兩天在讀spring的jdbc模板,對Spring源碼的精妙真是佩服得五體投地,極為經(jīng)典。 spring中真是集設(shè)計(jì)模式之大成,而且用得是爐火純青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無疑使用了模板模式,但spring并不是單純使用了模板方法,而是在此基礎(chǔ)上做了創(chuàng)新,配合callback(回調(diào))一起使用,用得極其靈活。
OK,為了防止文章再被拍磚,我寫得更詳細(xì)點(diǎn)吧,我們首先來回顧一下模板模式: 所謂模板板式,就是在父類中定義算法的主要流程,而把一些個(gè)性化的步驟延遲到子類中去實(shí)現(xiàn),父類始終控制著整個(gè)流程的主動(dòng)權(quán),子類只是輔助父類實(shí)現(xiàn)某些可定制的步驟。
有些抽象??? 好吧,我們用代碼來說話吧: 首先,父類要是個(gè)抽象類:
- public abstract class TemplatePattern {
-
- //模板方法
- public final void templateMethod(){
-
- method1();
- method2();//勾子方法
- method3();//抽象方法
- }
- private void method1(){
- System.out.println("父類實(shí)現(xiàn)業(yè)務(wù)邏輯");
- }
- public void method2(){
- System.out.println("父類默認(rèn)實(shí)現(xiàn),子類可覆蓋");
- }
- protected abstract void method3();//子類負(fù)責(zé)實(shí)現(xiàn)業(yè)務(wù)邏輯
- }
父類中有三個(gè)方法,分別是method1(),method2()和method3()。 method1()是私有方法,有且只能由父類實(shí)現(xiàn)邏輯,由于方法是private的,所以只能父類調(diào)用。 method2()是所謂的勾子方法。父類提供默認(rèn)實(shí)現(xiàn),如果子類覺得有必要定制,則可以覆蓋父類的默認(rèn)實(shí)現(xiàn)。 method3()是子類必須實(shí)現(xiàn)的方法,即制定的步驟。 由此可看出,算法的流程執(zhí)行順序是由父類掌控的,子類只能配合。
下面我們來寫第一個(gè)子類:
- public class TemplatePatternImpl extends TemplatePattern {
-
- @Override
- protected void method3() {
- System.out.println("method3()在子類TemplatePatternImpl中實(shí)現(xiàn)了?。?);
-
- }
-
- }
這個(gè)子類只覆蓋了必須覆蓋的方法,我們來測試一下:
- TemplatePattern t1 = new TemplatePatternImpl();
- t1.templateMethod();
在控制臺(tái)中我們可以看到:
- 父類實(shí)現(xiàn)業(yè)務(wù)邏輯
- 父類默認(rèn)實(shí)現(xiàn),子類可覆蓋
- method3()在子類TemplatePatternImpl中實(shí)現(xiàn)了??!
OK,我們來看看勾子方法的使用: 定義第2個(gè)子類,實(shí)現(xiàn)勾子方法:
- public class TemplatePatternImpl2 extends TemplatePattern {
-
- @Override
- protected void method3() {
- System.out.println("method3()在子類TemplatePatternImpl2中實(shí)現(xiàn)了!!");
-
- }
-
- /* (non-Javadoc)
- * @see com.jak.pattern.template.example.TemplatePattern#method2()
- */
- @Override
- public void method2() {
- System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法?。?);
- }
-
- }
來測試一下:
- TemplatePattern t2 = new TemplatePatternImpl2();
- t2.templateMethod();
我們看控制臺(tái):
- 父類實(shí)現(xiàn)業(yè)務(wù)邏輯
- 子類TemplatePatternImpl2覆蓋了父類的method2()方法??!
- method3()在子類TemplatePatternImpl2中實(shí)現(xiàn)了!!
OK,經(jīng)典的模板模式回顧完了(大家不要拍磚哦~~~~~~~~~~)
接下來,我們回到正題,自己模仿spring動(dòng)手寫一個(gè)基于模板模式和回調(diào)的jdbcTemplate。
回顧一下,spring為什么要封裝JDBC API,對外提供jdbcTemplate呢(不要仍雞蛋啊¥·%¥#%) 話說SUN的JDBC API也算是經(jīng)典了,曾經(jīng)在某個(gè)年代折服了一批人。但隨著歷史的發(fā)展,純粹的JDBC API已經(jīng)過于底層,而且不易控制,由開發(fā)人員直接接觸JDBC API,會(huì)造成不可預(yù)知的風(fēng)險(xiǎn)。還有,數(shù)據(jù)連接緩存池的發(fā)展,也不可能讓開發(fā)人員去手工獲取JDBC了。
好了,我們來看一段曾經(jīng)堪稱經(jīng)典的JDBC API代碼吧:
- public List<User> query() {
-
- List<User> userList = new ArrayList<User>();
- String sql = "select * from User";
-
- Connection con = null;
- PreparedStatement pst = null;
- ResultSet rs = null;
- try {
- con = HsqldbUtil.getConnection();
- pst = con.prepareStatement(sql);
- rs = pst.executeQuery();
-
- User user = null;
- while (rs.next()) {
-
- user = new User();
- user.setId(rs.getInt("id"));
- user.setUserName(rs.getString("user_name"));
- user.setBirth(rs.getDate("birth"));
- user.setCreateDate(rs.getDate("create_date"));
- userList.add(user);
- }
-
-
- } catch (SQLException e) {
- e.printStackTrace();
- }finally{
- if(rs != null){
- try {
- rs.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- try {
- pst.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- try {
- if(!con.isClosed()){
- try {
- con.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- }
- return userList;
- }
上面的代碼要若干年前可能是一段十分經(jīng)典的,還可能被作為example被推廣。但時(shí)過境遷,倘若哪位程序員現(xiàn)在再在自己的程序中出現(xiàn)以上代碼,不是說明該公司的開發(fā)框架管理混亂,就說明這位程序員水平太“高”了。 我們試想,一個(gè)簡單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來梳理一下: 1、獲取connection 2、獲取statement 3、獲取resultset 4、遍歷resultset并封裝成集合 5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常 6、..... 啊~~~~ 我快要暈了,在面向?qū)ο缶幊痰哪甏?,這樣的代碼簡直不能上人容忍。試想,上面我們只是做了一張表的查詢,如果我們要做第2張表,第3張表呢,又是一堆重復(fù)的代碼: 1、獲取connection 2、獲取statement 3、獲取resultset 4、遍歷resultset并封裝成集合 5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常 6、.....
這時(shí)候,使用模板模式的時(shí)機(jī)到了?。?!
通過觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是可定制的,因?yàn)槊繌埍矶加成洳煌膉ava bean。這部分代碼是沒有辦法復(fù)用的,只能定制。那就讓我們用一個(gè)抽象的父類把它們封裝一下吧:
- public abstract class JdbcTemplate {
-
- //template method
- public final Object execute(String sql) throws SQLException{
-
- Connection con = HsqldbUtil.getConnection();
- Statement stmt = null;
- try {
-
- stmt = con.createStatement();
- ResultSet rs = stmt.executeQuery(sql);
- Object result = doInStatement(rs);//abstract method
- return result;
- }
- catch (SQLException ex) {
- ex.printStackTrace();
- throw ex;
- }
- finally {
-
- try {
- stmt.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- try {
- if(!con.isClosed()){
- try {
- con.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- }
- }
-
- //implements in subclass
- protected abstract Object doInStatement(ResultSet rs);
- }
在上面這個(gè)抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負(fù)責(zé)實(shí)現(xiàn)。 好,我們來定義一個(gè)子類,并繼承上面的父類:
- public class JdbcTemplateUserImpl extends JdbcTemplate {
-
- @Override
- protected Object doInStatement(ResultSet rs) {
- List<User> userList = new ArrayList<User>();
-
- try {
- User user = null;
- while (rs.next()) {
-
- user = new User();
- user.setId(rs.getInt("id"));
- user.setUserName(rs.getString("user_name"));
- user.setBirth(rs.getDate("birth"));
- user.setCreateDate(rs.getDate("create_date"));
- userList.add(user);
- }
- return userList;
- } catch (SQLException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- }
由代碼可見,我們在doInStatement()方法中,對ResultSet進(jìn)行了遍歷,最后并返回。 有人可能要問:我如何獲取ResultSet 并傳給doInStatement()方法????呵呵,問這個(gè)問題的大多是新手。因?yàn)榇朔椒ú皇怯勺宇愓{(diào)用的,而是由父類調(diào)用,并把ResultSet傳遞給子類的。我們來看一下測試代碼:
- String sql = "select * from User";
- JdbcTemplate jt = new JdbcTemplateUserImpl();
- List<User> userList = (List<User>) jt.execute(sql);
就是這么簡單!!
文章至此仿佛告一段落,莫急!不防讓我們更深入一些...
試想,如果我每次用jdbcTemplate時(shí),都要繼承一下上面的父類,是不是有些不方面呢? 那就讓我們甩掉abstract這頂帽子吧,這時(shí),就該callback(回調(diào))上場了
所謂回調(diào),就是方法參數(shù)中傳遞一個(gè)接口,父類在調(diào)用此方法時(shí),必須調(diào)用方法中傳遞的接口的實(shí)現(xiàn)類。
那我們就來把上面的代碼改造一下,改用回調(diào)實(shí)現(xiàn)吧:
首先,我們來定義一個(gè)回調(diào)接口:
- public interface StatementCallback {
- Object doInStatement(Statement stmt) throws SQLException;
- }
這時(shí)候,我們就要方法的簽名改一下了:
- private final Object execute(StatementCallback action) throws SQLException
里面的獲取數(shù)據(jù)方式也要做如下修改:
- Object result = action.doInStatement(stmt);//abstract method
為了看著順眼,我們來給他封裝一層吧:
- public Object query(StatementCallback stmt) throws SQLException{
- return execute(stmt);
- }
OK,大功告成! 我們來寫一個(gè)測試類Test.java測試一下吧: 這時(shí)候,訪問有兩種方式,一種是內(nèi)部類的方式,一種是匿名方式。
先來看看內(nèi)部類的方式:
- //內(nèi)部類方式
- public Object query(final String sql) throws SQLException {
- class QueryStatementCallback implements StatementCallback {
-
- public Object doInStatement(Statement stmt) throws SQLException {
- ResultSet rs = stmt.executeQuery(sql);
- List<User> userList = new ArrayList<User>();
-
- User user = null;
- while (rs.next()) {
-
- user = new User();
- user.setId(rs.getInt("id"));
- user.setUserName(rs.getString("user_name"));
- user.setBirth(rs.getDate("birth"));
- user.setCreateDate(rs.getDate("create_date"));
- userList.add(user);
- }
- return userList;
-
- }
-
- }
-
- JdbcTemplate jt = new JdbcTemplate();
- return jt.query(new QueryStatementCallback());
- }
在調(diào)用jdbcTemplate.query()方法時(shí),傳一個(gè)StatementCallBack()的實(shí)例過去,也就是我們的內(nèi)部類。
再來看看匿名方式:
- //匿名類方式
- public Object query2(final String sql) throws Exception{
-
- JdbcTemplate jt = new JdbcTemplate();
- return jt.query(new StatementCallback() {
-
- public Object doInStatement(Statement stmt) throws SQLException {
- ResultSet rs = stmt.executeQuery(sql);
- List<User> userList = new ArrayList<User>();
-
- User user = null;
- while (rs.next()) {
-
- user = new User();
- user.setId(rs.getInt("id"));
- user.setUserName(rs.getString("user_name"));
- user.setBirth(rs.getDate("birth"));
- user.setCreateDate(rs.getDate("create_date"));
- userList.add(user);
- }
- return userList;
-
- }
- });
-
- }
相比之下,這種方法更為簡潔。 為什么spring不用傳統(tǒng)的模板方法,而加之以Callback進(jìn)行配合呢? 試想,如果父類中有10個(gè)抽象方法,而繼承它的所有子類則要將這10個(gè)抽象方法全部實(shí)現(xiàn),子類顯得非常臃腫。而有時(shí)候某個(gè)子類只需要定制父類中的某一個(gè)方法該怎么辦呢?這個(gè)時(shí)候就要用到Callback回調(diào)了。
離spring jdbcTemplate再近一點(diǎn) 上面這種方式基本上實(shí)現(xiàn)了模板方法+回調(diào)模式。但離spring的jdbcTemplate還有些距離。 我們可以再深入一些。。。
我們上面雖然實(shí)現(xiàn)了模板方法+回調(diào)模式,但相對于Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 RowMapper接口負(fù)責(zé)處理某一行的數(shù)據(jù),例如,我們可以在mapRow方法里對某一行記錄進(jìn)行操作,或封裝成entity。 ResultSetExtractor是數(shù)據(jù)集抽取器,負(fù)責(zé)遍歷ResultSet并根據(jù)RowMapper里的規(guī)則對數(shù)據(jù)進(jìn)行處理。 RowMapper和ResultSetExtractor區(qū)別是,RowMapper是處理某一行數(shù)據(jù),返回一個(gè)實(shí)體對象。而ResultSetExtractor是處理一個(gè)數(shù)據(jù)集合,返回一個(gè)對象集合。
當(dāng)然,上面所述僅僅是Spring JdbcTemplte實(shí)現(xiàn)的基本原理,Spring JdbcTemplate內(nèi)部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內(nèi),以及采用JdbcAccessor來管理DataSource和轉(zhuǎn)換異常等。
|