正如前文所看到的,JFinal的核心就是Servlet规范里的
javax.servlet.Filter
;而我们在web.xml中的配置也是让该Filter接收所有的请求。所以顺理成章地,我们需要将关注点集中到Filter.doFilter
上。
1. Filter.doFilter
jfinal正是在这里接收所有的请求,按照用户自定义的Route规则将请求调度给相应的Action。
//javax.servlet.Filter.doFilter
// 代码非常简约和清晰
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
request.setCharacterEncoding(encoding);
// 获取URL里的请求参数部分
String target = request.getRequestURI();
if (contextPathLength != 0) {
target = target.substring(contextPathLength);
}
boolean[] isHandled = {false};
try {
// 很容易就能注意到, 我们的关注中心应该是这里
// 按照上一篇文章, 我们已经知道的这里的handler其实质是在HandlerFactory中被组装而成的一个Handler链条; 并且ActionHandler位于该链条的最后一个, 确保最后一个被执行的Handler肯定是它
handler.handle(target, request, response, isHandled);
}
catch (Exception e) {
// 吐掉并记录异常
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
}
// 如果isHandled数组中的第一个Item其值为false,则代表本次请求还需要被其他Filter处理,处理链条继续往后。
if (isHandled[0] == false) {
chain.doFilter(request, response);
}
}
2. handler.handle
首先需要声明的是这里我们先只是大致的探讨,更详尽的细节就留到对jfinal的单独组件讲解中吧。
以下我从码云上的一个基于jfinal的项目框架jcbase中的运行时截图。可以看到通过Handler接口中的next属性,jfinal将用户配置的DruidStatViewHandler
和ResourceHandler
,加上jfinal默认载入的ActionHandler
一起组织成为一个链式结构。链式的顺序为 :DruidStatViewHandler -> ResourceHandler -> ActionHandler。
对于前两个Handler:
1. DruidStatViewHandler为jfinal提供的扩展,用来处理druid提供的监控功能。针对满足匹配条件的请求,该Handler会承接所有的请求处理逻辑。不会将逻辑向后传递给其他Handler或Filter。
2. ResourceHandler 则是jcbase的扩展。其主要功能是为自身需求,向ServletContext插入一些上下文信息,以统一和简化操作。
关于本节的重心ActionHandler
,这个Handler作为jfinal中非常关键的一个Handler,其承接了回调用户自定义的Action操作的重任,以及相关的Interceptor等等操作。
// ------------------------- ActionHandler.handle
/**
* handle
* 1: Action action = actionMapping.getAction(target)
* 2: new Invocation(...).invoke()
* 3: render(...)
*/
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
// 请求路径中有.传递过来,则直接终止本次请求的处理;例如我们在Spring和Struts2中会使用的.do和.action后缀
// 关于这个后缀名的请求, 如果有兼容性的需求, 或者习惯上的要求; 可以使用jfinal内置提供的FakeStaticHandler来完成。
if (target.indexOf('.') != -1) {
return ;
}
//声明本次请求处理完毕, 不再需要其他Filter的参与了。
isHandled[0] = true;
String[] urlPara = {null};
//actionMapping,其中存放着所有配置的映射关系,这里就是获取请求相匹配的处理Action
Action action = actionMapping.getAction(target, urlPara);
//404
// 没有找到配置的Action
if (action == null) {
if (log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs));
}
// 设计结构 Manager > Factory > instance
// 返回404, 请求到此结束 renderManager.getRenderFactory().getErrorRender(404).setContext(request, response).render();
return ;
}
Controller controller = null;
try {
// action是知道Controller的;每次请求都实例化一个全新的Controller实例;
// 这里也是扩展点之一,运行自定义的实例化策略
// Controller controller = action.getControllerClass().newInstance();
controller = controllerFactory.getController(action.getControllerClass());
// init还没放权给开发者
controller.init(action, request, response, urlPara[0]);
// 开发模式下打印日志,分为调用前打印,还是调用后打印
if (devMode) {
if (ActionReporter.isReportAfterInvocation(request)) {
new Invocation(action, controller).invoke();
ActionReporter.report(target, controller, action);
} else { // 调用后打印日志
ActionReporter.report(target, controller, action);
new Invocation(action, controller).invoke();
}
}
else {
// 就本次要调用的action和controller封装成一个Invocation,并发起调用
// 这里也是最重要的逻辑控制部分,Interceptor的执行正是在这里面完成的
new Invocation(action, controller).invoke();
}
// Render是controller级别
// 用户自定义的Action逻辑回调完毕之后, 就要将开始进行结果的渲染
// 这里的一个细节就是, 其实我们在Controller层里调用的renderXxx操作,在基类Controller中的实现其实就是给render字段赋值,而下面这个controller.getRender()正是将赋值完毕的render取到。然后继续下面的渲染操作
Render render = controller.getRender();
// 如果本次是Forward请求
if (render instanceof ForwardActionRender) {
String actionUrl = ((ForwardActionRender)render).getActionUrl();
if (target.equals(actionUrl)) {
throw new RuntimeException("The forward action url is the same as before.");
} else {
// 再调用一次
handle(actionUrl, request, response, isHandled);
}
return ;
}
// 如果本次用户Action没有调用renderXxx操作; 则满足下面这个判断
if (render == null) {
render = renderManager.getRenderFactory().getDefaultRender(action.getViewPath() + action.getMethodName());
}
// 执行渲染操作
render.setContext(request, response, action.getViewPath()).render();
}
catch (RenderException e) {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
}
catch (ActionException e) {
int errorCode = e.getErrorCode();
String msg = null;
if (errorCode == 404) {
msg = "404 Not Found: ";
} else if (errorCode == 401) {
msg = "401 Unauthorized: ";
} else if (errorCode == 403) {
msg = "403 Forbidden: ";
}
if (msg != null) {
if (log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn(msg + (qs == null ? target : target + "?" + qs));
}
} else {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
}
e.getErrorRender().setContext(request, response, action.getViewPath()).render();
}
catch (Exception e) {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
renderManager.getRenderFactory().getErrorRender(500).setContext(request, response, action.getViewPath()).render();
} finally {
// 收尾清理工作
if (controller != null) {
controller.clear();
}
}
}
3. Invocation
纵观上面的代码,我们已经发现了最关键的一行代码new Invocation(action, controller).invoke();
。接下来就让我们尝试剖析一下其中的细节。
// ------------------------- Invocation.invoke
/**
* Invocation is used to invoke the interceptors and the target method
*/
public void invoke() {
// 调用拦截器,
// 注意这里将自身传递给了拦截器,所以拦截器在执行完毕之前下方的else if里的逻辑是可以先于拦截器里所有的逻辑之前来执行的,
// 即下面else if里的逻辑执行完毕,继续执行拦截器中剩下的逻辑(也就是每个拦截器中inv.invoke()之后的代码;注意这里每个拦截器和Handler一样,都是需要使用者主动发起,才能继续链条的执行)
// 这里也就解释了为什么官方文档里一再强调:
// 在Interceptor接口的实现中, 必须调用 inv.invoke() 方法,才能将当前调用传递到后续的 Interceptor 与 Action。
if (index < inters.length) {
inters[index++].intercept(this);
}
else if (index++ == inters.length) { // index++ ensure invoke action only one time
try {
// 真正回调用户自定义的方法(controller层定义的action)
// Invoke the action
if (action != null) {
// 这里的返回值最终是通过调用本类的getReturnValue取走的
// 而不是我们通常所预想的通过本方法的返回值; 而且注意本方法并没有返回值
returnValue = action.getMethod().invoke(target, args);
}
// Invoke the method
else {
// if (!Modifier.isAbstract(method.getModifiers()))
// returnValue = methodProxy.invokeSuper(target, args);
if (useInjectTarget)
returnValue = methodProxy.invoke(target, args);
else
returnValue = methodProxy.invokeSuper(target, args);
}
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(e);
}
catch (RuntimeException e) {
throw e;
}
catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
这里我们来看下Interceptor
接口的定义:
/**
* com.jfinal.aop.Interceptor
*/
public interface Interceptor {
// 注意这里的方法参数,
// 结合上面的 Invocation.invoke里对 Interceptor 的调用; 我们可以在大脑里好好感觉一下,拦截器和最终方法之间的执行先后顺序逻辑。
// 这种设计思路非常值得学习, 并且这种近乎模板化的实现基本是可以直接套用的。
void intercept(Invocation inv);
}
所以在Interceptor
接口和Invocation
类的相互配合下,为我们展示一种非常经典并且实用的链式调用实现思路,多加体会和学习并将之应用到实际的项目中将让我们的程序变得非常灵活和拥有非常强的可扩展性。
最后补充一句的是,类似这种链式思路,其实在很多框架里都已经有过类似的,各有千秋的实现:
1. Apache专门出品的 commons-chain-1.2.jar
2. Mybatis里的Plugin
3. 等等
4. 图示
最后贴几张官方和网上找来的请求处理流程图,也省得自己绘制一次了。jfinal在这段处理链条中预留的扩展点已经足以满足绝大部分工程化的自定义需求,而且尽可能地减少了新概念的数量,极大降低了理解的难度曲线。
官方文档( JFinal 顶层架构图)
网上
相比较而言,下面这幅流程图比官方提供的要详细一些。但是对于jfinal已经有了些许了解的人而言,差距基本微乎其微了。
5. 总结
最后我们尝试总结下整个处理流程中的一些关键点:
1. jfinal使用在web.xml配置的JFinalFilter
来接收所有的请求。
2. 接收到的请求会依次经过用户通过JFinalConfig.configHandler(Handlers me)
配置的Handler(添加进me的Handler的顺序即为请求经过的顺序)
3. 每个Handler中通过next.handle
将请求的处理转嫁给下一个Handler。即每个Handler有完全的自主权决定决定请求是否在Handler链中继续往下执行。
4. 另外每个Handler可以通过设置第四个方法参数isHandled[0] = true;
来控制是否将请求发给其他Filter去处理。
5. 用户请求在经过一系列配置的Handler之后,最终达到jfinal内部的核心Handler——ActionHandler
。在该Handler中,将借助Invocation
来回调用户自定义的Action逻辑,以及一系列的相关Interceptor。
6. Interceptor
中也是需要通过主动调用inv.invoke();
来将对用户请求的逻辑传递下去,即每个Interceptor
也是拥有完全的自主权来决定是否继续请求处理逻辑。
7. 在上面的自定义Action method和Interceptor执行完毕之后,最终将选择合适的Render将返回值渲染给请求端。