此Demo實際在2014年上半年就已經完成了,只是到最近纔有時間和心情寫完此文。同時,將JFinal升級到了1.9,並採用Maven構建項目。php
另外,仔細想了想,Provider其實能夠不依託Tomcat之類的Web容器啓動,並驗證成功。html
有不少人認爲,既然有了JFinal,爲何還要Spring。卻不知一些基於Spring的很牛X的東東集成到JFinal中可以事半功倍。好比Dubbo這個高性能優秀的服務框架,它基於Spring,因而JFinal提供的Spring插件就能更方便地將Dubbo集成進我們的程序中,成爲高大上的程序。java
構建本Demo的目的只爲了向讀者演示如何將我們的程序改形成基於Dubbo的應用,選擇JFinal的Demo,使得JFinal讀者能夠快速進入情況,並且筆者也能省下開發Demo功能的時間。python
公司 / 組織mysql |
產品web |
說明spring |
JFinalsql |
JFinal 1.9數據庫 |
JFinal 是基於 Java 語言的極速 WEB + ORM 框架,其核心設計目標是開發迅速、代碼量少、學習簡單、功能強大、輕量級、易擴展、Restful。 在擁有Java語言全部優點的同時再擁有ruby、python、php等動態語言的開發效率!爲您節約更多時間,去陪戀人、家人和朋友 :) |
|
JFinal 1.9 Demo for Maven |
JFinal的Demo |
阿里巴巴 |
Dubbo |
http://www.oschina.net/p/dubbo Dubbo 是阿里巴巴公司開源的一個高性能優秀的服務框架,使得應用可經過高性能的 RPC 實現服務的輸出和輸入功能,能夠和 Spring框架無縫集成。 感謝Dubbo,使我們不須要太多專業知識和能力就可以很輕易地將程序轉變成分佈式這種高大上的概念型產品。 |
Druid |
http://www.oschina.net/p/druid Druid是Java語言中最好的數據庫鏈接池。Druid可以提供強大的監控和擴展功能。 |
|
Spring |
http://www.oschina.net/p/spring Spring Framework 是一個開源的Java/Java EE全功能棧(full-stack)的應用程序框架,以Apache許可證形式發佈,也有.NET平臺上的移植版本。該框架基於 Expert One-on-One Java EE Design and Development(ISBN 0-7645-4385-7)一書中的代碼,最初由 Rod Johnson 和 Juergen Hoeller等開發。Spring Framework 提供了一個簡易的開發方式,這種開發方式,將避免那些可能導致底層代碼變得繁雜混亂的大量的屬性文件和幫助類。 |
|
其它 |
slf4j log4j mysql-connector-java |
感謝以上項目及環境,使咱們能簡便快速地構建分佈式應用。但願它們能越作越強,也但願更多人從中受益。
Dubbo相關文檔已經存放在JFinalDubboDemoApi項目下,請讀者仔細閱讀。
用過Spring的讀者都清楚MVC中,C中的業務被分了層,Controller中只看見Service接口便可,實現是能夠隨配置更換的。
典型的就是下面的接口服務結構:
在分佈式應用中,你的應用部署在客戶端,而業務實現被部署在服務端,以下圖:
而使用Dubbo後,這一切就能輕易實現。本Demo中,按Dubbo的Consumer --> Api --> Provider概念,原JFinal Demo被切割成三部分:
JFinalDubboDemoConsumer.war
此war包部署在Consumer客戶端,只包含與Web交互有關內容,並調用接口Api來完成相關數據操做(不限於數據操做,只是JFinal Demo中只有數據操做適合提取成api,可不能誤導讀者)。
JFinalDubboDemoApi.jar
將全部業務服務(數據操做)的接口提取出來,集中到一個jar包中,便於Consumer端和Provider端引用(根據實際應用中的業務的要求,不一樣業務的服務接口應該打包成多個jar包,本Demo只須要一個jar包便可)。
要注意的是,Consumer端和Provider端要引用相同的Api,否則就會出亂子。
JFinalDubboDemoProvider
此war包部署在Provider服務端,提供JFinal Demo中Blog的數據服務,它實現數據服務接口Api(提取出的接口在JFinalDubboDemoApi.jar中),並鏈接數據庫完成數據操做。
三部分相互獨立,千萬不要將Api納入Consumer或者Provider包。否則,依賴關係不明確,Api也不方便爲其它程序引用。
Api項目分別被Consumer項目端和Provider項目引用,所以,被同時打包進兩個war包當中。具體部署結構請看下圖:
既然是分佈式服務,那麼Consumer端和Provider端均可實現集羣,從而真正體現分佈式的優點。如何實現集羣請參看後面的教程。
此章節只說明改造中必要說明的部分,其它內容請參見源碼。
新建一個java項目(注意,非Dynamic Web Project),項目命名爲「JFinalDubboDemoApi」。
項目中只有兩個類,Blog和BlogService:
Blog.java
public class Blog extends Model<Blog> { private static final long serialVersionUID = -6749384460553909926L; }
不聲明dao字段是由於原來Demo中Blog的dao字段用於數據庫操做,而Consumer端不與數據庫打交道,因此不須要提供dao。可是,Consumer端獲得Blog實例時,還可經過實例看到Model的各類接口。細心的讀者可能發現了,Blog仍是繼承自Model,並且少了dao字段的聲明。這是由於,此Demo中將Model作爲通訊用的Dto,即Data Transfer Object。
編碼時要注意了,概念要轉換,Model接口在客戶端已經不能使用了,否則會出錯。後面的章節會講到並不是只有Model作dto,能夠有其它選擇。
BlogService
public interface BlogService { Page<Blog> paginate(int pageNumber, int pageSize); void update(Blog blog); Blog save(Blog blog); Blog findById(String id); void deleteById(String id); }
將JFinal原Demo中全部的數據操做集成到了BlogService接口中,例如:原Controller中的getModel(Blog.class).save()這類代碼就提取接口成爲了save(Blog)。
原JFinal Demo項目被更名成「JFinalDubboDemoConsumer」。
標準的Dubbo服務消費方配置文件,改自Dubbo官方Demo:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 消費方應用名,用於計算依賴關係,不是匹配條件,不要與提供方同樣 --> <dubbo:application name="jfinal-duboo-demo-consumer" /> <!-- 設置本機IP,必定要正確 --> <dubbo:protocol host="192.168.1.100" /> <!-- 使用multicast廣播註冊中心暴露發現服務地址 --> <dubbo:registry protocol="multicast" address="multicast://224.5.6.7:2181" /> <!-- 聲明BlogService服務代理 --> <dubbo:reference id="blogService" interface="cn.gh.duboo.demo.service.BlogService" /> </beans>
裏面的參數都在Dubbo開發者指南中有說明,須要提到的是,最下面的配置註冊了blogService接口。注意:標籤是「dubbo:reference」。
SpringPlugin無構造參數啓動時會到「webapp\WEB-INF」下讀取「applicationContext.xml」配置。
在相應位置建立此文件,內容以下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <import resource="consumer.xml" /> </beans>
載入此文件即啓動Spring,按配置中的「import」標籤將加載Dubbo的配置文件「consumer.xml」。
原Demo的「DemoConfig.java」類名被改爲「DemoConsumerConfig.java」,相應地,「web.xml」中也得相應改動。
因爲Consumer端不須要數據庫操做,所以,「a_little_config.txt」被移動到JFinalDubboDemoProvider工程中,更名爲「duboo_demo_provider_config.txt」。
只有兩個方法須要作出修改:
@Override public void configConstant(Constants me) { me.setDevMode(true); } @Override public void configPlugin(Plugins me) { me.add(new SpringPlugin()); }
因爲用到了SpringPlugin插件,所以,「BlogController.java」中就用到與之配套的「Ioc」註解。
@Before({ BlogInterceptor.class, IocInterceptor.class }) public class BlogController extends Controller { @Inject.BY_NAME private BlogService blogService;
在類定義上使用@Before(IocInterceptor.class)是爲了告訴SpringPlugin插件,此類須要用到Spring的Bean注入。而@Inject.BY_NAME是告訴SpringPlugin插件,blogService字段須要注入,注入的實例是consumer.xml中聲明的同名Bean。這樣,類中的各方法就可使用blogService實例了。
注意:是注入,而不是初始化,而且注入的是一個代理(參見Dubbo文檔)。BlogService只是個接口,它的實現部署在Provider端。
請看類中各方法的變化:
public void index() { setAttr("blogPage", blogService.paginate(getParaToInt(0, 1), 10)); render("blog.html"); } public void add() { render("add.html"); } @Before(BlogValidator.class) public void save() { Blog blog = getModel(Blog.class, "blog"); blogService.save(blog); redirect("/blog"); } public void edit() { setAttr("blog", blogService.findById(getPara())); } @Before(BlogValidator.class) public void update() { Blog blog = getModel(Blog.class, "blog"); blogService.update(blog); redirect("/blog"); } public void delete() { blogService.deleteById(getPara()); redirect("/blog"); }
能夠看到,全部的數據操做都是經過blogService接口進行操做。
新建一個Dynamic Web項目,命名爲「JFinalDubboDemoProvider」。其實只建立成普通Java項目也能夠,緣由後面解釋。
標準的Dubbo服務提供方配置文件,改自Dubbo官方Demo:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <!-- 提供方應用信息,用於計算依賴關係 --> <dubbo:application name="jfinal-duboo-demo-provider" /> <!-- 使用multicast廣播註冊中心暴露服務地址 --> <dubbo:registry protocol="multicast" address="multicast://224.5.6.7:2181" /> <!-- 用dubbo協議在20880端口暴露服務,注意本機IP要設置正確 --> <dubbo:protocol name="dubbo" host="192.168.1.100" port="20880" /> <!-- 聲明Blog的Dao實例 --> <bean id="blogDao" class="cn.gh.duboo.demo.model.Blog" /> <!-- 聲明BlogService服務實例 --> <bean id="blogService" class="cn.gh.duboo.demo.service.impl.BlogServiceImpl"> <!-- 將Blog的Dao實例注入 --> <property name="blogDao" ref="blogDao" /> </bean> <!-- 聲明須要暴露的服務接口 --> <dubbo:service interface="cn.gh.duboo.demo.service.BlogService" ref="blogService" /> </beans>
前幾個參數都在Dubbo開發者指南中有說明。
配置中聲明瞭「blogService」這個Bean實例,它是BlogService的一個實現。並經過配置將Blog的Dao注入到這個Bean中,「BlogServiceImpl」中不用初始化blogDao就可使用了。若是有看不懂的讀者請惡補Spring配置。
最後,經過「dubbo:service」標籤暴露「BlogService」服務給Consumer端。「cn.gh.duboo.demo.service.BlogService」就是暴露出來的服務名,具體內容參見Dubbo文檔。
SpringPlugin無構造參數啓動時會到「webapp\WEB-INF」下讀取「applicationContext.xml」配置。
在相應位置建立此文件,內容以下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <import resource="provider.xml" /> </beans>
載入此文件即啓動Spring,按配置中的「import」標籤將加載Dubbo的配置文件「provider.xml」。
原JFinal Demo中的「a_little_config.txt」被移動到JFinalDubboDemoProvider工程中,更名爲「duboo_demo_provider_config.txt」。
內容不變,參數值請根據本身數據庫狀況自行修改。
將原JFinal Demo中的「DemoConfig.java」移動到項目中,並更名成「DemoProviderConfig.java」。一樣地,「web.xml」中也得相應改動。
這裏拋棄了C3P0數據源插件,改用Druid,由於它能夠監控Sql運行狀況。Druid的其它優勢請自行百度。
改動的地方以下:
@Override public void configConstant(Constants me) { loadPropertyFile("duboo_demo_provider_config.txt"); me.setDevMode(getPropertyToBoolean("devMode", false)); me.setViewType(ViewType.JSP); } @Override public void configHandler(Handlers me) { // 聲明Druid監控頁面URL me.add(new DruidStatViewHandler("/druid")); } @Override public void configPlugin(Plugins me) { // 配置Druid數據庫鏈接池插件 DruidPlugin dp = new DruidPlugin(getProperty("jdbcUrl"), getProperty("user"), getProperty("password").trim()); StatFilter stat = new StatFilter(); stat.setMergeSql(true); dp.addFilter(stat); WallFilter wall = new WallFilter(); wall.setDbType(JdbcConstants.MYSQL); dp.addFilter(wall); // 配置ActiveRecord插件 ActiveRecordPlugin arp = new ActiveRecordPlugin(dp); arp.setShowSql(getPropertyToBoolean("devMode", false)); arp.setDevMode(getPropertyToBoolean("devMode", false)); arp.addMapping("blog", Blog.class); // 映射blog 表到 Blog模型 arp.setDialect(new MysqlDialect()); // 配置Spring插件 SpringPlugin sp = new SpringPlugin(); // 加入各插件到Config me.add(dp); me.add(arp); me.add(sp); }
配置中沒有什麼特別的,只是啓動了Druid監控。部署到Tomcat後,經過http://www.id:port/druid便可監控Sql的執行狀況。
實現BlogService接口,整個Demo的數據操做代碼都存在此類中。
public class BlogServiceImpl implements BlogService { private Blog blogDao; public Page<Blog> paginate(int pageNumber, int pageSize) { return blogDao.paginate(pageNumber, pageSize, "select *", "from blog order by id asc"); } public void update(Blog blog) { if (blog == null) { return; } blog.update(); } public Blog save(Blog blog) { if (blog == null) { return null; } blog.save(); return blog; } public Blog findById(String id) { Blog blog = blogDao.findById(id); return blog; } public void deleteById(String id) { blogDao.deleteById(id); } /** * 經過Spring配置文件注入Blog的dao * @param blogDao */ public void setBlogDao(Blog blogDao) { this.blogDao = blogDao; } }
沒有什麼好介紹的,你們要注意的地方就是Blog的dao是經過Spring配置注入的。
因爲Provider只是個服務,那麼它就可以脫離Tomcat之類的Web容器獨立運行,因此我們就大膽的試試:
建立一個專門用於獨立啓動的類命名爲「DemoProviderApp.java」,在它的main()方法中加入以下代碼:
public class DemoProviderApp { public static void main(String[] args) throws InterruptedException { // 讀取配置文件 Prop p = PropKit.use("duboo_demo_provider_config.txt", "utf-8"); // 配置Druid數據庫鏈接池插件 DruidPlugin dp = new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p .get("password").trim()); WallFilter wall = new WallFilter(); wall.setDbType(JdbcConstants.MYSQL); dp.addFilter(wall); // 配置ActiveRecord插件 ActiveRecordPlugin arp = new ActiveRecordPlugin(dp); arp.addMapping("blog", Blog.class); // 映射blog 表到 Blog模型 arp.setDialect(new MysqlDialect()); arp.setShowSql(p.getBoolean("devMode", false)); arp.setDevMode(p.getBoolean("devMode", false)); // 配置Spring插件 SpringPlugin sp = new SpringPlugin( "src/main/webapp/WEB-INF/applicationContext.xml"); // 手動啓動各插件 dp.start(); arp.start(); sp.start(); System.out.println("Demo provider for Dubbo啓動完成。"); // 沒有這一句,啓動到這服務就退出了 Thread.currentThread().join(); } }
怎麼樣,幾行代碼就能夠將Provider服務做爲通常Java應用啓動。
因爲使用了JFinal的插件,只用少許代碼就達到目的,不然還要寫一大套代碼。前人種樹,後人乘涼,因此再次感謝 @JFinal 大大,並大喊:「JFinal威武」。
源碼地址:
Dubbo文檔:
系列文章: