JFinal源码解析与思想理解

动机

在做项目的过程中运用到了JFinal,由于是第一次看这样框架的源码,所以很多东西都不知道。想多了解一些架构的思想和Web学习的基本知识。本文主要从大致的方面介绍JFinal,对于细节不做深究,而且本文的源码只剪辑了真正源码的部分。

总体思想

首先要了解一哈基本的知识:

  • ORM:Object Relational Mapper,is the layer that sits between your database and your application. In Object -Oriented Programming, you work with Objects as your main point of reference.也就是说假如在数据库里面有一个表,那么在应用程序里面就有一个类与这个表相对应,类中的成员变量是数据库的列名,一个类的实例是数据库的一行数据。
  • ActiveRecord:是一种模式。然后我们先来理解Record,字段,也就是数据库里面的一行数据。对于ORM来说,我们定义一个Model类作为ORM的基类,然后继承这个ORM基类。
class User extends Model{
 ....
}
User user = new User();
user.setName("Ferrair");
user.save();

这样只需要直接的调用save()方法,就会在数据库里面插入一条数据。而在基类Model里面,save()方法封装了SQL语句。

对于Active可以把它理解为持久的意思,也就是说与数据库是持久连接。

然后可以参考这些文章

什么是ActiveRecord

What’s the difference between Active Record and Data Mapper?(这里面还有一种思想叫Data-Mapper)

- POJO:什么是POJO(Plain Ordinary Java Object),即简单Java对象,关于POJO可以把它和JavaBean进行比较,POJO比JavaBean限制要多的多,一般在应用程序里面的与数据库映射的对象就叫POJO.在JFinal里面也就是继承Model的类.

然后看看JFinal作者给出的图:

JFinal由Handler、Interceptor、Controller、Render、Plugin五大部分组成,以Action为参照,Handler处在扩展的最外围,Interceptor处在更贴近Action的周围,Controller承载Action处在扩展的中心,Render处于Action后端,Plugin处于在Action侧边位置。

在上面的图中首先一个Request过来进入到JFinalFiter,然后运用了责任链的模式,将请求一层层的传递,直到ActionHandler,在这里进行路由,路由到具体的Controller,而对于Model则是在Plugin里面实现的.这一切进行好了之后,就可以使用Render来渲染View了。大致流程如上。

采用了下面的模式:

- DB + ActiveRecord

- Web MVC + ORM

- 责任链的设计模式

Jfinal初始化

首先在web.xml定义了JFinalFilter,所以这个框架才可以被运行,在JFinalFilter里面的init()找到下面这些初始化的过程

temp = Class.forName(configClass).newInstance();
jfinalConfig = (JFinalConfig)temp; // 在web.xml得到具体的配置文件

jfinal.init(jfinalConfig, filterConfig.getServletContext()) // 真正的初始化过程

然后再去看看jfinal.init()

    boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
        this.servletContext = servletContext;
        this.contextPath = servletContext.getContextPath();

        initPathUtil();

        Config.configJFinal(jfinalConfig);  // start plugin and init log factory in this method
        constants = Config.getConstants();

        initActionMapping();
        initHandler();
        initRender();
        initOreillyCos();
        initTokenManager();

        return true;
    }

    private void initTokenManager() {
        ITokenCache tokenCache = constants.getTokenCache();
        if (tokenCache != null)
            TokenManager.init(tokenCache);
    }

    private void initHandler() {
        Handler actionHandler = new ActionHandler(actionMapping, constants);
        // 由于Handler是责任链模式,这是只是得到链的第一个元素
        handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
    }

    private void initOreillyCos() {
        OreillyCos.init(constants.getBaseUploadPath(), constants.getMaxPostSize(), constants.getEncoding());
    }

    private void initPathUtil() {
        String path = servletContext.getRealPath("/");
        PathKit.setWebRootPath(path);
    }

    private void initRender() {
        RenderFactory.me().init(constants, servletContext);
    }

    private void initActionMapping() {
        actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors());
        // buildActionMapping 运用反射设置好了Route,即把URI和Controller进行了匹配
        actionMapping.buildActionMapping();
        Config.getRoutes().clear();
    }

Config.configJFinal(jfinalConfig)里面可以看到:


/*
 * Config order: constant, route, plugin, interceptor, handler
 * JFinalConfig其实是我们自己实现JFinalConfig的一个类 这里得到具体的配置
 */
static void configJFinal(JFinalConfig jfinalConfig) {
        jfinalConfig.configConstant(constants);             initLogFactory();
        jfinalConfig.configRoute(routes);
        jfinalConfig.configPlugin(plugins);                 startPlugins(); // very important!!!
        jfinalConfig.configInterceptor(interceptors);
        jfinalConfig.configHandler(handlers);
    }

所以回到jfinal.init()方法里面可以看到initHandler()initActionMapping()把一些类给初始化了。

初始化的大致流程就是这样。我们总结一哈思路:

- 使用Filter对用户所有的请求进行拦截

- 获得JFinalConfig里面的配置方法的属性

- 依次对Handler,Route,Render进行初始化

Handler + Action

Handler可以分为程序员定义的和Jfinal自带的(也叫ActionHandler),而这里程序员自定的Handler只需要实现Handler接口,然后再注册到JFinalHandler里面就可以了。而这里主要是讨论ActionHandler

JFinal.initHandler()这个方法

private void initHandler() {
        Handler actionHandler = new ActionHandler(actionMapping, constants);
        handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
    }

可以看到在这里注册了一个ActionHandler。那么到现在还没有说什么是ActionHandler,ActionHandler就是处理客户端向URI请求,那么ActionHandler就是处理这类请求的

// 得到请求的Action
Action action = actionMapping.getAction(target, urlPara);
// 初始化Controller
Controller controller = action.getControllerClass().newInstance();
            controller.init(request, response, urlPara[0]);
// 在这里调用请求的URI对应的方法
new Invocation(action, controller).invoke();

// 这里重新定位到其他的URI对于的方法里面去
handle(actionUrl, request, response, isHandled);

DB + ActiveRecord

就像前面所说的那样ActiveRecord,Active是持久化的意思,Record是数据库里面的记录,在JFinal里面既然实现了这个模式,那么Model就是所说的ORM记录,只要有一个POJO类继承这个类,然后在把注册到JFinalConfig里面,然后数据库里面的列和这个POJO里面的属性一一对应的话,就可以调用Model里面的save(),find(),delete()进行数据库操作了。那么在JFinal里面是怎么实现的了?我们下面进入源码来看看.

首先有个IPlugin接口,定义了数据库连接池Plugin的启动与释放.

public interface IPlugin {
    boolean start();
    boolean stop();
}

然后ActiveRecordPlugin实现了这个接口,完成了数据库连接的一些配置,然后里面有个成员变量

private IDataSourceProvider dataSourceProvider = null;

也就是数据库连接池,在这里我们使用C3p0。

那么我们怎么对数据库进行CRUL呢?

Model里面,这里贴出find()的源码

/**
     * Find model.
     */
    private List<M> find(Connection conn, String sql, Object... paras) throws Exception {
        Config config = getConfig();
        Class<? extends Model> modelClass = getUsefulClass();
        if (config.devMode)
            checkTableName(modelClass, sql);

        PreparedStatement pst = conn.prepareStatement(sql);
        config.dialect.fillStatement(pst, paras);
        ResultSet rs = pst.executeQuery();
        List<M> result = ModelBuilder.build(rs, modelClass);
        DbKit.close(rs, pst);
        return result;
    }

使用了PreparedStatement预处理来防止SQL注入攻击,而dialect这个变量就是真正进行SQL语句的类,

public abstract class Dialect

为一个抽象类,其实现类可以为MySQLDialectOracleDialect等,这样做的好处就是无需在意具体的数据库类型,而只要在配置文件里面进行配置了之后,就可以操作数据库了。比如下面

public String forModelDeleteById(Table table) {
        String[] pKeys = table.getPrimaryKey();
        StringBuilder sql = new StringBuilder(45);
        sql.append("delete from `");
        sql.append(table.getName());
        sql.append("` where ");
        for (int i=0; i<pKeys.length; i++) {
            if (i > 0) {
                sql.append(" and ");
            }
            sql.append("`").append(pKeys[i]).append("` = ?");
        }
        return sql.toString();
    }

Render

后端给前段和移动端提供了API,在我的项目里是返回了JSON数据。那么为什么一行代码renderJson(jsonText)就可以实现这个功能呢?我们来看看JFinal的构架。

上文说到在JFinal.java里面有一个initRender()的方法。

private void initRender() {
        RenderFactory.me().init(constants, servletContext);
    }

这里采用的工厂方法,得到一个自己的实例,进行初始化。为什么这里要用Singeton模式了??因为对于所有的Controller只需要一个Render就可以了哈,这是由逻辑关系而决定的。init()代码如下:

public void init(Constants constants, ServletContext servletContext) {
        this.constants = constants;
        this.servletContext = servletContext;

        // init Render
        Render.init(constants.getEncoding(), constants.getDevMode());
        initFreeMarkerRender(servletContext);
        initVelocityRender(servletContext);
        initJspRender(servletContext);
        initFileRender(servletContext);

        // create mainRenderFactory
        if (mainRenderFactory == null) {
            ViewType defaultViewType = constants.getViewType();
            if (defaultViewType == ViewType.FREE_MARKER) {
                mainRenderFactory = new FreeMarkerRenderFactory();
            } else if (defaultViewType == ViewType.JSP) {
                mainRenderFactory = new JspRenderFactory();
            } else if (defaultViewType == ViewType.VELOCITY) {
                mainRenderFactory = new VelocityRenderFactory();
            } else {
                throw new RuntimeException("View Type can not be null.");
            }
        }

        // create errorRenderFactory
        if (errorRenderFactory == null) {
            errorRenderFactory = new ErrorRenderFactory();
        }

        if (xmlRenderFactory == null) {
            xmlRenderFactory = new XmlRenderFactory();
        }
    }

由于我们是renderJson(),所以这些舒适化暂时没有什么用,我们就不说了,对这个RenderFactory工厂进行初始化之后。当调用renderJson()的时候,Controller里面就可以看到这么简单的一行代码

public void renderJson(String key, Object value) {
        render = renderFactory.getJsonRender(key, value);
    }

那么在哪里是生成JSON数据的地方呢?我们进去renderFactory.getJsonRender(key, value)看看,

public Render getJsonRender(String key, Object value) {
        return new JsonRender(key, value);
    }

进一步的看看JsonRender

public class JsonRender extends Render{...}

public abstract class Render
{
    public abstract void render();
}

重点注意JsonRender继承Render类,而Render里面有一个render()的抽象方法,可以发现JsonRender.render()就会把JSON数据render给客户端,事实就是如此:

public void render() {
        if (jsonText == null)
            buildJsonText();

        PrintWriter writer = null;
        try {
            response.setHeader("Pragma", "no-cache");   // HTTP/1.0 caches might not implement Cache-Control and might only implement Pragma: no-cache
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);

            response.setContentType(forIE ? contentTypeForIE : contentType);
            writer = response.getWriter();
            writer.write(jsonText);
            writer.flush();
        } catch (IOException e) {
            throw new RenderException(e);
        }
        finally {
            if (writer != null)
                writer.close();
        }
    }

,那么render什么时候调用了?在一开始给出的图里面,发现Render是在ActionHandler里面的,既然在ActionHandler里面,就会在handler()方法里面被调用了。

if (render == null)
                render = renderFactory.getDefaultRender(action.getViewPath() + action.getMethodName());
            render.setContext(request, response, action.getViewPath()).render();

就是这么简单。

一些想法

虽然没有看过SSH和SSM的源码,但是JFinal其核心思想应该都是一样的.

- 首先在Controller里面利用注解继续路由=>这样的话,每个方法都是一个URI对应的。可以使得每个方法之间降低了耦合度,而在每个方法的内部,只处理一个URI,具有单一职责。

- ORM框架,使得程序员在编写数据库的时候,不必处理数据库的表,记录和POJO的对应关系。而且在代码的阅读与简洁性上更加的清晰。

- Handler,Interceptor与责任链模式。可以让程序员进行一些拓展进行AOP编程。使得框架的健壮性更好。

- 利用Dialect接口,和策略模式。只需在使用Dialect使用接口就可以了,而不必在意具体的实现类,实现了支持多数据库的操作。这也是策略模式的好处。

- Render的实现,利用工厂模式,可以制造出不同的Render类。而减少了Render的代码,同时工厂类制造的是抽象类,而不是具体实现类,增加了可拓展性。符合多个Render的需求。同时把Render具体渲染的逻辑放在了ActionHandler而不是在Controller,可以使得子类在继承Controller时,没有过多的代码逻辑,把程序员不需要关心的类放在了ActionHandler里面,程序员只需要了解Controller就可以.



好吧,我只是大二本科生,大家别见怪,理解的不是很好。

邮箱:[email protected]

时间: 2024-10-25 21:04:32

JFinal源码解析与思想理解的相关文章

Block源码解析和深入理解

Block源码解析和深入理解 Block的本质 Block是"带有自动变量值的匿名函数". 我们通过Clang(LLVM编译器)来将OC的代码转换成C++源码的形式,通过如下命令: clang -rewrite-objc 源代码文件名 下面,我们要转换的Block语法 1 2 3 4 5 6 7 int main(int argc, const char * argv[]) { void (^blk)(void) = ^{ printf("Block\n"); };

mybatis源码解析之架构理解

mybatis是一个非常优秀的开源orm框架,在大型的互联网公司,基本上都会用到,而像程序员的圣地—阿里虽然用的是自己开发的一套框架,但其核心思想也无外乎这些,因此,去一些大型互联网公司面试的时候,总是会问到对于这些开源框架的理解,有没有阅读开源框架的源码,虽说是面试造火箭,工作拧螺丝,但是让你造火箭的时候,你总得能伸把手啊.所以,我们既然不能改变大厂,那只能自己去提升源码阅读的能力了. mybatis其实做的事情很简单,就是封装了对数据库的访问,我们开发的时候,只需要写接口,传参,配置数据库地

Android EventBus源码解析, 带你深入理解EventBus

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢? 1.概述 一般使用EventBus的组件类,类似下面这种方式: [java] view plain copy public class SampleComponent extends Fragment { @Override public vo

Android AsyncTask完全解析,带你从源码的角度彻底理解

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

[学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.5版本就引入了一个AsyncTask类,使用它就可以非常灵活方便地从子线程切换到UI线程,我们本篇文章的主角也就正是它了. Asyn

[学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起

[转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识. 也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的 功能,ListView就不能滚动了?为什么图片轮播器里的图

(转) Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima