轉載:http://www.iteye.com/topic/713770?1306420721
最近一直在研讀spring源碼和學習設計模式,想把自己的一些領悟與大家分享,前幾天發了幾篇簡單的文章,可能由於文字過於簡單,幾次被評為新手貼,心中滴汗啊 沒辦法,工作太忙,大家都知道,寫篇文章是要很大精力地~~~~~
今天恰有時間,把這兩天的學習所得與大家分享,盡量寫得詳細一些,專家饒路走,新手覺得好贊一下(不要拍磚哦~~~~)。
文章源碼在附件中
注:本文目的意不在“重復發明輪子”,而是借此文來探討Spring JdbcTemplate的內部實現原理,掌握其運用精妙之處,以便在以后的“造輪運動”中能靈活運用。
話回正轉,這兩天在讀spring的jdbc模板,對Spring源碼的精妙真是佩服得五體投地,極為經典。
spring中真是集設計模式之大成,而且用得是爐火純青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無疑使用了模板模式,但spring並不是單純使用了模板方法,而是在此基礎上做了創新,配合callback(回調)一起使用,用得極其靈活。
OK,為了防止文章再被拍磚,我寫得更詳細點吧,我們首先來回顧一下模板模式:
所謂模板板式,就是在父類中定義算法的主要流程,而把一些個性化的步驟延遲到子類中去實現,父類始終控制着整個流程的主動權,子類只是輔助父類實現某些可定制的步驟。
有些抽象???
好吧,我們用代碼來說話吧:
首先,父類要是個抽象類:
1 public abstract class TemplatePattern { 2 3 //模板方法 4 public final void templateMethod(){ 5 6 method1(); 7 method2();//勾子方法 8 method3();//抽象方法 9 } 10 private void method1(){ 11 System.out.println("父類實現業務邏輯"); 12 } 13 public void method2(){ 14 System.out.println("父類默認實現,子類可覆蓋"); 15 } 16 protected abstract void method3();//子類負責實現業務邏輯 17 }
父類中有三個方法,分別是method1(),method2()和method3()。
method1()是私有方法,有且只能由父類實現邏輯,由於方法是private的,所以只能父類調用。
method2()是所謂的勾子方法。父類提供默認實現,如果子類覺得有必要定制,則可以覆蓋父類的默認實現。
method3()是子類必須實現的方法,即制定的步驟。
由此可看出,算法的流程執行順序是由父類掌控的,子類只能配合。
下面我們來寫第一個子類:
1 public class TemplatePatternImpl extends TemplatePattern { 2 3 @Override 4 protected void method3() { 5 System.out.println("method3()在子類TemplatePatternImpl中實現了!!"); 6 7 } 8 9 }
這個子類只覆蓋了必須覆蓋的方法,我們來測試一下:
1 TemplatePattern t1 = new TemplatePatternImpl(); 2 t1.templateMethod();
在控制台中我們可以看到:
1 父類實現業務邏輯 2 父類默認實現,子類可覆蓋 3 method3()在子類TemplatePatternImpl中實現了!!
OK,我們來看看勾子方法的使用:
定義第2個子類,實現勾子方法:
1 public class TemplatePatternImpl2 extends TemplatePattern { 2 3 @Override 4 protected void method3() { 5 System.out.println("method3()在子類TemplatePatternImpl2中實現了!!"); 6 7 } 8 9 /* (non-Javadoc) 10 * @see com.jak.pattern.template.example.TemplatePattern#method2() 11 */ 12 @Override 13 public void method2() { 14 System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法!!"); 15 } 16 17 }
1 public class TemplatePatternImpl2 extends TemplatePattern { 2 3 @Override 4 protected void method3() { 5 System.out.println("method3()在子類TemplatePatternImpl2中實現了!!"); 6 7 } 8 9 /* (non-Javadoc) 10 * @see com.jak.pattern.template.example.TemplatePattern#method2() 11 */ 12 @Override 13 public void method2() { 14 System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法!!"); 15 } 16 17 }
來測試一下:
1 TemplatePattern t2 = new TemplatePatternImpl2(); 2 t2.templateMethod();
我們看控制台:
父類實現業務邏輯
子類TemplatePatternImpl2覆蓋了父類的method2()方法!!
method3()在子類TemplatePatternImpl2中實現了!!
OK,經典的模板模式回顧完了(大家不要拍磚哦~~~~~~~~~~)
接下來,我們回到正題,自己模仿spring動手寫一個基於模板模式和回調的jdbcTemplate。
回顧一下,spring為什么要封裝JDBC API,對外提供jdbcTemplate呢(不要仍雞蛋啊¥·%¥#%)
話說SUN的JDBC API也算是經典了,曾經在某個年代折服了一批人。但隨着歷史的發展,純粹的JDBC API已經過於底層,而且不易控制,由開發人員直接接觸JDBC API,會造成不可預知的風險。還有,數據連接緩存池的發展,也不可能讓開發人員去手工獲取JDBC了。
好了,我們來看一段曾經堪稱經典的JDBC API代碼吧:
1 public List<User> query() { 2 3 List<User> userList = new ArrayList<User>(); 4 String sql = "select * from User"; 5 6 Connection con = null; 7 PreparedStatement pst = null; 8 ResultSet rs = null; 9 try { 10 con = HsqldbUtil.getConnection(); 11 pst = con.prepareStatement(sql); 12 rs = pst.executeQuery(); 13 14 User user = null; 15 while (rs.next()) { 16 17 user = new User(); 18 user.setId(rs.getInt("id")); 19 user.setUserName(rs.getString("user_name")); 20 user.setBirth(rs.getDate("birth")); 21 user.setCreateDate(rs.getDate("create_date")); 22 userList.add(user); 23 } 24 25 26 } catch (SQLException e) { 27 e.printStackTrace(); 28 }finally{ 29 if(rs != null){ 30 try { 31 rs.close(); 32 } catch (SQLException e) { 33 e.printStackTrace(); 34 } 35 } 36 try { 37 pst.close(); 38 } catch (SQLException e) { 39 e.printStackTrace(); 40 } 41 try { 42 if(!con.isClosed()){ 43 try { 44 con.close(); 45 } catch (SQLException e) { 46 e.printStackTrace(); 47 } 48 } 49 } catch (SQLException e) { 50 e.printStackTrace(); 51 } 52 53 } 54 return userList; 55 }
上面的代碼要若干年前可能是一段十分經典的,還可能被作為example被推廣。但時過境遷,倘若哪位程序員現在再在自己的程序中出現以上代碼,不是說明該公司的開發框架管理混亂,就說明這位程序員水平太“高”了。
我們試想,一個簡單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來梳理一下:
1、獲取connection
2、獲取statement
3、獲取resultset
4、遍歷resultset並封裝成集合
5、依次關閉connection,statement,resultset,而且還要考慮各種異常
6、.....
啊~~~~ 我快要暈了,在面向對象編程的年代里,這樣的代碼簡直不能上人容忍。試想,上面我們只是做了一張表的查詢,如果我們要做第2張表,第3張表呢,又是一堆重復的代碼:
1、獲取connection
2、獲取statement
3、獲取resultset
4、遍歷resultset並封裝成集合
5、依次關閉connection,statement,resultset,而且還要考慮各種異常
6、.....
這時候,使用模板模式的時機到了!!!
通過觀察我們發現上面步驟中大多數都是重復的,可復用的,只有在遍歷ResultSet並封裝成集合的這一步驟是可定制的,因為每張表都映射不同的java bean。這部分代碼是沒有辦法復用的,只能定制。那就讓我們用一個抽象的父類把它們封裝一下吧:
1 public abstract class JdbcTemplate { 2 3 //template method 4 public final Object execute(String sql) throws SQLException{ 5 6 Connection con = HsqldbUtil.getConnection(); 7 Statement stmt = null; 8 try { 9 10 stmt = con.createStatement(); 11 ResultSet rs = stmt.executeQuery(sql); 12 Object result = doInStatement(rs);//abstract method 13 return result; 14 } 15 catch (SQLException ex) { 16 ex.printStackTrace(); 17 throw ex; 18 } 19 finally { 20 21 try { 22 stmt.close(); 23 } catch (SQLException e) { 24 e.printStackTrace(); 25 } 26 try { 27 if(!con.isClosed()){ 28 try { 29 con.close(); 30 } catch (SQLException e) { 31 e.printStackTrace(); 32 } 33 } 34 } catch (SQLException e) { 35 e.printStackTrace(); 36 } 37 38 } 39 } 40 41 //implements in subclass 42 protected abstract Object doInStatement(ResultSet rs); 43 }
在上面這個抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負責實現。
好,我們來定義一個子類,並繼承上面的父類:
1 public class JdbcTemplateUserImpl extends JdbcTemplate { 2 3 @Override 4 protected Object doInStatement(ResultSet rs) { 5 List<User> userList = new ArrayList<User>(); 6 7 try { 8 User user = null; 9 while (rs.next()) { 10 11 user = new User(); 12 user.setId(rs.getInt("id")); 13 user.setUserName(rs.getString("user_name")); 14 user.setBirth(rs.getDate("birth")); 15 user.setCreateDate(rs.getDate("create_date")); 16 userList.add(user); 17 } 18 return userList; 19 } catch (SQLException e) { 20 e.printStackTrace(); 21 return null; 22 } 23 } 24 25 }
由代碼可見,我們在doInStatement()方法中,對ResultSet進行了遍歷,最后並返回。
有人可能要問:我如何獲取ResultSet 並傳給doInStatement()方法啊??呵呵,問這個問題的大多是新手。因為此方法不是由子類調用的,而是由父類調用,並把ResultSet傳遞給子類的。我們來看一下測試代碼:
1 String sql = "select * from User"; 2 JdbcTemplate jt = new JdbcTemplateUserImpl(); 3 List<User> userList = (List<User>) jt.execute(sql);
就是這么簡單!!
文章至此仿佛告一段落,莫急!不防讓我們更深入一些...
試想,如果我每次用jdbcTemplate時,都要繼承一下上面的父類,是不是有些不方面呢?
那就讓我們甩掉abstract這頂帽子吧,這時,就該callback(回調)上場了
所謂回調,就是方法參數中傳遞一個接口,父類在調用此方法時,必須調用方法中傳遞的接口的實現類。
那我們就來把上面的代碼改造一下,改用回調實現吧:
首先,我們來定義一個回調接口:
1 public interface StatementCallback { 2 Object doInStatement(Statement stmt) throws SQLException; 3 }
這時候,我們就要方法的簽名改一下了:
1 private final Object execute(StatementCallback action) throws SQLException
里面的獲取數據方式也要做如下修改:
1 Object result = action.doInStatement(stmt);//abstract method
為了看着順眼,我們來給他封裝一層吧:
1 public Object query(StatementCallback stmt) throws SQLException{ 2 return execute(stmt); 3 }
OK,大功告成!
我們來寫一個測試類Test.java測試一下吧:
這時候,訪問有兩種方式,一種是內部類的方式,一種是匿名方式。
先來看看內部類的方式:
1 //內部類方式 2 public Object query(final String sql) throws SQLException { 3 class QueryStatementCallback implements StatementCallback { 4 5 public Object doInStatement(Statement stmt) throws SQLException { 6 ResultSet rs = stmt.executeQuery(sql); 7 List<User> userList = new ArrayList<User>(); 8 9 User user = null; 10 while (rs.next()) { 11 12 user = new User(); 13 user.setId(rs.getInt("id")); 14 user.setUserName(rs.getString("user_name")); 15 user.setBirth(rs.getDate("birth")); 16 user.setCreateDate(rs.getDate("create_date")); 17 userList.add(user); 18 } 19 return userList; 20 21 } 22 23 } 24 25 JdbcTemplate jt = new JdbcTemplate(); 26 return jt.query(new QueryStatementCallback()); 27 }
在調用jdbcTemplate.query()方法時,傳一個StatementCallBack()的實例過去,也就是我們的內部類。
再來看看匿名方式:
1 //匿名類方式 2 public Object query2(final String sql) throws Exception{ 3 4 JdbcTemplate jt = new JdbcTemplate(); 5 return jt.query(new StatementCallback() { 6 7 public Object doInStatement(Statement stmt) throws SQLException { 8 ResultSet rs = stmt.executeQuery(sql); 9 List<User> userList = new ArrayList<User>(); 10 11 User user = null; 12 while (rs.next()) { 13 14 user = new User(); 15 user.setId(rs.getInt("id")); 16 user.setUserName(rs.getString("user_name")); 17 user.setBirth(rs.getDate("birth")); 18 user.setCreateDate(rs.getDate("create_date")); 19 userList.add(user); 20 } 21 return userList; 22 23 } 24 }); 25 26 }
相比之下,這種方法更為簡潔。
為什么spring不用傳統的模板方法,而加之以Callback進行配合呢?
試想,如果父類中有10個抽象方法,而繼承它的所有子類則要將這10個抽象方法全部實現,子類顯得非常臃腫。而有時候某個子類只需要定制父類中的某一個方法該怎么辦呢?這個時候就要用到Callback回調了。
離spring jdbcTemplate再近一點
上面這種方式基本上實現了模板方法+回調模式。但離spring的jdbcTemplate還有些距離。
我們可以再深入一些。。。
我們上面雖然實現了模板方法+回調模式,但相對於Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。
RowMapper接口負責處理某一行的數據,例如,我們可以在mapRow方法里對某一行記錄進行操作,或封裝成entity。
ResultSetExtractor是數據集抽取器,負責遍歷ResultSet並根據RowMapper里的規則對數據進行處理。
RowMapper和ResultSetExtractor區別是,RowMapper是處理某一行數據,返回一個實體對象。而ResultSetExtractor是處理一個數據集合,返回一個對象集合。
當然,上面所述僅僅是Spring JdbcTemplte實現的基本原理,Spring JdbcTemplate內部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內,以及采用JdbcAccessor來管理DataSource和轉換異常等。
接下來的主題:進行范型的改造
我們可能發現,上面的接口中返回的還都是Object對象,在范型日益盛行的今天,我們怎能忍受每次都要把得到的對象進行強制類型轉換?那我們就要心情享用JDK1.5給我們帶來的范型盛宴。