此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文档:
系列文章: