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

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

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

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

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

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

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

那么,可以看到

  1. this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);

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

  1. 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

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

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

然后进行映射

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

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

  1. public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<? extends Model<?>> modelClass) {
    tableList.add(new Table(tableName, primaryKey, modelClass));
    return this;
    }
  2.  
  3. public ActiveRecordPlugin addMapping(String tableName, Class<? extends Model<?>> modelClass) {
    tableList.add(new Table(tableName, modelClass));
    return this;
    }

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

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

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

第三步:创建映射关系

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

我们进去看看

  1. 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.");
  2.  
  3. this.name = name.trim();
    this.modelClass = modelClass;
    }
  4.  
  5. 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.");
  6.  
  7. this.name = name.trim();
    setPrimaryKey(primaryKey.trim()); // this.primaryKey = primaryKey.trim();
    this.modelClass = modelClass;
    }

这两个方法都是为Table里的成员变量赋值,第二个方法,也就是带primaryKey参数的那个多出一行,我们看看这一行干了什么

  1. setPrimaryKey(primaryKey.trim()); // this.primaryKey = primaryKey.trim();
  1. 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是我自己定义的类)

  1. public class FinalConfig extends JFinalConfig

这儿涉及到初始化的加载了,我简单的讲一下。

整个JFinal的入口是web.xml的一段配置:

  1. <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,还是点进去看看。

  1. public final class JFinalFilter implements Filter

这个类实现了Filter接口,那就得实现方法init(),doFilter(),destroy()方法。

我们去看init()方法:

  1. public void init(FilterConfig filterConfig) throws ServletException {
    createJFinalConfig(filterConfig.getInitParameter("configClass"));
  2.  
  3. if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)
    throw new RuntimeException("JFinal init error!");
  4.  
  5. handler = jfinal.getHandler();
    constants = Config.getConstants();
    encoding = constants.getEncoding();
    jfinalConfig.afterJFinalStart();
  6.  
  7. String contextPath = filterConfig.getServletContext().getContextPath();
    contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
    }

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

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

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

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

  1. boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
    this.servletContext = servletContext;
    this.contextPath = servletContext.getContextPath();
  2.  
  3. initPathUtil();
  4.  
  5. Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method
    constants = Config.getConstants();
  6.  
  7. initActionMapping();
    initHandler();
    initRender();
    initOreillyCos();
    initI18n();
    initTokenManager();
  8.  
  9. return true;

看这行,下面这行主要是通过Config来加载暴露给程序员的核心文件,JFinalConfig的子类FinalConfig。

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

再点进去

  1. * 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);

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

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

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

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

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

  1. 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 =
  2. (com.jfinal.plugin.activerecord.ActiveRecordPlugin)plugin;
    if (arp.getDevMode() == null)
    arp.setDevMode(constants.getDevMode());
    }
  3.  
  4. 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 =
  5. "Plugin start error: " + plugin.getClass().getName() + ". \n" + e.getMessage();
    log.error(message, e);
    throw new RuntimeException(message, e);
    }
    }
    }
    }

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

  1. for (IPlugin plugin : pluginList) {

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

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

  1. boolean success = plugin.start();

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

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

  1. public boolean start() {
    if (isStarted)
    return true;
  2.  
  3. if (dataSourceProvider != null)
    dataSource = dataSourceProvider.getDataSource();
    if (dataSource == null)
    throw new RuntimeException("ActiveRecord start error:
  4. ActiveRecordPlugin need DataSource or DataSourceProvider");
  5.  
  6. if (config == null)
    config = new Config(configName, dataSource, dialect,
  7. showSql, devMode, transactionLevel, containerFactory, cache);
    DbKit.addConfig(config);
  8.  
  9. boolean succeed = TableBuilder.build(tableList, config);
    if (succeed) {
    Db.init();
    isStarted = true;
    }
    return succeed;
    }

我们直接看与DB映射有关的代码,首先是取得dataSource,dataSourceProvider这个忘了没,忘了就翻到最前面,第一步讲的。

  1. config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache);

这行代码中的dataSource 在插件里配置的C3P0数据源。这里的Config与前面加载FinalConfig的可不是一个啊,千万别看错了,这个是DB的 com.jfinal.plugin.activerecord.Config。

第五步:TableBuilder

来自ActiveRecordPlugin.java

  1. boolean succeed = TableBuilder.build(tableList, config);
  1. 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,
  2. maybe the table " + temp.getName() + " is not exists.");
    throw new ActiveRecordException(e);
    }
    finally {
    config.close(conn);
    }
    }

这里循环所有的tableList,对每个Table对象进行建表。那么我们先看看Table是用什么来存储数据库映射关系的,相信大家都能猜到是Map了。

  1. public class Table {
  2.  
  3. private String name;
    private String primaryKey;
    private String secondaryKey = null;
    private Map<String, Class<?>> columnTypeMap; // config.containerFactory.getAttrsMap();
  4.  
  5. private Class<? extends Model<?>> modelClass;

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

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

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

那么第六步:doBuild详解。

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

  1. @SuppressWarnings("unchecked")
    private static void doBuild(Table table, Connection conn, Config config) throws SQLException {
  2.  
  3. // 初始化 Table 里的columnTypeMap字段。
    table.setColumnTypeMap(config.containerFactory.getAttrsMap());
    // 取得主键,如果取不到的话,默认设置"id"。
    // 记不记得最开始的两个同名不同参的方法 addMapping(...),
  4. 在这才体现出后续处理的不同。
    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)) {
  5. // java.util.Data can not be returned
    // java.sql.Date, java.sql.Time,
  6. 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
  7. ("You've got new type to mapping. Please add code in " + TableBuilder.class.getName()
  8. + ". The ColumnClassName can't be mapped: " + colClassName);
    }
    }
  9.  
  10. rs.close();
    stm.close();
    }

这里巧妙的运用了 where 1=2的无检索条件结果,通过ResultSetMetaData rsmd = rs.getMetaData(); 导出了DB模型,这招确实漂亮。之前我还冥思苦相,他是怎么做的呢,看着此处源码,茅塞顿开。

接着,把编辑好的Table实例,放到TableMapping的成员变量 Model<?>>, Table> modelToTableMap 里去,TableMapping是单例的。

  1. private final Map<Class<? extends Model<?>>, Table> modelToTableMap=
  2. new HashMap<Class<? extends Model<?>>, Table>();
  1. public void putTable(Table table) {
    modelToTableMap.put(table.getModelClass(), table);
    }

这样,所有的映射关系就都存在TableMapping的modelToTableMap

  1. tableMapping.putTable(table);

再将modelToConfig都放入DbKit.modelToConfig里。

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

第七步,使用

Model里的save方法举例:

  1. /**
    * Save model.
    */
    public boolean save() {
    Config config = getConfig();
    Table table = getTable();
  2.  
  3. StringBuilder sql = new StringBuilder();
    List<Object> paras = new ArrayList<Object>();
    config.dialect.forModelSave(table, attrs, sql, paras);
    // if (paras.size() == 0) return false;
  4. // The sql "insert into tableName() values()" works fine, so delete this line
  5.  
  6. // --------
    Connection conn = null;
    PreparedStatement pst = null;
    int result = 0;
    try {
    conn = config.getConnection();
    if (config.dialect.isOracle())
    pst = conn.prepareStatement(sql.toString(),
  7. new String[]{table.getPrimaryKey()});
    else
    pst = conn.prepareStatement(sql.toString(),
  8. Statement.RETURN_GENERATED_KEYS);
  9.  
  10. 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);
    }
    }
  1. Config config = getConfig();

上面这行就是调用DbKit的方法,取得DB配置。

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

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

  1. Table table = getTable();
  1. private Table getTable() {
    return TableMapping.me().getTable(getClass());
    }

JFinal源码详解的更多相关文章

  1. Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

    Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

  2. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  3. 条件随机场之CRF++源码详解-预测

    这篇文章主要讲解CRF++实现预测的过程,预测的算法以及代码实现相对来说比较简单,所以这篇文章理解起来也会比上一篇条件随机场训练的内容要容易. 预测 上一篇条件随机场训练的源码详解中,有一个地方并没有 ...

  4. [转]Linux内核源码详解--iostat

    Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

  5. saltstack源码详解一

    目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...

  6. Shiro 登录认证源码详解

    Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...

  7. udhcp源码详解(五) 之DHCP包--options字段

    中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说 ...

  8. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  9. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

随机推荐

  1. go基础语法

    定义变量: 可放在函数内,或直接放在包内使用var集中定义变量使用:=定义变量写的短一些 package main import ( "fmt" "math" ...

  2. 20162314 Sortingtest-work after class

    20162314 Sortingtest-work after class Content Data : 90 8 7 56 123 235 9 1 653. Use JDB or IDEA to t ...

  3. Eclipse安装zylin[转]

    本文转载自:https://blog.csdn.net/dns888222/article/details/9263485 Eclipse安装zylin 在网上搜的是安装页为http://www.zy ...

  4. Flutter中集成Font Awesome

    1.添加引用 在 pubspec.yaml文件中,加入 font awesome的引用 dependencies: flutter: sdk: flutter # The following adds ...

  5. RMI远程方法调用

    RMI远程方法调用:适用于 客户端 调用 服务器 内的方法:(Kotlin 语言编写) 如果业务为二个服务器之间的通信,还是得用消息队列的形式,因为RMI 不适合 双向 调用 下面介绍RMI 的使用方 ...

  6. hadoop的安装配置

    资源下载路径:https://archive.cloudera.com/cdh5/cdh/5/:https://archive.cloudera.com/cdh5/cdh/5/hadoop-2.6.0 ...

  7. 两个常见tomcat警告分析

    1. 警告描述: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.e ...

  8. ubuntu 16.04网速监控脚本

    #!/bin/bashif [ $# -ne 1 ];thendev="enp2s0"elsedev=$1fi while :doRX1=`/sbin/ifconfig $dev ...

  9. Educational Codeforces Round 42 (Rated for Div. 2)F - Simple Cycles Edges

    http://codeforces.com/contest/962/problem/F 求没有被两个及以上的简单环包含的边 解法:双联通求割顶,在bcc中看这是不是一个简单环,是的话把整个bcc的环加 ...

  10. JAVA常用数据结构API

    Quene