Jfinal 由Handler、Interceptor、Controller、Render、Plugin 五大部分组成
JFinal流程图:
JFinal架构图:
JFinalConfig
基于JFinal的web项目需要创建一个继承自JFinalConfig类的子类,该类用于对整个web项目进行配置。
Jfinal子类需要实现6个抽象方法,如下所示
public class DemoConfig extends JFinalConfig{
public void configConstant(Constants me){}
public void configRoute(Constants me){}
public void configEngine(Routes me){}
public void configPlugin(Plugins me){}
public void configInterceptor(Interceptors me){}
public void configHandler(Handlers me){}
}
configConstant(Constants me)
此方法来配置Jfinal常量值,如开发模式常量devModel的配置,如下代码配置了JFinal运行在开发模式下:
public void configConstant(constants me){
me.setDevMode(true);
}
在开发模式下,JFinal会对每次请求输出报告,如:URL、Controller、method、以及请求参数
configRoute(Routes me)
此方法来配置JFinal访问路由,如下
public void configRoute(Routes me){
me.setBaseViewPath("/view");
me.addInterceptor(new FrontINterceptor());
me.add("/hello",HelloController.class);
}
代码配置将“/hello”映射到HelloController 这个控制器,通过http://localhost/hello 将访问HelloController.index()方法,而http://localhost/hello/methodName将访问到HelloController.methodName()方法。
finalView=baseViewPath+viewPath+view
注意:当 view以“/”字符打头时表示绝对路径,baseViewPath与viewPath将被忽略。
Jfinal路由规则如下
url组成 | 访问目标 |
---|---|
controllerKey | Controller.index() |
controllerKey/method | Controller.method() |
controllerKey/method/v0-v1 | Controller.method(),所带Url参数值为:v0-v1 |
controllerKey/v0-v1 | Controller.index 所带Url参数值为:v0-v1 |
Jfinal 默认用减号“-”来分隔多个值(constants.setUrlParaSeparator(String)设置分隔符),在Controller中可以通过getPara(int index)分别取出这些值。
controllerKey、method、urlPara这三部分必须使用“/”分隔。
注意:controllerKey自身也可以包含“/” 如:“/admin/article”。
JFinal在以上路由规则外,还提供了@ActionKey注解 可以打破原有规则。
public class UserController extends Controller {
@ActionKey("/login")
public void login(){
render("login.html");
}
}
该注解会覆盖之前生成的路径,使路径强制变为“/login” 该注解还可以让actionKey中使用减号或数字等字符,如“/user/123-456”
定制路由
public class FrontRoutes extends Routes {
public void config() {
setBaseViewPath("/view/front");
add("/",IndexController.class);
add("/blog",BlogController.class);
}
}
public class AdminRoutes extends Routes {
public void config() {
setBaseViewPath("/view/admin");
addInterceptor(new AdminController());
add("/admin",AdminController.class);
add("/admin/user",UserController.class);
}
}
public class MyJFinalConfig extends JFinalConfig {
public void configRoute(Routes route) {
route.add(new FrontRoutes());
route.add(new AdminRoutes());
}
public void configConstant(Constant constant){}
public void configEngine(Engine engine){}
public void configPlugin(Plugins plugin){}
public void configInterceptor(Interceptor interceptor){}
public void configHandler(Handlers handler){}
}
如上三段代码,可以将路由按功能分类,方便多人开发。
SetBasePath() 设置了各自Controller.render(view)时使用的baseViewPath。
AdminRoutes 添加了一个路由级别的拦截器,会在这个路由下的方法执行之前做一些处理工作。
configEngine(Engine engine)
此方法用来配置 Template Engine 示例代码:
public void configEngine(Engine engine) {
engine.addSharedFunction("/_view/common/__layout.html");
engine.addSharedFunction("/_view/common/_paginate.html");
engine.addSharedFunction("/_view/_admin/common/__admin_layout.html");
}
上面的方法向模板引擎中添加了三个定义了共享函数的模板文件。
configPlugin(Plugins plugin)
此方法来配置JFinal的Plugin 如下代码配置了Druid连接池插件与ActiceRecord数据库访问插件
public void configPlugin(Plugins plugin) {
loadPropertyFile("your_app_config.properties");
DruidPlugin dp=new DruidPlugin(getProperty("jdbcUrl"),
getProperty("user"),getProperty("password"));
plugin.add(dp);
ActiceRecordPlugin arp=new ActiveRecordPlugin(dp);
plugin.add(arp);
arp.addMapping("user",User.class);
}
JFinal 插件架构是其主要扩展方式之一,可以方便的创建插件并应用到项目中去。
configInterceptor(Interceptors interceptor)
此方法来配置JFinal的全局拦截器,全局拦截器将拦截所有的action请求,除非使用@Clear注解在Controller中清除,如下代码配置了名为AuthInterceptor的拦截器。
public void configInterceptor(Interceptors interceptor) {
interceptor.add(new AuthInterceptor());
}
Interceptor配置粒度分为Global、Inject、Class、Method四个层次。其中以上代码配置为全局。
configHandler(Handlers handler)
此方法用来配置JFinal的Handler。Handler可以接管所有的web请求,并对应用拥有完全的控制权,可以很方便地实现更高层的功能性扩展。
public void configHandler(Handlers handler) {
handler.add(new ResourceHandler());
}
afterJFinalStart()和beforeJFinalStop()
JFinalConfig中afterJFinalStart()与beforeJFinalStop方法 如果覆盖,JFinal会在启动之后回调afterJFinalStart,在系统关闭前回调beforeJFinalStop,可以在项目启动后,和项目停止运行前进行额外操作,如系统启动后创建调度线程或在系统关闭前写回缓存。
PropKit
PropKit 工具类用来操作外部配置文件。可以极度方便地在系统任意时候使用。
public class MyJFinalConfig extends JFinalConfig {
public void configConstant(Constants constant) {
//第一次使用use加载的配置将生成主配置,可以通过PropKit.get(...)直接取值
PropKit.use("little_config.properties");
constant.setDevMode(PropKet.getBoolean("devMode"));
}
public void configPlugin(Plugins plugin) {
String redisHost=PropKit.use("redis_config.properties").get("host");
int redisPort=PropKit.use("redis_config.properties").getInt("port");
RedisPlugin rp=new RedisPlugin("myRedis",redisHost,redisPort);
plugin.add(redisPlugin);
Prop prop=PropKit.use("db_config.properties");
DruidPlugin dp=new DruidPlugin(prop.get("jdbcUrl"),prop.get("user"),prop.get("password"));
plugin.add(dp);
}
}
如上,PorpKit 可同时加载多个配置文件,第一个被加载的配置文件可以使用PropKit.get()方法直接操作,非第一个被加载的配置文件需要使用PropKit.use(...).get(...)来获得。
PropKit.use(...)方法在加载配置文件内容以后会将数据缓存在内存中,用PropKit.useless(...)将缓存内容清除。
Controller
Controller是定义action方法的地点,一个Controller可以包含多个action。Controller是线程安全的。
public class HelloController extends Controller {
public void index() {
renderText("此方法是一个action");
}
public void test() {
renderText("此方法是一个action");
}
}
以上代码定义了两个action,HelloController.index()和HelloController.test() 在Controller中提供了getPara、getModel系列方法setAttr方法以及render系列方法供action使用。
getPara 系列方法
getPara(String)是对HttpServletRequest.getParameter(String name)的封装,这类方法都是转调该方法。
getPara(int)或无参 是获取urlPara中所带的参数值。
getParaMap 与 getParaNames 分别对应HttpServletRequest的getParameterMap与getParameterNames。
方法 | 描述 |
---|---|
getPara("title") | 返回页面表单域名为title参数值 |
getParaToInt("age") | 返回页面表单域名为age参数值并转为int |
getPara(0) | 返回url请求中urlPara参数的第一个值,如http://localhost/controllerKey/method/v0-v1-v2 这个请求将返回v0 |
getPara() | 返回v0-v1-v2 |
约定字母N与n可以表示负号 getParaToInt(2) 2-5-N8 将会返回-8
getBean 与getModel系列方法
getModel用来接收页面表单传递过来的model对象,表单域名称以“modelName.attrName”方式命名,getModel使用的attrName必须与数据表字段名完全一样。
getBean支持传统的javaBean
getModel与getBean区别在于前者使用数据表字段名,而后者使用与setter方法一致的属性名进行数据注入。
如果表单域名称为“blog.title”作为name属性,blog正好是Blog类的首字母小写可直接Blog blog=getModel(Blog.class)获取。若非首字母小写需要使用Blog blog=getModel(Blog.class,"othername")
setAttr
setAttr(String,Object)封装了HttpServletRequest.setAttribute(String,Object),该方法可以将各种数据传递给view 并显示。
getFile 文件上传
Controller 提供了getFile系列方法支持文件上传。
注意:如果客户端请求问multipart request(form 表单使用了entype="multipart/form-data"),那么必须先调用getFile系列方法才能使用getPara系列方法正常工作,因为multipart request需要通过getFile系列方法解析请求体中的数据,包括参数。同样的道理在Interceptor、Validator中也需要先调用getFile。
文件默认上传至项目根路径下的upload子路径之下,该路径称为文件上传基础路径。可以在JFinalConfig.configConstant(Constants constant)方法中,通过constant.setBaseUploadPath(baseUploadPath)设置文件上传基础路径,该路径接收以"/"打头或者以windows磁盘盘符打头的绝对路径,即可将基础路径指向项目根路径之外,方便单机多实例部署。当路径参数设置为相对路径时,则是以项目根路径为基础的相对路径。
renderFile 文件下载
Controller提供了renderFile系列方法支持文件下载。文件默认下载路径为项目根路径下的download子路径之下,该路径称为文件下载举出路径。可以在JFinalConfig.configConstant(Constants constant) 方法中通过constant.setBaseDownloadPath(baseDownloadPath)设置文件下载基础路径。路径结构与上传时相同。
session 操作方法
通过setSessionAttr(key,value)可以向session中存放数据,getSessionAttr(key)可以从session中读取数据。还可以通过getSession()得到session对象从而使用全面的session API.
render 系列方法
render系列方法将渲染 不同类型的视图并返回给客户端。JFinal 目前支持视图类型有:JFinal Templte、FreeMarker、JSP、Volocity、JSON、File、Text、Html等等。除了JFinal支持的视图类型外,还可以通过继承Render抽象类来无限扩展视图类型。
通常情况下使用COntroller.render(String)方法来渲染视图,视图类型由JFinalConfig.configConstant(Constants constant)配置中的constants.setViewType(ViewType)来决定,该设置方法支持的ViewType有:JFINAL_TEMPLATE、FreeMarker、JSP、Velocity,默认为JFINAL_TEMPLATE。
此外,还可以通过constant.setRenderFactory(IRenderFactory) 来设置Controller中所有render系列方法所使用的Render实现类。
假设在JFinalConfig.configRoute(Routes route)中有如下Controller映射配置:routes.add("/user",UserController.class,"/path"),render(String view)使用例子:
方法调用 | 描述 |
---|---|
render("test.html") | 渲染名为test.html的视图,该视图的全路径为“/path/test.html” |
render("/other_path/test.html") | 渲染名为test.html的视图 全路径为/other_path/test.html,当参数以/开头时将采用绝对路径。 |
renderTmplate("test.html") | 渲染名为test.html的视图,且视图类型为JFinalTemplate。 |
renderFreeMarker("test.html") | 渲染名为test.html的视图,视图类型为FreeMarker |
renderJsp("test.html") | 渲染名为test.html的视图,视图类型为Jsp |
renderVelocity(test.html) | 渲染名为test.html的视图,且视图类型为Velocity |
renderJson() | 将所有通过Controller.setAttr(String,Object)设置的变量转换成json数据并渲染。 |
renderJson("users",userList) | 以users为根,仅将userList中的数据转换成json数据并渲染 |
renderJson(user) | 将user对象转换成json数据并渲染 |
renderJson("{"age":18}") | 直接渲染json字符串 |
renderJson(new String[]{"user","blog"}) | 仅将setAttr("user",user)与setAttr("blog",blog)设置的属性转换成json并渲染。使用setAttr设置的其他属性不转换为json |
renderFile("test.zip") | 渲染名为test.zip的文件,一般用于文件下载 |
renderText("Hello JFinal") | 渲染纯文本内容"Hello JFinal" |
renderHtml("hello Html") | 渲染Html内容"Hello Html" |
renderError(404,"test.html") | 渲染名为test.html的文件,且状态为404 |
RednerError(500,"test.html") | 渲染名为test.html的文件,且状态为500 |
renderNull() | 不渲染,即不向客户端返回数据 |
render(new XmlRender) | 使用自定义xmlReader渲染 |
- 注意
- IE 需要特殊处理
- 除renderError方法以外,在调用render系列方法后程序并不会立即返回,如果需要立即返回请使用return语句。
- 3.在一个action中多次调用render方法只有最后一次有效。
AOP
JFinal 采用急速化的AOP设计,专注AOP最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入IOC也无需使用XML
Interceptor
interceptor可以对方法进行拦截,并提供机会在方法的前后添加切面代码,实现AOP的核心目标。Interceptor接口仅仅定义了一个方法 void intercept(Invocation inv).以下是简单的示例:
public class DemoInterceptor implements Interceptor {
public void intercept(Invocation inv) {
System.out.println("Before method");
inv.invoke();
System.out.println("After method");
}
}
Invocation作为Interceptor接口intercept方法中唯一参数,提供了很多便利的方法在拦截器中使用。以下为Invocation中的方法:
方法 | 描述 |
---|---|
void invoke() | 传递本次调用,调用剩下的拦截器与目标方法 |
Controller getController() | 获取Action调用的Controller对象(仅用于控制层拦截) |
String getActionKey() | 获取Action调用的action key值(仅用于控制层拦截) |
String getControllerKey() | 获取Action调用的controller key值(仅用于控制层拦截) |
String getViewPath() | 获取Action调用的视图路径(仅用于控制层拦截) |
获取被拦截方法所属的对象 | |
Method getMethod() | 获取被拦截方法的Method对象 |
String getMethodName() | 获取被拦截方法的方法名 |
Object[] getArgs() | 获取被拦截方法的所有参数值 |
Object getArg(int) | 获取被拦截方法指定序号的参数值 |
获取被拦截方法的返回值 | |
void setArg(int) | 设置被拦截方法指定序号的参数值 |
void setReturn Value(Object) | 设置被拦截方法的返回值 |
boolean isActionInvocation() | 判断是否为Action调用,也即是否为控制层拦截 |
@Before
Before注解用来对拦截器进行配置,该注解可配置Class Method级别的拦截器,以下是代码示例
//配置一个Class级别的拦截器,它将拦截本类中所有方法
@Before(AaaInterceptor.class)
public class BlogController extends Controller {
//配置多个Method级别的拦截器,仅拦截本方法
@Before({BbbInterceptor.class,CccInter.class})
public void index() {
}
//未配置method级别拦截器,但会被class级别拦截器AaaInterceptor所拦截
public void show() {
}
}
当某个method被多个级别拦截器所拦截,拦截器各级别执行顺序为:Global、Inject、Class、Method,如果同级中有多个拦截器,那么同级中配置在前面的先执行。
Clear
拦截器从上到下依次分为Global、Inject、Class、Method 四个层次,Clear用于清除自身所处层次以上层的拦截器。
Clear生命在Method层时,针对Global、Inject、Class进行清除。Clear生命在Class层时,将针对Global、Inject进行清除。Clear注解携带参数时清除目标层中指定的拦截器。
Clear用法记忆技巧
共有Global、Inject、Class、Method四层拦截器
清除只针对Clear本身所处层的向上所有层,本层与下层的不清除。
不带参数时清除所有拦截器,带参数时清除指定拦截器。
例如在后台管理系统,配置了一个全局的权限拦截器,但是其登陆action就必须清除掉它,否则无法完成登陆操作,以下是示例代码:
@Before (AuthInterceptor.class)
public class UserController extends Controller {
//AuthInterceptor 已被Clear清除掉,不会被其拦截
@Clear
public void login() {
}
public void show() {
}
}
Clear注解带有参数时,能清除指定的拦截器。以下是一个更全面的代码:
@Before(AAA.class)
public class UserController extends Controller {
@Clear
@Before(BBB.class)
public void login() {
global、Class级别的拦截器将被清除,但本方法上的声明的BBB不受影响
}
@clear({AAA.class,CCC.class})
@Before(CCC.class)
public void show() {
//虽然Clear注解中指定清除CCC,但它无法被清除,因为操作只针对本层以上的各层
}
}
interceptor 的触发
JFinal中的AOP被划分为控制层AOP 以及业务层AOP,严格来说业务层AOP并非仅限于业务层使用,因为JFinal AOP可以应用于其他任何地方。
控制层拦截器的触发,只需发起action请求即可。业务层拦截器的触发需要先使用enhance方法对目标对象进行增强,然后调用目标方法即可。以下是业务层AOP使用例子:
public class OrderService {
//配置事物拦截器
@Before(Tx.class)
public void payment(int orderId,int userId) {
//service code here
}
}
public class OrderController extends Controller {
public void payment() {
//使用enhance方法对业务层进行增强,时期具有AOP能力
OrderService service =enhance(OrderService.class);
//调用payment方法时将会触发拦截器
service.payment(getParaToInt("OrderId"),getParaToInt("userId"));
}
}
以上代码中OrderService是业务层类,其中payment方法之上配置了Tx事物拦截器,OrderController是控制器,在其中使用了enhance方法对OrderService进行了增强,随后调用其payment方法便可触发Tx拦截器。简言之,业务层AOP的触发相对于控制层仅需多调用一次enhance方法即可,而Interceptor、Before、Clear的使用方法完全一样。
Duang、Enhancer
Duang、Enhancer用来对目标进行增强,让其拥有AOP的能力。以下是代码示例:
public class TestMain {
//使用Duang.duang方法在任何地方对目标进行增强
OrderService service =Duang.duang(OrderService.class);
//调用payment方法将会触发拦截器
service.payment(...);
//使用Enhancer.enhance方法可以在任何地方对目标进行增强
OrderService service=Enhancer.enhance(OrderService.class);
}
Duang.duang()、Enhancer.enhance()与Controller.enhance()系方法在更能上完全一样,他们除了支持类增强以外,还支持对象增强,例如duang(new OrderService())以对象为参数的用法,功能本质上是一样的。
使用Duang、ENhancer类可以对任意目标在任何地方增强,所以JFinal的AOP可以应用于非web项目,只需要引入jfinal.jar包,然后使用Enhancer.enhance()或Duang.duang()即可极速使用Jfinal的AOP功能。
Inject拦截器
Inject拦截器是指在使用enhance或duang方法增强时使用参数传入拦截器。Inject可以对目标完全无侵入地应用AOP。
假如需要增强的目标在jar包内,无法使用Before注解对其配置拦截器,此时使用Inject拦截器可以对jar包中的目标进行增强。如下是Inject拦截器示例:
public void injectDemo() {
//为enhance方法传入拦截器称为Inject拦截器,下面代码中的Tx称为Inject拦截器
OrderService service =Enhancer.enhance(OrderService.class,Tx.class);
service.payment(...);
}
如上代码中Enhance.enhance()方法的第二个参数Tx.class被称为Inject拦截器,使用此方法可以无侵入地对目标进行AOP增强。
Inject拦截器可以被认为就是Class级别拦截器,只是次序在Class级别之前而已。
Routes级别拦截器
Routes级别拦截器是指在Routes中添加的拦截器,如下是示例
public class AdminRoutes extends Routes {
public void config() {
addInterceptor(new AdminInterceptor());
add("/admin",IndexAdminController.class,"/index");
add("/admin/project",ProjectAdminController.class,"/project");
add("/admin.share",ShareAdminController.class,"/share");
}
}
以上的addInterceptor(new AdminAuthInterceptor())向AdminRoutes中添加了拦截器,该拦截器AdminAuthInterceptor将拦截添加在AdminRoutes中所有Controller,例如,在本例中,该拦截器将拦截IndexAdminController、ProjectAdminController、ShareAdminController中所有的action方法。
Routes级别的拦截器在本质上与Inject拦截器完全相同,同样也在是class级别拦截器之前被调用。
Actice Record
ActiveRecord 是Jfinal最核心的组成部分之一,通过ActiceRecord来操作数据库,将极大地减少代码量,极大地提升开发效率。
ActiveRecordPlugin
ActiveRecord是作为JFinal的Plugin而存在的,所以使用时需要在JfinalConfig中配置ActiceRecordPlugin。
以下是配置代码:
public class DemoConfig extends JFinalConfig {
public void configPlugin(Plugins plugin) {
DruidPlugin dp =new DruidPlugin("jdbc:mysql://localhost/db_name","username","password");
plugin.add(dp);
ActiceRecordPlugin arp=new ActiceRecordPlugin(dp);
plugin.add(arp);
arp.addMapping("user",User.class);
arp.addMapping("article","article_id",Article.class);
}
}
以上代码配置了两个插件:DruidPlugin与ActiceRecordPlugin,前者是druid数据源插件,后者是ActiveRecord支持插件。ActiveRecord中定义了addMapp(String tableName,Class extends Model> modelClass)方法,该方法建立数据库表名到Model的映射关系。
另外,以上代码中arp.addMapping("user",User.class),表的主键名默认为id,如果主键名称为"user_id"则需要手动指定,如:arp.addMapping("user","user_id",User.class).
Model
model是acticeRecord中最重要的组件之一,它充当MVC模式中Model部分,以下是Model定义示例代码:
public class User extends Model {
public static final User dao=new User().dao();
}
以上代码中的User通过继承Model,便立即拥有众多方便的操作数据库的方法。在User中声明的dao静态对象是为了方便查询操作而定义的,该对象并不是必须的。基于ActiceRecord的Model无需定义属性,无需定义getter、setter方法,无需XML配置,无需Annotation配置,极大降低了代码量。
以下为Model的一些常见用法:
//创建name属性为James,age属性为25的User对象并添加到数据库
new User().set("name","James").set("age",25).save();
//删除id值为25的User
User.dao.deleteById(25);
//查询id为25的User将其name属性改为James并更新到数据库
User.dao.findById(25).set("name","James").update();
//查询id值为25的User,且仅仅取name与age两个字段的值
User user=User.dao.findBuIdLoadColumns(25,"name,age");
//获取user的name属性
String userName=user.getStr("name");
//获取user的age属性
int age=user.getInt("age");
//查询所有年龄大于18岁的user
List users=User.dao.find("select * from user where age>18");
//分页查询年龄大于18的user,当前页号为1,每页10个user
Page userPage =User.dao.paginamte(1,10,"select *","from user where age > ?",18);
- 特别注意
- User中定义的public static final User dao对象是全局共享的,只能用于数据库查询,不能用于数据承载对象。数据承载需要使用new User().set(...)来实现。