Eclipse上Maven环境配置使用:https://www.cnblogs.com/tangshengwei/p/6341462.htmlcss
W3CSchool教程:https://www.w3cschool.cn/jfinal/ia1z1qjd.htmlhtml
Jfinal API:https://www.jfinal.com/doc前端
基于 JFinal 的 web 项目须要建立一个继承自 JFinalConfig 类的子类,该类用于对整个 web项目进行配置。
java
JFinalConfig 子类须要实现五个抽象方法,以下所示:mysql
public class DemoConfigextends JFinalConfig {
public void configConstant(Constants me){}
public void configRoute(Routesme) {} publicvoid configPlugin(Plugins me) {} public void configInterceptor(Interceptors me) {} public void configHandler(Handlersme) {} }
此方法用来配置 JFinal 常量值,如开发模式常量 devMode 的配置,默认视图类型 ViewType的配置,以下代码配置了 JFinal 运行在开发模式下且默认视图类型为 JSP:linux
public void configConstant(Constantsme) { me.setDevMode(true); me.setViewType(ViewType.JSP); }
在开发模式下,JFinal 会对每次请求输出报告,如输出本次请求的 Controller、Method 以 及请求所携带的参数。JFinal 支持 JSP、FreeMarker、Velocity 三种经常使用视图。web
此方法用来配置访问路由,以下代码配置了将 "/hello" 映射到HelloController这个控制器,经过如下的配置,http://localhost/hello 将访问 HelloController.index() 方法,而http://localhost/hello/methodName 将访问到 HelloController.methodName() 方法。ajax
public void configRoute(Routes me) { // 若是要将控制器超类中的 public 方法映射为 action 配置成 true,通常不用配置 me.setMappingSuperClass(false); me.setBaseViewPath("/view"); me.addInterceptor(new FrontInterceptor()); me.add("/hello", HelloController.class); }
Routes.setBaseViewPath(baseViewPath) 方法用于为该 Routes 内部的全部 Controller 设置视图渲染时的基础路径,该基础路径与Routes.add(…, viewPath) 方法传入的viewPath以及 Controller.render(view) 方法传入的 view 参数联合组成最终的视图路径,规则以下:正则表达式
finalView = baseViewPath + viewPath + viewredis
注意:当view以 “/” 字符打头时表示绝对路径,baseViewPath 与 viewPath 将被忽略。
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。
JFinal 路由规则以下表:
从表中能够看出,JFinal 访问一个确切的Action须要使用 controllerKey与 method 来精肯定位,当method 省略时默认值为 index。
urlPara 是为了能在 url 中携带参数 值,urlPara 能够在一次请求中同时携带多个值,JFinal 默认使用减号“-”来分隔多个值(可 经过constants. setUrlParaSeparator(String)设置分隔符),在 Controller 中能够经过 getPara(intindex)分别取出这些值。controllerKey、method、urlPara 这三部分必须使用正斜杠“/”分隔。 注意,controllerKey 自身也能够包含正斜杠“/”,如“/admin/article”,这样实质上实现了struts2 的 namespace 功能。
JFinal在以上路由规则以外还提供了ActionKey注解,能够打破原有规则,如下是代码示例:
public class UserController extends Controller { @ActionKey("/login") public void login() { render("login.html"); } }
假定 UserController 的 controllerKey 值为“/user”,在使用了@ActionKey(“/login”)注解以 后,actionKey 由原来的“/user/login”变为了“/login”。该注解还可让 actionKey 中使用减号或 数字等字符,如“/user/123-456”。
若是 JFinal 默认路由规则不能知足需求,开发者还能够根据须要使用 Handler 定制更加个 性化的路由,大致思路就是在 Handler 中改变第一个参数 String target 的值。
JFinal 路由还能够进行拆分配置,这对大规模团队开发特别有用,如下是代码示例:
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 AdminInterceptor()); add("/admin",AdminController.class); add("/admin/user", UserController.class); } } public class MyJFinalConfigextends JFinalConfig{ publicvoid configRoute(Routesme) {
me.add(new FrontRoutes()); // 前端路由 me.add(new AdminRoutes()); // 后端路由 } public void configConstant(Constantsme) {} public void configPlugin(Pluginsme) {} public void configInterceptor(Interceptorsme) {} public void configHandler(Handlersme) {} }
如上三段代码,FrontRoutes 类中配置了系统前端路由,AdminRoutes 配置了系统后端路由, MyJFinalConfig.configRoute(…)方法将拆分后的这两个路由合并起来。使用这种拆分配置不只 可让 MyJFinalConfig 文件更简洁, 并且有利于大规模团队开发, 避免多人同时修改 MyJFinalConfig 时的版本冲突。
FrontRoutes与AdminRoutes中分别使用setBaseViewPath(…)设置了各自Controller.render(view)时使用的baseViewPath。
AdminRoutes 还经过addInterceptor(new AdminInterceptor())添加了 Routes 级别的拦截器,该拦截器将拦截 AdminRoutes 中添加的全部 Controller,至关于业务层的inject拦截器,会在class拦截器以前被调用。这种用法能够避免在后台管理这样的模块中的全部class上使用@Before(AdminInterceptor.class),减小代码冗余。
此方法用来配置 JFinal 的 Plugin,以下代码配置了 C3p0 数据库链接池插件与 ActiveRecord数据库访问插件。经过如下的配置,能够在应用中使用 ActiveRecord 很是方便地操做数据库。
public void configPlugin(Pluginsme) { loadPropertyFile("your_app_config.txt"); C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"), getProperty("user"), getProperty("password")); me.add(c3p0Plugin); ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);me.add(arp); arp.addMapping("user",User.class); }
JFinal 插件架构是其主要扩展方式之一,能够方便地建立插件并应用到项目中去。
此方法用来配置 JFinal 的全局拦截器,全局拦截器将拦截全部 action 请求,除非使用@Clear 在 Controller 中清除,以下代码配置了名为 AuthInterceptor 的拦截器。
public void configInterceptor(Interceptorsme){
me.add(newAuthInterceptor()); }
Interceptor 配置粒度分为 Global、Class、Method 三个层次,其中以上代码配置粒度为全局。Class 与 Method 级的 Interceptor 配置将在后续章节中详细介绍。
此方法用来配置 JFinal 的 Handler,以下代码配置了名为 ResourceHandler 的处理器,Handler 能够接管全部 web 请求,并对应用拥有彻底的控制权,能够很方便地实现更高层的功能性扩 展。
public void configHandler(Handlers me) { me.add(new ResourceHandler()); }
JFinalConfig 中的 afterJFinalStart()与 beforeJFinalStop()方法供开发者在 JFinalConfig 继承类中 覆盖 。 JFinal 会在系统启动完成后回调 afterJFinalStart() 方 法 , 会 在 系 统 关 闭 前 回 调 beforeJFinalStop()方法。这两个方法能够很方便地在项目启动后与关闭前让开发者有机会进行 额外操做,如在系统启动后建立调度线程或在系统关闭前写回缓存。
PropKit 工具类用来操做外部配置文件。PropKit 能够极度方便地在系统任意时空使用,如 下是示例代码:
userName=james
email=no-reply@jfinal.com
devMode=true
PropKit.use("config.txt"); String userName = PropKit.get("userName"); String email = PropKit.get("email"); // Prop 配合用法 Prop p = PropKit.use("config.txt"); Boolean devMode = p.getBoolean("devMode");
以下是在项目中具体的使用示例:
public class AppConfig extends JFinalConfig{ public void configConstant(Constantsme) { // 第一次使用use加载的配置将成为主配置,能够经过PropKit.get(...)直接取值 PropKit.use("a_little_config.txt");
me.setDevMode(PropKit.getBoolean("devMode")); } public void configPlugin(Pluginsme) { // 非第一次使用use加载的配置,须要经过每次使用use来指定配置文件名再来取值 String redisHost= PropKit.use("redis_config.txt").get("host"); int redisPort= PropKit.use("redis_config.txt").getInt("port");
RedisPlugin rp =new RedisPlugin("myRedis", redisHost,redisPort); me.add(rp); // 非第一次使用 use加载的配置,也能够先获得一个Prop对象,再经过该对象来获取值 Prop p =PropKit.use("db_config.txt"); DruidPlugin dp = new DruidPlugin(p.get("jdbcUrl"), p.get("user")…); me.add(dp); } }
如上代码所示,PropKit 可同时加载多个配置文件,第一个被加载的配置文件可使用 PorpKit.get(…)方法直接操做,非第一个被加载的配置文件则须要使用 PropKit.use(…).get(…) 来操做。PropKit 的使用并不限于在 YourJFinalConfig 中,能够在项目的任何地方使用, JFinalConfig 的 getProperty 方法其底层依赖于 PropKit 实现。
Controller 是 JFinal 核心类之一,该类做为 MVC 模式中的控制器。基于 JFinal 的 Web 应 用的控制器须要继承该类。Controller 是定义 Action 方法的地点,是组织 Action 的一种方式, 一个 Controller 能够包含多个 Action。Controller 是线程安全的。
Controller 以及在其中定义的 public 无参方法称为一个 Action。Action 是请求的最小单位。
Action 方法必须在 Controller 中声明,该方法必须是 public 可见性且没有形参。
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 使用。
若是但愿 controller 中的 public 方法不成为一个 action,可使用 @NotAction 注解。@NotAction 注解一般用于引入了 BaseController 的中间 Controller,例如:
public class BaseController extends Controller { // 不但愿成为 action,仅供子类调用,或拦截器中调用 @NotAction public void getLoginUser() { } }
Controller 提供了 getPara 系列方法用来从请求中获取参数。getPara 系列方法分为两种类型。 第 一 种 类 型 为 第 一 个 形 参 为 String 的 getPara 系列 方法 。 该 系 列 方法 是对 HttpServletRequest.getParameter(String name) 的 封 装 , 这 类 方 法 都 是 转 调 了 HttpServletRequest.getParameter(String name)。第二种类型为第一个形参为 int 或无形参的 getPara 系列方法。该系列方法是去获取 urlPara 中所带的参数值。getParaMap 与 getParaNames 分别对应 HttpServletRequest 的 getParameterMap 与 getParameterNames。
记忆技巧:第一个参数为 String 类型的将获取表单或者 url 中问号挂参的域值。第一个参数为int 或无参数的将获取 urlPara 中的参数值。
getPara 使用例子:
方法调用 |
返回值 |
getPara(”title”) |
返回页面表单域名为“title”参数值 |
getParaToInt(”age”) |
返回页面表单域名为“age”的参数值并转为 int 型 |
getPara(0) |
返回 url 请求中的 urlPara 参数的第一个值,如 http://localhost/controllerKey/method/v0-v1-v2 这个请求将 返回”v0” |
getParaToInt(1) |
返回 url 请求中的 urlPara 参数的第二个值并转换成 int 型,如 http://localhost/controllerKey/method/2-5-9 这个请求将返回 5 |
getParaToInt(2) |
如 http://localhost/controllerKey/method/2-5-N8 这个 请求将返回 -8。注意:约定字母 N 与 n 能够表示负 号,这对 urlParaSeparator 为 “-” 时很是有用。 |
getPara() |
返回 url 请求中的 urlPara 参数的总体值, 如 http://localhost/controllerKey/method/v0-v1-v2 这个 请求将返回”v0-v1-v2” |
getModel 用来接收页面表单域传递过来的 model 对象,表单域名称以”modelName.attrName” 方式命名。除了 getModel 之外,还提供了一个 getBean 方法用于支持传统的 Java Bean。如下 是一个简单的示例:
// 定义Model,在此为Blog public class Blog extends Model<Blog> { public static final Blog me = new Blog(); } // 在页面表单中采用modelName.attrName形式为做为表单域的name <form action="/blog/save" method="post"> <input name="blog.title" type="text"> <input name="blog.content" type="text"> <input value="提交" type="submit"> </form> public class BlogController extends Controller { public void save() { // 页面的modelName正好是Blog类名的首字母小写 Blog blog = getModel(Blog.class); // 若是表单域的名称为 "otherName.title"可加上一个参数来获取 blog = getModel(Blog.class, "otherName"); } }
上面代码中,表单域采用了”blog.title”、”blog.content”做为表单域的 name 属性,”blog”是类 文件名称”Blog”的首字母变小写,”title”是 blog 数据库表的 title 字段,若是但愿表单域使用任 意的 modelName , 只 需 要 在 getModel 时 多 添 加 一 个 参 数 来 指 定 , 例 如 : getModel(Blog.class, ”otherName”)。
若是但愿传参时避免使用 modelName 前缀,可使用空串做为 modelName 来实现:getModel(Blog.class, “”); 这对开发纯 API 项目很是有用。
setAttr(String, Object)转调了 HttpServletRequest.setAttribute(String, Object),该方法能够将 各类数据传递给 View 并在 View 中显示出来。
Controller 提供了 getFile 系列方法支持文件上传。特别注意:若是客户端请求为 multipart request(form 表单使用了 enctype="multipart/form-data"),那么必须先调用 getFile 系列方法才 能使 getPara 系列方法正常工做,由于 multipart request 须要经过 getFile 系列方法解析请求体中 的数据,包括参数。
文件默认上传至项目根路径下的 upload 子路径之下,该路径称为文件上传基础路径。能够 在 JFinalConfig.configConstant(Constants me)方法中经过 me.setBaseUploadPath(baseUploadPath) 设置文件上传基础路径,该路径参数接受以”/”打头或者以 windows 磁盘盘符打头的绝对路径, 便可将基础路径指向项目根径以外,方便单机多实例部署。当该路径参数设置为相对路径时, 则是以项目根为基础的相对路径。
Controller 提供了 renderFile 系列方法支持文件下载。 文件默认下载路径为项目根路径下的 download 子路径之下,该路径称为文件下载基础路径。能够在 JFinalConfig.configConstant(Constants me) 方 法 中 通 过 me.setBaseDownloadPath(baseDownloadPath) 设置文件下载基础路径,该路径参数接受以”/”打 头或者以 windows 磁盘盘符打头的绝对路径,便可将基础路径指向项目根径以外,方便单机 多实例部署。当该路径参数设置为相对路径时,则是以项目根为基础的相对路径。
renderFile 系列方法用于下载文件。
renderFile 方法使用一个 baseDownloadPath 参数为基础路径去寻找文件。以标准的 maven 项目为例,该参数默认值指向目录:src/main/webapp/download
如下是在默认配置下的使用示例:
// 最终下载文件为:src/main/webapp/download/file.zip renderFile("file.zip"); // 最终下载文件为:src/main/webapp/download/abc/def/file.zip renderFile("abc/deb/file.zip");
如上所示,最终下载文件老是:baseDownloadPath + renderFile 传入的参数
baseDownloadPath 的存在至关于固定了一个基础路路径。renderFile 老是以该路径为基础路径去寻找文件。
baseDownloadPath 还能够在 configConstant(Constants me) 中自由配置,例如:
me.setBaseDownloadPath("files");
以标准的 maven 项目为例,以上配置的 baseDonwnloadPath 值将指向目录 src/main/webapp/files。
此外,还能够将 baseDownloadPath 配置为绝对路径,那么该路径将跳出项目以外,例如:
// linux、mac 系统以字符 "/" 打头是绝对路径 me.setBaseDownloadPath("/var/download"); // windows 系统以盘符打头也是绝对路径 me.setBaseDownloadPath("D:/download");
以上配置 Linux 下以 "/" 打头则表示是绝对路径,那么 renderFile 将去该路径 "/var/download" 之下去寻找下载文件。
这种配置能够跳出项目以外,便于项目资源与下载资源进行分离,也便于集群部署(单机多实例部署)时多个节点能够共享同一个目录,共享同一份下载文件。
renderFile(File file) 方法直接使用 File 参数去获取下载文件,可脱离 baseDownloadPath 的束缚,指向任意地点的文件,例如:
String file = "D:/my-project/share/files/jfinal-all.zip"; renderFile(new File(file));
如上所示,File 指向了一个任意地点的文件,跳出了 baseDownloadPath 的束缚。
若是不想使用下载文件原有的文件名,还能够指定新的下载文件名:
renderFile("老文件名.txt", "新文件名.txt");
经过 setSessionAttr(key, value)能够向 session 中存放数据,getSessionAttr(key)能够从 session中读取数据。还能够经过 getSession()获得 session 对象从而使用全面的 session API。
render 系列方法将渲染不一样类型的视图并返回给客户端。JFinal 目前支持的视图类型有:
FreeMarker、JSP、Velocity、JSON、File、Text、Html 等等。除了 JFinal 支持的视图型之外,还能够经过继承 Render 抽象类来无限扩展视图类型。
一般状况下使用 Controller.render(String)方法来渲染视图,使用 Controller.render(String)时 的 视 图 类 型 由 JFinalConfig.configConstant(Constants constants) 配 置 中 的 constants. setViewType(ViewType)来决定,该设置方法支持的 ViewType 有:FreeMarker、JSP、Velocity, 不进行配置时的缺省配置为 FreeMarker。
此外,还能够经过 constants.setMainRenderFactory(IMainRenderFactory) 来设置 Controller.render(String)所使用的视图,IMainRenderFactory 专门用来对 Controller.render(String) 方法扩展除了 FreeMarker、JSP、Velocity 以外的视图。
假设 在 JFinalConfig.configRoute(Routes routes) 中有 以下 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”,即当参数以”/”开头时将 采用绝对路径。 |
其它 render 方法使用例子:
方法调用 |
描述 |
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。 |
renderError (500 , “test.html”) |
渲染名为 test.html 的文件,且状态为 500。 |
renderNull() |
不渲染,即不向客户端返回数据。 |
render(new XmlRender()) |
使用自定义的 XmlRender 来渲染。 |
注意:
1:IE 不支持 contentType 为 application/json,在 ajax 上传文件完成后返回 json 时 IE 提示下载文 件 , 解 决 办 法 是 使 用 : render(new JsonRender().forIE()) 或 者 render(new JsonRender(params).forIE())。这种状况只出如今 IE 浏览器 ajax 文件上传,其它普通 ajax 请求 没必要理会。
2:除 renderError 方法之外,在调用 render 系列的方法后程序并不会当即返回,若是须要当即 返回须要使用 return 语句。在一个 action 中屡次调用 render 方法只有最后一次有效。
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
传统 AOP 实现须要引入大量繁杂而多余的概念,例如:Aspect、Advice、Joinpoint、Poincut、 Introduction、Weaving、Around 等等,而且须要引入 IOC 容器并配合大量的 XML 或者 annotation 来进行组件装配。
传统 AOP 不但学习成本极高,开发效率极低,开发体验极差,并且还影响系统性能,尤 其是在开发阶段形成项目启动缓慢,极大影响开发效率。
JFinal 采用极速化的 AOP 设计,专一 AOP 最核心的目标,将概念减小到极致,仅有三个 概念:Interceptor、Before、Clear,而且无需引入 IOC 也无需使用繁杂的 XML。
Interceptor 能够对方法进行拦截,并提供机会在方法的先后添加切面代码,实现 AOP 的 核心目标。Interceptor 接口仅仅定了一个方法 void intercept(Invocation inv)。如下是简单的示例:
public class DemoInterceptor implements Interceptor { public void intercept(Invocation inv) {
System.out.println("Before method invoking");
inv.invoke(); System.out.println("After method invoking"); } }
以上代码中的 DemoInterceptor 将拦截目标方法,而且在目标方法调用先后向控制台输出 文本。inv.invoke()这一行代码是对目标方法的调用,在这一行代码的先后插入切面代码能够很 方便地实现 AOP。
nvocation 做为 Interceptor 接口 intercept 方法中的惟一参数,提供了不少便利的方法在拦 截器中使用。如下为 Invocation 中的方法:
方法 |
描述 |
void invoke() |
传递本次调用,调用剩下的拦截器与目标方法 |
Controller getController() |
获取 Action 调用的 Controller 对象(仅用于控制层拦截) |
String getActionKey() |
获取 Action 调用的 action key 值(仅用于控制层拦截) |
String getControllerKey() |
获取 Action 调用的 controller key 值(仅用于控制层拦截) |
String getViewPath() |
获取 Action 调用的视图路径(仅用于控制层拦截) |
<T> T getTarget() |
获取被拦截方法所属的对象 |
Method getMethod() |
获取被拦截方法的 Method 对象 |
String getMethodName() |
获取被拦截方法的方法名 |
Object[] getArgs() |
获取被拦截方法的全部参数值 |
Object getArg(int) |
获取被拦截方法指定序号的参数值 |
<T> T getReturnValue() |
获取被拦截方法的返回值 |
void setArg(int) |
设置被拦截方法指定序号的参数值 |
void setReturnValue(Object) |
设置被拦截方法的返回值 |
boolean isActionInvocation() |
判断是否为 Action 调用,也便是否为控制层拦截 |
Before 注解用来对拦截器进行配置,该注解可配置 Class、Method 级别的拦截器,如下是 代码示例:
如上代码所示,Before 能够将拦截器配置为 Class 级别与 Method 级别,前者将拦截本类 中全部方法,后者仅拦截本方法。此外 Before 能够同时配置多个拦截器,只需用在大括号内 用逗号将多个拦截器进行分隔便可。
除了 Class 与 Method 级别的拦截器之外,JFinal 还支持全局拦截器以及 Inject 拦截器(Inject拦截将在后面介绍),全局拦截器分为控制层全局拦截器与业务层全局拦截器,前者拦截控制 层全部 Action 方法,后者拦截业务层全部方法。
全局拦截器须要在 YourJFinalConfig 进行配置,如下是配置示例:
public class AppConfig extends JFinalConfig { public void configInterceptor(Interceptors me) { // 添加控制层全局拦截器 me.addGlobalActionInterceptor(new GlobalActionInterceptor()); // 添加业务层全局拦截器 me.addGlobalServiceInterceptor(new GlobalServiceInterceptor()); // 为兼容老版本保留的方法,功能与addGlobalActionInterceptor彻底同样 me.add(new GlobalActionInterceptor()); } }
当某个 Method 被多个级别的拦截器所拦截,拦截器各级别执行的次序依次为:Global、 Inject、Class、Method,若是同级中有多个拦截器,那么同级中的执行次序是:配置在前面的 先执行。
拦截器从上到下依次分为 Global、Inject、Class、Method 四个层次,Clear 用于清除自身 所处层次以上层的拦截器。
Clear 声明在 Method 层时将针对 Global、Inject、Class 进行清除。Clear 声明在 Class 层时 将针对 Global、Inject 进行清除。Clear 注解携带参数时清除目标层中指定的拦截器。
l 共有 Global、Inject、Class、Method 四层拦截器
l 清除只针对 Clear 自己所处层的向上全部层,本层与下层不清除
l 不带参数时清除全部拦截器,带参时清除参数指定的拦截器
在某些应用场景之下,须要移除 Global 或 Class 拦截器。例如某个后台管理系统,配置了 一个全局的权限拦截器,可是其登陆 action 就必须清除掉她,不然没法完成登陆操做,如下是 代码示例:
// login方法须要移除该权限拦截器才能正常登陆 @Before(AuthInterceptor.class) public class UserController extends Controller { // AuthInterceptor 已被Clear清除掉,不会被其拦截 @Clear public void login() { } // 此方法将被AuthInterceptor拦截 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})// 清除指定的拦截器AAA与CCC @Before(CCC.class) public void show() { // 虽然Clear注解中指定清除CCC,但她没法被清除,由于清除操做只针对于本层以上的各层 } }
JFinal 中的 AOP 被划分为控制层 AOP 以及业务层 AOP,严格来讲业务层 AOP 并不是仅限 于在业务层使用,由于 JFinal AOP 能够应用于其它任何地方。
控制层拦截器的触发,只需发起 action 请求便可。业务层拦截器的触发须要先使用 enhance方法对目标对象进行加强,而后调用目标方法便可。如下是业务层 AOP 使用的例子:
// 定义须要使用AOP的业务层类 public class OrderService { // 配置事务拦截器 @Before(Tx.class) public void payment(int orderId, int userId) { // service code here } } // 定义控制器,控制器提供了enhance系列方法可对目标进行AOP加强 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 方法对 OrderSevice 进行了加强,随后调用其 payment 方法即可触发 Tx 拦截器。简言之,业务层 AOP 的触发相对于控制层仅需多调用一次 enhance 方法便可,而 Interceptor、Before、Clear 的使用方法彻底同样。
Duang、Enhancer 用来对目标进行加强,让其拥有 AOP 的能力。如下是代码示例:
public class TestMain{ public void main(String[] args) { // 使用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 拦截器是指在使用 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 拦截器与前面谈到的 Global、Class、Method 级别拦截器是同一层次上的概念。与 Class 级拦截器同样,Inject 拦截器将拦截被加强目标中的全部方法。Inject 拦截器能够被认为 就是 Class 级拦截器,只不过执行次序在 Class 级拦截器以前而已。
ActiveRecord 是 JFinal 最核心的组成部分之一,经过 ActiveRecord 来操做数据库,将极大 地减小代码量,极大地提高开发效率。
ActiveRecord 是做为 JFinal 的 Plugin 而存在的,因此使用时须要在 JFinalConfig 中配置ActiveRecordPlugin。
如下是 Plugin 配置示例代码:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) { C3p0Plugin cp = new C3p0Plugin("jdbc:mysql://localhost/db_name", "userName", "password"); me.add(cp); ActiveRecordPlugin arp = new ActiveRecordPlugin(cp);
me.add(arp); arp.addMapping("user", User.class);
arp.addMapping("article", "article_id", Article.class); } }
以上代码配置了两个插件:C3p0Plugin 与 ActiveRecordPlugin,前者是 c3p0 数据源插件, 后者是 ActiveRecrod 支持插件。ActiveReceord 中定义了 addMapping(String tableName, Class<? extends Model> modelClass>)方法,该方法创建了数据库表名到 Model 的映射关系。
另外,以上代码中 arp.addMapping(“user”, User.class),表的主键名为默认为“id”,若是主 键名称为 “user_id”则须要手动指定,如:arp.addMapping(“user”, “user_id”, User.class)。
Model 是 ActiveRecord 中最重要的组件之一,它充当 MVC 模式中的 Model 部分。如下是Model 定义示例代码:
public class User extends Model<User> { public static final User dao = new User(); }
以上代码中的 User 经过继承 Model,便当即拥有的众多方便的操做数据库的方法。在 User 中声明的 dao 静态对象是为了方便查询操做而定义的,该对象并非必须的。基于 ActiveRecord 的 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.findByIdLoadColumns (25).set("name", "James").update(); // 查询id值为25的user, 且仅仅取name与age两个字段的值 User user = User.dao.findByIdLoadColumns (25, "name, age"); // 获取user的name属性 String userName = user.getStr("name"); // 获取user的age属性 Integer userAge = user.getInt("age"); // 查询全部年龄大于18岁的user List<User> users = User.dao.find("select * from user where age>18"); // 分页查询年龄大于18的user,当前页号为1,每页10个user Page<User> userPage = User.dao.paginate(1, 10, "select *", "from user where age > ?", 18);
特别注意:User 中定义的 public static final User dao 对象是全局共享的,只能用于数据库查询, 不能用于数据承载对象。数据承载须要使用 new User().set(…)来实现。
JFinal 2.1 版本提供了 ModelGenerator 、 BaseModelGenerator 、 MappingKitGernator 、 DataDictionaryGenerator,分别生成 Model、BaseModel、MappingKit、DataDictionary 四类文件。 可根据数据表自动化生成这四类文件。
相对于 JFinal 2.1 以前的版本,生成后的 Model 继承自 BaseModel 而非继承自 Model, BaseModel 中拥有 getter、setter 方法遵照传统 java bean 规范,Model 继承自 BaseModel 即完成 了 JavaBean 与 Model 合体,拥有了传统 JavaBean 全部的优点,而且全部的 getter、setter 方法 彻底无需人工干预,数据表有任何变更一键从新生成便可。
具体用法可在 jfinal 官网下载相关 GeneratorDemo,用法极度简单。
Db 类及其配套的 Record 类,提供了在 Model 类以外更为丰富的数据库操做功能。使用 Db 与 Record 类时,无需对数据库表进行映射,Record 至关于一个通用的 Model。如下为 Db + Record 模式的一些常见用法:
// 建立name属性为James,age属性为25的record对象并添加到数据库 Record user = new Record().set("name", "James").set("age", 25); Db.save("user", user); // 删除id值为25的user表中的记录 Db.deleteById("user", 25); // 查询id值为25的Record将其name属性改成James并更新到数据库 user = Db.findById("user", 25).set("name", "James"); Db.update("user", user); // 获取user的name属性 String userName = user.getStr("name"); // 获取user的age属性 Integer userAge = user.getInt("age"); // 查询全部年龄大于18岁的user List<Record> users = Db.find("select * from user where age > 18"); // 分页查询年龄大于18的user,当前页号为1,每页10个user Page<Record> userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);
如下为事务处理示例:
boolean succeed = Db.tx(new IAtom(){ public boolean run() throws SQLException { int count = Db.update("update account set cash = cash - ? where id = ?", 100, 123); int count2 = Db.update("update account set cash = cash + ? where id = ?", 100, 456); return count == 1 && count2 == 1; }});
以上两次数据库更新操做在一个事务中执行,若是执行过程当中发生异常或者 invoke()方法 返回 false,则自动回滚事务。
ActiveRecord 支持声名式事务,声明式事务须要使用 ActiveRecordPlugin 提供的拦截器来 实现,拦截器的配置方法见 Interceptor 有关章节。如下代码是声明式事务示例:
// 本例仅为示例, 并未严格考虑帐户状态等业务逻辑 @Before(Tx.class) public void trans_demo() { // 获取转帐金额 Integer transAmount = getParaToInt("transAmount"); // 获取转出帐户id Integer fromAccountId = getParaToInt("fromAccountId"); // 获取转入帐户id Integer toAccountId = getParaToInt("toAccountId"); // 转出操做 Db.update("update account set cash = cash - ? where id = ?", transAmount, fromAccountId); // 转入操做 Db.update("update account set cash = cash + ? where id = ?", transAmount, toAccountId); }
以上代码中,仅声明了一个 Tx 拦截器即为 action 添加了事务支持。除此以外 ActiveRecord 还配备了 TxByActionKeys、TxByActionKeyRegex、TxByMethods、TxByMethodRegex,分别 支持 actionKeys、actionKey 正则、actionMethods、actionMethod 正则声明式事务,如下是示例代码:
public void configInterceptor(Interceptors me) {
me.add(new TxByMethodRegex("(.*save.*|.*update.*)"));
me.add(new TxByMethods("save", "update")); me.add(new TxByActionKeyRegex("/trans.*"));
me.add(new TxByActionKeys("/tx/save", "/tx/update"));
}
上例中的 TxByRegex 拦截器可经过传入正则表达式对 action 进行拦截,当 actionKey 被正 则匹配上将开启事务。TxByActionKeys 能够对指定的 actionKey 进行拦截并开启事务, TxByMethods 能够对指定的 method 进行拦截并开启事务。
注意:MySql 数据库表必须设置为 InnoDB 引擎时才支持事务,MyISAM 并不支持事务。
ActiveRecord 可使用缓存以大大提升性能,如下代码是 Cache 使用示例:
public void list() { List<Blog> blogList = Blog.dao.findByCache("cacheName", "key", "select * from blog"); setAttr("blogList", blogList).render("list.html"); }
上例 findByCache 方 法 中 的 cacheName 需 要 在 ehcache.xml 中配置 如: <cache name="cacheName" …> 。 此 外 Model.paginateByCache(…) 、 Db.findByCache(…) 、 Db.paginateByCache(…)方法都提供了 cache 支持。在使用时,只需传入 cacheName、key 以及 在 ehccache.xml 中配置相对应的 cacheName 就能够了
目前 ActiveRecordPlugin 提供了 MysqlDialect、OracleDialect、AnsiSqlDialect 实现类。 MysqlDialect 与 OracleDialect 分别实现对 Mysql 与 Oracle 的支持,AnsiSqlDialect 实现对遵照 ANSI SQL 数据库的支持。如下是数据库 Dialect 的配置代码:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) {
ActiveRecordPlugin arp = new ActiveRecordPlugin(…);
me.add(arp); // 配置Postgresql方言 arp.setDialect(new PostgresqlDialect()); } }
JFinal ActiveRecord 自然支持表关联操做,并不须要学习新的东西,此为无招胜有招。表 关联操做主要有两种方式:一是直接使用 sql 获得关联数据;二是在 Model 中添加获取关联数据的方法。
假定现有两张数据库表:user、blog,而且 user 到 blog 是一对多关系,blog 表中使用 user_id关联到 user 表。以下代码演示使用第一种方式获得 user_name:
public void relation() { String sql = "select b.*, u.user_name from blog b inner join user u on b.user_id=u.id where b.id=?"; Blog blog = Blog.dao.findFirst(sql, 123); String name = blog.getStr("user_name"); }
如下代码演示第二种方式在 Blog 中获取相关联的 User 以及在 User 中获取相关联的Blog:
public class Blog extends Model<Blog>{ public static final Blog dao = new Blog(); public User getUser() { return User.dao.findById(get("user_id")); } } public class User extends Model<User>{ public static final User dao = new User(); public List<Blog> getBlogs() { return Blog.dao.find("select * from blog where user_id=?", get("id")); } }
JFinal ActiveRecord 从 2.0 版本开始,采用极简设计支持复合主键,对于 Model 来讲须要 在映射时指定复合主键名称,如下是具体例子:
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); // 多数据源的配置仅仅是以下第二个参数指定一次复合主键名称 arp.addMapping("user_role", "userId, roleId", UserRole.class); //同时指定复合主键值便可查找记录 UserRole.dao.findById(123, 456); //同时指定复合主键值便可删除记录 UserRole.dao.deleteById(123, 456);
如上代码所示,对于 Model 来讲,只须要在添加 Model 映射时指定复合主键名称便可开 始使用复合主键,在后续的操做中 JFinal 会对复合主键支持的个数进行检测,当复合主键数量 不正确时会报异常,尤为是复合主键数量不够时可以确保数据安全。复合主键不限定只能有两 个,能够是数据库支持下的任意多个。
对于 Db + Record 模式来讲,复合主键的使用不须要配置,直接用便可:
Db.findById("user_role", "roleId, userId", 123, 456);
Db.deleteById("user_role", "roleId, userId", 123, 456);
Oracle 数据库具备必定的特殊性,JFinal 针对这些特殊性进行了一些额外的支持以方便广 大的 Oracle 使用者。如下是一个完整的 Oracle 配置示例:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) { C3p0Plugin cp = new C3p0Plugin(……); //配置Oracle驱动 cp. setDriverClass("oracle.jdbc.driver.OracleDriver"); me.add(cp); ActiveRecordPlugin arp = new ActiveRecordPlugin(cp); me.add(arp); // 配置Oracle方言 arp.setDialect(new OracleDialect()); // 配置属性名(字段名)大小写不敏感容器工厂 arp.setContainerFactory(new CaseInsensitiveContainerFactory()); arp.addMapping("user", "user_id", User.class); }
因为 Oracle 数据库会自动将属性名(字段名)转换成大写,因此须要手动指定主键名为大写, 如:arp.addMaping(“user”, “ID”, User.class)。若是想让 ActiveRecord 对属性名(字段名)的大 小 写 不 敏 感 可 以 通 过 设 置 CaseInsensitiveContainerFactory 来达到 , 有 了 这 个 设 置 , 则 arp.addMaping(“user”, “ID”, User.class)再也不须要了。
另外,Oracle 并未直接支持自增主键,JFinal 为此提供了便捷的解决方案。要让 Oracle 支 持自动主键主要分为两步:一是建立序列,二是在 model 中使用这个序列,具体办法以下:
1:经过以下办法建立序列,本例中序列名为:MY_SEQ
CREATE SEQUENCE MY_SEQ INCREMENT BY 1 MINVALUE 1 MAXVALUE 9999999999999999 START WITH 1 CACHE 20;
2:在 YourModel.set(…)中使用上面建立的序列
// 建立User并使用序列 User user = new User().set("id", "MY_SEQ.nextval").set("age", 18); user.save(); // 获取id值 Integer id = user.get("id");
序列的使用很简单,只须要 yourModel.set(主键名, 序列名 + “.nextval”)就能够了。特别注意这里的 “.nextval” 后缀必定要是小写,OracleDialect 对该值的大小写敏感。
ActiveRecordPlugin 可同时支持多数据源、多方言、多缓存、多事务级别等特性,对每一个 ActiveRecordPlugin 可进行彼此独立的配置。简言之 JFinal 能够同时使用多数据源,而且可 以针对这多个数据源配置独立的方言、缓存、事务级别等。
当使用多数据源时,只须要对每一个 ActiveRecordPlugin 指定一个 configName 便可,以下是代码示例:
public void configPlugin(Plugins me) { // mysql 数据源 C3p0Plugin dsMysql = new C3p0Plugin(…); me.add(dsMysql); // mysql ActiveRecrodPlugin 实例,并指定configName为 mysql ActiveRecordPlugin arpMysql = new ActiveRecordPlugin("mysql", dsMysql); me.add(arpMysql); arpMysql.setCache(new EhCache()); arpMysql.addMapping("user", User.class); // oracle 数据源 C3p0Plugin dsOracle = new C3p0Plugin(…); me.add(dsOracle); // oracle ActiveRecrodPlugin 实例,并指定configName为 oracle ActiveRecordPlugin arpOracle = new ActiveRecordPlugin("oracle", dsOracle); me.add(arpOracle); arpOracle.setDialect(new OracleDialect()); arpOracle.setTransactionLevel(8); arpOracle.addMapping("blog", Blog.class); }
以上代码建立了创了两个 ActiveRecordPlugin 实例 arpMysql 与 arpOrace,特别注意建立实 例的同时指定其 configName 分别为 mysql 与 oracle。arpMysql 与 arpOracle 分别映射了不一样的Model,配置了不一样的方言。
对于 Model 的使用,不一样的 Model 会自动找到其所属的 ActiveRecrodPlugin 实例以及相关 配置进行数据库操做。假如但愿同一个 Model 可以切换到不一样的数据源上使用,也极度方便, 这种用法很是适合不一样数据源中的 table 拥有相同表结构的状况,开发者但愿用同一个 Model 来操做这些相同表结构的 table,如下是示例代码:
public void multiDsModel() { // 默认使用arp.addMapping(...)时关联起来的数据源 Blog blog = Blog.me.findById(123); // 只需调用一次use方法便可切换到另外一数据源上去 blog.use("backupDatabase").save(); }
上例中的代码,blog.use(“backupDatabase”)方法切换数据源到 backupDatabase 并直接将数 据保存起来。
特别注意:只有在同一个 Model 但愿对应到多个数据源的 table 时才须要使用 use 方法,若是 同一个 Model 惟一对应一个数据源的一个 table,那么数据源的切换是自动的,无需使用 use 方法。
对于 Db + Record 的使用,数据源的切换须要使用 Db.use(cnfigName)方法获得数据库操做 对象,而后就能够进行数据库操做了,如下是代码示例:
// 查询 dsMysql数据源中的 user List<Record> users = Db.use("mysql").find("select * from user"); // 查询 dsOracle数据源中的 blog List<Record> blogs = Db.use("oracle").find("select * from blog");
以上两行代码,分别经过 configName 为 mysql、oracle 获得各自的数据库操做对象,而后 就能够如同单数据彻底同样的方式来使用数据库操做 API 了。简言之,对于 Db + Record 来 说,多数据源相比单数据源仅需多调用一下 Db.use(configName),随后的 API 使用方式彻底一 样。
注意最早建立的 ActiveRecrodPlugin 实例将会成为主数据源,能够省略 configName。最早建立的 ActiveRecrodPlugin 实例中的配置将默认成为主配置,此外还能够经过设置 configName为 DbKit.MAIN_CONFIG_NAME 常量来设置主配置。
ActiveRecordPlugin 能够独立于 java web 环境运行在任何普通的 java 程序中,使用方式极 度简单,相对于 web 项目只须要手动调用一下其 start() 方法便可当即使用。如下是代码示例:
public class ActiveRecordTest { public static void main(String[] args) { DruidPlugin dp = new DruidPlugin("localhost", "userName", "password");
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
arp.addMapping("blog", Blog.class); // 与web环境惟一的不一样是要手动调用一次相关插件的start()方法 dp.start(); arp.start(); // 经过上面简单的几行代码,便可当即开始使用 new Blog().set("title", "title").set("content", "cxt text").save();
Blog.me.findById(123); } }
注意:ActiveRecordPlugin 所依赖的其它插件也必须手动调用一下 start()方法,如上例中的 dp.start()。
EhCachePlugin 是 JFinal 集成的缓存插件,经过使用 EhCachePlugin 能够提升系统的并发 访问速度。
EhCachePlugin 是做为 JFinal 的 Plugin 而存在的,因此使用时须要在 JFinalConfig 中配置EhCachePlugin,如下是 Plugin 配置示例代码:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) { me.add(new EhCachePlugin()); } }
CacheInterceptor 能够将 action 所需数据所有缓存起来,下次请求到来时若是 cache 存在则 直接使用数据并 render,而不会去调用 action。此用法可以使 action 彻底不受 cache 相关代码所 污染,即插即用,如下是示例代码:
@Before(CacheInterceptor.class) public void list() { List<Blog> blogList = Blog.dao.find("select * from blog");
User user = User.dao.findById(getParaToInt());
setAttr("blogList", blogList); setAttr("user", user);
render("blog.html"); }
上例中的用法将使用 actionKey 做为 cacheName,在使用以前须要在 ehcache.xml 中配置以 actionKey 命名的 cache 如:<cache name="/blog/list" …>,注意 actionKey 做为 cacheName 配置 时斜杠”/”不能省略。此外 CacheInterceptor 还能够与 CacheName 注解配合使用,以此来取代默认的 actionKey 做为actionName,如下是示例代码:
@Before(CacheInterceptor.class) @CacheName("blogList") public void list() { List<Blog> blogList = Blog.dao.find("select * from blog");
setAttr("blogList", blogList); render("blog.html"); }
以上用法须要在 ehcache.xml 中配置名为 blogList 的 cache 如:<cache name="blogList" …>
EvictInterceptor 能够根据 CacheName 注解自动清除缓存。如下是示例代码:
@Before(EvictInterceptor.class) @CacheName("blogList") public void update() { getModel(Blog.class).update(); redirect("blog.html"); }
上例中的用法将清除 cacheName 为 blogList 的缓存数据,与其配合的 CacheInterceptor 会 自动更新 cacheName 为 blogList 的缓存数据。
CacheKit 是缓存操做工具类,如下是示例代码:
public void list() { List<Blog> blogList = CacheKit.get("blog", "blogList"); if (blogList == null) { blogList = Blog.dao.find("select * from blog");
CacheKit.put("blog", "blogList", blogList); } setAttr("blogList", blogList); render("blog.html"); }
CacheKit 中最重要的两个方法是 get(String cacheName, Object key)与 put(String cacheName,Object key, Object value)。get 方法是从 cache 中取数据,put 方法是将数据放入 cache。参数 cacheName 与 ehcache.xml 中的<cache name="blog" …>name 属性值对应;参数 key 是指取值用 到的 key;参数 value 是被缓存的数据。
如下代码是 CacheKit 中重载的 CacheKit.get(String, String, IDataLoader)方法使用示例:
public void list() { List<Blog> blogList = CacheKit.get("blog", "blogList", newIDataLoader(){ public Object load() { return Blog.dao.find("select * from blog"); }}); setAttr("blogList", blogList); render("blog.html"); }
CacheKit.get 方法提供了一个 IDataLoader 接口,该接口中的 load()方法在缓存值不存在时 才会被调用。该方法的具体操做流程是:首先以 cacheName=blog 以及 key=blogList 为参数去 缓存取数据,若是缓存中数据存在就直接返回该数据,不存在则调用 IDataLoader.load()方法来 获取数据。
EhCache 的使用须要有 ehcache.xml 配置文件支持,该配置文件中配置了不少 cache 节点, 每一个 cache 节点会配置一个 name 属性,例如:<cache name="blog" …>,该属性是 CacheKit 取值所必须的。其它配置项如 eternal、 overflowToDisk、timeToIdleSeconds、 timeToLiveSeconds 详见 EhCache 官方文档。
RedisPlugin 是支持 Redis 的极速化插件。使用 RedisPlugin 能够极度方便的使用 redis,该 插件不只提供了丰富的 API,并且还同时支持多 redis 服务端。Redis 拥有超高的性能,丰富的 数据结构,自然支持数据持久化,是目前应用很是普遍的 nosql 数据库。对于 redis 的有效应 用可极大提高系统性能,节省硬件成本。
RedisPlugin 是做为 JFinal 的 Plugin 而存在的,因此使用时须要在 JFinalConfig 中配置RedisPlugin,如下是 RedisPlugin 配置示例代码:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) { // 用于缓存bbs模块的redis服务 RedisPlugin bbsRedis = new RedisPlugin("bbs", "localhost"); me.add(bbsRedis); // 用于缓存news模块的redis服务 RedisPlugin newsRedis = new RedisPlugin("news", "192.168.3.9"); me.add(newsRedis); } }
以上代码建立了两个 RedisPlugin 对象,分别为 bbsRedis 和 newsRedis。最早建立的 RedisPlugin 对象所持有的 Cache 对象将成为主缓存对象,主缓存对象可经过 Redis.use()直接获 取,不然须要提供 cacheName 参数才能获取,例如:Redis.use(“news”)。
Redis 与 Cache 联合起来能够很是方便地使用 Redis 服务,Redis 对象经过 use()方法来获取 到 Cache 对象,Cache 对象提供了丰富的 API 用于使用 Redis 服务,下面是具体使用示例:
public void redisDemo() { // 获取名称为bbs的Redis Cache对象 Cache bbsCache = Redis.use("bbs"); bbsCache.set("key", "value"); bbsCache.get("key"); // 获取名称为news的Redis Cache对象 Cache newsCache = Redis.use("news"); newsCache.set("k", "v"); newsCache.get("k"); // 最早建立的Cache将成为主Cache,因此能够省去cacheName参数来获取 bbsCache = Redis.use();
// 主缓存能够省去cacheName参数 bbsCache.set("jfinal", "awesome"); }
以上代码中经过”bbs”、”news”作为 use 方法的参数分别获取到了两个 Cache 对象,使用这 两个对象便可操做其所对应的 Redis 服务端。
一般状况下只会建立一个 RedisPlugin 链接一个 redis 服务端,使用 Redis.use().set(key,value)便可。
RedisPlugin 也 可 以 在 非 web 环 境 下 使 用 , 只需 引入 jfinal.jar 然 后 多 调用一下redisPlugin.start()便可,如下是代码示例:
public class RedisTest { public static void main(String[] args) { RedisPlugin rp = new RedisPlugin("myRedis", "localhost"); // 与web下惟一区别是须要这里调用一次start()方法 rp.start(); Redis.use().set("key", "value"); Redis.use().get("key"); } }
Validator 是 JFinal 校验组件,在 Validator 类中提供了很是方便的校验方法,学习简单,使用方便。
Validator 自身实现了 Interceptor 接口,因此它也是一个拦截器,配置方式与拦截器彻底一 样。如下是 Validator 示例:
public class LoginValidator extends Validator { protected void validate(Controller c) {
validateRequiredString("name", "nameMsg", "请输入用户名");
validateRequiredString("pass","passMsg", "请输入密码"); } protected void handleError(Controller c)
{
c.keepPara("name"); c.render("login.html"); } }
protected void validator(Controller c)方法中能够调用 validateXxx(…)系列方法进行后端校 验,protected void handleError(Controller c)方法中能够调用 c.keepPara(…)方法将提交的值再传 回页面以便保持原先输入的值,还能够调用 c.render(…) 方法来返回相应的页面。 注意 handleError(Controller c)只有在校验失败时才会调用。
以上代码 handleError 方法中的 keepXxx 方法用于将页面表单中的数据保持住并传递回页, 以便于用户无需再重复输入已经经过验证的表单域,若是传递过来的是 model 对象,可使用 keepModel 方法来保持住用户输入过的数据。
Validator 配置方式与拦截器彻底同样,见以下代码:
public class UserController extends Controller { @Before(LoginValidator.class) // 配置方式与拦截器彻底同样 public void login() { } }
JFinal 为国际化提供了极速化的支持,国际化模块仅三个类文件,使用方式要比 spring 这 类框架容易得多。
I18n 对象可经过资源文件的 baseName 与 locale 参数获取到与之相对应的 Res 对象,Res 对象提供了 API 用来获取国际化数据。
如下给出具体使用步骤:
如下是基于以上步骤之后的代码示例:
// 经过locale参数en_US获得对应的Res对象 Res resEn = I18n.use("en_US"); // 直接获取数据 String msgEn = resEn.get("msg"); // 获取数据并使用参数格式化 String msgEnFormat = resEn.format("msg", "james", new Date()); // 经过locale参数zh_CN获得对应的Res对象 Res resZh = I18n.use("zh_CN"); // 直接获取数据 String msgZh = resZh.get("msg"); // 获取数据并使用参数格式化 String msgZhFormat = resZh.format("msg", "詹波", new Date()); // 另外,I18n还能够加载未使用me.setI18nDefaultBaseName()配置过的资源文件,惟一的不一样是 // 须要指定baseName参数,下面例子须要先建立 otherRes_en_US.properties文件 Res otherRes = I18n.use("otherRes", "en_US"); otherRes.get("msg");
I18nInterceptor 拦截器是针对于 web 应用提供的一个国际化组件,如下是在 freemarker 模板 中使用的例子:
//先将I18nInterceptor配置成全局拦截器 public void configInterceptor(Interceptors me) { me.add(new I18nInterceptor()); } // 而后在freemarker中便可经过_res对象来获取国际化数据 ${_res.get("msg")}
以上代码经过配置了 I18nInterceptor 拦截 action 请求,而后便可在 freemarker 模板文件中 经过名为_res 对象来获取国际化数据,I18nInterceptor 的具体工做流程以下:
以上步骤 I18nInterceptor 中的变量名”_locale”、”_res”均可以在建立 I18nInterceptor 对象时 进行指定,不指定时将使用默认值。还能够经过继承 I18nInterceptor 而且覆盖 getLocalPara、 getResName、getBaseName 来定制更加个性化的功能。
在有些 web 系统中,页面须要国际化的文本过多,而且 css 以及 html 也由于国际化而 大不相同,对于这种应用场景先直接制作多套同名称的国际化视图,并将这些视图以 locale 为 子目录分类存放,最后使用 I18nInterceptor 拦截器根据 locale 动态切换视图,而没必要对视图中 的文本逐个进行国际化切换,只需将 I18nInterceptor.isSwitchView 设置为 true 便可,省时省力。
JFinal 默认使用 FreeMarker 做为 View,为了使 eclipse jee 能正确识别 html,因此默认使 用”.html”做为 FreeMarker 视图文件的扩展名(原为”.ftl”)。
若是须要使用 JSP 做为默认视图须要在 configConstant(Constants me)方法中进行配置,见 以下配置:
public void configConstant(Constants me) {
me.setDevMode(true); me.setViewType(ViewType.JSP); }
如下代码为 FreeMarker 常用的指令与插值:
<table> <#list userList as user> <tr> <td>${user.name}</td> <td>${user.age}</td> <td>${user.email}</td> </tr> </#list> </table>
以上代码将 userList 中的 user 对象循环输出。
能够经过 FreeMarkerRender.getConfiguration().setSharedVariable(“myKit”, new MyKit()) 为FreeMarker 设置共享工具类,在 view 中使用 ${myKit.method(para)}。
JFinal 采用微内核全方位扩展架构,全方位是指其扩展方式在空间上的表现形式。JFinal 由 Handler、Interceptor、Controller、Render、Plugin 五大部分组成。本章将简单介绍此架构以 及基于此架构所作的一些较为经常使用的扩展。
JFinal 顶层架构图以下: