JFinal实现原理

刚接触Jfinal,后面继续整理;

Jfinal源码解析
http://blog.csdn.net/soul_code/article/category/6337939

JFinal初始化过程浅析
http://blog.csdn.net/huilangeliuxin/article/details/37961385

JFinal实现原理
http://blog.csdn.net/huilangeliuxin/article/details/37961385

JFinal实现原理

一切都从web.xml开始说起:

当服务器初始化的时候会初始化web.xml里面的相关配置信息;

下面我们来看一个重要的过滤器配置:JFinalFilter。下面是相关的配置信息。

<filter>
    <filter-name>jfinal</filter-name>
    <filter-class>com.jfinal.core.JFinalFilter</filter-class>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>configClass</param-name>
        <param-value>net.dreamlu.common.WebConfig</param-value>
    </init-param>
</filter>

这就意味着所有的请求都要经过JFinalFiter过滤了

从中我们可以看到在web.xml之中配置了一个名为JFinalFilter的过滤器。下面我们来看看JFinalFiter的源码。

/**
 * JFinal framework filter
 */
public final class JFinalFilter implements Filter {

    private Handler handler;
    private String encoding;
    private JFinalConfig jfinalConfig;
    private Constants constants;
    private static final JFinal jfinal = JFinal.me();
    private static Log log;
    private int contextPathLength;

    // 系统在初始化Servlet的时候自动调用该方法;
    public void init(FilterConfig filterConfig) throws ServletException {
        // 创建JFianlConfig对象
        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();

        // afterJFinalStart 方法
        jfinalConfig.afterJFinalStart();

        // 得到项目根路径
        String contextPath = filterConfig.getServletContext().getContextPath();
        contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
    }

    // request和response的作用不用过多介绍了。这个FilterChain的左右主要是用来连续调用下一个Filter时候使用的,
    // 下面给出了FilterChain的作用介绍.
    /**

   * A FilterChain is an object provided by the servlet container to the developer

   * giving a view into the invocation chain of a filtered request for a resource. Filters

   * use the FilterChain to invoke the next filter in the chain, or if the calling filter

   * is the last filter in the chain, to invoke the resource at the end of the chain.

   *

   * @see Filter

   * @since Servlet 2.3

   **/
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        request.setCharacterEncoding(encoding);

        String target = request.getRequestURI();
        if (contextPathLength != 0)

            // 得到其ServletPath以及相关的参数
            target = target.substring(contextPathLength);

        boolean[] isHandled = {false};
        try {
            // isHandler用来判断该Target是否应该被相应的handler处理;
            // 这是整个Filter的最核心方法
            handler.handle(target, request, response, isHandled);
        }
        catch (Exception e) {
            if (log.isErrorEnabled()) {
                String qs = request.getQueryString();
                log.error(qs == null ? target : target + "?" + qs, e);
            }
        }
        // 该Target没被处理,进入下一个handler;
        if (isHandled[0] == false)
            chain.doFilter(request, response);
    }

    public void destroy() {
        // beforeJFinalStop方法
        jfinalConfig.beforeJFinalStop();
        // 停止插件
        jfinal.stopPlugins();
    }

    // 创建JFianlConfig对象, 读取app配置文件
    private void createJFinalConfig(String configClass) {
        if (configClass == null)
            throw new RuntimeException("Please set configClass parameter of JFinalFilter in web.xml");

        Object temp = null;
        try {
            temp = Class.forName(configClass).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Can not create instance of class: " + configClass, e);
        }

        if (temp instanceof JFinalConfig)
            jfinalConfig = (JFinalConfig)temp;
        else
            throw new RuntimeException("Can not create instance of class: " + configClass + ". Please check the config in web.xml");
    }

    static void initLog() {
        log = Log.getLog(JFinalFilter.class);
    }
}

让我们重点看看这个handler的由来。

首先由handler = jfinal.getHandler();知道这个handler是由jfinal对象得来的,现在让我们看看jfinal的部分源码:

/**
 * JFinal
 */
public final class JFinal {

    private Constants constants;
    private ActionMapping actionMapping;
    private Handler handler;    // 定义了其他成员变量;
    private ServletContext servletContext;
    private String contextPath = "";
    private static IServer server;

    private static final JFinal me = new JFinal();

    private JFinal() {
    }

    public static JFinal me() {
        return me;
    }

    // 初始化JFinal时候调用的方法(在上面已经提到过这一点)
    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 = 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());
        actionMapping.buildActionMapping();
        Config.getRoutes().clear();
    }

    // 停止插件;从后往前遍历的
    void stopPlugins() {
        List<IPlugin> plugins = Config.getPlugins().getPluginList();
        if (plugins != null) {
            for (int i=plugins.size()-1; i >= 0; i--) {     // stop plugins
                boolean success = false;
                try {
                    success = plugins.get(i).stop();
                }
                catch (Exception e) {
                    success = false;
                    LogKit.error(e.getMessage(), e);
                }
                if (!success) {
                    System.err.println("Plugin stop error: " + plugins.get(i).getClass().getName());
                }
            }
        }
    }

    Handler getHandler() {
        return handler;
    }

    public Constants getConstants() {
        return Config.getConstants();
    }

    public String getContextPath() {
        return contextPath;
    }

    public ServletContext getServletContext() {
        return this.servletContext;
    }

    public Action getAction(String url, String[] urlPara) {
        return actionMapping.getAction(url, urlPara);
    }

    public List<String> getAllActionKeys() {
        return actionMapping.getAllActionKeys();
    }

    // start方法,会在main方法调用;
    public static void start() {
        server = ServerFactory.getServer();
        server.start();
    }

    public static void start(String webAppDir, int port, String context, int scanIntervalSeconds) {
        server = ServerFactory.getServer(webAppDir, port, context, scanIntervalSeconds);
        server.start();
    }

    public static void stop() {
        server.stop();
    }

    /**
     * Run JFinal Server with Debug Configurations or Run Configurations in Eclipse JavaEE
     * args example: WebRoot 80 / 5
     */
    public static void main(String[] args) {
        if (args == null || args.length == 0) {
            server = ServerFactory.getServer();
            server.start();
        }
        else {
            String webAppDir = args[0];
            int port = Integer.parseInt(args[1]);
            String context = args[2];
            int scanIntervalSeconds = Integer.parseInt(args[3]);
            server = ServerFactory.getServer(webAppDir, port, context, scanIntervalSeconds);
            server.start();
        }
    }
}

由这里我们知道handler是由HandlerFactory的getHandler方法得来的。

让我们再次看看HandlerFactory的部分源码以探个究竟:

/**
 * HandlerFactory.
 */
public class HandlerFactory {

    private HandlerFactory() {

    }

    /**
     * Build handler chain
     *
     * 显然这里返回的是一个actionHandler为首handler chain。
     */
    @SuppressWarnings("deprecation")
    public static Handler getHandler(List<Handler> handlerList, Handler actionHandler) {
        Handler result = actionHandler;

        for (int i=handlerList.size()-1; i>=0; i--) {
            Handler temp = handlerList.get(i);
            temp.next = result;
            temp.nextHandler = result;
            result = temp;
        }

        return result;
    }
}

显然这里返回的是一个actionHandler为首handler chain。

让我们再来看看这个:

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

此处传进去并不是简单的handler,而是他的子类ActionHandler,并且传进去了有两个参数,一个是ActionMapping类的变量,一个是constants。对于后者将就是一些常量的设置所以不进行过多介绍。让我们先看看ActionMapping之后再来看这个ActionHandler。

/**
 * ActionMapping
 */
final class ActionMapping {

    private static final String SLASH = "/";
    private Routes routes;
    // private Interceptors interceptors;

    private final Map<String, Action> mapping = new HashMap<String, Action>();

    ActionMapping(Routes routes, Interceptors interceptors) {
        this.routes = routes;
        // this.interceptors = interceptors;
    }

    private Set<String> buildExcludedMethodName() {
        Set<String> excludedMethodName = new HashSet<String>();
        Method[] methods = Controller.class.getMethods();
        for (Method m : methods) {
            if (m.getParameterTypes().length == 0)
                excludedMethodName.add(m.getName());
        }
        return excludedMethodName;
    }

    void buildActionMapping() {
        mapping.clear();
        Set<String> excludedMethodName = buildExcludedMethodName();
        InterceptorManager interMan = InterceptorManager.me();

        for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) {
            Class<? extends Controller> controllerClass = entry.getValue();
            Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);

            boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
            Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
            for (Method method : methods) {
                String methodName = method.getName();
                if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0)
                    continue ;
                if (sonOfController && !Modifier.isPublic(method.getModifiers()))
                    continue ;

                Interceptor[] actionInters = interMan.buildControllerActionInterceptor(controllerInters, controllerClass, method);
                String controllerKey = entry.getKey();

                ActionKey ak = method.getAnnotation(ActionKey.class);
                String actionKey;
                if (ak != null) {
                    actionKey = ak.value().trim();
                    if ("".equals(actionKey))
                        throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");

                    if (!actionKey.startsWith(SLASH))
                        actionKey = SLASH + actionKey;
                }
                else if (methodName.equals("index")) {
                    actionKey = controllerKey;
                }
                else {
                    actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
                }

                Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey));
                if (mapping.put(actionKey, action) != null)
                    throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
            }
        }

        // support url = controllerKey + urlParas with "/" of controllerKey
        Action action = mapping.get("/");
        if (action != null)
            mapping.put("", action);
    }

    private static final String buildMsg(String actionKey, Class<? extends Controller> controllerClass, Method method) {
        StringBuilder sb = new StringBuilder("The action \"")
            .append(controllerClass.getName()).append(".")
            .append(method.getName()).append("()\" can not be mapped, ")
            .append("actionKey \"").append(actionKey).append("\" is already in use.");

        String msg = sb.toString();
        System.err.println("\nException: " + msg);
        return msg;
    }

    /**
     * Support four types of url
     * 1: http://abc.com/controllerKey                 ---> 00
     * 2: http://abc.com/controllerKey/para            ---> 01
     * 3: http://abc.com/controllerKey/method          ---> 10
     * 4: http://abc.com/controllerKey/method/para     ---> 11
     * The controllerKey can also contains "/"
     * Example: http://abc.com/uvw/xyz/method/para
     */
    Action getAction(String url, String[] urlPara) {
        Action action = mapping.get(url);
        if (action != null) {
            return action;
        }

        // --------
        int i = url.lastIndexOf(SLASH);
        if (i != -1) {
            action = mapping.get(url.substring(0, i));
            urlPara[0] = url.substring(i + 1);
        }

        return action;
    }

    List<String> getAllActionKeys() {
        List<String> allActionKeys = new ArrayList<String>(mapping.keySet());
        Collections.sort(allActionKeys);
        return allActionKeys;
    }
}

在ActionMapping中定义了一个路由(routes)和一个Interceptors,这个routes类里面主要的核心是两个Map,内容如下(截取了部分源码过来):

//每一个访问路径(controllerKey)都对应有一个相应的controller,并作为一对Entry放到map中
private final Map<String, Class<? extends Controller>> map = new HashMap<String, Class<? extends Controller>>();

//每一个访问路径(controllerKey)都对应一个在项目中的实际存放路径(WEB-INF/index.jsp等等),并作为一对Entry放到viewPathMap中

private final Map<String, String> viewPathMap = new HashMap<String, String>();

因此我们知道这个ActionHandler就是处理一些关于ActionMapping中对应的ControllerKey与Controller.class的事情。

所以现在既然这些都已经清楚了,我们可以看看ActionHandler的庐山真面目了。

在ActionHandler中我们可以看到这样一行注释:

/**
 * handle
 * 1: Action action = actionMapping.getAction(target)
 * 2: new Invocation(...).invoke()
 * 3: render(...)
 */

这就解释了handle方法需要做的事情了,首先是根据ActionMapping获得相应的Action,然后利用反射进行方法的调用,最后把结果映射到相应的页面上去。这就是核心的三个步骤了,接下来让我们详细的读一下这个源码:

/**
 * ActionHandler
 */
final class ActionHandler extends Handler {

    private final boolean devMode;
    private final ActionMapping actionMapping;
    private static final RenderFactory renderFactory = RenderFactory.me();
    private static final Log log = Log.getLog(ActionHandler.class);

    public ActionHandler(ActionMapping actionMapping, Constants constants) {
        this.actionMapping = actionMapping;
        this.devMode = constants.getDevMode();
    }

    /**
     * 这里进行了核心的handle方法描述:
     * 这里的target为以下格式:http://localhost:8080/ContextPath/ControllerKey/MethodName/parameters
     *
     */

    /**
     * handle
     * 1: Action action = actionMapping.getAction(target)
     * 2: new Invocation(...).invoke()
     * 3: render(...)
     * 首先是根据ActionMapping获得相应的Action,然后利用反射进行方法的调用,最后把结果映射到相应的页面上去;
     */
    public final void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        if (target.indexOf('.') != -1) {  // .love,就过滤了;
            return ;
        }

        isHandled[0] = true;
        String[] urlPara = {null};
        Action action = actionMapping.getAction(target, urlPara);   // 1.根据ActionMapping获得相应的Action;

        if (action == null) {
            if (log.isWarnEnabled()) {
                String qs = request.getQueryString();
                log.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs));
            }
            renderFactory.getErrorRender(404).setContext(request, response).render();
            return ;
        }

        try {
            Controller controller = action.getControllerClass().newInstance();  // Action所在的控制器;Controller
            controller.init(request, response, urlPara[0]);     // Controller初始化,配置request, response

            if (devMode) {
                if (ActionReporter.isReportAfterInvocation(request)) {
                    new Invocation(action, controller).invoke();    // 利用反射进行方法的调用;
                    ActionReporter.report(controller, action);
                } else {
                    ActionReporter.report(controller, action);
                    new Invocation(action, controller).invoke();    // 利用反射进行方法的调用;
                }
            }
            else {
                new Invocation(action, controller).invoke();    // 2. 然后利用反射进行方法的调用;
            }

            Render render = controller.getRender();     // 3. 结果映射到相应的页面上;
            if (render instanceof ActionRender) {
                String actionUrl = ((ActionRender)render).getActionUrl();
                if (target.equals(actionUrl))
                    throw new RuntimeException("The forward action url is the same as before.");
                else
                    handle(actionUrl, request, response, isHandled);
                return ;
            }

            if (render == null)
                render = renderFactory.getDefaultRender(action.getViewPath() + action.getMethodName());
            render.setContext(request, response, action.getViewPath()).render();
        }
        catch (RenderException e) {
            if (log.isErrorEnabled()) {
                String qs = request.getQueryString();
                log.error(qs == null ? target : target + "?" + qs, e);
            }
        }
        catch (ActionException e) {
            int errorCode = e.getErrorCode();
            if (errorCode == 404 && log.isWarnEnabled()) {
                String qs = request.getQueryString();
                log.warn("404 Not Found: " + (qs == null ? target : target + "?" + qs));
            }
            else if (errorCode == 401 && log.isWarnEnabled()) {
                String qs = request.getQueryString();
                log.warn("401 Unauthorized: " + (qs == null ? target : target + "?" + qs));
            }
            else if (errorCode == 403 && log.isWarnEnabled()) {
                String qs = request.getQueryString();
                log.warn("403 Forbidden: " + (qs == null ? target : target + "?" + qs));
            }
            else if (log.isErrorEnabled()) {
                String qs = request.getQueryString();
                log.error(qs == null ? target : target + "?" + qs, e);
            }
            e.getErrorRender().setContext(request, response, action.getViewPath()).render();
        }
        catch (Throwable t) {
            if (log.isErrorEnabled()) {
                String qs = request.getQueryString();
                log.error(qs == null ? target : target + "?" + qs, t);
            }
            renderFactory.getErrorRender(500).setContext(request, response, action.getViewPath()).render();
        }
    }
}

到这里,我们简单了看了一下JFinal的实现原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,924评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,776评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,954评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,576评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,589评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,105评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,480评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,457评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,998评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,026评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,174评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,812评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,502评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,979评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,101评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,674评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,204评论 2 363

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,397评论 25 707
  • 在此特此声明:一下所有链接均来自互联网,在此记录下我的查阅学习历程,感谢各位原创作者的无私奉献 ! 技术一点一点积...
    远航的移动开发历程阅读 11,149评论 12 197
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,730评论 18 139
  • 大学毕业的第一年春节,感觉不大相同。 能给父母长辈买新衣,成就感爆棚,趁着年轻,趁着工作,能出去走走,觉...
    IMCAI阅读 412评论 0 0
  • 从前慢 开学一个多月之后,凉爽的秋风拂过将开学时期的燥热慢慢抚平,一中所处的县城是典型的南方城市,所以在这里秋天感...
    61b7a69f3875阅读 564评论 1 3