Jfinal-Plugin源码解读

2023-02-22,,

PS:cnxieyang@163.com/xieyang@e6yun.com

本文就Jfinal-plugin的源码进行分析和解读

Plugin继承及实现关系类图如下,常用的是Iplugin的三个集成类:DruidPlugin、RedisPlugin、ActiveRecordPlugin,下文就父类及3个实现类做解读
IPlugin详解
IPlugin为Jfinal插件实现类的接口,提供了两个抽象的方法:boolean start(); boolean stop();供子类实现,Jfinal在加载时,调用实现类的start()方法启动插件,所谓插件是将某个业务模块进行了封装,如redis、dbcp等。定义方法如下

	boolean start();
	boolean stop();  

IDataSourceProvider详解
IDataSourceProvider提供获取DataSource的方法,其本身为接口,凡是与数据库相关的插件都可以实现IDataSourceProvider接口,接口中的方法如下图

DataSource getDataSource();

如何加载自定义插件
1.在JFinalConfig实现类的configPlugin(Plugins me)方法中创建对象,将启动逻辑写到插件继承类的start方法中
2.在JFinal的init-->Config.configJFinal(jfinalConfig)-->startPlugins()中完成对插件的start方法的调用,加载插件成功
RedisPlugin源码解读(不深入到redis,仅读取到Jredis对象的获取,其余部分在后续的redis章节讲解)
PS:此处基本上没有难度,自行研究即可
包括:主缓存、redis缓存的缓存、缓存方法的封装
DruidPlugin源码解读(仅读取到数据库连接的获取,其余部分在后续得Druid高级使用模块讲解)
DruidPlugin主要是多Druid的DruidDataSource注入了相关的参数变量,不做分析
ActiveRecordPlugin源码
ActiveRecordPlugin是Jfinal引入的持久层的新思路,下文重点讲解什么是ActiveRecordPlugin、用途、高级使用等
ActiveRecordPlugin实现的是DB+ActiveRecord模式

/**
     * 配置插件
     */
    public void configPlugin(Plugins me) {
        // 配置C3p0数据库连接池插件
        DruidPlugin druidPlugin = createDruidPlugin();
        me.add(druidPlugin);

        // 配置ActiveRecord插件,需要传入IDataSourceProvider dataSourceProvider,druidPlugin刚刚好是IDataSourceProvider的子类        //A1:初始化ActiveRecordPlugin 
         ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);        //A2:所有映射在中自动化搞定,加载映射关系        arp.addMapping("blog", "id", Blog.class);        me.add(arp);    }
ActiveRecordPlugin如何初始化1.调用下方构造
public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {
        this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);
    }

2.继续调用ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider)构造,如下

public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider) {
        this(configName, dataSourceProvider, DbKit.DEFAULT_TRANSACTION_LEVEL);
    }

3.传入事务的级别后再调用构造,如下

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");
        }        //IDataSourceProvider 的对象赋值
       this.dataSourceProvider = dataSourceProvider;       //赋值当前对象的config属性的属性值,PS代码省略       this.config = new Config(configName, null, transactionLevel); }

模型与表明如何映射
JFinal为我们提供重载的两个方法,实现对数据表与实体模型的映射,如下放所示

  //带有主键,主键以逗号隔开   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;
    }

1.有主键的Table的初始化

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.modelClass = modelClass;
    }
//将以逗号隔开的主键拆分数数组并赋值给primaryKey对象void setPrimaryKey(String primaryKey) {
String[] arr = primaryKey.split(",");
for (int i=0; i<arr.length; i++)
arr[i] = arr[i].trim();
this.primaryKey = arr;
}

2.无主键的Table的初始化

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

3.存储Table关系到List中
ps:ActiveRecordPlugin类中提供了私有集合private List<Table> tableList = new ArrayList<Table>();用来存储table映射到List中

ActiveRecordPlugin加载并初始化映射关系
PS:插件的启动在JFinal启动源码解读博客章节做了详细介绍<http://www.cnblogs.com/cnxieyang/p/7236734.html>请点击参照。
    循坏调用所有插件的Start方法,完成插件的启动,ActiveRecordPlugin的start启动方法如下,下方对所有实现做详细介绍

public boolean start() {
		if (isStarted) {
			return true;
		}
		if (config.dataSource == null && dataSourceProvider != null) {
			config.dataSource = dataSourceProvider.getDataSource();
		}
		if (config.dataSource == null) {
			throw new RuntimeException("ActiveRecord start error: ActiveRecordPlugin need DataSource or DataSourceProvider");
		}

		config.sqlKit.parseSqlTemplate();
		//C1:构建映射
		new TableBuilder().build(tableList, config);                //添加配置
               	DbKit.addConfig(config);
		isStarted = true;
		return true;
	}

1.C1:构建映射调用build方法

void build(List<Table> tableList, Config config) {
        // 支持 useAsDataTransfer(...) 中的 arp.start() 正常运作
        if (config.dataSource instanceof NullDataSource) {
            return ;
        }

        Table temp = null;
        Connection conn = null;
        try {
            conn = config.dataSource.getConnection();
            TableMapping tableMapping = TableMapping.me();
            for (Table table : tableList) {
                temp = table;                //C11:构建
                doBuild(table, conn, config);                //Map集合中存储映射关系
                tableMapping.putTable(table);
                DbKit.addModelToConfigMapping(table.getModelClass(), config);
            }
        } 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);
        }
    }

C11:实现如下

@SuppressWarnings("unchecked")
    private void doBuild(Table table, Connection conn, Config config) throws SQLException {
        table.setColumnTypeMap(config.containerFactory.getAttrsMap());
        if (table.getPrimaryKey() == null) {
            table.setPrimaryKey(config.dialect.getDefaultPrimaryKey());
        }
        //构建查询数据库字段的sql
        String sql = config.dialect.forTableBuilderDoBuild(table.getName());
        Statement stm = conn.createStatement();
        ResultSet rs = stm.executeQuery(sql);
        ResultSetMetaData rsmd = rs.getMetaData();
        //遍历数据表字段,将数据表字段类型映射成JAVA类型,并存储到Table对象中
        for (int i=1; i<=rsmd.getColumnCount(); i++) {
            String colName = rsmd.getColumnName(i);
            String colClassName = rsmd.getColumnClassName(i);

            Class<?> clazz = javaType.getType(colClassName);
            if (clazz != null) {
                table.setColumnType(colName, clazz);
            }
            else {
                int type = rsmd.getColumnType(i);
                if (type == Types.BINARY || type == Types.VARBINARY || 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();
    }

备注:以上完成了ActiveRecordPlugin的初始化,后续详解如何使用Db+ActiveRecord的方式加载或者写入数据
PS:时间不早了,休息了。。。

Jfinal-Plugin源码解读的相关教程结束。

《Jfinal-Plugin源码解读.doc》

下载本文的Word格式文档,以方便收藏与打印。