JFinal

登录 注册

初识jFinal

    刚开始学习jFinal,借鉴了文档、jfinal_demo与网上的相关知识,为了更好的整理所学内容写了这篇笔记。

1、配置类需要继承JFinalConfig类,其中有6个配置方法。

//配置常量

public void configConstant(Constants me) {
	// 加载少量必要配置,随后可用PropKit.get(...)获取值
	PropKit.use("a_little_config.txt");
	me.setDevMode(PropKit.getBoolean("devMode", false));//设置开发模式
}

//配置路由

public void configRoute(Routes me) {
	me.add("/", IndexController.class, "/index");	
	me.add("/blog", BlogController.class);	
}
// 第三个参数为该Controller的视图存放路径	
// 第三个参数省略时默认与第一个参数值相同,在此即为 "/blog"

Routes 类中添加路由的方法有两个:

public Routes add(String controllerKey, Class<? extends Controller> controllerClass, String viewPath)

public Routes add(String controllerKey, Class<? extends Controller> controllerClass)

    第一个参数controllerKey是指访问某个Controller所需要的一个字符串,该字符串唯一对应一个Controller,controllerKey仅能定位到Controller。

    第二个参数controllerClass是该controllerKey所对应到的Controller。

    第三个参数viewPath是指Controller返回的视图的相对路径(该参数具体细节将在Controller相关章节中给出)。当viewPath未指定时默认值为controllerKey。

//配置引擎

public void configEngine(Engine me) {
    //共享模板函数配置
    //如果模板中通过 #define 指令定义了 template function,
    //并且希望这些 template function 可以在其它模板中直接调用的话,可以进行如下配置
    me.addSharedFunction("/common/_layout.html");
    me.addSharedFunction("/common/_paginate.html");

}

//配置插件

public void configPlugin(Plugins me) {
	// 配置C3p0数据库连接池插件
	DruidPlugin druidPlugin = createDruidPlugin();
	me.add(druidPlugin);
	// 配置ActiveRecord插件
	// 所有映射在 MappingKit 中自动化搞定
	_MappingKit.mapping(arp);
	me.add(arp);
}

//拦截器配置

public void configInterceptor(Interceptors me) {}

//处理器配置

//JFinal对action及interceptor处理自身也是一个Handler名叫ActionHandler
public void configHandler(Handlers me) {}

2、控制器路径

    例如请求路径:http://localhost:8080/blog/save,系统会根据路由配置找到BlogController类调用其中的save()方法,最后由save()方法重定向到blog.html页面。

clipboard.png

3、Controller中的参数获取

3.1 Controller提供了getPara系列方法用来从请求中获取参数。getPara系列方法分为两种类型。

    第一种类型为第一个形参为String的getPara系列方法。该系列方法是对HttpServletRequest.getParameter(String name)的封装。

    第二种类型为第一个形参为int或无形参的getPara系列方法。该系列方法是去获取urlPara中所带的参数值。

    从0开始的下标,顺序获取url中"/"后面的参数,如果没有参数就是把"/"后的参数变成一个整体获取。各参数之间以"-"号为分隔符,如果要表示负数则使用约定字母N与n表示。

clipboard1.png

3.2 getModel用来接收页面表单域传递过来的model对象,表单域名称以”modelName.attrName”方式命名,getModel使用的attrName必须与数据表字段名完全一样。

3.3 getBean方法用于支持传统Java Bean,包括支持使用jfinal生成器生成了getter、setter方法的Model,页面表单传参时使用与setter方法相一致的attrName,而非数据表字段名。

clipboard2.png

clipboard3.png

    getModel与getBean区别在于前者使用数据库表字段名而后者使用与setter方法一致的属性名进行数据注入。建议优先使用getBean方法。

4、Controller处理数据后返回数据

    setAttr(String, Object)转调了HttpServletRequest.setAttribute(String, Object),该方法可以将各种数据传递给View并在View中显示出来。

clipboard4.png

5、拦截器Interceptor

    不同于Spring中的需要配置拦截路径的拦截器,jfinal中的AOP功能是在方法上添加拦截器,直接显示此方法将被哪些拦截器拦截。也具备了可以拦截全局的拦截器。有些方法不需要拦截器的情况可以用@clear注解进行清除(清除只针对Clear本身所处层的向上所有层,本层与下层不清除)。首先创建一个拦截器。

clipboard5.png

    以上代码中的 BlogInterceptor 将拦截目标方法,并且在目标方法调用前后向控制台输出文本。inv.invoke() 这一行代码是对目标方法的调用,在这一行代码的前后插入切面代码可以很方便地实现AOP。注意:必须调用 inv.invoke() 方法,才能将当前调用传递到后续的 Interceptor 与 Action。

    Before注解用来对拦截器进行配置,该注解可配置Class、Method级别的拦截器,以下是代码示例:

// 配置一个Class级别的拦截器,她将拦截本类中的所有方法
@Before(BlogInterceptor .class)
public class BlogController extends Controller {
// 配置多个Method级别的拦截器,仅拦截本方法
     @Before({BlogInterceptor .class, CccInter.class})
     public void index() {
     } 
// 未配置Method级别拦截器,但会被Class级别拦截器AaaInter所拦截
     public void show() {
     }
}

配置全局拦截器clipboard6.png

    当某个Method被多个级别的拦截器所拦截,拦截器各级别执行的次序依次为:Global、Inject、Class、Method,如果同级中有多个拦截器,那么同级中的执行次序是:配置在前面的先执行。控制层拦截器的触发,只需发起action请求即可。业务层拦截器的触发需要先使用enhance方法对目标对象进行增强,然后调用目标方法即可。OrderService service = enhance(OrderService.class);

6、校验器

    Validator自身实现了Interceptor接口,所以它也是一个拦截器,配置方式与拦截器完全一样。clipboard7.png

7、数据库操作

7.1 ActiveRecord 是 JFinal 的核心组件,一个 Model 对象唯一对应数据库表中的一条记录,而对应关系依靠的是数据库表的主键值。

    建立了数据库表名到Model的映射关系,并在configPlugin(1、>>配置插件)上配置ActiveRecord插件。clipboard8.png

    ActiveRecord 模块提供了一个 Generator 工具类,可自动生成 Model、BaseModel、MappingKit、DataDictionary 四类文件,使用生成器通常只需配置Generator的四个参数即可。clipboard9.png

    BaseModel是用于被最终的Model继承的基类,所有的getter、setter方法都将生成在此文件内,BaseModel不需要人工维护,在数据库有任何变化时重新生成一次即可。

    MappingKit用于生成table到Model的映射关系,并且会生成主键/复合主键的配置,也即无需在configPlugin(Plugins me)方法中书写任何样板式的映射代码。

    DataDictionary是指生成的数据字典,会生成数据表所有字段的名称、类型、长度、备注、是否主键等信息。

7.2 Model是ActiveRecord中最重要的组件之一,它充当MVC模式中的Model部分。以下是Model定义示例代码:

Model操作数据方法clipboard10.png

    以上代码中的User通过继承Model,便立即拥有的众多方便的操作数据库的方法。在User中声明的dao静态对象是为了方便查询操作而定义的,该对象并不是必须的。

    User中定义的 public static final User dao对象是全局共享的,只能用于数据库查询,不能用于数据承载对象。数据承载需要使用new User().set(…)来实现。

clipboard11.png

7.3 DB+record操作数据

    DB+record就更方便,它不需要model,也不需要在configPlugin添加映射,直接使用,Db映射的表可以任意改变。Record相当于一个通用的Model。clipboard12.png

7.4 分页操作

    Model 与 Db 中提供了最常用的分页API:paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras)其中的参数含义分别为:当前页的页号、每页数据条数、sql语句的select部分、sql语句除了select以外的部分、查询参数。

    paginate(int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras),相对于第一种仅仅多了一个boolean isGroupBySql参数,以下是代码示例:

dao.paginate(1, 10, true, "select *", "from girl where age > ? group by age", 18);

    以上代码中 sql 的最外层有一个 group by age,所以第三个参数 isGroupBySql 要传入 true 值。 

    如果是嵌套型sql,但是 group by 不在最外层,那么第三个参数必须为 false,例如:select * from (select x from t group by y) as temp。

    再次强调:isGroupBy 参数只有在最外层 sql 具有 group by 子句时才能为 true 值,嵌套 sql 中仅仅内层具有 group by 子句时仍然要使用 false。

    paginateByFullSql(int pageNumber, int pageSize, String totalRowSql, String findSql, Object... paras)。

    相对于其它 paginate API,将查询总行数与查询数据的两条sql独立出来,这样处理主要是应对具有复杂order by语句或者select中带有distinct的情况,只有在使用第一种paginate出现异常时才需要使用该API,以下是代码示例:

String from = "from girl where age > ?";
String totalRowSql = "select count(*) " + from;
String findSql = "select * " + from + " order by age";
dao.paginateByFullSql(1, 10, totalRowSql, findSql, 18);

    上例代码中的order by子句并不复杂,所以仍然可以使用第一种API搞定。

8、事务

手动配置事务clipboard13.png

注解配置事务clipboard14.png

    以上两次数据库更新操作在一个事务中执行,如果执行过程中发生异常或者run()方法返回false,则自动回滚事务。

9、文件上传

    form表单文件上传,在config文件中的  configConstant方法中配置上传路径 me.setBaseUploadPath("upload/"); 

    导入cos-26Dec2008.jar包

    下载地址:http://download.csdn.net/detail/long_1234567/3201385

    form表单添加 enctype=”multipart/form-data”clipboard15.png

    后台controller中一行代码就ok了

    默认上传路径为工程目录下”/upload”

    文件夹下UploadFile files = getFile(getPara("file"));

    错误1:java.lang.RuntimeException: java.io.IOException: Posted content length of 39052690 exceeds limit of 10485760上传的文件大小超出了限制。

解决方法:clipboard16.png

10、指令

10.1 输出指令#( )

    只需要为该指令传入任何表达式,指令会将这些表达式的求值结果进行输出,当表达式的值为null时没有任何输出,不会报异常。所以,对于 #(value) 这类输出不需要对value进行null值判断,如下是代码示例:

    #(value) #(object.field) #(object.field ??)代码中的object为null时将会报异常,此时需要使用空合安全取值调用运算符:object.field??

    #(a > b ? x : y) #(seoTitle ?? "JFinal 俱乐部")

    #(object.method(), null)注意代码中的输出指令参数为一个逗号表达式,逗号表达式的整体求值结果为最后一个表达式的值,而输出指令对于null值不做输出,所以这行代码相当于是仅仅调用了object.method()方法去实现某些操作。

10.2 #for指令

#for(x : list)			
#(x.field)
#end 
#for(x : map)
#(x.key)
#(x.value)
#else
    您还没有写过博客,点击此处<a href="/blog/add">开博</a>
#end

    for 指令还支持#else分支语句,在for指令迭代次数为0时,将执行#else分支内部的语句

    除了Map、List以外,for指令还支持Collection、Iterator、array普通数组、Iterable、Enumeration、null值的迭代,用法在形式上与前面的List迭代完全相同,都是#for(id : target)的形式,对于null值,for指令会直接跳过不迭代。

    for指令支持的所有状态值如下示例:clipboard17.png

10.3 #set指令

    set指令用于声明变量同时对其赋值,也可以是为已存在的变量进行赋值操作。set指令只接受赋值表达式,以及用逗号分隔的赋值表达式列表。

    #set(x = 123) #set(a = 1, b = 2, c = a + b) #set(array[0] = 123)

    #set(map["key"] = 456)

10.4 #include 指令

    include指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用。#include("sidebar.html")

10.5 #define 指令

    define指令可以定义模板函数(Template Function)。通过define指令,可以将需要被重用的模板片段定义成一个个的 template function,在调用的时候可以通过传入参数实现功能。

    调用define定义的模板函数的格式为:#@name(p1, p2…, pn),模板函数调用比指令调用多一个@字符,多出的@字符用来与指令调用区别开来。模板函数还支持安全调用,格式为:#@name?(p1, p2…, pn),安全调用只需在模板函数名后面添加一个问号即可。安全调用是指当模板函数未定义时不做任何操作。

clipboard18.png

     clipboard19.png    以上代码中先是通过#@layout()调用了前面定义过的layout()这个模板函数,而这个模板函数中又分别调用了#@main()、#@css?()、#@js?()这三个模板函数,其中后两个是安全调用,所以对于不需要额外的css、js文件的模板,则不需要定义这两个方法,安全调用在调用不存在的模板函数时会直接跳过。


评论