SSH專案實戰OA-dubbo
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" />
|
(表現層)呼叫服務的方式如下:
<!-- 增加引用遠端服務配置, <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