我記得有人告訴我。「面試一下spring源代碼。看ioc、aop源代碼"那爲何要看這些開源框架的源代碼呢,事實上很是多人都是"應急式"的去讀。就像讀一篇文章一下,用最快的速度把文章從頭至尾讀一遍,那結果就是當你讀完它,你也不清楚它講了一個什麼故事,想表達什麼。java
一個優秀的架構的源代碼我以爲就好像一本名著同樣。你的「文學」水平越高。你就越能讀出做者設計的精妙之處。web
一篇源代碼在你不一樣水平的時候,能讀出不一樣的東西。所以,我以爲優秀的框架的源代碼是經久不衰的,重複讀多少次都不嫌多,直到你能設計出預期並駕齊驅甚至超越它的優美的架構。面試
讀源代碼起初是一件很是痛苦的事兒。想趕忙把它像流水帳同樣的讀完;慢慢實力加強後,會感受到讀源代碼能夠不費力氣的讀通。再假以時日。就能看出這些精妙的設計模式的組合。我有一個朋友。典型的源代碼癡狂症,他跟我說他第一次看見spring的源代碼,感受特別興奮,讀了一宿沒睡覺.......好吧,我還有很是長的路需要走~spring
話說多了。咱們趕忙入正題:sql
JFinal的框架我24號的一篇博文寫到過。它優秀的地方在精簡代碼上,那麼有兩處源代碼是我認爲是值得咱們要好好解析一下,一處是初始化載入—servlet跳轉。還有一處是DB+ActiveRecord的映射。數據庫
那麼DB映射相對照較簡單,咱們此次就先來看看。設計模式
首先咱們看看代碼。仍是以前我寫過的 dog與cat的故事。架構
來自FinalConfig.java
app
// 採用DB+ActiveRecord模式 ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); me.add(arp); // 進行DB映射 arp.addMapping("animal", AnimalModel.class);這三行代碼就是載入DB映射的關鍵,那麼咱們複習一下,JFinal的DB映射無需配置文件。無需與DB相應的POJO,僅僅需要寫一個類。繼承Model<M extends Model>就能夠。
第一步:爲ActiveRecordPlugin的 private IDataSourceProvider dataSourceProvider 賦值。框架
那麼咱們先來看看ActiveRecordPlugin的構造器。
來自ActiveRecordPlugin.java
public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) { this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider); }這裏重要的是dataSourceProvider。IDataSourceProvider是一個接口,它的執行時類型是
來自C3p0Plugin.java
public class C3p0Plugin implements IPlugin, IDataSourceProvider{...}那麼。可以看到
來自ActiveRecordPlugin.java
this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);這段代碼又繼續讀取還有一個重載的構造器,而後調用了
來自ActiveRecordPlugin.java
public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider, int transactionLevel) { if (StrKit.isBlank(configName)) throw new IllegalArgumentException("configName can not be blank"); if (dataSourceProvider == null) throw new IllegalArgumentException("dataSourceProvider can not be null"); this.configName = configName.trim(); this.dataSourceProvider = dataSourceProvider; this.setTransactionLevel(transactionLevel); }最重要的就是這行代碼: this.dataSourceProvider = dataSourceProvider;
這時。ActiveRecordPlugin的static變量的dataSourceProvider就已經被賦爲C3p0Plugin的實例了。
第二步:定義映射用POJO
來自AnimalModel.java
public class AnimalModel extends Model<AnimalModel> {...}這裏Model的源代碼咱們一會再看。現在不着急。
而後進行映射
來自FinalConfig.java
// 進行DB映射 arp.addMapping("animal", AnimalModel.class);這裏咱們又回到了ActiveRecordPlugin類裏。它實際上有兩個addMapping方法。僅僅是參數不一樣。
來自ActiveRecordPlugin.java
public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<?咱們看到,第一個方法多了一個參數 String primaryKey。個人代碼裏用的是第二個方法。這兩個方法實際上都調用了tableList.add(Table tbl)方法。咱們看看tableList是什麼extends Model<?>> modelClass) { tableList.add(new Table(tableName, primaryKey, modelClass)); return this; } public ActiveRecordPlugin addMapping(String tableName, Class<? extends Model<?
>> modelClass) { tableList.add(new Table(tableName, modelClass)); return this; }
來自ActiveRecordPlugin.java
private List<Table> tableList = new ArrayList<Table>();它是ActiveRecordPlugin的一個成員變量,並且是private的,那咱們可以猜到,tableList保存了所有的映射關係。(ActiveRecordPlugin真是強大,後面會愈來愈強大~)。
第三步:建立映射關係
來自ActiveRecordPlugin.java
new Table(tableName, primaryKey, modelClass) new Table(tableName, modelClass)咱們進去看看
來自Table.java
public Table(String name, Class<? extends Model<?這兩個方法都是爲Table裏的成員變量賦值,第二個方法,也就是帶primaryKey參數的那個多出一行。咱們看看這一行幹了什麼>> modelClass) { if (StrKit.isBlank(name)) throw new IllegalArgumentException("Table name can not be blank."); if (modelClass == null) throw new IllegalArgumentException("Model class can not be null."); this.name = name.trim(); this.modelClass = modelClass; } public Table(String name, String primaryKey, Class<? extends Model<?>> modelClass) { if (StrKit.isBlank(name)) throw new IllegalArgumentException("Table name can not be blank."); if (StrKit.isBlank(primaryKey)) throw new IllegalArgumentException("Primary key can not be blank."); if (modelClass == null) throw new IllegalArgumentException("Model class can not be null."); this.name = name.trim(); setPrimaryKey(primaryKey.trim()); // this.primaryKey = primaryKey.trim(); this.modelClass = modelClass; }
來自Table.java
setPrimaryKey(primaryKey.trim()); // this.primaryKey = primaryKey.trim();
void setPrimaryKey(String primaryKey) { String[] keyArr = primaryKey.split(","); if (keyArr.length > 1) { if (StrKit.isBlank(keyArr[0]) || StrKit.isBlank(keyArr[1])) throw new IllegalArgumentException("The composite primary key can not be blank."); this.primaryKey = keyArr[0].trim(); this.secondaryKey = keyArr[1].trim(); } else { this.primaryKey = primaryKey; } }這種做用就是爲Table下的primaryKey 和 secondaryKey賦值。
第四步:載入ActiveRecordPlugin
那麼代碼好像跟到這裏就完事了。怎麼回事?是否是跟丟了?
別忘了,ActiveRecordPlugin是在FinalConfig裏的configPlugin方法載入的。那麼又有誰來載入FinalConfig呢?
PS:(FinalConfig是我自定義的類)
public class FinalConfig extends JFinalConfig這兒涉及到初始化的載入了,我簡單的講一下。
整個JFinal的入口是web.xml的一段配置:
來自web.xml
<web-app> <filter> <filter-name>jfinal</filter-name> <filter-class>com.jfinal.core.JFinalFilter</filter-class> <init-param> <param-name>configClass</param-name> <param-value>com.demo.config.FinalConfig</param-value> </init-param> </filter>接着咱們看到了關鍵的累 JFinalFilter。仍是點進去看看。
public final class JFinalFilter implements Filter這個類實現了Filter接口,那就得實現方法init(),doFilter(),destroy()方法。
咱們去看init()方法:
來自JFinalFilter.java
public void init(FilterConfig filterConfig) throws ServletException { createJFinalConfig(filterConfig.getInitParameter("configClass")); if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false) throw new RuntimeException("JFinal init error!"); handler = jfinal.getHandler(); constants = Config.getConstants(); encoding = constants.getEncoding(); jfinalConfig.afterJFinalStart(); String contextPath = filterConfig.getServletContext().getContextPath(); contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length()); }繞過其它的載入,直接看這行
來自JFinalFilter.java
if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)咱們看看jfinal的類型是 private static final JFinal jfinal = JFinal.me();
那麼咱們去JFinal類裏看看它的init方法。
來自JFinal.java
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) { this.servletContext = servletContext; this.contextPath = servletContext.getContextPath(); initPathUtil(); Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method constants = Config.getConstants(); initActionMapping(); initHandler(); initRender(); initOreillyCos(); initI18n(); initTokenManager(); return true; }看這行,如下這行主要是經過Config來載入暴露給程序猿的核心文件,JFinalConfig的子類FinalConfig。
來自JFinal.java
Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method再點進去
來自com.jfinal.core.Config.java
/* * Config order: constant, route, plugin, interceptor, handler */ static void configJFinal(JFinalConfig jfinalConfig) { jfinalConfig.configConstant(constants); initLoggerFactory(); jfinalConfig.configRoute(routes); jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!! jfinalConfig.configInterceptor(interceptors); jfinalConfig.configHandler(handlers); }這段代碼實際上有個地方特別坑!就是
來自com.jfinal.core.Config.java
jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!
這行代碼一共作了兩件事,第一件事是jfinalConfig.configPlugin(plugins);來載入插件。還記得咱們以前寫的FinalConfig裏的configPlugin(Plugins me) 方法嗎?
來自FinalConfig.java
/** * Config plugin * 配置插件 * JFinal有本身首創的 DB + ActiveRecord模式 * 此處需要導入ActiveRecord插件 */ @Override public void configPlugin(Plugins me) { // 讀取db配置文件 loadPropertyFile("db.properties"); // 採用c3p0數據源 C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"),getProperty("user"), getProperty("password")); me.add(c3p0Plugin); // 採用DB+ActiveRecord模式 ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); me.add(arp); // 進行DB映射 arp.addMapping("animal", AnimalModel.class); }它實際上就是經過me.add來載入插件。經過Config的 private static final Plugins plugins = new Plugins(); 來裝載。
第二件事就是 發現沒有,後面的startPlugins()不是凝視!是一個方法。這塊實在太坑了,恰巧。這就是咱們要找到的地方。
這種方法的代碼有點長,但因爲很是重要,我不得不都貼出來。
來自ActiveRecordPlugin.java
private static void startPlugins() { List<IPlugin> pluginList = plugins.getPluginList(); if (pluginList != null) { for (IPlugin plugin : pluginList) { try { // process ActiveRecordPlugin devMode if (plugin instanceof com.jfinal.plugin.activerecord.ActiveRecordPlugin) { com.jfinal.plugin.activerecord.ActiveRecordPlugin arp = (com.jfinal.plugin.activerecord.ActiveRecordPlugin)plugin; if (arp.getDevMode() == null) arp.setDevMode(constants.getDevMode()); } boolean success = plugin.start(); if (!success) { String message = "Plugin start error: " + plugin.getClass().getName(); log.error(message); throw new RuntimeException(message); } } catch (Exception e) { String message = "Plugin start error: " + plugin.getClass().getName() + ". \n" + e.getMessage(); log.error(message, e); throw new RuntimeException(message, e); } } } }上面這種方法一共同擁有兩個地方要注意一下。
來自ActiveRecordPlugin.java
for (IPlugin plugin : pluginList) {上面這行是循環所有的插件,並且啓動插件的start()方法。
那麼。咱們中有一個插件記不記得是ActiveRecordPlugin的實例?那麼
來自ActiveRecordPlugin.java
boolean success = plugin.start();這行代碼就會運行ActiveRecordPlugin下的start()代碼。最終繞回來了!!
紅軍二萬五千里長徵。爲了證實這個調用,我寫了多少字....
那麼咱們看ActiveRecordPlugin下的start()方法吧。實際上這個start()方法是因爲實現了IPlugin接口裏的start()方法。
來自ActiveRecordPlugin.java
public boolean start() { if (isStarted) return true; if (dataSourceProvider != null) dataSource = dataSourceProvider.getDataSource(); if (dataSource == null) throw new RuntimeException("ActiveRecord start error: ActiveRecordPlugin need DataSource or DataSourceProvider"); if (config == null) config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache); DbKit.addConfig(config); boolean succeed = TableBuilder.build(tableList, config); if (succeed) { Db.init(); isStarted = true; } return succeed; }咱們直接看與DB映射有關的代碼。首先是取得dataSource,dataSourceProvider這個忘了沒。忘了就翻到最前面,第一步講的。
來自ActiveRecordPlugin.java
config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache);這行代碼中的dataSource 在插件裏配置的C3P0數據源。
這裏的Config與前面載入FinalConfig的可不是一個啊,千萬別看錯了,這個是DB的 com.jfinal.plugin.activerecord.Config。
第五步:TableBuilder
來自ActiveRecordPlugin.java
boolean succeed = TableBuilder.build(tableList, config);來自TableBuilder.java
static boolean build(List<Table> tableList, Config config) { Table temp = null; Connection conn = null; try { conn = config.dataSource.getConnection(); TableMapping tableMapping = TableMapping.me(); for (Table table : tableList) { temp = table; doBuild(table, conn, config); tableMapping.putTable(table); DbKit.addModelToConfigMapping(table.getModelClass(), config); } return true; } catch (Exception e) { if (temp != null) System.err.println("Can not create Table object, maybe the table " + temp.getName() + " is not exists."); throw new ActiveRecordException(e); } finally { config.close(conn); } }這裏循環所有的tableList,對每個Table對象進行建表。那麼咱們先看看Table是用什麼來存儲數據庫映射關係的,相信你們都能猜到是Map了。
來自Table.java
public class Table { private String name; private String primaryKey; private String secondaryKey = null; private Map<String, Class<?>> columnTypeMap; // config.containerFactory.getAttrsMap(); private Class<? extends Model<?columnTypeMap是keyword段。暫且記下來。>> modelClass;
如下咱們仍是回到TableBuilder裏的doBuild(table, conn, config);方法。
這個纔是DB映射的關鍵。我事實上直接講這一個類就可以的......這種方法代碼實在太多了,我貼部分代碼作解說吧。
那麼第六步:doBuild具體解釋。
這塊有點類,我直接在代碼裏寫凝視吧:
來自TableBuilder.java
@SuppressWarnings("unchecked") private static void doBuild(Table table, Connection conn, Config config) throws SQLException { // 初始化 Table 裏的columnTypeMap字段。 table.setColumnTypeMap(config.containerFactory.getAttrsMap()); // 取得主鍵,假設取不到的話,默認設置"id"。 // 記不記得最開始的兩個同名不一樣參的方法 addMapping(...),在這才體現出興許處理的不一樣。 if (table.getPrimaryKey() == null) table.setPrimaryKey(config.dialect.getDefaultPrimaryKey()); // 此處假設沒有設置方言,則默認 Dialect dialect = new MysqlDialect(); Mysql的方言。 // sql爲"select * from `" + tableName + "` where 1 = 2"; String sql = config.dialect.forTableBuilderDoBuild(table.getName()); Statement stm = conn.createStatement(); ResultSet rs = stm.executeQuery(sql); //取得個字段的信息 ResultSetMetaData rsmd = rs.getMetaData(); // 匹配映射 for (int i=1; i<=rsmd.getColumnCount(); i++) { String colName = rsmd.getColumnName(i); String colClassName = rsmd.getColumnClassName(i); if ("java.lang.String".equals(colClassName)) { // varchar, char, enum, set, text, tinytext, mediumtext, longtext table.setColumnType(colName, String.class); } else if ("java.lang.Integer".equals(colClassName)) { // int, integer, tinyint, smallint, mediumint table.setColumnType(colName, Integer.class); } else if ("java.lang.Long".equals(colClassName)) { // bigint table.setColumnType(colName, Long.class); } // else if ("java.util.Date".equals(colClassName)) { // java.util.Data can not be returned // java.sql.Date, java.sql.Time, java.sql.Timestamp all extends java.util.Data so getDate can return the three types data // result.addInfo(colName, java.util.Date.class); // } else if ("java.sql.Date".equals(colClassName)) { // date, year table.setColumnType(colName, java.sql.Date.class); } else if ("java.lang.Double".equals(colClassName)) { // real, double table.setColumnType(colName, Double.class); } else if ("java.lang.Float".equals(colClassName)) { // float table.setColumnType(colName, Float.class); } else if ("java.lang.Boolean".equals(colClassName)) { // bit table.setColumnType(colName, Boolean.class); } else if ("java.sql.Time".equals(colClassName)) { // time table.setColumnType(colName, java.sql.Time.class); } else if ("java.sql.Timestamp".equals(colClassName)) { // timestamp, datetime table.setColumnType(colName, java.sql.Timestamp.class); } else if ("java.math.BigDecimal".equals(colClassName)) { // decimal, numeric table.setColumnType(colName, java.math.BigDecimal.class); } else if ("[B".equals(colClassName)) { // binary, varbinary, tinyblob, blob, mediumblob, longblob // qjd project: print_info.content varbinary(61800); table.setColumnType(colName, byte[].class); } else { int type = rsmd.getColumnType(i); if (type == Types.BLOB) { table.setColumnType(colName, byte[].class); } else if (type == Types.CLOB || type == Types.NCLOB) { table.setColumnType(colName, String.class); } else { table.setColumnType(colName, String.class); } // core.TypeConverter // throw new RuntimeException("You've got new type to mapping. Please add code in " + TableBuilder.class.getName() + ". The ColumnClassName can't be mapped: " + colClassName); } } rs.close(); stm.close(); }這裏巧妙的運用了 where 1=2的無檢索條件結果。經過ResultSetMetaData rsmd = rs.getMetaData(); 導出了DB模型,這招確實美麗。以前我還冥思苦相,他是怎麼作的呢,看着此處源代碼。茅塞頓開。
接着,把編輯好的Table實例。放到TableMapping的成員變量 Model<?>>, Table> modelToTableMap 裏去,TableMapping是單例的。
來自TableMapping.java
private final Map<Class<? extends Model<?>>, Table> modelToTableMap = new HashMap<Class<?
extends Model<?>>, Table>();
public void putTable(Table table) { modelToTableMap.put(table.getModelClass(), table); }這樣。所有的映射關係就都存在TableMapping的modelToTableMap
來自TableBuilder.java
tableMapping.putTable(table);
。再將modelToConfig都放入DbKit.modelToConfig裏。
來自TableBuilder.java
DbKit.addModelToConfigMapping(table.getModelClass(), config);
第七步,使用
Model裏的save方法舉例:
來自Model.java
/** * Save model. */ public boolean save() { Config config = getConfig(); Table table = getTable(); StringBuilder sql = new StringBuilder(); List<Object> paras = new ArrayList<Object>(); config.dialect.forModelSave(table, attrs, sql, paras); // if (paras.size() == 0) return false; // The sql "insert into tableName() values()" works fine, so delete this line // -------- Connection conn = null; PreparedStatement pst = null; int result = 0; try { conn = config.getConnection(); if (config.dialect.isOracle()) pst = conn.prepareStatement(sql.toString(), new String[]{table.getPrimaryKey()}); else pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS); config.dialect.fillStatement(pst, paras); result = pst.executeUpdate(); getGeneratedKey(pst, table); getModifyFlag().clear(); return result >= 1; } catch (Exception e) { throw new ActiveRecordException(e); } finally { config.close(pst, conn); } }
Config config = getConfig();
上面這行就是調用DbKit的方法,取得DB配置。
來自Model.java
public static Config getConfig(Class<?如下這段代碼是去單例的TableMapping裏取得表的詳細信息。extends Model> modelClass) { return modelToConfig.get(modelClass); }
來自Model.java
Table table = getTable();
private Table getTable() { return TableMapping.me().getTable(getClass()); }以上。就是DB+ActiveRecord的核心調用流程,下次我會帶來初始化流程,只是這是個大活的,預計到單獨的章節來寫。