一、首先,到底什么是框架?
想要回答這個問題,我們要慢慢來。
①
首先從DRY原則開始說起
Don't Repeat Yourself,不要重復你的代碼。
DRY原則的重要性怎么提都不過分,很多人說編程是種機械性的工作,而有很多程序員也自嘲為碼農,意為編程成了一種沒有技術含量的體力性工作。如果不想淪為這個境界,首先需要的就是將DRY原則融入你的血液,在今后的編碼工作中加以運用。
1)最初級的DRY:語法級別
System.out.println(1); System.out.println(2); …… System.out.println(10);
我想只要學過基礎語法,都會采用下面的形式。
for (int i = 1; i <= 10; i++) { System.out.println(i); }
如果發現有任何人采用上面一種形式的編碼形式,那么不用懷疑,他對於編程絕對還沒有入門。
我們當然會選擇省力的做法,這種做法不但省力,還會有利於我們后續修改或擴展這組代碼,如:
for (int i = 1; i <= 10; i++) { System.out.println(i * 2 + 1); }
我們進行這樣的修改,只需要修改一處,而上面的形式卻需要修改10處,當然會更麻煩且更容易出錯,所以請記住能不重復就不重復。
2)進階的DRY原則:方法級別
當我們經常寫一些重復性代碼時,我們就要注意看能否將其抽取出來成為一個方法,如:
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
讓我們將其抽取到一個方法 threadSleep() 中,這樣我們只需要調用 threadSleep() 就可以實現原來的功能,不但所需敲擊的代碼更少,而且代碼看起來更加清楚明白。而為了增加這個方法的復用性,我們還可以將其中固定的數字抽取成為參數,如:
private static void threadSleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } }
這樣我們就可以利用這個方法實現不同時間的sleep了。要注意提高代碼的復用性也是實踐DRY原則的一個重要方法,在后面我們也可以看到框架為了提高所謂的靈活性進行的一些設計,如在適當的位置增加擴展點。
3)繼續進階的DRY原則:類型級別
現在我們看一個類
public class Person { private String name; private int age; // Setter & Getter ... }
我們新建一些Person類實例,並進行一些操作:
Person person = new Person(); person.setName("jack"); person.setAge(18); Person person2 = new Person(); person2.setName("rose"); person2.setAge(17); ..... System.out.printf("Name: %s, Age:%d\n", person.getName(), person.getAge()); System.out.printf("Name: %s, Age:%d\n", person2.getName(), person2.getAge()); .....
觀察這些代碼,其實有很大的DRY改造空間,首先可以添加一個構造方法
public Person(String name, int age) { this.name = name; this.age = age; }
其次,可以添加一個toString()方法
public String toString() { return String.format("Name: %s, Age: %d", name, age); }
這樣的話,上面的代碼就可以改成下面的形式。
Person person = new Person("jack", 18); Person person2 = new Person("rose", 17); ...... System.out.println(person.toString()); System.out.println(person2.toString()); ......
4)繼續繼續進階的DRY原則:多個類組合級別
上面的代碼我們其實還是有改善空間,就是利用容器類
List<Person> list = new ArrayList<>(); list.add(new Person("jack", 18)); list.add(new Person("rose", 17)); ...... list.forEach(p -> System.out.println(p));
這里利用JDK8的Stream API以及Lambda表達式輸出,其實可以進一步簡化為
list.forEach(System.out::println);
這里我們可以看到,基本上我們寫代碼只寫有變化的代碼,而盡量不寫機械性重復性的代碼,其實后面我們就會知道,這就叫專注於業務邏輯,所謂業務邏輯就是你這個項目中,與別的項目都不一樣的地方,必須由你親自去編寫實現的部分。
其實容器類很大程度上也是為了幫助我們編寫代碼而被設計出來的,首先讓我們不必為每一個對象起名字(省去了person,person2,...等變量),然后又為批量操作提供了可能性。像是這樣一系列有用的類組合起來可以稱之為類庫。常用的類庫有Commons-Lang包等,為我們提供了一大批實用方法,我之所以提到類庫,也是因為框架其實也是一種特殊的類庫,但是卻與一般的類庫有着本質的不同。
②
設計模式,更高層級的DRY應用
上面我講到了DRY原則的幾個層次,一般情況下大家也早就這樣使用了,屬於入門之后很容易自己就想到得一些層次。但是設計模式不一樣,設計模式是經過長時間編碼之后,經過系統性的總結所提出的針對某一類問題的最佳解決方案,又稱之為最佳實踐。
而在小規模的編碼工作中,其實並不需要什么設計模式,只有大型程序才有設計模式發揮的空間,所以我們需要借助一些特定領域有足夠規模的問題來了解一下設計模式存在的必要性。
1)連接數據庫,進行一些操作,並安全釋放數據庫連接。
public static boolean updatePassword(String username, String password, String newpassword) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; boolean success = false; try { conn = beginTransaction(); stmt = conn.prepareStatement("select id, password from user where username = ?"); stmt.setString(1, username); rs = stmt.executeQuery(); if (rs.next()) { if (rs.getString("password").equals(password)) { PreparedStatement stmt2 = null; try { stmt2 = conn.prepareStatement("update user set password = ? where id = ?"); stmt2.setString(1, newpassword); stmt2.setLong(2, rs.getLong("id")); success = stmt2.executeUpdate() > 0; } finally { safeClose(stmt2); } } } commitTransaction(conn); return success; } catch (SQLException e) { rollbackTransaction(conn); throw new RuntimeException(e); } finally { safeClose(rs); safeClose(stmt); safeClose(conn); } }
上面是一個簡單的數據庫事務,雖然只有一個查詢和一個更新,但是想要將其繼續簡化卻並不容易,雖然其中有關於業務邏輯的部分只是少量幾行代碼,但是初始化,異常,提交,回滾操作讓我們很難抽取出一個合適的方法來。雖然我們已經抽取出了 begin,commit,rollback,safeClose等方法,但是仍嫌繁瑣。
我們發現之所以我們難以抽取方法,主要是因為流程,因為里面牽扯到流程控制,而流程控制一般是由我們程序員來控制的,所以也就必然需要我們手動編碼來完成。難道真的就不能繼續簡化了嗎?這就是需要設計模式的時候了。
2)應用設計模式「模板方法模式」
public static boolean updatePassword(String username, String password, String newpassword) { return connection(conn -> statement(conn, "select id, password from user where username = ?", stmt -> { stmt.setString(1, username); return resultSet(stmt, rs -> { if (rs.next()) { if (rs.getString("password").equals(password)) { long id = rs.getLong("id"); return statement(conn, "update user set password = ? where id = ?", stmt2 -> { stmt2.setString(1, newpassword); stmt2.setLong(2, id); return stmt2.executeUpdate() == 1; }); } } return false; }); })); }
可以看到,所有的conn,stmt,rs的開啟和關閉,事務的提交和回滾都不用自己手動編寫代碼進行操作了,之所以可以達到這個效果,就是因為使用了模板方法設計模式,核心就是通過回調方法傳遞想對資源進行的操作,然后將控制權交給另一個方法,讓這個方法掌握流程控制,然后適當的時候回調我們的代碼(也就是我們自己寫的業務邏輯相關的代碼)。
這是需要額外寫的幾個方法
public interface ConnectionCallback<T> { T doConnection(Connection conn) throws SQLException; } public interface StatementCallback<T> { T doStatement(PreparedStatement stmt) throws SQLException; } public interface ResultSetCallback<T> { T doResultSet(ResultSet rs) throws SQLException; } public static <T> T connection(ConnectionCallback<T> callback) { Connection conn = null; T result = null; try { conn = beginTransaction(); result = callback.doConnection(conn); commitTransaction(conn); } catch (SQLException e) { rollbackTransaction(conn); throw new RuntimeException(e); } finally { safeClose(conn); } return result; } public static <T> T statement(Connection conn, String sql, StatementCallback<T> callback) throws SQLException { PreparedStatement stmt = null; T result = null; try { stmt = conn.prepareStatement(sql); result = callback.doStatement(stmt); } finally { safeClose(stmt); } return result; } public static <T> T resultSet(PreparedStatement stmt, ResultSetCallback<T> callback) throws SQLException { ResultSet rs = null; T result = null; try { rs = stmt.executeQuery(); result = callback.doResultSet(rs); } finally { safeClose(rs); } return result; }
你們可能會疑惑,這些代碼加上我們寫的業務邏輯的代碼,比原來的代碼還要長,有什么必要使用這個設計模式。這正是我前面已經指出的一個問題,那就是要你的程序規模足夠大才有必要應用設計模式,試想如果你有上百個乃至上千個數據庫操作方法需要寫,那么是不是寫這幾個額外的方法,就不算什么了呢。
其實這正是DRY原則在更高層次上的應用,即結合設計模式來達到更高層次的代碼復用效果,進而應用DRY原則。而想要在這個層次繼續向上攀升,那就必須是結合眾多設計模式以及一些高層架構設計,能夠幫助我們實現這一目的的就是框架。
3)框架,是設計模式的集大成者,是DRY原則的最高應用
先讓我們來看一下,使用框架會是什么樣的一種體驗?
這里以Hibernate + Spring聲明式事務為例
@Transactional public boolean updatePassword(String username, String password, String newpassword) { User user = (User) session().createQuery("from User where username = :username") .setString("username", username) .uniqueResult(); if (user != null && user.getPassword().equals(password)) { user.setPassword(newpassword); return true; } return false; }
可以發現令人驚訝的簡潔,而且代碼邏輯異常清晰,完全不需要考慮conn,stmt,rs等資源的釋放,以及事務的提交和回滾,但是這些事情其實框架已經默默的幫我們做到了。這才叫真正的專注於業務邏輯,盡最大可能的只寫與業務邏輯有關的代碼。
當然這些框架的效果雖然神奇,其實只要細細探究其內部原理,是完全可以理解並掌握的。
二、那么問題就來了,框架到底是什么?要不要學,怎么學?
上面我說過了,框架其實就是一個或一組特殊的類庫,特殊在什么地方?特殊在控制權轉移!
框架與一般類庫不同的地方是,我們調用類庫,而框架調用我們。也就是說框架掌握整個程序的控制權,我們必須一定程度上把程序流程的控制權交給框架,這樣框架才能更好的幫助我們。
下面以JavaWeb開發為例再進行一些說明,並順便簡單介紹一下JavaWeb的一些脈絡。
①
靜態網頁時代
本來網站都是一個個靜態HTML組成的,或許這些網頁還是用Dreamweaver寫的,但是這樣的靜態頁面顯然不能滿足我們,很快我們就迎來了動態網頁的時代。
②
Servlet時代
如果熟悉HTTP協議的話,我們就知道其實訪問網頁的過程不過是一次TCP連接罷了。瀏覽器發起TCP連接到服務器,服務器接受請求,然后返回HTML代碼作為響應。那么我們完全可以等到接受到請求之后,再動態生成HTML代碼返回給客戶端。
Servlet就是這么做的,其主要代碼不過是利用out.write()一點一點的輸出HTML代碼罷了。當然我們可以在其中摻雜一點動態的東西,如返回當前的時間。
out.write("<!DOCTYPE html>\r\n"); out.write("<html>\r\n"); out.write("<head>\r\n"); out.write("<title>Index Page</title>\r\n"); out.write("</head>\r\n"); out.write("<body>\r\n"); out.write("Hello, " + new Date() + "\r\n"); out.write("</body>\r\n"); out.write("</html>\r\n");
③ JSP包打天下的時代
純粹的Servlet很是丑陋,給前端程序員理解和修改這樣的代碼帶來了很多困難。因此JSP技術被發明了出來,原理也不復雜,就是不直接寫Servlet,而是先寫好JSP文件,再由服務器將JSP文件編譯成Servlet。而JSP中是以常見的HTML標簽為主,這樣前端程序員就能方便的修改這些代碼了。
<!DOCTYPE html> <html> <head> <title>Index Page</title> </head> <body> Hello, <%=new Date()%> </body> </html>
PS:由只使用 Servlet到使用JSP,雖然是一個簡單的變化,但這迎合了前后端專業分工的大趨勢,讓前段人員只需要懂得HTML/CSS/JavaScrip代碼就可以開始工作,而不需要學習Servlet那枯燥無味的用法,因此借着JSP技術的東風,JavaWeb技術迅速的擴展開來了。
④ Servlet + JSP 時代
隨着JSP技術的發展,用它寫成的網站也越來越大,業務邏輯也越來越復雜。開發人員漸漸發現整個網站漸漸的再次變成了一團亂麻,不僅僅是JSP中夾雜了大量的Java代碼,頁面之間的耦合關系也越來越緊密。
即便是要修改一個簡單的按鈕文本,或者是引入一段靜態的內容,也需要打開越來越龐大的JSP頁面,艱難到找到需要修改的部分,有時還不僅僅是一處,這種修改是有很大的風險的,完全有可能引入新的錯誤。
這時候開發者漸漸意識到,僅僅使用JSP是不行的,JSP承擔了太多的責任。這時人們又想起了Servlet,Servlet中主要使用Java代碼,處理業務邏輯非常輕松。如果JSP只使用HTML代碼,而將業務邏輯的代碼轉移到Servlet中,就可以大大的減輕JSP的負擔,並且讓前后端分工更加明確。
作者:Intopass
鏈接:http://www.zhihu.com/question/25654738/answer/31302541
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。
一次典型的訪問是這樣的流程:
1. 用戶輸入網址或點擊鏈接或提交表單,瀏覽器發起請求
2. --> 通過互聯網,通過HTTP協議 -->
3. Tomcat接受到HTTP請求,生成HttpServletRequest對象,根據Web.xml的配置,調用開發者編寫的HttpServlet,HttpServlet根據請求內容,調用JavaBean獲取數據,JavaBean從數據庫獲取數據,返回HttpServlet,HttpServlet將數據轉發給JSP,JSP負責將數據渲染為HTML,由Tomcat負責將HTML轉化為HTTP響應,返回客戶端。
4. --> 通過互聯網,通過HTTP協議 -->
5. 客戶端瀏覽器接收到HTTP響應,瀏覽器將HTML渲染為頁面,並運行其中可能存在的JavaScript進一步調整界面。
整個流程必須由開發者精確設計才能運作流暢,其中客戶端HTML和JavaScript屬於前端設計,服務器運行的其他內容屬於后端設計。雖然符合J2EE規范的Tomcat等應用服務器已經幫我們實現了最復雜的一塊,即HTTP協議部分,還給我們提供了JSP這個模板引擎,以及自定義標簽等手段。但是在控制層,在模型層,J2EE能給我們的幫助少之甚少。
就拿用戶提交一個表單為例,而我們在Servlet中獲取參數為例,雖然不用我們解析HTTP報文,應該已經是要謝天謝地了,但是我們要做的事情仍然很多,分析一下:
1. 客戶端傳過來的數據全是文本,而我們需要的是Java對象。
2. 凡是文本就有編碼問題,而這需要前后端配合解決。
3. 客戶端的輸入是不可信的,我們必須校驗參數的合法性。
4. 我們還必須將校驗結果反饋給客戶,並且最好不要讓客戶全部重新輸入。
5. 我們往往不是只有一個參數需要,而是有幾個甚至更多參數,要妥善的處理各種情況組合。
這些事情幾乎全部都需要我們手動編碼來完成,幾乎每一個 Servlet 都充斥着這樣的代碼,設置編碼,獲取參數,校驗參數,校驗通不過返回錯誤信息,校驗通過則進行業務處理。而更重要的是,獲取參數僅僅是整個流程中的一小步,我們的Servlet中存在着大量的重復性,機械性代碼,而處理業務邏輯的代碼可能只有一兩行。
⑥
JavaWeb框架
既然存在着大量的重復,我們當然不能忍,必須請出DRY大法。顯然JavaWeb應用是一個規模龐大,流程復雜的應用,我們正需要JavaWeb框架的幫助。以Struts2框架為例,他能給我們什么幫助呢?
1. 在控制層,由Struts2的核心控制器接管控制權,將本來在Web.xml進行配置的一些工作,轉移到自定義的struts.xml文件中,這個文件的配置形式更友好。
2. Struts2封裝了Serlvet Api,使用POJO對象作為控制器(Action),大量使用反射,不要求繼承特定類,有利於復用及單元測試。提供ActionSupport類,結合struts2標簽,能很方面實現的校驗信息的收集及反饋。
3. 提供國際化支持,在顯示層有國際化相關的標簽,在控制層由國際化相關的API。提供基於配置的校驗及JS生成技術。智能化的參數類型轉換,支持自定義轉換器。提供Action攔截器,方便實現AOP模式。
4. 提供了基於OGNL表達式的數據共享模式,前后端數據交流更簡單,提供了Struts2標簽庫,簡單好用,支持多種模板,如FreeMarker,支持各種插件,如JSON,支持整合多種框架,如Spring。總之一句話,能在各方各面給我們強大的幫助。
⑦
所以當然要學框架,要用框架,那么要怎么學?
1. 用框架要知其然,還要知其所以然,要大體明白框架實現一個功能特性的原理,不能只是會用,只是覺得很神奇就可以了。就拿前面的Hibernate + Spring聲明式事務為例,要弄明白框架這部分是怎么實現的。
2. 首先要夯實你的語言基礎,如JavaSE基礎,語法掌握,用法掌握,有些同學語法還不熟練就開始學框架,等於地基沒打就起高樓,你可能會快一步,但是遲早要遇到瓶頸,甚至摔跟頭。
3. 那么何時開始學習框架?我不建議新手一開始就直接使用框架。
就好像一開始學習編程語言,大家都不推薦直接使用IDE,一定要用命令行自己編譯運行幾個文件之后,了解清楚了之后才可以使用IDE,要不然對於底層原理不了解,遇到問題沒法自己手動排查。
4. 使用框架也是一樣,如果不是自己寫多了重復性的代碼,就很難理解框架為什么要這么設計。如果不嘗試幾種不同的實現,就很難理解框架為了靈活性而做出的設計和擴展點。如果不寫幾十個權限檢查語句,就很難理解AOP到底有什么好處。
5. 框架這么好,我該全部使用框架嗎?首先只有在規模以上的程序中,才有應用框架的必要,一個簡單的程序沒必要使用框架,當然如果你很熟練,使用也無所謂。
6. 要學習一下框架的核心源代碼,要為擴展框架做好准備,因為雖然框架基本上還算靈活,但是面對錯綜復雜的業務需求,永遠不可能面面俱到,而你不了解框架的話,可能會給你實現業務需求造成麻煩。這也是有些人堅持使用Servlet+JSP原生開發,而不是用框架的理由。
7. 只要程序大了,歸根究底還是要使用框架的,不是用別人寫好的,就是自己寫一套。這里我不建議自己寫,不要重復造輪子,總有專業造輪子的。你草草寫就的往往不如別人已經千錘百煉的代碼。除非你是為了學習與研究的目的,自己寫,那就是一件很好的事情。