Jfinal源码详解

JFinal的框架我24号的一篇博文写到过,它优秀的地方在精简代码上,那么有两处源码是我觉得是值得我们要好好解析一下,一处是初始化加载—servlet跳转,另一处是DB+ActiveRecord的映射。

那么DB映射相对比较简单,我们这次就先来看看。

首先我们看看代码,还是之前我写过的 dog与cat的故事。

// 采用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的构造器。

public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {      this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);  }  

这里重要的是dataSourceProvider,IDataSourceProvider是一个接口,它的运行时类型是

public class C3p0Plugin implements IPlugin, IDataSourceProvider{...}  

那么,可以看到

this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);  

这段代码又继续读取另一个重载的构造器,然后调用了

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

public class AnimalModel extends Model<AnimalModel> {...}  

这里Model的源码我们一会再看,现在不着急。

然后进行映射

// 进行DB映射  arp.addMapping("animal", AnimalModel.class);  

这里我们又回到了ActiveRecordPlugin类里,它实际上有两个addMapping方法,只是参数不同。

public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<? 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;  }  

我们看到,第一个方法多了一个参数 String primaryKey,我的代码里用的是第二个方法。这两个方法实际上都调用了tableList.add(Table tbl)方法,我们看看tableList是什么

private List<Table> tableList = new ArrayList<Table>();  

它是ActiveRecordPlugin的一个成员变量,并且是private的,那我们可以猜到,tableList保存了所有的映射关系。(ActiveRecordPlugin真是强大,后面会越来越强大~)。

第三步:创建映射关系

new Table(tableName, primaryKey, modelClass)  new Table(tableName, modelClass)

我们进去看看

public Table(String name, Class<? extends Model<?>> 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里的成员变量赋值,第二个方法,也就是带primaryKey参数的那个多出一行,我们看看这一行干了什么

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-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()方法:

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());  } 

绕过其他的加载,直接看这行

if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)  

我们看看jfinal的类型是 private static final JFinal jfinal = JFinal.me();

那么我们去JFinal类里看看它的init方法。

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。

Config.configJFinal(jfinalConfig);  // start plugin and init logger factory in this method

再点进去

* Config order: constant, route, plugin, interceptor, handler */  tatic void configJFinal(JFinalConfig jfinalConfig) {  jfinalConfig.configConstant(constants);             initLoggerFactory();  jfinalConfig.configRoute(routes);  jfinalConfig.configPlugin(plugins);                 startPlugins(); // very important!!!  jfinalConfig.configInterceptor(interceptors);  jfinalConfig.configHandler(handlers);  

这段代码实际上有个地方特别坑!就是

jfinalConfig.configPlugin(plugins);                 startPlugins(); // very important!!!  

这行代码一共做了两件事,第一件事是jfinalConfig.configPlugin(plugins);来加载插件。还记得我们之前写的FinalConfig里的configPlugin(Plugins me) 方法吗?

/**  * 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()不是注释!是一个方法,这块实在太坑了,恰巧,这就是我们要找到的地方。

这个方法的代码有点长,但因为很重要,我不得不都贴出来。

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);                  }              }          }      }  

上面这个方法一共有两个地方要注意一下,

for (IPlugin plugin : pluginList) {  

上面这行是循环所有的插件,并且启动插件的start()方法。

那么,我们中有一个插件记不记得是ActiveRecordPlugin的实例?那么

boolean success = plugin.start(); 

这行代码就会执行ActiveRecordPlugin下的start()代码。终于绕回来了!!红军二万五千里长征,为了证明这个调用,我写了多少字....

那么我们看ActiveRecordPlugin下的start()方法吧,实际上这个start()方法是因为实现了IPlugin接口里的start()方法。

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这个忘了没,忘了就翻到最前面,第一步讲的。

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);  
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了。

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<?>> modelClass;  

columnTypeMap是关键字段,暂且记下来。

下面我们还是回到TableBuilder里的doBuild(table, conn, config);方法。

这个才是DB映射的关键,我其实直接讲这一个类就可以的......这个方法代码实在太多了,我贴部分代码做讲解吧。

那么第六步:doBuild详解。

这块有点类,我直接在代码里写注释吧:

@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是单例的。

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

tableMapping.putTable(table);  

再将modelToConfig都放入DbKit.modelToConfig里。

DbKit.addModelToConfigMapping(table.getModelClass(), config);  

第七步,使用

Model里的save方法举例:

/**  * 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配置。

public static Config getConfig(Class<? extends Model> modelClass) {      return modelToConfig.get(modelClass);  }  

下面这段代码是去单例的TableMapping里取得表的具体信息。

Table table = getTable();  
private Table getTable() {      return TableMapping.me().getTable(getClass());  }  
时间: 2024-08-30 14:23:19

Jfinal源码详解的相关文章

Android ArrayMap源码详解

尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159 分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray,在上一篇<Android SparseArray源码详解>中我们讲到SparseArray是以纯数组的形式存储的,一个数组存储的是key值一个数组存储的是value值,今天我们分析的ArrayMap和SparseArray有点类似,他也是以纯数组的形式存储,不过不同的是他的

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Java concurrent AQS 源码详解

一.引言 AQS(同步阻塞队列)是concurrent包下锁机制实现的基础,相信大家在读完本篇博客后会对AQS框架有一个较为清晰的认识 这篇博客主要针对AbstractQueuedSynchronizer的源码进行分析,大致分为三个部分: 静态内部类Node的解析 重要常量以及字段的解析 重要方法的源码详解. 所有的分析仅基于个人的理解,若有不正之处,请谅解和批评指正,不胜感激!!! 二.Node解析 AQS在内部维护了一个同步阻塞队列,下面简称sync queue,该队列的元素即静态内部类No

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Spring IOC源码详解之容器依赖注入

Spring IOC源码详解之容器依赖注入 上一篇博客中介绍了IOC容器的初始化,通过源码分析大致了解了IOC容器初始化的一些知识,先简单回顾下上篇的内容 载入bean定义文件的过程,这个过程是通过BeanDefinitionReader来完成的,其中通过 loadBeanDefinition()来对定义文件进行解析和根据Spring定义的bean规则进行处理 - 事实上和Spring定义的bean规则相关的处理是在BeanDefinitionParserDelegate中完成的,完成这个处理需

Spring IOC源码详解之容器初始化

Spring IOC源码详解之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比较典型的代码 ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDe

IntentService源码详解

IntentService可以做什么: 如果你有一个任务,分成n个子任务,需要它们按照顺序完成.如果需要放到一个服务中完成,那么IntentService就会使最好的选择. IntentService是什么: IntentService是一个Service(看起来像废话,但是我第一眼看到这个名字,首先注意的是Intent啊.),所以如果自定义一个IntentService的话,一定要在AndroidManifest.xml里面声明. 从上面的"可以做什么"我们大概可以猜测一下Inten

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

butterknife源码详解

butterknife源码详解 作为Android开发者,大家肯定都知道大名鼎鼎的butterknife.它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码.最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理. 如果你之前不了解Annotation,那强烈建议你先看注解使用. 废多看图: 从图中可以很直观的看出它的module结构,以及使用示例代码. 它的目录和我们在注解使用这篇文章中介绍的一样,大体