JFinal 源码超详细解析之DB+ActiveRecord

我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc、aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下,用最快的速度把文章从头到尾读一遍,那结果就是当你读完它,你也不清楚它讲了一个什么故事,想表达什么。

一个优秀的架构的源码我认为就好像一本名著一样,你的“文学”水平越高,你就越能读出作者设计的精妙之处。一篇源码在你不同水平的时候,能读出不同的东西,因此,我觉得优秀的框架的源码是经久不衰的,反复读多少次都不嫌多,直到你能设计出预期并驾齐驱甚至超越它的优美的架构。

读源码起初是一件很痛苦的事儿,想赶紧把它像流水账一样的读完;慢慢实力增强后,会感觉到读源码能够不费力气的读通;再假以时日,就能看出这些精妙的设计模式的组合。我有一个朋友,典型的源码痴狂症,他跟我说他第一次看见spring的源码,感觉特别兴奋,读了一宿没睡觉.......好吧,我还有很长的路需要走~

话说多了,我们赶紧入正题:

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

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

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

来自FinalConfig.java

        // 采用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<? 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是什么

来自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<?>> 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参数的那个多出一行,我们看看这一行干了什么

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

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

下面我们还是回到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<? extends Model> modelClass) {
		return modelToConfig.get(modelClass);
	}

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

来自Model.java

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

以上,就是DB+ActiveRecord的核心调用流程,下次我会带来初始化流程,不过这是个大活,估计要分开三章来写吧。

JFinal 源码超详细解析之DB+ActiveRecord

时间: 2024-10-10 02:40:55

JFinal 源码超详细解析之DB+ActiveRecord的相关文章

JFinal 源码分析 [DB+ActiveRecord]

我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下,用最快的速度把文章从头到尾读一遍,那结果就是当你读完它,你也不清楚它讲了一个什么故事,想表达什么. 一个优秀的架构的源码我认为就好像一本名著一样,你的“文学”水平越高,你就越能读出作者设计的精妙之处.一篇源码在你不同水平的时候,能读出不同的东西,因此,我觉得优秀的框架的源码是经久不衰的,反复读多少次都不嫌多,

Python:Sqlmap源码精读之解析xml

XML <?xml version="1.0" encoding="UTF-8"?> <root> <!-- MySQL --> <dbms value="MySQL"> <cast query="CAST(%s AS CHAR)"/> <length query="LENGTH(%s)"/> <isnull query=&quo

MyBatis 源码分析 - 配置文件解析过程

* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAliases和typeHandlers等,本文的篇幅也主要在对这三个配置解析过程的分析上.下面,我们来一起看一下本篇文章的目录结构. 从目录上可以看出,2.3节.2.5节和2.8节的内容比较多.其中2.3节是关于settings配置解析过程的分析,除了对常规的 XML 解析过程分析,本节额外的分析了元

Spark技术内幕:Worker源码与架构解析

首先通过一张Spark的架构图来了解Worker在Spark中的作用和地位: Worker所起的作用有以下几个: 1. 接受Master的指令,启动或者杀掉Executor 2. 接受Master的指令,启动或者杀掉Driver 3. 报告Executor/Driver的状态到Master 4. 心跳到Master,心跳超时则Master认为Worker已经挂了不能工作了 5. 向GUI报告Worker的状态 说白了,Worker就是整个集群真正干活的.首先看一下Worker重要的数据结构: v

【转】Android 带你从源码的角度解析Scroller的滚动实现原理

今天给大家讲解的是Scroller类的滚动实现原理,可能很多朋友不太了解该类是用来干嘛的,但是研究Launcher的朋友应该对他很熟悉,Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 在介绍Scrol

算法核心——空间复杂度和时间复杂度超详细解析

算法核心——空间复杂度和时间复杂度超详细解析 一.什么是算法 算法: 一个有限指令集 接受一些输入(有些情况下不需要收入) 产生输出 一定在有限步骤之后终止 每一条指令必须: 有充分明确的目标,不可以有歧义 计算机能处理的范围之内 描述应不依赖于任何一种计算机语言以及具体的实现手段 其实说白了,算法就是一个计算过程解决问题的方法.我们现在已经知道数据结构表示数据是怎么存储的,而“程序=数据结构+算法”,数据结构是静态的,算法是动态的,它们加起来就是程序. 对算法来说有输入,有输出,相当于函数有参

JFinal源码解析与思想理解

动机 在做项目的过程中运用到了JFinal,由于是第一次看这样框架的源码,所以很多东西都不知道.想多了解一些架构的思想和Web学习的基本知识.本文主要从大致的方面介绍JFinal,对于细节不做深究,而且本文的源码只剪辑了真正源码的部分. 总体思想 首先要了解一哈基本的知识: ORM:Object Relational Mapper,is the layer that sits between your database and your application. In Object -Orien

Jfinal源码详解

JFinal的框架我24号的一篇博文写到过,它优秀的地方在精简代码上,那么有两处源码是我觉得是值得我们要好好解析一下,一处是初始化加载—servlet跳转,另一处是DB+ActiveRecord的映射. 那么DB映射相对比较简单,我们这次就先来看看. 首先我们看看代码,还是之前我写过的 dog与cat的故事. // 采用DB+ActiveRecord模式 ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); me.add(arp

从源码的角度解析View的事件分发

有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的. 在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了.目前虽