转载

浅谈JFinal源码,短小精悍之道

这篇文章的前提是对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使用注解将方法注入进去。

四.JFinalFilter过滤请求

相信大家对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的弊端。

原文  https://juejin.im/post/5c1dd61bf265da613b6fc1e7
正文到此结束
Loading...