Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,本质上是个远程服务调用的分布式框架.由于我们子系统中web工程和service工程是发布在不同的Tomcat,所以需要使用Dubbo来发布和引用服务.
安装Zookeeper
Dubbo一般会使用Zookeeper作为注册中心,所以我们需要在linux虚拟机中安装Zookeeper服务.
安装方法可以参考我的一篇博客:
Ubuntu下安装zookeeper
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
如果不用Dubbo,单一工程中spring的配置可能如下:
<bean id="xxxService" class="com.xxx.XxxServiceImpl" /> <bean id="xxxAction" class="com.xxx.XxxAction"> <property name="xxxService" ref="xxxService" /> </bean> |
一旦用了Dubbo,在本地服务的基础上,只需做简单配置,即可完成远程化服务。例如,将上面的配置文件拆分成两份,将服务定义部分放在服务提供方remote-provider.xml,将服务引用部分放在服务消费方remote-consumer.xml,并在提供方增加暴露服务配置<dubbo:service>,在消费方增加引用服务配置<dubbo:reference>。所以,我们要是使用了Dubbo的话,就要分为发布服务和调用服务两部分了,发布服务方式如下:
<!-- 和本地服务一样实现远程服务 --> <bean id="xxxService" class="com.xxx.XxxServiceImpl" /> <!-- 增加暴露远程服务配置,interface:指定服务的接口,ref:指定接口的实现类 --> <dubbo:service interface="com.xxx.XxxService" ref="xxxService" />
|
(表现层)调用服务的方式如下:
<!-- 增加引用远程服务配置,interface:指定服务的接口--> <dubbo:reference id="xxxService" interface="com.xxx.XxxService" /> <!-- 和本地服务一样使用远程服务 --> <bean id="xxxAction" class="com.xxx.XxxAction"> <property name="xxxService" ref="xxxService" /> </bean>
|
为了更好说明dubbo的使用方法,让我们能先构建出一个完整的功能,如根据Id查询用户信息,实现service层,dao层和controller层
构建service层和dao层
首先在OA-system-interface中创建包com.QEcode.OA.service,并在该包下,新建一个接口UserService
然后我们在OA-system-service的com.QEcode.OA.service.impl包下新建一个实现类,如下图所示。
写完发现,我们还没创建UserDao类,所以还要在OA-system-dao的src/main/java目录下新建包com.QEcode.OA.dao.impl,并在dao包下新建接口UserDao,在impl包下新建实现类UserDaoImpl
此外,由于我们需要使用注解来将Dao的实现类注入到spring容器中,以及spring帮我们封装好的hibernate session对象HibernateTemplate,所以需要添加spring的依赖.
那么为什么要使用HibernateTemplate呢?
我们使用HibernateTemplate,有一个很重要的原因就在于我们不想直接控制事务,不想直接去获取,打开Session,开始一个事务,处理异常,提交一个事务,最后关闭一个Session.HibernateTemplate 是Hibernate操作进行封装,我们只要简单的条用HibernateTemplate 对象,传入hql和参数,就获得查询接口,至于事务的开启,关闭,都交给HibernateTemplate 对象来处理我们自己只专注于业务,不想去作这些重复而繁琐的操作。
<!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> </dependency> |
现在OA.system.dao工程的pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.QEcode</groupId>
<artifactId>OA-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>OA-system-dao</artifactId>
<dependencies>
<dependency>
<groupId>com.QEcode</groupId>
<artifactId>OA-system-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
</dependencies>
</project>
现在来写我们的持久层userDao-------等等,先不要急着写userDao.让我们思考一个问题?每一个Dao都需要有增删改查的操作,而这些操作大致上是相同,那么我们需要在每个Dao类中都重复写上同样的代码吗?显然,这是不符合可重用性原则的,我们可以把这些功能一样的代码抽取出来,构成一个BaseDao类,然后让每一个Dao实现类都继承BaseDao,那么我们就不用每次都写增删改查了.
下面让我们来新建这个BaseDao.
在dao包下新建接口BaseDao,并在impl包下新建BaseDaoImpl
由于BaseDao是一个抽取dao基本功能的接口,有着增删改查等方法,但是这有个问题,那就是hibernate的方法大多数都需要传入一个实体类的Class对象,并且当我们查询到结果时,需要返回一个实体类对象.
比如根据id查找user的方法:User getById(Long userId),需要返回一个User对象,可是,BaseDao是被所有的Dao所实现,其他Dao需要返回的就不是User对象,而是它自己的实体类对象,所以,我们不能在BaseDao中限定实体类的类型.那么要怎么办呢?可能有人会想到了,那就是泛型<T>.在BaseDao中,所有实体类型都用泛型来表示,只有当BaseDao被实现时,由实现类根据其本身的类型来指定T的具体类型.
BaseDao具体实现如下:
public interface BaseDao<T> {
/**
* @Description:保存实体
* @param entity
*/
public void save(T entity);
/**
* @Description:修改实体
* @param entity
*/
public void update(T entity);
/**
* @Description:根据id删除实体
* @param id
*/
public void delete(Long id);
/**
* @Description:根据id查询实体
* @param id 实体id
* @return
*/
public T findById(long id);
/**
* @Description:根据多个id获取多个实体
* @param ids
* @return
*/
public List<T> findByIds(Long[] ids);
/**
* @Description:查询实体列表
* @return
*/
public List<T> findAll();
}
下面是BaseDaoImpl的内容:现在我们已经写出了接口,那么就要实现BaseDaoImpl,它实现了BaseDao接口。
public interface BaseDao<T> {
/**
* @Description:保存实体
* @param entity
*/
public void save(T entity);
/**
* @Description:修改实体
* @param entity
*/
public void update(T entity);
/**
* @Description:根据id删除实体
* @param id
*/
public void delete(Long id);
/**
* @Description:根据id查询实体
* @param id 实体id
* @return
*/
public T findById(long id);
/**
* @Description:根据多个id获取多个实体
* @param ids
* @return
*/
public List<T> findByIds(Long[] ids);
/**
* @Description:查询实体列表
* @return
*/
public List<T> findAll();
}
现在我们已经实现了BaseDaoImpl了...........并没有,细心一点就会发现Class<T> clazz是一个空值,我们需要给它赋值.那么clazz是什么呢,它是继承了BaseDaoImpl类的dao中实体类的Class对象,如UserDaoImpl继承了BaseDaoImpl,那么clazz=User.Class.而想要获取到Class对象,就要设及到反射了.
如果不了解反射,可以参考我的一篇博客:
Java基础-反射
我们可以在创建BaseDaoImpl的时候,初始化clazz对象
public BaseDaoImpl(){
//利用反射得到T的类型
ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
this.clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
getGenericSuperclass方法的作用是返回直接继承的父类(包含泛型参数)
通过下面的一个例子来了解.getGenericSuperclass()
|
有两个类Person和Student,并且Student继承了Person,那么Student.class.getGenericSuperclass()返回的是cn.test.Person<cn.test.Test>
完整的BaseDaoImpl类如下:
public class BaseDaoImpl<T> implements BaseDao<T> {
@Resource(name="hibernateTemplate")
protected HibernateTemplate hibernateTemplate;
private Class<T> clazz;
public BaseDaoImpl(){
//利用反射得到T的类型
ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
this.clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
/**
* @Description:保存实体
* @param entity
*/
public void save(T entity){
hibernateTemplate.save(entity);
}
/**
* @Description:修改实体
* @param entity
*/
public void update(T entity){
hibernateTemplate.update(entity);
}
/**
* @Description:根据id删除实体
* @param id
*/
public void delete(Long id){
Object object = hibernateTemplate.get(clazz, id);
hibernateTemplate.delete(object);
}
/**
* @Description:根据id查询实体
* @param id 实体id
* @return
*/
public T findById(long id){
return hibernateTemplate.get(clazz , id);
}
/**
* @Description:根据多个id获取多个实体
* @param ids
* @return
*/
public List<T> findByIds(Long[] ids){
//防止空指针异常
if (ids == null || ids.length ==0) {
//返回一个空的集合
return Collections.EMPTY_LIST;
}
List<T> list = new ArrayList<T>();
for (Long id : ids) {
list.add(hibernateTemplate.get(clazz, id));
}
return list;
}
/**
* @Description:查询实体列表
* @return
*/
public List<T> findAll(){
return (List<T>) hibernateTemplate.find("from "+clazz.getSimpleName());
}
}
抽取出BaseDao和BaseDaoImpl后,让我们来修改一下我们原来的Dao
好,我们的UserDao已经改造好了,别忘了将UserDaoImpl注入到spring容器中,并且当我们要创建其他Dao类的时候,也要和UserDao一样继承BaseDao.
对了,我们在Dao层使用了注解来将dao注入到spring容器中,那么我们必须要让spring容器启动时扫描dao包,而我们之前是没有配置的,所以我们要在OA-system-service下的applicationContext-dao.xml中添加包扫描
<!-- 配置spring容器创建时要扫描的包 --> <context:component-scan base-package="com.QEcode.OA.dao"></context:component-scan> |
现在applicationContext-dao.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:resource/*.properties"/>
<!-- 配置spring容器创建时要扫描的包 -->
<context:component-scan base-package="com.QEcode.OA.dao"></context:component-scan>
<!-- 配置hibernateTemplate,用于持久层 -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- hibernate参数设置 -->
<property name="hibernateProperties">
<props>
<!-- 数据库方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- 显示sql语句-->
<prop key="hibernate.show_sql">true</prop>
<!-- 格式化SQL语句 -->
<prop key="hibernate.format_sql">true</prop>
<!-- create:根据映射关系生成表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="current_session_context_class">org.springframework.orm.hibernate5.SpringSessionContext</prop>
</props>
</property>
<!-- 使用注解后,用该方式指定实体类的包 -->
<property name="packagesToScan">
<array>
<value>com.QEcode.OA.pojo</value>
</array>
</property>
</bean>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<property name="minIdle" value="${jdbc.minIdle}" />
</bean>
</beans>
接下来,让我们回到service层,修改一下方法名为findById
不过,既然在Dao层我们知道了要将一些重复的代码抽取出来,形成一个BaseDao,那么在service层,我们也要学会把重复的代码抽取出来,不过service层有点不同,那就是每个service的功能是一样的,但是具体实现是不同的,所以我们只能将接口抽取出来,而不能把实现类也抽取出来.由于service接口是放在OA-system-interface中,所以在OA-system-interface下新建一个接口BaseService,并且让UserService继承BaseService
我们将Service层常用的方法抽取出来,放到BaseService中
public interface BaseService <T> {
/**
* @Description:查询所有
* @return
*/
public List<T> findAll();
/**
* @Description:删除
* @param role
*/
public void delete(Long id);
/**
* @Description:增加
* @param role
*/
public void save(T entity);
/**
* @Description:根据id查询
* @param roleId
* @return
*/
public T findById(Long id);
/**
* @Description:更新
* @param role
*/
public void update(T entity);
/**
* @Description:根据id数组查询
* @param roleIds
* @return
*/
public List<T> findByIds(Long[] entityIds);
}
不要忘了在UserServiceImpl中重写BaseService的方法.
现在我们只需要实现findById方法,其他方法等需要的时候再写.还要,要记得将UserServiceImpl注入到spring容器中
构建controller层
在OA-system-web工程中的com.QEcode.OA.controller包下创建UserAction.
UserAction内容如下:
@Controller
public class UserAction {
@Autowired
private UserService userService;
private User user;
public String findById(){
user = userService.findById(user.getUserId());
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
此外还要在struts.xml中配置action
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- 配置Struts2常量 -->
<!-- 禁用动态方法调用 -->
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<!-- 开启开发模式,只在项目开发阶段配置 -->
<constant name="struts.devMode" value="true" />
<!-- 配置访问后缀为action -->
<constant name="struts.action.extension" value="action"/>
<!-- 把主题配置成simple -->
<constant name="struts.ui.theme" value="simple" />
<!-- 用户 UserAction -->
<package name="userAction" extends="struts-default" namespace="/user">
<action name="userAction_*" class="userAction" method="{1}">
<result name="{1}">/WEB-INF/jsp/userAction/{1}.jsp</result>
<result name="success">/WEB-INF/success.jsp</result>
</action>
</package>
</struts>
我们已经构建了一个完整的功能,不过现在我们是无法使用的,因为wen工程和service工程是运行在不同的Tomcat中,web工程是无法直接调用service中的方法的.要想成功运行,必须使用dubbo来发布和引用服务
===============================================================================================
在写博客的时候,可能在项目中有一些问题没有被发现,在我修改后,忘记写到博客上,所以我将这个项目上传到github上,大家可以在github上获取项目的代码
下面是github地址,大家Fork and Star
OA-Reconsitution