• JFinal的启动源码解读


    本文对Jfinal的启动源码做解释说明。

    PS:Jfinal启动容器可基于Tomcat/Jetty等web容器启动,本文基于Jetty的启动方式做启动源码的解读和分析,tomcat类似。

      • 入口  
        JFinalConfig的继承类的Main方法为入口,实例代码继承类为:DemoConfig,Main方法如下:
        复制代码
        public static void main(String[] args) {
        /**
        * 特别注意:Eclipse 之下建议的启动方式
        */
        JFinal.start("WebRoot", 80, "/", 5);
        
        }
        复制代码
        启动时,从WebRoot-->Web-INF-->web.xml开始
        web.xml代码如下:
        复制代码
        <?xml version="1.0" encoding="UTF-8"?>
        <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
            <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.common.DemoConfig</param-value>
                </init-param>
            </filter>
            
            <filter-mapping>
                <filter-name>jfinal</filter-name>
                <url-pattern>/*</url-pattern>
            </filter-mapping>
        </web-app>
        复制代码
      • JFinalFilter执行源码解读
        web容器调用web.xml中的JFinalFilter过滤器,注入:com.demo.common.DemoConfig全路径,JFinalFilter中三个重要的方法分别是:init(FilterConfig filterConfig)、doFilter(ServletRequest req, ServletResponse res, FilterChain chain)、destroy()。
        启动入口为init方法,方法截图如下
        复制代码
         1 public void init(FilterConfig filterConfig) throws ServletException {
         2         createJFinalConfig(filterConfig.getInitParameter("configClass"));//A.解析web.xml中configClass全路径类并初始化
         3         
         4         if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false) {//B.基于Jfinal.init加载启动资源,包含控制器、拦截器等
         5             throw new RuntimeException("JFinal init error!");
         6         }
         7         
         8         handler = jfinal.getHandler();//C.获取处理器
         9         constants = Config.getConstants();//D.获取常量
        10         encoding = constants.getEncoding();//E.获取编解码器
        11         jfinalConfig.afterJFinalStart();//F.启动web容器
        12         
        13         String contextPath = filterConfig.getServletContext().getContextPath();//G.获取server路径
        14         contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());//H.对路径特殊处理
        15     }
        复制代码

        A.解析web.xml中configClass全路径类

         createJFinalConfig(filterConfig.getInitParameter("configClass"));
        PS:上方法主要是为了实例化web.xml中configClass对象,JVM在装载class时候,基于类加载机制创建configClass对应的对象实例,代码分析如下
        复制代码
        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");
                }
        //此类创建了一个对象JFinalConfig的继承类对象而已,不做深入追究 }
        复制代码

        B.基于Jfinal.init加载启动资源

        jfinal.init(jfinalConfig, filterConfig.getServletContext())
        复制代码

        boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
        this.servletContext = servletContext;
        this.contextPath = servletContext.getContextPath();
        //B1.获取web容器运行的跟目录,并存储到PathKit.webRootPath变量中
        initPathUtil();

        //B2.初始化constant, route, engine, plugin, interceptor, handler等信息,实际是调用jfinalConfig

        Config.configJFinal(jfinalConfig); // start plugin, init log factory and init engine in this method
        constants = Config.getConstants();
        //B3.初始化映射,包含controller和intercept
        initActionMapping();

        //B4.初始化Handler
        initHandler();

        //B5.初始化Render
        initRender();

        //B6.初始化不知道
        initOreillyCos();

        //初始化Token
        initTokenManager();

        return true;
        }

        复制代码

         B1.获取web容器运行的跟目录,并存储到PathKit.webRootPath变量中
        initPathUtil();

        复制代码
        /**
        
        * 初始化web根路径
        
        */
        
        private void initPathUtil() {
        String path = servletContext.getRealPath("/");
        PathKit.setWebRootPath(path);
        }
        复制代码

        B2.初始化constant, route, engine, plugin, interceptor, handler等信息

        Config.configJFinal(jfinalConfig);

        复制代码
        static void configJFinal(JFinalConfig jfinalConfig) {
                jfinalConfig.configConstant(constants);//调用 JFinalConfig 子类的 configConstant,自行跟踪

        initLogFactory();//B21:初始化日志工厂 基于log4j封装class的getlog方法,不做解释
        initEngine();//B22:初始化引擎 jfinalConfig.configRoute(routes);//调用 JFinalConfig 子类的方法,配置用户自定义controller
                jfinalConfig.configEngine(engine);//调用 JFinalConfig 子类的方法,配置引擎
                jfinalConfig.configPlugin(plugins);//调用 JFinalConfig 子类的方法,配置用户自定义的插件、redis插件、dbcp插件
                startPlugins();B23:启动插件 very important!!!
                jfinalConfig.configInterceptor(interceptors);//调用 JFinalConfig 子类的方法,配置用户自定义的拦截器
                jfinalConfig.configHandler(handlers);//调用 JFinalConfig 子类的方法,配置用户自定义hander

        }
        复制代码

        B21.初始化log

        复制代码
        /**
        *初始化log的核心是创建logFactory对象,尝试创建log4j,如果创建失败,则使用JDK默认log工厂,详情省略
        */
        static void init() { if (defaultLogFactory == null) { try { Class.forName("org.apache.log4j.Logger"); Class<?> log4jLogFactoryClass = Class.forName("com.jfinal.log.Log4jLogFactory"); defaultLogFactory = (ILogFactory)log4jLogFactoryClass.newInstance(); // return new Log4jLogFactory(); } catch (Exception e) { defaultLogFactory = new JdkLogFactory(); } } }
        复制代码

        B22:初始化引擎

        initEngine()
        复制代码
        /**
        * 设置开发模式和模板文件跟目录,这个方法是Jfinal默认调用的,如果要更新engine里面的变量的话,则JFinalConfig继承类可重写configEngine(Engine engine);
        */
        private static void initEngine() { engine.setDevMode(constants.getDevMode()); engine.setBaseTemplatePath(PathKit.getWebRootPath()); }
        复制代码

        B23:启动插件
           PS:类似dbcp、redis等的初始化在jfinal中被定义成了插件的方式,startPlugins中我们重点强调什么是插件、插件能干啥?

        startPlugins()中并没有启动插件,仅仅是在jfinalConfig.configPlugin(plugins)后,设置插件的开发模式

        复制代码
        private static void startPlugins() {
        //获取插件列表 List<IPlugin> pluginList = plugins.getPluginList(); if (pluginList == null) { return ; } 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()); } } //启动插件,这步骤特别的重要,下个博客重点说明启动插件能干啥用。。。。xieyang@163.com/xieyang@e6yun.com if (plugin.start() == false) { 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() + ". " + e.getMessage(); log.error(message, e); throw new RuntimeException(message, e); } } }
        复制代码

         B3.初始化映射,包含controller和intercept
        initActionMapping();

        PS:initActionMapping方法的本质将controller的映射和拦截器存储到ActionMapping,然后调用ActionMapping.buildActionMapping完成映射关系的初始化,下文对如何进行映射给予分析和说明
        备注:未完待续明天继续2017-07-25

        复制代码
        void buildActionMapping() {
                mapping.clear();
        //通过反射获取controller父类的无参数的方法名集合 Set<String> excludedMethodName = buildExcludedMethodName();
        //拦截器管理类 InterceptorManager interMan = InterceptorManager.me();
        //getRoutesList():获取用户自定义的controller、名字和模板路径及一个空的route,PS后续详解 for (Routes routes : getRoutesList()) {
        //遍历routes,获取每一个route对象 for (Route route : routes.getRouteItemList()) {
        //获取当前route的class,此class实在route.me.add的时候添加进来的 Class<? extends Controller> controllerClass = route.getControllerClass();
        //PS:从拦截器管理器中获取当前controller的拦截器集合,具体实现方式为:获取controller的Before类注解value为Intercept子类的注解值,通过反射初始化全局单例方式的拦截器,并以集合的方式返回,读者自行研读此处代码 Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass); //判断当前controllerClass的超类是不是Controller.class boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
        //如果是true,则返回当前类的所有的包括public/private/protected/default修饰的方法。否则的话,返回当前类及父类的public方法 Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods()); for (Method method : methods) { String methodName = method.getName();
        //当前方法不在父类的无参方法集合中或者当前方法的参数值不为零,则认为认为是非public方法,不做controller的映射,直接返回 if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0) continue ;
        //当前类的是controller的子类,但是方法是非public,则直接返回 if (sonOfController && !Modifier.isPublic(method.getModifiers())) continue ; //PS:有点难, Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method); String controllerKey = route.getControllerKey(); 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 {
        //请求的URL路径 actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName; } //对请求URL的详细介绍 Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath()));
        //配置映射关系 if (mapping.put(actionKey, action) != null) { throw new RuntimeException(buildMsg(actionKey, controllerClass, method)); } } } } routes.clear(); // support url = controllerKey + urlParas with "/" of controllerKey Action action = mapping.get("/"); if (action != null) { mapping.put("", action); } }
        复制代码

         B4.初始化Handler

          initHandler();
          PS:主要是为了初始化链接结构

        复制代码
        /**
             * Build 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;
            }
        复制代码

        B5.初始化Render
        initRender();PS:初始化模板引擎

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

        备注:后续讲解模板引擎怎么初始化的问题

        //B6.初始化不知道
        initOreillyCos();

        //初始化Token
        initTokenManager();PS初始化Token

        以上完成了server初始化操作,所有请求的封装都是基于server-容器实现,代码仅到这一层,详细需要跟踪容器的实现。
        天道酬勤!

  • 相关阅读:
    2021.10 db2转换openGauss个人工作总结及心得
    cpu 超过100%,我的排查步骤
    记美团一面,凉凉~
    Eureka挂掉,服务之间能否正常调用?
    jpa中的常用关键字
    Linux修改文件目录所属用户和组
    linux 下如何查看端口占用?
    windows 下如何查看端口占用情况?
    docker学习网站
    api接口文档生成,无需其他配置一键生成基于文本注释
  • 原文地址:https://www.cnblogs.com/YuyuanNo1/p/9773542.html
Copyright © 2020-2023  润新知