这篇文章的前提是对JFnial有了大致的了解,不熟悉的话可以看一下官网文档https://www.jfinal.com/doc,非常简单的一个框架,回到原题,老大说有一个JFinal的项目以后有可能让我维护,于是让我熟悉一下JFinal的原理,看了JFinal的官网文档和源码才发现JFinal真是短小精悍,言简意赅。因为之前只用过Spring的mvc和公司之前封装的MVC,话不多说,上源码...
JFinal.start("src/main/resources", 9527, "/"); 复制代码
这时候JFinal就启动了,指定端口,这时候会加载resources目录下的WEB-INF/的web.xml文件,正式环境可以自己封装指定具体的文件,在这里就不具体说了
<filter> <filter-name>jfinal</filter-name> <filter-class>com.jfinal.core.JFinalFilter</filter-class> <init-param> <param-name>configClass</param-name> <param-value>com.jf.Config</param-value> </init-param> </filter> <filter-mapping> <filter-name>jfinal</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 复制代码
可以看到JFinal是使用过滤器的原理拦截所有请求这也是和SpringMVC的不同之一,同样可以看到我们需要制定init-param,这相当于JFinal的配置文件,这也是需要我们自己去实现的。
import com.jfinal.config.*; import com.jfinal.template.Engine; public class Config extends JFinalConfig { @Override public void configConstant(Constants me) { //配置全局常量 } @Override public void configRoute(Routes me) { //配置路由,所有的Controller需要在这里配置,这也是我们今天所讲的主题 me.add("/user", UserController.class); } @Override public void configEngine(Engine me) { //配置模板 } @Override public void configPlugin(Plugins me) { //配置插件 //druid 数据库连接池插件 DruidPlugin druidPlugin = new DruidPlugin("url", "username", "password"); me.add(druidPlugin); //配置ActiveRecord插件 ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin); _MappingKit.mapping(arp);//这里是将数据库里的实体类指定连接池(_MappingKit这里是自动生成的实体类所对应映射类) me.add(arp); } @Override public void configInterceptor(Interceptors me) { //这里配置拦截器 } @Override public void configHandler(Handlers me) { //这里是配置自定义handler,因为JFnial是链式调用,所以允许我们自定义handler(Handler是选择并调用Controller,后面会讲) } } 复制代码
public void configRoute(Routes me) { //配置路由,所有的Controller需要在这里配置,这也是我们今天所讲的主题 me.add("/user", UserController.class); } 复制代码
我们点开me.add方法看下怎么封装的
public Routes add(String controllerKey, Class<? extends Controller> controllerClass) { return add(controllerKey, controllerClass, controllerKey); } 复制代码
继续点进去
public Routes add(String controllerKey, Class<? extends Controller> controllerClass, String viewPath) { routeItemList.add(new Route(controllerKey, controllerClass, viewPath)); return this; } 复制代码
可以看到controllerKey和viewPath就是我们之前配置的"/user"路径,controllerClass就是我们传入的UserController.class对象,这里封装成Route对象并集合在routeItemList集合里,好,我们看一下routeItemList会在什么时候被调用。
protected void buildActionMapping() { mapping.clear(); Set<String> excludedMethodName = buildExcludedMethodName(); InterceptorManager interMan = InterceptorManager.me(); for (Routes routes : getRoutesList()) { for (Route route : routes.getRouteItemList()) { Class<? extends Controller> controllerClass = route.getControllerClass(); 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(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 { actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName; } 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); } } 复制代码
在ActionMapping我们找到了routes.getRouteItemList()方法,这个封装的方法有点长,我们慢慢来讲. 首先将mapping对象清空,找到excludedMethodName不需要执行的方法(这里是Controller的方法,我们不需要对它的方法进行缓存,因为我们需要缓存的是它子类的方法),这里会找到所配置的拦截器管理InterceptorManager(在这里就不详细解析了)。
mapping.clear(); Set<String> excludedMethodName = buildExcludedMethodName(); InterceptorManager interMan = InterceptorManager.me(); 复制代码
遍历routes.getRouteItemList(),就是我们之前看到的将UserController封装成Route让后放入的list里,route.getControllerClass()获取我们配置的Controller对象,这里会获取我们配置的Controller方法,如果是Controller子类则获取它声明的方法,反之获取所有的方法,然后遍历。
Class<? extends Controller> controllerClass = route.getControllerClass(); Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass); boolean sonOfController = (controllerClass.getSuperclass() == Controller.class); Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods()); 复制代码
下面这段代码做的就是匹配拦截器和将方法的key提取出来,默认的是我们之前传入的controllerKey加methodName方法的名字或者可以用注解ActionKey指定名字
String methodName = method.getName(); if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0) continue ; if (sonOfController && !Modifier.isPublic(method.getModifiers())) continue ; 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 { actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName; } 复制代码
最后封装成Action对象,并放入map里。
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)); } 复制代码
这里做的事情其实就是将Controller的方法封装Action对象一个map里,这里的代码先告一段落,也许到了这里大家会有一些思路了,和springmvc类似,springmvc使用注解将方法注入进去。
相信大家对filter都不陌生了,这里我们直接进入handler.handle方法,这里默认的实现是ActionHandler,我们直接进入
handler.handle(target, request, response, isHandled); 复制代码
进入handle方法,我们又看到了一个熟悉的对象actionMapping,我们根据请求的url获取到Action(封装了我们请求的方法),
Action action = actionMapping.getAction(target, urlPara); 复制代码
我们可以看到,我们得到了Controller 的class对象,点进去getController方法可以看到controllerClass.newInstance(),也就是说我们每次请求都会创建一个Controller对象,这也就是说Controller对象必须是无参构造,init对Controller进行初始化,将request, response对Controller进行绑定。
Controller controller = null; // Controller controller = action.getControllerClass().newInstance(); controller = controllerFactory.getController(action.getControllerClass()); controller.init(action, request, response, urlPara[0]); 复制代码
下面这个Controller例子可以看到,getRequest()可以获得request对象,并从request对象读取信息,这也就解释了Controller为什么方法都是没有参数的,同理返回数据也是通过我们传入的response对象。
public class UserController extends Controller { public void getById() { String param = HttpKit.readData(getRequest()); JSONObject json = JSON.parseObject(param); Integer id = json.getInteger("id"); User user = getUserById(id);//这里不做业务处理了 renderJson(user); } private User getUserById(Integer id) { return new User(); } } 复制代码
下面就会执行Controller里我们指定的Method方法了new Invocation(action, controller).invoke();点进去可以看到会先执行过滤器,过滤器执行完才会执行Controller方法,最后可以看到Render render = controller.getRender(),我们得到Render对象,然后对请求是进行转发还是给客户端返回数据。
首先ActionHandler是链式调用的,我们可以自己实现一个ActionHandler,这也是一个扩展的方向。我们发现我们每次的请求都是创建一个新的Controller对象,并绑定request和response对象,这里我也是看了其他大佬的博客,这里这么做是典型的空间换取时间,这样会加快每次的请求速度,但同时会占用较大的内存。其实这里还有一个有意思的模块是ActiveRecordPlugin,大家有兴趣的话可以自己看看,非常简单易懂,操作起来也非常简单,但同时对于复杂的sql不太友好,这也是ActiveRecordPlugin的弊端。