大三學期漸末,事情也挺多的,上周就開始着手整合SSH框架,到現在才真正的完成,過程中碰到了許多小問題(小問題大折騰,哭臉.jpg)。本着善始善終的原則,最終把它給完成了。
本篇文章就在:
win7 64位,eclipse最新版(2017.9),Spring4.3.13,Hibernate5.0.1,Struts2.3.34 環境下,整合SSH。在下一篇文章,我們也來使用Maven來整合一下(畢竟學了就要用嘛,哈哈)。
首先先介紹一下jar包:
以上是我使用完整的jar包清單,我們分別導入了三個框架所需要的jar包,在導jar包的時候需要注意以下幾點:
- jar包沖突:struts2的javassist和Hibernate的javassist有沖突,我們選擇最新的版本的(根據你的自身情況選擇)。
- log4j,log4j-api,log4jcore,slf4j-api,slf4j-log4j12的的沖突,我們依舊看情況選擇最新版本。
- c3p0jar包,我們選擇最新的(框架之間有重復的記得刪除重復)。
- Spring整合struts2需要導入Spring-web和struts2-spring-plugin jar包,不要丟掉。
- 數據庫jar驅動jar包,根據自身使用的數據庫進行選擇
- 因為我這里使用Spring對事務的管理,所以還要導入事務的jar包,spring -jdbc的jar包,spring-orm,Aspectj的jar包。
以上都是我們需要特別注意的,否則整合的過程中會給你帶來不小的麻煩
jar包就介紹差不多了,那我們就來開始吧:
首先看一下我的目錄結構:
項目大體可以分為三層,視圖層,service層,DAO層,因為我們這里沒有什么業務,單純的是調用DAO,所以可能service層和DAO層之間的區別不是很明顯。
其實三個框架的整合,就是將Hibernate的session創建交給Spring,將Struts2的Action交給Spring。
(一)在Hibernate中,我們自己通過以下的一系列操作獲取session:
//加載配置文件
Configuration config = new Configuration().configure();
//根據配置文件創建會話工廠
SessionFactory factory = config.buildSessionFactory();
//根據會話工廠創建會話
Session session = factory.getCurrentSession();
//創建一個事物對象
Transaction tx = session.beginTransaction();
//new 一個學生對象
Student student = new Student("小三",19,99);
//將對象持久化到數據表中
session.save(student);
//提交事務
tx.commit();
//關閉會話
session.close();
//關閉工廠
factory.close();
同樣為了解耦,在項目中,我們不再自己手動的來獲取session了,而是通過Spring來幫我們創建,並且service層中需要DAO,DAO需要session,也是Spring進行注入。
(二)在Struts2中,我們通過自己在Struts2的主配置文件中指定對應請求的Action的全限定類名,Struts2和Spring整合則是將Action的創建交給了Spring,由Spring來管理Action對象。
接下來我們就這兩個方面分別整合Spring和Hibernate,Spring和Struts2,最后在Struts2 Action的execute方法中調用service,對業務進行操作。
下面為了代碼的可讀性,博主不會將代碼分塊分析,很重要的將會指出,大多數的過程說明將在注釋中給出:
整合Spring和Hibernate:
先給出我們的基本代碼:
//DAO接口:
public interface StudentDao {
void insert(Student student);
void delete(Student student);
void update(Student student);
List<Student> selectAllStudents();
boolean selectStudentByIdAndName(String name,int age);
}
//DAO的實現類,里面注入了SessionFactory對象,利用這個我們可以獲取session
public class StudentDaoImpl implements StudentDao{
//這里的sessionFactory由Spring進行注入
private SessionFactory sessionFactory;
//所以這里需要setter方法,這里的getter方法順帶添上,如果以后需要獲取sessionFactory的話可以調用
public SessionFactory getSessionFactory() {
return sessionFactory;
}
//依賴注入,需要setter方法
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public void insert(Student student) {
sessionFactory.getCurrentSession().save(student);
}
@Override
public void delete(Student student) {
sessionFactory.getCurrentSession().delete(student);
}
@Override
public void update(Student student) {
sessionFactory.getCurrentSession().update(student);
}
@Override
public List<Student> selectAllStudents() {
String hql = "from Student";
return sessionFactory.getCurrentSession().createQuery(hql).list();
}
//通過name和age來判別學生是否存在
@Override
public boolean selectStudentByIdAndName(String name, int age) {
String hql = "from Student where name=? and age=?";
boolean flag = false;
if(sessionFactory.getCurrentSession().createQuery(hql).setString(0, name).setInteger(1, age).uniqueResult()!=null) {
flag = true;
}
return flag;
}
}
上面的DAO,我們獲取session不再使用原始的方法了,而是使用Spring注入的方式為我們程序獲取session,具體的SessionFactory配置,將在后面的Spring配置文件給出。
接下來我們看Service:
//service接口
public interface StudentService {
void add(Student student);
void remove(Student student);
void modify(Student student);
List<Student> findAllStudents();
boolean findByNameAndAge(String name,int age);
}
//service實現類
public class StudentServiceImpl implements StudentService {
//這里的Dao對象是由Spring注入,下面要有setter方法
private StudentDao studentdao;
public StudentDao getStudentdao() {
return studentdao;
}
public void setStudentdao(StudentDao studentdao) {
this.studentdao = studentdao;
}
@Override
public void add(Student student) {
studentdao.insert(student);
}
@Override
public void remove(Student student) {
studentdao.delete(student);
}
@Override
public void modify(Student student) {
studentdao.update(student);
}
@Override
public List<Student> findAllStudents() {
return studentdao.selectAllStudents();
}
@Override
public boolean findByNameAndAge(String name, int age) {
return studentdao.selectStudentByIdAndName(name, age);
}
}
接着便是我們Spring的配置文件(下面的配置文件是完整的配置文件,即整合ssh的完整配置文件,其實也就是在整合Hibernate的基礎上注冊了Action類的bean):
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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">
<!-- 注冊c3p0數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!--處理中文亂碼問題-->
<property name="jdbcUrl" value="jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8"/>
<property name="user" value="root"/>
<property name="password" value="123"/>
<!-- ?useUnicode=true&characterEncoding=utf8 -->
</bean>
<!-- 注冊sessionFactory -->
<bean id="MysessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--不要缺少classpath,否則在整合Struts2時候會找不到映射文件-->
<property name="mappingDirectoryLocations" value="classpath:com/testSpring/Entity"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate5.SpringSessionContext</prop>
</props>
</property>
</bean>
<!-- 注冊studentDao -->
<bean id="StudentDao" class="com.testSpring.Dao.StudentDaoImpl">
<property name="sessionFactory" ref="MysessionFactory"></property>
</bean>
<!-- 注冊studentService -->
<bean id="studentservice" class="com.testSpring.Service.StudentServiceImpl">
<property name="studentdao" ref="StudentDao"/>
</bean>
<!-- 將Action交由Spring來管理 ref里面的studentservice引用的是上面的bean,這個是多例的,因為每個請求對應一個Action,不能多個用戶共用一個Action-->
<bean id="RegisterAction" class="com.testSpring.Action.RegisterAction" scope="prototype">
<property name="studentservice" ref="studentservice"/>
</bean>
<!-- 注冊事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="MysessionFactory"/>
</bean>
<!-- 注冊事務通知 -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add" isolation="DEFAULT" propagation="REQUIRED"/>
<tx:method name="remove" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="modify" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="findAllStudents" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
<tx:method name="findByNameAndAge" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- aop配置切入點 -->
<aop:config>
<aop:pointcut expression="execution(* *..Service.*.*(..))" id="myPointCut"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
整合Hibernate,
我們需要注冊SessionFactory,
class為org.springframework.orm.hibernate5.LocalSessionFactoryBean
,位於我們Spring orm包下,對於不同版本的Hibernate,我們應該選用不同的整合class。
上面的Spring主配置文件中用<property name="hibernateProperties">
屬性來替代了我們導入Hibernate的主配置文件,當然我們也可以直接導入Hibernate的主配置文件,不過為了簡潔,我們這樣比較方便。
關於配置文件后面對事務的管理,我們這里就不多說了,我的前幾篇文章都有詳細的介紹,有興趣的同學可以去看看:
http://blog.csdn.net/qq_39266910/article/details/78826171
如果做到上面的這些,我們便可以進行測試了:
//測試類
public class Test01 {
private StudentService service;
@Before
public void before() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
service = (StudentService)ac.getBean("studentservice");
}
@Test
public void test01() {
service.add(new Student("中文",18));
System.out.println("Success");
}
@Test
public void test02() {
Student student = new Student();
student.setId(1);
service.remove(student);
}
@Test
public void test03() {
Student student = new Student("張三",25);
student.setId(10);
service.modify(student);
}
@Test
public void test06() {
System.out.println(service.findAllStudents());
}
@Test
public void test07() {
System.out.println(service.findByNameAndAge("中", 18));
}
}
以上就是Spring整合Hibernate的全過程,接下來我們來整合Struts2:
Spring整合Struts2
首先是Struts2的主配置文件:
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="true" />
<package name="default" namespace="/" extends="struts-default" >
<!--下面的全限定類名可以改為RegisterAction,當我們在Spring中注冊當前Action類的bean-->
<action name="register" class="com.testSpring.Action.RegisterAction">
<result name="success">/welcome.jsp</result>
<result name="error">/error.jsp</result>
</action>
</package>
</struts>
接着是對應的Action:
public class RegisterAction extends ActionSupport{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
//Spring會為我們自動注入service,但是這個屬性名要和Spring主配置文件里面注冊的studentservice的id保持一致。
//或者將Action交由Spring管理,在Spring配置Action的bean,為bean注入service,如果這樣,我們在struts2主配置文件的class就不必寫成Action的全限定類名,而是Spring中注冊的id。
private StudentService studentservice;
public RegisterAction() {
super();
}
public RegisterAction(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 注入Service,我們需要保留set方法
public void setStudentservice(StudentService studentservice) {
this.studentservice = studentservice;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String execute() {
System.out.println(studentservice);
System.out.println(name);
System.out.println(age);
studentservice.add(new Student(name,age));
return SUCCESS;
}
}
以上是模擬完成一個注冊功能,view層發送一個請求,包含姓名和年齡,后台負責接收,並調用service層進行處理,service層調用DAO,DAO調用SessionFactory獲取session,最終達到對數據庫的操作。
如果僅僅這樣你是不是忘了些什么?
①我們需要在web.xml中添加Struts2的核心過濾器。
②設置一個監聽器,監聽當web容器創建的時候,即創建我們的Spring容器,這樣我們不再需要自己加載Spring的主配置文件。
③設置web容器全局參數,自定義Spring主配置文件的位置和命名
具體的看web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!-- 自定義Spring主配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 使用ContextLoaderListener初始化Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 定義Struts 2的FilterDispathcer的Filter -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<!-- FilterDispatcher用來初始化Struts 2並且處理所有的WEB請求。 -->
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
我們一並給出jsp頁面(success和error頁面就不寫了,一個形式):
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>register</title>
</head>
<body>
<form action="register" method="post">
姓名<input type="text" name="name"><br>
年齡<input type="text" name="age"><br>
<input type="submit" value="注冊">
</form>
</body>
</html>
接下來我們進行測試:
點擊后,跳轉到:
最后看看數據庫:
以上的ssh整合大體上是沒有什么問題的,但是碰上延時加載的話會出現一些意想不到的事情,在講Hibernate的session的時候,我們說過session有兩種獲取的方式,一個是getCurrentSession,另一個是openSession,它們兩個獲取的session的區別是,getSession獲得的session必須要在事務中執行,也就說沒有事務是不能獲取session的,當我們使用session.load進行查詢的時候,這就是一個延時加載,執行加載方法的時候會產生一個代理,這個代理是一個空代理,只有當我們真正需要這個代理的詳細數據的時候,才會真正的進行查詢,但是當它真正的查詢的時候,已經沒有了事務(因為我們這里的事務是通過Spring整合AspectJ,通過AOP的方式實現添加事務的),所以這個時候也就沒有了session,所以當再執行詳情查詢的時候就會報錯(no session)。
所以我們需要在web.xml中添加一個過濾器,來獲取session,這個過濾器的名字叫做OpenSessionInViewFilter,添上這個過濾器后,當我們進行延時加載的話,就不會再出現no session的情況了!
在OpenSessionInViewFilter的源碼中,獲取session是利用的SessionFactory,也就是我們自己在Spring的注冊的SessionFactory,且在里面,這個類有一個默認的SessionFactory名字就叫做sessionFactory:
注意:添加這個過濾器,一定要在Struts2的核心過濾器之前!
具體原因是:Struts2的核心過濾器中,當有Action請求的時候,會執行executeAction方法,即執行Action,不會有chain.doFilter(執行下一個過濾器),有源碼有真相:
這里的mapping就是對應的action請求。
下面是openSessionInViewFilter的具體配置方法,初始化參數是為了自定義我們的sessionFactory的bean id,因為openSessionInViewFilter里面有setter方法,可以為之前設置好的默認值進行修改。
總結:當代碼都寫出來了,覺得很簡單,但是這過程中一直小bug不斷(當然大多數都是由自己的粗心造成的),其實三個框架之間的真核無非就是將所有關於類的創建管理交由Spring,由Spring來為需要的注入所需要的bean,不再需要手動的創建一個個的類,使得各個層級之間耦合度降低,即使一層代碼出現了問題不需要修改另一層的代碼,便於我們項目的維護和更新,也便於出現問題能夠即使定位出錯的位置。
以上是自己的心得體會,代碼均由博主親自驗證,可以運行,文章方便博主以后查閱,也供大家參考,如有錯誤不吝賜教!