很多人认为,既然有了JFinal,为什么还要Spring。殊不知一些基于Spring的很牛X的东东集成到JFinal中能够事半功倍。比如Dubbo这个高性能优秀的服务框架,它基于Spring,于是JFinal提供的Spring插件就能更方便地将Dubbo集成进咱们的程序中,成为高大上的程序。
1. Dubbo Demo概述
此Demo实际在2014年上半年就已经完成了,只是到最近才有时间和心情写完此文。同时,将JFinal升级到了1.9,并采用Maven构建项目。
另外,仔细想了想,Provider其实可以不依托Tomcat之类的Web容器启动,并验证成功。
1.1. JFinal Spring插件
有很多人认为,既然有了JFinal,为什么还要Spring。殊不知一些基于Spring的很牛X的东东集成到JFinal中能够事半功倍。比如Dubbo这个高性能优秀的服务框架,它基于Spring,于是JFinal提供的Spring插件就能更方便地将Dubbo集成进咱们的程序中,成为高大上的程序。
1.2. 基于jfinal_demo_for_maven
构建本Demo的目的只为了向读者演示如何将咱们的程序改造成基于Dubbo的应用,选择JFinal的Demo,使得JFinal读者可以快速进入状况,而且笔者也能省下开发Demo功能的时间。
1.3. 项目依赖及感谢
公司 / 组织 | 产品 | 说明 |
JFinal | JFinal 1.9 | 链接地址 JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。 在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 |
JFinal 1.9 Demo for Maven | JFinal的Demo | |
阿里巴巴 | Dubbo | 链接地址 Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。 感谢Dubbo,使咱们不需要太多专业知识和能力就能够很轻易地将程序转变成分布式这种高大上的概念型产品。 |
Druid | 链接地址 Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。 | |
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 |
1 | 1 |
感谢以上项目及环境,使我们能简便快速地构建分布式应用。希望它们能越做越强,也希望更多人从中受益。
Dubbo相关文档已经存放在JFinalDubboDemoApi项目下,请读者仔细阅读。
2. Demo结构
2.1. 三体结构
用过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包中,便于服务端和客户端引用(根据实际应用中的业务的要求,不同业务的服务接口应该打包成多个jar包,本Demo只需要一个jar包即可)。
要注意的是,Consumer端和Provider端要引用相同的Api,不然就会出乱子。
- JFinalDubboDemoProvider
此war包部署在Provider服务端,提供JFinal Demo中Blog的数据服务,它实现数据服务接口Api(提取出的接口在JFinalDubboDemoApi.jar中),并连接数据库完成数据操作。
三部分相互独立,千万不要将Api归入Consumer或者Provider包。不然,依赖关系不明确,Api也不方便为其它程序引用。
2.2. 部署结构
Api项目分别被Consumer项目端和Provider项目引用,因此,被同时打包进两个war包当中。具体部署结构请看下图:
既然是分布式服务,那么Consumer端和Provider端都可实现集群,从而真正体现分布式的优势。如何实现集群请参看后面的教程。
3. Demo实现
此章节只说明改造中必要说明的部分,其它内容请参见源码。
3.1. JFinalDubboDemoApi
新建一个java项目(注意,非Dynamic Web Project),项目命名为“JFinalDubboDemoApi”。
项目中只有两个类,Blog和BlogService:
-
Blog.java
1
2
3
4 1public class Blog extends Model<Blog> {
2 private static final long serialVersionUID = -6749384460553909926L;
3}
4
不声明dao字段是因为原来Demo中Blog的dao字段用于数据库操作,而Consumer端不与数据库打交道,所以不需要提供dao。但是,Consumer端得到Blog实例时,还可通过实例看到Model的各种接口。细心的读者可能发现了,Blog还是继承自Model,而且少了dao字段的声明。这是因为,此中只将Model做为通信用的Dto,即Data Transfer Object。
所以编码时要注意了,概念要转换,这些接口在客户端已经不能使用了,不然会出错。后面的章节会讲到并非只有Model做dto,可以有其它选择。
-
BlogService
1
2
3
4
5
6
7
8
9
10
11
12 1public interface BlogService {
2 Page<Blog> paginate(int pageNumber, int pageSize);
3
4 void update(Blog blog);
5
6 Blog save(Blog blog);
7
8 Blog findById(String id);
9
10 void deleteById(String id);
11}
12
将JFinal原Demo中所有的数据操作集成到了BlogService接口中,例如:原Controller中的getModel(Blog.class).save()这类代码就提取接口成为了update(Blog)。
3.2. JFinalDubboDemoConsumer
原JFinal Demo项目被改名成“JFinalDubboDemoConsumer”。
3.2.1. Dubbo Consumer配置
3.2.1.1. consumer.xml
标准的Dubbo服务消费方配置文件,改自Dubbo官方Demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6
7http://www.springframework.org/schema/beans/spring-beans.xsd
8
9
10http://code.alibabatech.com/schema/dubbo
11
12 http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
13 <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
14 <dubbo:application name="jfinal-duboo-demo-consumer" />
15
16 <!-- 设置本机IP,一定要正确 -->
17 <dubbo:protocol host="192.168.1.100" />
18
19 <!-- 使用multicast广播注册中心暴露发现服务地址 -->
20 <dubbo:registry protocol="multicast" address="multicast://224.5.6.7:2181" />
21
22 <!-- 声明BlogService服务代理 -->
23 <dubbo:reference id="blogService"
24 interface="cn.gh.duboo.demo.service.BlogService" />
25</beans>
26
里面的参数都在Dubbo开发者指南中有说明,需要提到的是,最下面的配置注册了blogService接口。注意:标签是“dubbo:reference”。
3.2.1.2. applicationContext.xml
SpringPlugin无构造参数启动时会到“webapp\WEB-INF”下读取“applicationContext.xml”配置。在相应位置创建此文件,内容如下:
1
2
3
4
5
6
7
8 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
5
6 <import resource="consumer.xml" />
7</beans>
8
载入此文件即启动Spring,按配置中的“import”标签将加载Dubbo的配置文件“consumer.xml”。
3.2.2. 改造Config
原Demo的“DemoConfig.java”类字被改成“DemoConsumerConfig.java”,相应地,“web.xml”中也得相应改动。
由于Consumer端不需要数据库操作,因此,“a_little_config.txt”被移动到JFinalDubboDemoProvider工程中,改名为“duboo_demo_provider_config.txt”。
只有两个方法需要做出修改:
1
2
3
4
5
6
7
8
9
10 1@Override
2 public void configConstant(Constants me) {
3 me.setDevMode(true);
4 }
5
6 @Override
7 public void configPlugin(Plugins me) {
8 me.add(new SpringPlugin());
9 }
10
3.2.3. 改造BlogController.java
由于用到了SpringPlugin插件,因此,“BlogController.java”中就用到与之配套的“Ioc”注解。
1
2
3
4
5
6 1@Before({ BlogInterceptor.class, IocInterceptor.class })
2public class BlogController extends Controller {
3
4 @Inject.BY_NAME
5 private BlogService blogService;
6
在类定义上使用@Before(IocInterceptor.class)是为了告诉SpringPlugin插件,此类需要用到Spring的Bean注入。而@Inject.BY_NAME是告诉SpringPlugin插件,blogService字段需要注入,注入的实例是consumer.xml中声明的同名Bean。这样,类中的各方法就可以使用blogService实例了。
注意:是注入,而不是初始化,并且注入的是一个代理(参见Dubbo文档)。BlogService只是个接口,它的实现部署在服务端。
请看类中各方法的变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 1 public void index() {
2 setAttr("blogPage", blogService.paginate(getParaToInt(0, 1), 10));
3 render("blog.html");
4 }
5
6 public void add() {
7 render("add.html");
8 }
9
10 @Before(BlogValidator.class)
11 public void save() {
12 Blog blog = getModel(Blog.class, "blog");
13 blogService.save(blog);
14 redirect("/blog");
15 }
16
17 public void edit() {
18 setAttr("blog", blogService.findById(getPara()));
19 }
20
21 @Before(BlogValidator.class)
22 public void update() {
23 Blog blog = getModel(Blog.class, "blog");
24 blogService.update(blog);
25 redirect("/blog");
26 }
27
28 public void delete() {
29 blogService.deleteById(getPara());
30 redirect("/blog");
31 }
32
可以看到,所有的数据操作都是通过blogService接口进行操作。
3.3. JFinalDubboDemoProvider
新建一个Dynamic Web项目,命名为“JFinalDubboDemoProvider”。其实只创建成普通Java项目也可以,原因后面解释。
3.3.1. Dubbo Provider配置
3.3.1.1. provider.xml
标准的Dubbo服务提供方配置文件,改自Dubbo官方Demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd
6 http://code.alibabatech.com/schema/dubbo
7 http://code.alibabatech.com/schema/dubbo/dubbo.xsd
8 ">
9
10 <!-- 提供方应用信息,用于计算依赖关系 -->
11 <dubbo:application name="jfinal-duboo-demo-provider" />
12
13 <!-- 使用multicast广播注册中心暴露服务地址 -->
14 <dubbo:registry protocol="multicast" address="multicast://224.5.6.7:2181" />
15
16 <!-- 用dubbo协议在20880端口暴露服务,注意本机IP要设置正确 -->
17 <dubbo:protocol name="dubbo" host="192.168.1.100" port="20880" />
18
19 <!-- 声明Blog的Dao实例 -->
20 <bean id="blogDao" class="cn.gh.duboo.demo.model.Blog" />
21
22 <!-- 声明BlogService服务实例 -->
23 <bean id="blogService" class="cn.gh.duboo.demo.service.impl.BlogServiceImpl">
24 <!-- 将Blog的Dao实例注入 -->
25 <property name="blogDao" ref="blogDao" />
26 </bean>
27
28 <!-- 声明需要暴露的服务接口 -->
29 <dubbo:service interface="cn.gh.duboo.demo.service.BlogService"
30 ref="blogService" />
31
32</beans>
33
前几个参数都在Dubbo开发者指南中有说明。
配置中声明了“blogService”这个Bean实例,它是BlogService的一个实现。并通过配置将Blog的Dao注入到这个Bean中,“BlogServiceImpl”中不用初始化blogDao就可以使用了。如果有看不懂的读者请恶补Spring配置。
最后,通过“dubbo:service”标签暴露“BlogService”服务给Consumer端。“cn.gh.duboo.demo.service.BlogService”就是暴露出来的服务名,具体内容参见Dubbo文档。
3.3.1.2. applicationContext.xml
SpringPlugin无构造参数启动时会到“webapp\WEB-INF”下读取“applicationContext.xml”配置。
在相应位置创建此文件,内容如下:
1
2
3
4
5
6
7
8 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
5
6 <import resource="provider.xml" />
7</beans>
8
载入此文件即启动Spring,按配置中的“import”标签将加载Dubbo的配置文件“provider.xml”。
3.3.2. duboo_demo_provider_config.txt
原JFinal Demo中的“a_little_config.txt”被移动到JFinalDubboDemoProvider工程中,改名为“duboo_demo_provider_config.txt”。
内容不变,参数值请根据自己数据库情况自行修改。
3.3.3. 改造Config
将原JFinal Demo中的“DemoConfig.java”移动到项目中,并改名成“DemoProviderConfig.java”。同样地,“web.xml”中也得相应改动。
这里抛弃了C3P0数据源插件,改用Druid,因为它可以监控Sql运行情况。Druid的其它优点请自行百度。
改动的地方如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 1@Override
2 public void configConstant(Constants me) {
3 loadPropertyFile("duboo_demo_provider_config.txt");
4 me.setDevMode(getPropertyToBoolean("devMode", false));
5 me.setViewType(ViewType.JSP);
6 }
7
8 @Override
9 public void configHandler(Handlers me) {
10 // 声明Druid监控页面URL
11 me.add(new DruidStatViewHandler("/druid"));
12 }
13
14 @Override
15 public void configInterceptor(Interceptors me) {
16 }
17
18 @Override
19 public void configPlugin(Plugins me) {
20 // 配置Druid数据库连接池插件
21 DruidPlugin dp = new DruidPlugin(getProperty("jdbcUrl"),
22 getProperty("user"), getProperty("password").trim());
23
24 StatFilter stat = new StatFilter();
25 stat.setMergeSql(true);
26 dp.addFilter(stat);
27
28 WallFilter wall = new WallFilter();
29 wall.setDbType(JdbcConstants.MYSQL);
30
31 dp.addFilter(wall);
32
33 // 配置ActiveRecord插件
34 ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
35
36 arp.setShowSql(getPropertyToBoolean("devMode", false));
37 arp.setDevMode(getPropertyToBoolean("devMode", false));
38 arp.addMapping("blog", Blog.class); // 映射blog 表到 Blog模型
39 arp.setDialect(new MysqlDialect());
40
41 // 配置Spring插件
42 SpringPlugin sp = new SpringPlugin();
43
44 // 加入各插件到Config
45 me.add(dp);
46 me.add(arp);
47 me.add(sp);
48 }
49
配置中没有什么特别的,只是启动了Druid监控。部署到Tomcat后,通过http://www.id:port/druid即可监控Sql的执行情况。
3.3.4. BlogServiceImpl.java
实现BlogService接口,整个Demo的数据操作代码都存在此类中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 1public class BlogServiceImpl implements BlogService {
2
3 private Blog blogDao;
4
5 public Page<Blog> paginate(int pageNumber, int pageSize) {
6 return blogDao.paginate(pageNumber, pageSize, "select *", "from blog order by id asc");
7 }
8
9 public void update(Blog blog) {
10 if (blog == null) {
11 return;
12 }
13 blog.update();
14 }
15
16 public Blog save(Blog blog) {
17 if (blog == null) {
18 return null;
19 }
20 blog.save();
21 return blog;
22 }
23
24 public Blog findById(String id) {
25 Blog blog = blogDao.findById(id);
26 return blog;
27 }
28
29 public void deleteById(String id) {
30 blogDao.deleteById(id);
31 }
32
33 /**
34 * 通过Spring配置文件注入Blog的dao
35 * @param blogDao
36 */
37 public void setBlogDao(Blog blogDao) {
38 this.blogDao = blogDao;
39 }
40}
41
没有什么好介绍的,大家要注意的地方就是Blog的dao是通过Spring配置注入的。
3.3.5. 启动类
由于Provider只是个服务,那么它就能够脱离Tomcat之类的应用服务器独立运行,所以咱们就大胆的试试:
创建一个专门用于独立启动的类命名为“DemoProviderApp.java”,在它的main()方法中加入如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 1public class DemoProviderApp {
2
3 public static void main(String[] args) throws InterruptedException {
4 // 读取配置文件
5 Prop p = PropKit.use("duboo_demo_provider_config.txt", "utf-8");
6
7 // 配置Druid数据库连接池插件
8 DruidPlugin dp = new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p
9 .get("password").trim());
10
11 WallFilter wall = new WallFilter();
12 wall.setDbType(JdbcConstants.MYSQL);
13 dp.addFilter(wall);
14
15 // 配置ActiveRecord插件
16 ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
17
18 arp.addMapping("blog", Blog.class); // 映射blog 表到 Blog模型
19 arp.setDialect(new MysqlDialect());
20 arp.setShowSql(p.getBoolean("devMode", false));
21 arp.setDevMode(p.getBoolean("devMode", false));
22
23 // 配置Spring插件
24 SpringPlugin sp = new SpringPlugin(
25 "src/main/webapp/WEB-INF/applicationContext.xml");
26
27 // 手动启动各插件
28 dp.start();
29 arp.start();
30 sp.start();
31
32 System.out.println("Demo provider for Dubbo启动完成。");
33
34 // 没有这一句,启动到这服务就退出了
35 Thread.currentThread().join();
36 }
37}
38
怎么样,几行代码就可以将Provider服务作为一般Java应用启动。
由于使用了JFinal的插件,只用少量代码就达到目的,否则还要写一大套代码。前人种树,后人乘凉,所以再次感谢 @JFinal 大大,并大喊:“JFinal威武”。