JFinal4.3 框架总结(一)

1 Servlet过滤器——JFinalFilter

init流程

1 创建JFinalConfig

2 初始化JFinal框架

初始化servletContext

初始化PathKit工具的webRootPath;

初始化JFinal框架配置

    jfinalConfig.configConstant(constants); initLogFactory(); initEngine();

    jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!

    jfinalConfig.configRoute(routes);

    jfinalConfig.configEngine(engine);

    jfinalConfig.configInterceptor(interceptors);

    jfinalConfig.configHandler(handlers);

初始化ActionMapping(请求映射配置)

    根据Routes的配置解析Controller,生成整个系统的Action映射;

初始化Handler(前置处理器)

    根据自己定义的JFinalConfig的回调方法configHandler创建的;

    创建ActionHandler并初始化,初始化ControllerFactory;

    将自己定义的Handler按顺序链接,最后链接上ActionHandler;

初始化Render(渲染组件)

初始化OreillyCos(上传组件)

初始化Token

3 过滤器启动完成,回调jfinalConfig.afterJFinalStart();

4 JFinalFilter关联上Handler,这样就可以开始接受请求了;


doFilter流程

1 request.setCharacterEncoding(encoding);

2 获取请求URI,去掉上下文路径;

3 调用Handler

4 最终,如果是Action请求,Handler调用Invocation,Invocation执行Interceptor栈,Interceptor调用Controller,Controller返回Render,输出;

或者是非Action请求,直接服务器上的文件;


2 前置处理器——Handler

接口定义

public abstract class Handler {

protected Handler next; // Handler指针

public abstract void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled);

}

参数说明

target,请求URL链接(不包含上下文路径);

request,请求对象;

reponse,响应对象;

isHandled,是否处理完成?true 已处理完成,不需要执行后续的Filter;false 未处理完成,需要执行后续的Filter;设置为数组是为了引用传递参数;


说明

前置处理器Handler,为了在执行Action前做一些准备工作;


工具类

HandlerFactory,用于将Handler列表以及ActionHandler串联起来;


内置Handler

ContextPathHandler,将上下文路径request.getContextPath()放入request的属性中,便于前端显示;

UrlSkipHandler,指定要排除的请求地址,不需要进入框架流程,直接进入下一个Filter;

FakeStaticHandler,允许请求地址带扩展名的方式请求,比如:.do、.action、.jsp、.php、.asp、.aspx等等,实际是为了迷惑攻击者,不想暴露自己的web服务器类型;使用RestFul风格,就没有必要使用这个功能了!

ServerNameRedirect301Handler,301永久重定向的处理器;

ActionHandler,调用控制器Controller的最后一个Handler处理器,Controller调用执行情况重点看这里;对Controller的AOP注入,执行返回Render,执行渲染Render;支持渲染为json、text、跳转服务端请求、服务端重定向等;


说明

对应静态资源的访问是不需要经过框架的,JFinal框架本身支持RestFul风格的请求,遇到具体的.jpg、.png、.css、.js、.zip等,可以在Handler中使用

if (target.indexOf('.') != -1) {

return ;

}

直接返回即可;


Handler设计模式是职责链模式的一种经典的应用场景!参考说明:http://blog.sina.com.cn/s/blog_667ac0360102x4ej.html


3 拦截器——Interceptor、AOP

JFinal拦截器支持Global、Routes、Class、Method四个范围的拦截器配置,可以精确到方法级别,粒度划分是最细了,完全符合所有场景的使用;

底层使用代理类的方式,V4.2之前使用cglib的方式生成代理类;V4.2之后是使用自创的设计,Enjoy、Class Loader、Dynamic Compile 美妙结合的一种实现,代码可读性强、简洁;

注意:类继承若是带有泛型的,想生成代理类,建议使用cglib,这种比较复杂;目前的V4.3对这种复杂的代理生成有点问题!


拦截器接口定义

public interface Interceptor {

void intercept(Invocation inv); // 经典实现方式,参考Struts2;都是伴随一个拦截器的执行器Invocation,由Invocation执行拦截器栈的;

}


拦截器栈定义

public abstract class InterceptorStack implements Interceptor

说明

自定义拦截器可以继承InterceptorStack,实现模板方法config(),在config()中调用addInterceptors()取添加拦截器;这样就可以构造一个拦截器栈;

拦截器栈在执行的时候是以插入到当前拦截器栈的形式执行的;比如:拦截器栈A,里面有拦截器A1,A2,A3;拦截器栈B有拦截器B1,B2,B3;当B放在A1, B, A2, A3,则执行顺序是A1, B1, B2, B3, A2, A3;


原型拦截器定义

public abstract class PrototypeInterceptor implements Interceptor

说明

设计目的是为了满足支持并发访问的拦截器,就是带状态的拦截器;执行的时候每次都会被new,所以是线程安全的;


Aop的工具类

Aop工具类

get(Class targetClass),创建/返回目标类 + 注入目标类;

注意,不可以为没有默认构造函数的类进行注入!带参数的构造函数的类可以使用AopManager进行注入!

具体实现,调用了ProxyFactory的get方法,进行代理类的源码生成(针对方法结合所有拦截器)、编译、类加载,然后保存目标类型与代理类型的映射;

inject(Object targetObject),注入目标对象;


AopFactory具体实现类

持有所有单例对象的容器singletonCache,将单例类型与具体对象进行映射;

持有抽象类、接口与具体实现类的映射;为了给抽象类、接口进行注入,也可以通过在@Inject中指明要注入的类型来手动指定;


AopManager管理器

setInjectDependency(boolean injectDependency),设置对 Controller、Interceptor、Validator 进行依赖注入,默认为 false;

setInjectSuperClass(boolean injectSuperClass),设置对父类进行注入,默认为false;

addSingletonObject(Object singletonObject),添加单例对象;该对象可以带参构造函数,创建后,再使用该方法添加到容器;

setSingleton(boolean singleton),设置被注入的对象是否为单例,可在目标类上使用 @Singleton(boolean) 覆盖此默认值;

setAopFactory(AopFactory aopFactory),设置Aop的实现类AopFactory;

addMapping(Class from, Class to),添加父类到子类的映射,或者接口到实现类的映射。


拦截器设计模式是经典的对AOP的一种实现,JFinal的代理类生成也是有史以来最简洁的设计,对任意使用场景的AOP都是支持的;


AOP注解

@Before,配置类、方法的拦截器;

@Clear,清除上层拦截器;

@Singleton,注入单例;

@Inject,对字段的注入;


4 控制器——Controller

4.1 极简路由规则

在configRoute方法配置Controller的路由,每个Controller对应一个controllerKey;

如下代码配置了将 "/hello" 映射到HelloController这个控制器,通过以下的配置,http://localhost/hello  将访问 HelloController.index() 方法,而http://localhost/hello/methodName  将访问到 HelloController.methodName() 方法。

public void configRoute(Routes me) {

    // 如果要将控制器超类中的 public 方法映射为 action 配置成 true,一般不用配置

    me.setMappingSuperClass(false);

    

    me.setBaseViewPath("/view");

    me.addInterceptor(new FrontInterceptor());

    me.add("/hello", HelloController.class);

}

JFinal 仅有四种路由,路由规则如下表:

url组成访问目录

controllerKeycontroller.index()

controllerKey/methodcontroller.method()

controllerKey/method/v0-v1controller.method(),带参数v0、v1;

controllerKey/v0-v1controller.index(),带参数v0、v1;


JFinal访问一个确切的Action(Action定义见3.2节)需要使用controllerKey与method来精确定位,当method省略时默认值为index。

urlPara是为了能在url中携带参数值,urlPara可以在一次请求中同时携带多个值,JFinal默认使用减号“-”来分隔多个值(可通过constants. setUrlParaSeparator(String)设置分隔符),在Controller中可以通过getPara(int index)分别取出这些值。controllerKey、method、urlPara这三部分必须使用正斜杠“/”分隔。

注意,controllerKey自身也可以包含正斜杠“/”,如“/admin/article”,这样实质上实现了struts2的namespace功能。

JFinal在以上路由规则之外还提供了ActionKey注解,可以打破原有规则,对路由做特殊定制;

测试/aaa/aaa.jsp这样带扩展名能否访问到方法?这是为了对原框架升级为JF框架,比如.jsp、.do等需要转为不带状态的请求;

可以!有多种方法:

1)自定义ActionHandler,以及资源文件的过滤Handler要让.jsp能够通过!

2)使用FakeStaticHandler方式来伪造.xxx的状态,不过这样的话,系统就统一使用.xxx这种方式了;若想使用/xxx/xxx与/xxx/xxx.jsp混合使用,也可以,需要自己特殊处理下handle方法;

3)直接在前置Handler里面特殊处理下,自己手动去掉.jsp、.do这样的东西;比如:

public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {

int index = target.lastIndexOf(".do"); // 若要定义.do的请求映射到Controller,可以这样做;

if (index != -1) {

target = target.substring(0, index);

}

// 静态资源直接返回

if (target.indexOf('.') != -1) {

return ;

}

request.setAttribute(contextPathName, request.getContextPath());

next.handle(target, request, response, isHandled);

}


RestFul的实现,参考:http://www.jfinal.com/share/230

目前大部分系统并没有真正意义实现RestFul的风格,http协议只使用get、post:


4.2 Action

在 Controller 之中定义的 public 方法称为Action。Action 是请求的最小单位。


@NotAction 注解

如果希望 controller 中的 public 方法不成为一个 action,可以使用 @NotAction 注解。

自 jfinal 3.6 开始,控制器超类中的所有方法默认不会被映射为 action。(也就是自 jfinal 3.6 版本开始上例中 BaseController 中的 @NotAction 默认已经不需要了,因为 BaseController 是你最终控制器 XxxController 的超类)

如果希望超类中的方法也被映射为 action 只需添加一行配置:

public void configRoute(Routes me) {

    me.setMappingSuperClass(true);

}


Action参数注入

Action 参数注入是指为 action 方法传入参数,可以省去 getPara(...) 代码直接获得参数值,以下是代码示例:

public class ProjectController extends Controller {

    public void index(Project project) {

       project.save();

       render("index.html");

    }

}

Action 参数注入可以代替 getPara、getBean、getModel 系列方法获取参数,使用 File、UploadFile 参数时可以代替 getFile 方法实现文件上传。这种传参方式还有一个好处是便于与 swagger 这类第三方无缝集成,生成API文档。

重要用法:如果 action 形参是一个 model 或者 bean,原先通过 getBean(User.class, "") 获取时第二个参数为空字符串或null,那么与之等价的形参注入只需要用一下 @Para("") 注解即可:

public void action(@Para("")User user) { …. }


4.3 get/getPara系列方法

request参数获取系列方法;

jfinal 3.5 重要更新:jfinal 3.5 版本新增了 getRawData() 方法,可以很方便地从 http 请求 body 中获取 String 型的数据,通常这类数据是 json 或 XML 数据,例如:

String json = getRawData();

User user = FastJson.getJson().parse(json, User.class);

以上代码通过 getRawData() 获取到了客户端传过来的 String 型的 json 数据库。 getRawData() 方法可以在一次请求交互中多次反复调用,不会抛出异常。


4.4 getBean/getModel系列方法

JavaBean的注入,将request的字符串参数注入到Model活着Bean中;


4.5 set/setAttr方法

调用的是HttpServletRequest.setAttribute(String, Object),主要用于视图的显示;


4.6 render系列方法

一些列渲染方法,常用的渲染数据类型都包含了,包括文件下载;


4.7 setSessionAttr/getSessionAttr

对seesion的操作;


4.8 getFile文件上传

Controller提供了getFile系列方法支持文件上传,依赖com.jfinal.cos包;


4.9 keep系方法

把request的参数保存到request的属性里面,主要供模板输出;



5 渲染——Render

JFinal支持多种类型的视图渲染,JFINAL_TEMPLATE、JSP、FREE_MARKER、VELOCITY;

Controller里面含有render系列的方法,方便进行渲染操作;


Render

渲染对象,JFinal的实现有:

TemplateRender,Jfinal的模板渲染,根据request里设置的属性进行渲染;

XmlRender,使用的是jfinal的模板渲染,contentType不同;

JsonRender,json渲染;IE浏览器不支持application/json类型,可以使用forIE()方法来指定支持IE并使用text/html类型输出;

TextRender,文本渲染;

JavascriptRender,js渲染;

HtmlRender,html渲染;

FreeMarkerRender,freemarker渲染;实际上使用了JFinal,一般不会使用freemarker;

VelocityRender,velocity渲染;实际上使用了JFinal,一般不会使用velocity;

FileRender,下载文件渲染,支持断点续传下载;

JspRender,JSP渲染,一般不会使用JSP;

QrCodeRender,使用zxing渲染二维码;

RedirectRender,默认302重定向;

Redirect301Render,301重定向;

ErrorRender,系统错误的渲染,默认是html版的;如果前端系统既有html又有json请求,那么可以自己实现一个系统错误的渲染,同时支持html与json;

参考:https://www.jfinal.com/share/868

NullRender,空渲染;什么也不做;


RenderFactory

渲染工厂,支持创建不同类型的渲染对象;也支持根据系统配置,设置默认的渲染工厂(默认是JFINAL_TEMPLATE);

核心方法:public Render getRender(String view),可以根据默认的渲染工厂创建渲染对象;


RenderManager

对渲染模块的管理,负责初始化配置;


RenderException

渲染异常是RuntimeException异常;


评论区

JFinal

2019-07-17 19:45

非常细致的源码解读,有些内容可以当成是文档的进阶来阅读, 谢谢分享

howboy5

2019-07-18 15:56

so good

风满楼

2020-06-20 18:40

PrototypeInterceptor!!!