JFinal框架详细教程(一)


Jfinal 由Handler、Interceptor、Controller、Render、Plugin 五大部分组成

JFinal流程图:

JFinal框架详细教程(一)_第1张图片
JFinal流程图

JFinal架构图:

JFinal框架详细教程(一)_第2张图片
此处输入图片的描述

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渲染
  • 注意
    1. IE 需要特殊处理
    1. 除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调用的视图路径(仅用于控制层拦截)
T getTarget() 获取被拦截方法所属的对象
Method getMethod() 获取被拦截方法的Method对象
String getMethodName() 获取被拦截方法的方法名
Object[] getArgs() 获取被拦截方法的所有参数值
Object getArg(int) 获取被拦截方法指定序号的参数值
T getReturnValue() 获取被拦截方法的返回值
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 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(...)来实现。

你可能感兴趣的:(JFinal框架详细教程(一))