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

栏目: Java · 发布时间: 6年前

内容简介:这篇文章的前提是对JFnial有了大致的了解,不熟悉的话可以看一下官网文档https://www.jfinal.com/doc,非常简单的一个框架,回到原题,老大说有一个JFinal的项目以后有可能让我维护,于是让我熟悉一下JFinal的原理,看了JFinal的官网文档和源码才发现JFinal真是短小精悍,言简意赅。因为之前只用过Spring的mvc和公司之前封装的MVC,话不多说,上源码...这时候JFinal就启动了,指定端口,这时候会加载resources目录下的WEB-INF/的web.xml文件

这篇文章的前提是对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的弊端。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Host Your Web Site In The Cloud

Host Your Web Site In The Cloud

Jeff Barr / SitePoint / 2010-9-28 / USD 39.95

Host Your Web Site On The Cloud is the OFFICIAL step-by-step guide to this revolutionary approach to hosting and managing your websites and applications, authored by Amazon's very own Jeffrey Barr. "H......一起来看看 《Host Your Web Site In The Cloud》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具