SSH框架完全整合


大三學期漸末,事情也挺多的,上周就開始着手整合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&amp;characterEncoding=utf8"/>
		<property name="user" value="root"/>
		<property name="password" value="123"/>
		<!-- ?useUnicode=true&amp;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,不再需要手動的創建一個個的類,使得各個層級之間耦合度降低,即使一層代碼出現了問題不需要修改另一層的代碼,便於我們項目的維護和更新,也便於出現問題能夠即使定位出錯的位置。


以上是自己的心得體會,代碼均由博主親自驗證,可以運行,文章方便博主以后查閱,也供大家參考,如有錯誤不吝賜教!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM