浅析初始化过程
首先要从 web 容器进行初始化
<?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.fw.config.MppConfig</param-value> </init-param> </filter> <filter-mapping> <filter-name>jfinal</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
从 web.xml 可以看出,容器初始化的时候会加载 JFinalFilter 这个类并且调用其 init 方法。
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; 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()); } 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"); } // ... }
init 方法中的参数正是 web.xml 中的 JFinalFilter 的初始化参数,即 com.fw.config.MppConfig,它继承于 JFinalConfig,在 createJFinalConfig 中利用该参数使用反射机制得到 JFinalConfig 的实例。
jfinal.init
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) { this.servletContext = servletContext; this.contextPath = servletContext.getContextPath(); initPathUtil(); Config.configJFinal(jfinalConfig); constants = Config.getConstants(); initActionMapping(); initHandler(); initRender(); initOreillyCos(); initTokenManager(); return true; }
第一、initPathUtil,初始化 Path工具类的 webRootPath(即项目根路径)。
private void initPathUtil() { String path = servletContext.getRealPath("/"); PathKit.setWebRootPath(path); }
第二、Config.configJFinal 加载 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); }
配置常量,初始化 Log 工具类,配置路由,配置插件,开启插件,配置拦截器,配置 handler。
下面看看我项目中的 JFinalConfig 实例。
public class MppConfig extends JFinalConfig { //配置常量 public void configConstant(Constants me) { PropKit.use("jdbc.properties"); me.setDevMode(PropKit.getBoolean("devMode", false)); } //配置路由 public void configRoute(Routes me) { me.add(new RoutesMapping()); } public static C3p0Plugin createC3p0Plugin() { return new C3p0Plugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password").trim()); } //配置插件 public void configPlugin(Plugins me) { // 配置C3p0数据库连接池插件 C3p0Plugin C3p0Plugin = createC3p0Plugin(); me.add(C3p0Plugin); // 配置ActiveRecord插件 ActiveRecordPlugin arp = new ActiveRecordPlugin(C3p0Plugin); me.add(arp); // 配置属性名(字段名)大小写不敏感容器工厂 oracle arp.setContainerFactory(new CaseInsensitiveContainerFactory()); //缓存插件 me.add(new EhCachePlugin()); // 所有配置在 MappingKit 中搞定 ModelMapping.mapping(arp); } //配置全局拦截器 public void configInterceptor(Interceptors me) { me.add(new SessionInViewInterceptor());//session拦截器,用于在View模板中取出session值 } //配置处理器,接管所有 web 请求 public void configHandler(Handlers me) { me.add(new ContextPathHandler("contextPath"));//设置上下文路径 } //系统启动完成后回调,如创建调度线程 public void afterJFinalStart(){} //系统关闭前回调,如写回缓存 public void beforeJFinalStop(){} }
第三、初始化 ActionMapping、Handler、Render等。
initActionMapping(); initHandler(); initRender(); initOreillyCos(); initTokenManager();
Init ActionMapping
private void initActionMapping() { actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors()); actionMapping.buildActionMapping(); Config.getRoutes().clear(); }
第一、创建 ActionMapping,映射所有访问路径和其对应的Action,填充得到一个 HashMap。下面是创建 ActionMapping 代码和注释,涉及到其他类的源码请自行查看。
final class ActionMapping { private static final String SLASH = "/"; private Routes routes; // ActionMapping 映射 private final Map<String, Action> mapping = new HashMap<String, Action>(); // 构造方法,routes 参数传进来 ActionMapping(Routes routes, Interceptors interceptors) { this.routes = routes; } 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(); // 这个方法返回的是 Controller接口的所有方法集合。 Set<String> excludedMethodName = buildExcludedMethodName(); // 得到 InterceptorManager 的实例 InterceptorManager interMan = InterceptorManager.me(); // 遍历一个 Entry 集合。Entry 的 key 为访问路径,value 为其对应的 Controller。 for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) { // 得到访问路径对应的 Controller class Class<? extends Controller> controllerClass = entry.getValue(); // 如果 Controller class没有拦截器注解,则返回一个空数组。反之返回这个类所有拦截器组成的数组 Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass); boolean sonOfController = (controllerClass.getSuperclass() == Controller.class); // 这里必定为 true // getDeclaredMethods 得到这个类的所有方法以及其接口的所有方法,不包括继承的方法 Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods()); for (Method method : methods) { String methodName = method.getName(); //若这个方法是继承自 Controller的方法 或 该方法有参数,过滤掉 if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0) continue ; // 若这个方法不是 public 方法,过滤掉 if (sonOfController && !Modifier.isPublic(method.getModifiers())) continue ; // 想进行到这里,这个方法必须满足:不是继承自 Controller、不能有参数、必须是 public 方法 // 得到包含所有拦截器的数组(包括全局的拦截器,类级别的拦截器、方法级别的拦截器) Interceptor[] actionInters = interMan.buildControllerActionInterceptor(controllerInters, controllerClass, method); String controllerKey = entry.getKey(); ActionKey ak = method.getAnnotation(ActionKey.class); String actionKey; // ActionKey 不为空的话为设置自定义的访问路径(说明有方法被注有 @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; } // ActionKey为空,methodName 为 index的情况下:actionKey = controllerKey else if (methodName.equals("index")) { actionKey = controllerKey; } // ActionKey为空,methodName 不为 index的情况下:actionKey = controllerKey +"/" + methodName else { actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName; } // 组合装配成一个 Action Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey)); // 填充 HashMap(访问路径为 key,Action 为 value) 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); } // ... }
第二、Config.routes 已经用过了,以后也不会再用上了,清空路由的所有信息。
Init Handler
private void initHandler() { Handler actionHandler = new ActionHandler(actionMapping, constants); handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler); }
第一、创建一个 ActionHandler 实例 actionHandler。
第二、HandlerFactory.getHandler 创建一个 Handler 链,链尾是 actionHandler,并且返回链首。
public class HandlerFactory { private HandlerFactory() {} /** * 创建一个 handler 链条,最后返回的 result 是 handler 链的头部,链尾是 ActionHandler */ @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; } }
Init Render
private void initRender() { RenderFactory.me().init(constants, servletContext); }
继续跟~
public class RenderFactory { public void init(Constants constants, ServletContext servletContext) { this.constants = constants; this.servletContext = servletContext; // 初始化 Render Render.init(constants.getEncoding(), constants.getDevMode()); // 初始化编码和开发模式 initFreeMarkerRender(servletContext); // 初始化 FreeMarkerRender initVelocityRender(servletContext); initJspRender(servletContext); initFileRender(servletContext); // 创建 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."); } } // 创建 errorRenderFactory if (errorRenderFactory == null) { errorRenderFactory = new ErrorRenderFactory(); } if (xmlRenderFactory == null) { xmlRenderFactory = new XmlRenderFactory(); } } private void initFreeMarkerRender(ServletContext servletContext) { try { Class.forName("freemarker.template.Template"); // 加载 freemarker 模板 FreeMarkerRender.init(servletContext, Locale.getDefault(), constants.getFreeMarkerTemplateUpdateDelay()); } catch (ClassNotFoundException e) { // 若加载模板失败,比如没有引入 jar 包 LogKit.logNothing(e); } } // ... } public class FreeMarkerRender extends Render { private static final String contentType = "text/html; charset=" + getEncoding(); private static final Configuration config = new Configuration(); static void init(ServletContext servletContext, Locale locale, int template_update_delay) { // 初始化 freemarker 配置:config = new Configuration(); // 设置 freemarker 页面的存放位置为项目根路径 config.setServletContextForTemplateLoading(servletContext, "/"); // 设置开发模式下更新延迟时间 if (getDevMode()) { config.setTemplateUpdateDelay(0); } else { config.setTemplateUpdateDelay(template_update_delay); } // - Set an error handler that prints errors so they are readable with // a HTML browser. // config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // - Use beans wrapper (recommmended for most applications) config.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER); config.setDefaultEncoding(getEncoding()); // 设置默认编码 config.setOutputEncoding(getEncoding()); // 设置输出编码 // - Set the default locale config.setLocale(locale /* Locale.CHINA */ ); // 设置时区 config.setLocalizedLookup(false); // 去掉int型输出时的逗号, 例如: 123,456 // config.setNumberFormat("#"); // config.setNumberFormat("0"); 也可以 config.setNumberFormat("#0.#####"); config.setDateFormat("yyyy-MM-dd"); config.setTimeFormat("HH:mm:ss"); config.setDateTimeFormat("yyyy-MM-dd HH:mm:ss"); } // ... }
Init 上传组件
private void initOreillyCos() { OreillyCos.init(constants.getBaseUploadPath(), constants.getMaxPostSize(), constants.getEncoding()); }
很简单,附上相关代码:
public class OreillyCos { // 先加载上传组件 public static void init(String uploadPath, int maxPostSize, String encoding) { if (StrKit.isBlank(uploadPath)) { throw new IllegalArgumentException("uploadPath can not be blank."); } try { Class.forName("com.oreilly.servlet.MultipartRequest"); doInit(uploadPath, maxPostSize, encoding); } catch (ClassNotFoundException e) { LogKit.logNothing(e); } } private static void doInit(String uploadPath, int maxPostSize, String encoding) { uploadPath = uploadPath.trim(); uploadPath = uploadPath.replaceAll("\\\\", "/"); String baseUploadPath; // 判断上传路径是否是绝对路径,如果不是把它加工成绝对路径 if (PathKit.isAbsolutelyPath(uploadPath)) { baseUploadPath = uploadPath; } else { baseUploadPath = PathKit.getWebRootPath() + File.separator + uploadPath; } // remove "/" postfix if (baseUploadPath.equals("/") == false) { if (baseUploadPath.endsWith("/")) { baseUploadPath = baseUploadPath.substring(0, baseUploadPath.length() - 1); } } MultipartRequest.init(baseUploadPath, maxPostSize, encoding); } // ... } public class MultipartRequest extends HttpServletRequestWrapper { private static String baseUploadPath; private static int maxPostSize; private static String encoding; static void init(String baseUploadPath, int maxPostSize, String encoding) { MultipartRequest.baseUploadPath = baseUploadPath; MultipartRequest.maxPostSize = maxPostSize; MultipartRequest.encoding = encoding; } // ... }
Init Token令牌
private void initTokenManager() { ITokenCache tokenCache = constants.getTokenCache(); if (tokenCache != null) TokenManager.init(tokenCache); }
先给个代码吧:
public class TokenManager { private static ITokenCache tokenCache; private static Random random = new Random(); private TokenManager() { } public static void init(ITokenCache tokenCache) { if (tokenCache == null) return; TokenManager.tokenCache = tokenCache; long halfTimeOut = Const.MIN_SECONDS_OF_TOKEN_TIME_OUT * 1000 / 2; // Token最小过期时间的一半时间作为任务运行的间隔时间 new Timer().schedule(new TimerTask() {public void run() {removeTimeOutToken();}}, halfTimeOut, halfTimeOut); } // ... }