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异常;