刚开始学习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页面。
3、Controller中的参数获取
3.1 Controller提供了getPara系列方法用来从请求中获取参数。getPara系列方法分为两种类型。
第一种类型为第一个形参为String的getPara系列方法。该系列方法是对HttpServletRequest.getParameter(String name)的封装。
第二种类型为第一个形参为int或无形参的getPara系列方法。该系列方法是去获取urlPara中所带的参数值。
从0开始的下标,顺序获取url中"/"后面的参数,如果没有参数就是把"/"后的参数变成一个整体获取。各参数之间以"-"号为分隔符,如果要表示负数则使用约定字母N与n表示。
3.2 getModel用来接收页面表单域传递过来的model对象,表单域名称以”modelName.attrName”方式命名,getModel使用的attrName必须与数据表字段名完全一样。
3.3 getBean方法用于支持传统Java Bean,包括支持使用jfinal生成器生成了getter、setter方法的Model,页面表单传参时使用与setter方法相一致的attrName,而非数据表字段名。
getModel与getBean区别在于前者使用数据库表字段名而后者使用与setter方法一致的属性名进行数据注入。建议优先使用getBean方法。
4、Controller处理数据后返回数据
setAttr(String, Object)转调了HttpServletRequest.setAttribute(String, Object),该方法可以将各种数据传递给View并在View中显示出来。
5、拦截器Interceptor
不同于Spring中的需要配置拦截路径的拦截器,jfinal中的AOP功能是在方法上添加拦截器,直接显示此方法将被哪些拦截器拦截。也具备了可以拦截全局的拦截器。有些方法不需要拦截器的情况可以用@clear注解进行清除(清除只针对Clear本身所处层的向上所有层,本层与下层不清除)。首先创建一个拦截器。
以上代码中的 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() { } }
配置全局拦截器
当某个Method被多个级别的拦截器所拦截,拦截器各级别执行的次序依次为:Global、Inject、Class、Method,如果同级中有多个拦截器,那么同级中的执行次序是:配置在前面的先执行。控制层拦截器的触发,只需发起action请求即可。业务层拦截器的触发需要先使用enhance方法对目标对象进行增强,然后调用目标方法即可。OrderService service = enhance(OrderService.class);
6、校验器
Validator自身实现了Interceptor接口,所以它也是一个拦截器,配置方式与拦截器完全一样。
7、数据库操作
7.1 ActiveRecord 是 JFinal 的核心组件,一个 Model 对象唯一对应数据库表中的一条记录,而对应关系依靠的是数据库表的主键值。
建立了数据库表名到Model的映射关系,并在configPlugin(1、>>配置插件)上配置ActiveRecord插件。
ActiveRecord 模块提供了一个 Generator 工具类,可自动生成 Model、BaseModel、MappingKit、DataDictionary 四类文件,使用生成器通常只需配置Generator的四个参数即可。
BaseModel是用于被最终的Model继承的基类,所有的getter、setter方法都将生成在此文件内,BaseModel不需要人工维护,在数据库有任何变化时重新生成一次即可。
MappingKit用于生成table到Model的映射关系,并且会生成主键/复合主键的配置,也即无需在configPlugin(Plugins me)方法中书写任何样板式的映射代码。
DataDictionary是指生成的数据字典,会生成数据表所有字段的名称、类型、长度、备注、是否主键等信息。
7.2 Model是ActiveRecord中最重要的组件之一,它充当MVC模式中的Model部分。以下是Model定义示例代码:
Model操作数据方法
以上代码中的User通过继承Model,便立即拥有的众多方便的操作数据库的方法。在User中声明的dao静态对象是为了方便查询操作而定义的,该对象并不是必须的。
User中定义的 public static final User dao对象是全局共享的,只能用于数据库查询,不能用于数据承载对象。数据承载需要使用new User().set(…)来实现。
7.3 DB+record操作数据
DB+record就更方便,它不需要model,也不需要在configPlugin添加映射,直接使用,Db映射的表可以任意改变。Record相当于一个通用的Model。
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、事务
手动配置事务
注解配置事务
以上两次数据库更新操作在一个事务中执行,如果执行过程中发生异常或者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”
后台controller中一行代码就ok了
默认上传路径为工程目录下”/upload”
文件夹下UploadFile files = getFile(getPara("file"));
错误1:java.lang.RuntimeException: java.io.IOException: Posted content length of 39052690 exceeds limit of 10485760上传的文件大小超出了限制。
解决方法:
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指令支持的所有状态值如下示例:
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),安全调用只需在模板函数名后面添加一个问号即可。安全调用是指当模板函数未定义时不做任何操作。
以上代码中先是通过#@layout()调用了前面定义过的layout()这个模板函数,而这个模板函数中又分别调用了#@main()、#@css?()、#@js?()这三个模板函数,其中后两个是安全调用,所以对于不需要额外的css、js文件的模板,则不需要定义这两个方法,安全调用在调用不存在的模板函数时会直接跳过。