1、MVC架构,设计精巧,使用简单
2、遵循COC原则,零配置,无xml
3、独创Db + Record模式,灵活便利
4、ActiveRecord支持,使数据库开发极致快速
5、自动加载修改后的java文件,开发过程中无需重启web server
6、AOP支持,拦截器配置灵活,功能强大
7、Plugin体系结构,扩展性强
8、多视图支持,支持FreeMarker、JSP、Velocity
9、强大的Validator后端校验功能
10、功能齐全,拥有struts2的绝大部分功能
11、体积小仅218K,且无第三方依赖
public class CoreConfig extends JFinalConfig {
/** 常量配置 **/
@Override
public void configConstant(Constants me) {
loadPropertyFile("system_config_info.txt");
// 设置开发模式
me.setDevMode(getPropertyToBoolean("devMode", true));
me.setUrlParaSeparator(Const.SYMBOLAMPERSAND);
}
/** 处理器配置 接管所有web请求,可在此层做功能性的拓展 **/
@Override
public void configHandler(Handlers me) {
DruidStatViewHandler dvh = new DruidStatViewHandler("/druid");
me.add(dvh);
}
/** 拦截器配置 类似与struts2拦截器,处理action **/
@Override
public void configInterceptor(Interceptors me) {
me.add(new TxByMethods("find","update","delete","save")); //声明式事物管理
}
/** 插件配置 JFinal集成了很多插件:redis,druid,quartz... **/
@Override
public void configPlugin(Plugins me) {
/** 数据库监控druid **/
DruidPlugin dp = new DruidPlugin(getProperty("jdbcUrl"),
getProperty("user"), getProperty("password"));
dp.addFilter(new StatFilter()); //sql监控
dp.addFilter(new WallFilter()); //防止sql注入
//dp.addFilter(new WebStatFilter());
WallFilter wall = new WallFilter();
wall.setDbType("mysql"); //mysql
dp.addFilter(wall);
me.add(dp);
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
me.add(arp);
//表映射关系
arp.addMapping(BlogConst.BLOGTABLE, "id", Blog.class);
/** redis缓存支持根据不同模块使用缓存,目前我创建一个关于blog的缓存块 **/
RedisPlugin blogRedis = new RedisPlugin(BlogConst.BLOGTABLE,
"localhost");
me.add(blogRedis);
/** 定时任务插件 目前配置了一个每5秒钟执行一次的定时脚本**/
QuartzPlugin quartzPlugin = new QuartzPlugin("job.properies");
me.add(quartzPlugin);
}
/** 路由配置:1.如下配置;2.写个类如:BlogRoute继承Routes,配置,me.add(new BlogRoute());也可 **/
/** 路径设置:1.第三个参数;2.可通过注解@ActionKey("/index")方式 **/
@Override
public void configRoute(Routes me) {
me.add("/", IndexController.class, "/index");
/** controller配置路径 **/
me.add("/blog", BlogController.class);
}
/*插件关闭之前【方法可选择性使用】**/
public void beforeJFinalStop(){
System.out.println("插件关闭"); //这里我们可以加一些业务监控代码
};
/*插件加载完后【方法可选择性使用】**/
public void afterJFinalStart(){
System.out.println("加载完毕"); //这里我们可以加一些业务监控代码
};
<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.jfinal.config.CoreConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
createJFinalConfig(filterConfig.getInitParameter("configClass")); //这里获取web.xml配置的class,实例化JFinalConfig
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");
}
实例化JFinalConfig的子类CoreConfig(我们业务需要配置的信息)
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
this.servletContext = servletContext; //获取servletContext
this.contextPath = servletContext.getContextPath(); //获取contextPath
initPathUtil(); //初始化根路径
Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method
constants = Config.getConstants();
initActionMapping();
initHandler();
initRender();
initOreillyCos();
initTokenManager();
return true;
}
1.2.1 看一下
Config.configJFinal(jfinalConfig);
static void configJFinal(JFinalConfig jfinalConfig) {
jfinalConfig.configConstant(constants); initLoggerFactory();
jfinalConfig.configRoute(routes);
jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!
jfinalConfig.configInterceptor(interceptors);
jfinalConfig.configHandler(handlers);
}
JFinal源码config包里面有5个Final修饰的类,是数据初始化的核心内容
public Routes add(String controllerKey, Class controllerClass, String viewPath) {
if (controllerKey == null)
throw new IllegalArgumentException("The controllerKey can not be null");
// if (controllerKey.indexOf(".") != -1)
// throw new IllegalArgumentException("The controllerKey can not contain dot character: \".\"");
controllerKey = controllerKey.trim();
if ("".equals(controllerKey))
throw new IllegalArgumentException("The controllerKey can not be blank");
if (controllerClass == null)
throw new IllegalArgumentException("The controllerClass can not be null");
if (!controllerKey.startsWith("/"))
controllerKey = "/" + controllerKey;
if (map.containsKey(controllerKey))
throw new IllegalArgumentException("The controllerKey already exists: " + controllerKey);
map.put(controllerKey, controllerClass);
if (viewPath == null || "".equals(viewPath.trim())) // view path is controllerKey by default
viewPath = controllerKey;
viewPath = viewPath.trim();
if (!viewPath.startsWith("/")) // "/" added to prefix
viewPath = "/" + viewPath;
if (!viewPath.endsWith("/")) // "/" added to postfix
viewPath = viewPath + "/";
if (baseViewPath != null) // support baseViewPath
viewPath = baseViewPath + viewPath;
viewPathMap.put(controllerKey, viewPath);
return this;
}
map:{/=class com.demo.jfinal.controller.index.IndexController, /blog=class com.demo.jfinal.controller.blog.BlogController}
viewPathMap:{/=/index/, /blog=/blog/}
public Plugins add(IPlugin plugin) {
if (plugin != null)
this.pluginList.add(plugin);
return this;
}
[com.jfinal.plugin.druid.DruidPlugin@79efbd29, com.jfinal.plugin.activerecord.ActiveRecordPlugin@6a6c0d49, com.jfinal.plugin.redis.RedisPlugin@5bdc6a34, com.jfinal.ext.plugin.quartz.QuartzPlugin@63c0ab09]
可以看出这些就是我们配置的一些插件信息。 /**
* The same as addGlobalActionInterceptor. It is used to compatible with earlier version of jfinal
*/
public Interceptors add(Interceptor globalActionInterceptor) {
if (globalActionInterceptor != null)
this.globalActionInterceptor.add(globalActionInterceptor);
return this;
}
[com.jfinal.plugin.activerecord.tx.TxByMethods@1438c5fa]
public Handlers add(Handler handler) {
if (handler != null)
handlerList.add(handler);
return this;
}
加载后的Handlers数据:
[com.jfinal.plugin.druid.DruidStatViewHandler@488d4100]
基础数据都已经加载出来了,下面就是组装数据结构了。
private void initActionMapping() {
actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors());
actionMapping.buildActionMapping();
}
重头戏在
buildActionMapping方法上面,这里会组装ActionMapping的Map<String, Action> mapping参数。组装的基本规则为:第一个for:遍历Routes map中所有的 Controller,第二个for:遍历Controller中的所有method,根据配置的controllerKey和method作为mapping的key,对应数据封装成Action,Action类里面封装了请求的一些基本信息(配置的controllerKey,方法,拦截器),有了这些。最后当执行ActionHandler时候,会匹配controllerKey,返回对应的Action,最后通过反射执行对应Controller的method方法。获取数据,Render出去。看一下
buildActionMapping方法具体做了什么事情吧。
void buildActionMapping() {
mapping.clear();
Set excludedMethodName = buildExcludedMethodName(); //获取Controller里面所有无参数的方法,主要为了区分自定义Controller的方法
ActionInterceptorBuilder interceptorBuilder = new ActionInterceptorBuilder();
Interceptor[] globalInters = interceptors.getGlobalActionInterceptor();
interceptorBuilder.addToInterceptorsMap(globalInters); //获取全局拦截器
for (Entry> entry : routes.getEntrySet()) {
Class controllerClass = entry.getValue();
Interceptor[] controllerInters = interceptorBuilder.buildControllerInterceptors(controllerClass);//获取注解拦截器,这里支持@Before形式的Controller级别
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[] methodInters = interceptorBuilder.buildMethodInterceptors(method); //方法级别拦截器
Interceptor[] actionInters = interceptorBuilder.buildActionInterceptors(globalInters, controllerInters, methodInters, method);//最终都会放在一个拦截器数组里面(每个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数据,每个Controller的method对应一个Action。
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 actoin = mapping.get("/");
if (actoin != null)
mapping.put("", actoin);
}
最后可以看一下mapping里面的保存的数据:(业务里面我定义了两个Controller,IndexController(只有index方法)和BlogController(只有find方法))
private void initHandler() {
Handler actionHandler = new ActionHandler(actionMapping, constants);
handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
}
/**
* Build handler chain
*/
public static Handler getHandler(List handlerList, Handler actionHandler) {
Handler result = actionHandler;
for (int i=handlerList.size()-1; i>=0; i--) {
Handler temp = handlerList.get(i);
temp.nextHandler = result;
result = temp;
}
return result;
}
private void initRender() {
RenderFactory renderFactory = RenderFactory.me(); //单例类RenderFactory,JFinal通过该类返回数据
renderFactory.init(constants, servletContext);
}
public void init(Constants constants, ServletContext servletContext) {
this.constants = constants;
RenderFactory.servletContext = servletContext;
// init Render
Render.init(constants.getEncoding(), constants.getDevMode());
initFreeMarkerRender(servletContext); //初始化freeMarkerRender
initVelocityRender(servletContext); //初始化VelocityRender
initJspRender(servletContext); //初始化 JspRender
initFileRender(servletContext); //初始化FileRender
// 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();
}
}
1.2.5 initOreillyCos方法
private void initOreillyCos() { //初始化参数
OreillyCos.init(constants.getUploadedFileSaveDirectory(), constants.getMaxPostSize(), constants.getEncoding());
}
private void initTokenManager() {
ITokenCache tokenCache = constants.getTokenCache(); //初始化token信息
if (tokenCache != null)
TokenManager.init(tokenCache);
}
handler = jfinal.getHandler();
constants = Config.getConstants();
encoding = constants.getEncoding();
jfinalConfig.afterJFinalStart();
String contextPath = filterConfig.getServletContext().getContextPath();
contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
相关阅读:JFinal 启动、请求源码简析(下篇)
本文来自网易实践者社区,经作者田躲躲授权发布。