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 頂層架構圖以下: