目錄
2.在src下創建並配置applicationcontext.xml文件
1.基於TransactionTemplate的編程式事務管理
一、SpringIOC
(一)SpringIOC的概念
IOC(Inversion of Control),即控制反轉,不是什麼技術而是一種設計思想,這是Spring的核心。所謂IOC,對於Spring框架來說,就是由Spring來負責控制對象的生命週期和對象間的關係。
傳統JavaSE程序設計,是程序主動去創建依賴對象,也就是正轉。而IOC是由專門一個容器來創建這些對象,即由IOC容器來控制對象的創建,由容器幫我們查找及注入依賴對象,對象只是被動的接受依賴對象,也就是反轉。所以控制的什麼被反轉了?就是:獲得依賴對象的方式反轉了。
IOC是一個重要的面向對象編程的法則,它能指導我們如何設計出鬆耦合、更優良的程序。在傳統應用程序中都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合 ,難於測試;有了IOC容器後,把創建和查詢依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程序的整個體系結構變得非常靈活。
其實IOC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程序原本是老大,要獲取聲明資源都是主動出擊,但是在IOC/DI思想中,應用程序就變成被動的了,被動的等待IOC容器來創建並注入它所需要的資源了。IOC很好的體現了面向對象設計法則之——好萊塢法則“別找我們,我們找你”,即由IOC容器幫對象找相應的依賴對象並注入,而不是由對象主動去找。
SpringIOC對項目對象的解耦的顆粒度是粗顆粒的。一般在層與層之間,模塊兒與模塊兒之間的對象使用IOC進行解耦,層內部的對象一般還是直接創建。例如:servlet和service層要使用IOC解耦,service和mapper層要使用IOC解耦。
(二)SpringIOC的使用
1.所需jar包
commons-logging-1.1.3.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
2.在src下創建並配置applicationcontext.xml文件
<beans xmlns="http://www.springframework.org/schema/beans"
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
">
<!-- 在配置文件中配置需要被管理的對象 -->
<bean id="user1" class="com.bjsxt.pojo.User2"></bean>
</beans>
3.在java代碼中的應用
//1.獲取Spring容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
//2.獲取Spring容器對象中的被管理的對象
User u1 = (User) ac.getBean("uaser1");
(三)SpringIOC創建對象的三種方式
SpringIOC三種創建對象的方式:帶有固定初始化數據的對象或空對象。
1.構造器方式
<!-- 無參數構造器:SpringIOC的默認方式。直接使用bean標籤配置即可 -->
<bean id="user" class="com.bjsxt.pojo.User"></bean>
<!-- 有參數構造器: 告訴Spring容器創建一個帶有我們希望的初始化數據的對象-->
<bean id="user2" class="com.bjsxt.pojo.User">
<constructor-arg index="0" name="uid" type="int" value="1"></constructor-arg>
<constructor-arg index="1" name="uname" type="String" value="zhangsna"></constructor-arg>
<constructor-arg index="2" name="uage" type="int" value="18"></constructor-arg>
</bean>
2.工廠方式
<!--
靜態工廠:生產對象的方法是靜態的,工廠被spring容器所管理即可,工廠內部的對象不需要管理
使用:聲明bean標籤,class屬性爲靜態工廠類全限定路徑,fatory-method爲生產對象的方法
作用:實現了工廠生產對象的過程和代碼的解耦
-->
<bean id="sfactory" class="com.bjsxt.factory.StudentFactory" factory-method="newIntance"></bean>
<!--
動態工廠:生產對象的方法是非靜態的,需要先獲取工廠的實例化對象,然後再調用方法生產對象
使用:聲明bean標籤,class屬性爲動態工廠的全限定路徑
聲明bean標籤,class屬性爲工廠生產的對象的全限定路徑
factory-bean爲動態工廠bean標籤的id
fatory-method爲動態工廠生產對象的方法名
作用:實現了工廠生產對的過程和代碼的解耦
-->
<bean id="sfactory2" class="com.bjsxt.factory.StudentFactory2"></bean>
<bean id="student" class="com.bjsxt.pojo.User" factory-bean="sfactory2" factory-method="newIntance"></bean>
3.屬性注入方式
<!-- 屬性注入方式 -->
<bean id="user3" class="com.bjsxt.pojo.User">
<!-- 基本類型屬性 -->
<property name="uid" value="2"></property>
<property name="uname" value="lisi"></property>
<property name="uage" value="30"></property>
<!-- 數組類型 -->
<property name="str">
<!--
使用array標籤聲明該屬性的值爲一個數組對象,value標籤表示一個基本類型的數組的元素
可以繼續嵌套array標籤使用,表示一個數組元素
-->
<array>
<value>aaa</value>
<value>bbb</value>
</array>
</property>
<!-- 集合類型 -->
<!-- list集合 -->
<property name="list">
<!--list標籤表明該屬性賦值爲list集合,使用value標籤賦值基本類型數據-->
<list>
<value>jjj</value>
<value>AAA</value>
</list>
</property>
<!-- map集合 -->
<property name="map">
<!-- 使用map子標籤標示該屬性的值爲map集合對象,使用entry標籤聲明初始化的鍵值對數據 -->
<map>
<entry key="a1" value="kkkQ"></entry>
<entry key="b2" value="2222"></entry>
</map>
</property>
</bean>
(四)Spring的DI依賴注入
ID(Dependency Injection),依賴注入:對象之間依賴關係由容器在運行期決定。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰來實現。
IOC和DI其實是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解爲容器控制對象的這一個層面,很難讓人想到誰來維護對象關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對於IOC而言,“依賴注入”明確描述了“被注入對象依賴IOC容器配置依賴對象”。
理解DI的關鍵是:“誰依賴誰,爲什麼需要依賴,誰注入誰,注入了什麼”。
①誰依賴誰:應用程序依賴於IOC容器
②爲什麼需要依賴:應用程序需要IOC容器來提供對象需要的外部資源
③誰注入了誰:IOC容器注入應用程序某個對象。
④注入了什麼:注入某個對象所需要的外部資源(對象、資源、常量數據等)
1.構造器注入之依賴注入
在Spring創建一個對象的同時給其引用類型的屬性賦值。在Spring容器的配置文件中使用bean標籤配置被管理的對象,在bean標籤中使用constructor-arg標籤指明要調用的構造器。
<bean id="s" class="com.bjsxt.pojo.Student">
<property name="sname" value="李四"></property>
</bean>
<bean id="user" class="com.bjsxt.pojo.User">
<constructor-arg index="0" name="uid" type="int" value="1"></constructor-arg>
<constructor-arg index="1" name="name" type="String" value="王五"></constructor-arg>
<constructor-arg index="2" name="uage" type="int" value="18"></constructor-arg>
<constructor-arg index="3" name="s" type="com.bjsxt.pojo.Student" ref="s"></constructor-arg>
</bean>
2.屬性注入之依賴注入
在bean標籤下使用property標籤,但是用其ref屬性,屬性值爲要注入的bean對象的ID
<bean id="s2" class="com.bjsxt.pojo.Student">
<property name="sname" value="德瑪西亞"></property>
</bean>
<bean id="user2" class="com.bjsxt.pojo.User">
<property name="uid" value="2"></property>
<property name="name" value="賞金獵人"></property>
<property name="uage" value="25"></property>
<property name="s" ref="s2"></property>
</bean>
3.Spring的自動注入
在Spring的配置文件中使用依賴注入時,如果要被注入的bean的ID和注入的bean的屬性名相同,我們仍然需要在配置文件中聲明注入方式,比較麻煩。解決:自動注入(自動注入是針對依賴注入的)。
設置bean標籤的屬性autowire的值爲以下其中一個:byName、byType、constructor、default、no
<!-- byName:通過屬性名和要注入的Bean的id相同的規則 -->
<!-- 配置學生bean -->
<bean id="s1" class="com.bjsxt.pojo.Student" autowire="byName"></bean>
<!--配置教師bean -->
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<!-- byType:通過屬性名和要注入的bean的類型相同的規則 -->
<!-- 配置學生bean -->
<bean id="s2" class="com.bjsxt.pojo.Student" autowire="byType"></bean>
<!--配置教師bean -->
<bean id="t2" class="com.bjsxt.pojo.Teacher"></bean>
<!-- constructor:根據構造器的形參的類型和要注入的bean的類型相同的規則注入 -->
<!-- 配置學生bean -->
<bean id="s3" class="com.bjsxt.pojo.Student" autowire="constructor"></bean>
<!--配置教師bean -->
<bean id="t3" class="com.bjsxt.pojo.Teacher"></bean>
<!-- default:使用全局的注入配置,在頂層標籤beans中使用屬性default-autowire聲明全局自動注入方式-->
<!-- 配置學生bean -->
<bean id="s4" class="com.bjsxt.pojo.Student" autowire="default"></bean>
<!--配置教師bean -->
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<!-- no:不使用自動注入,使用手動方式的依賴注入,也就是使用property標籤方式 -->
(五)SpringIOC的Scope屬性
Spring容器在被創建的時候就完成了對其配置的所有的對象的初始化創建,所以連續獲取同一個bean對象,返回的是同一個對象。但是如果我們想獲取不同的怎麼辦?讓Spring容器在我們每次獲取對象的時候都重新創建。設置bean標籤的scope屬性。
singleton:默認值,表示在Spring容器被創建的時候完成對象的初始化創建,連續多次獲取都返回同一個對象。
prototype:表示該bean在Spring容器被創建的時候不需要初始化創建,每次獲取的時候都重新創建並返回。
request:每次請求重新創建。
session:每個會話創建。
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-autowire="byName"
>
<!--配置Student的bean -->
<!--單例的 -->
<bean id="s" class="com.bjsxt.pojo.Student" scope="singleton"></bean>
<!--多例的 -->
<bean id="s2" class="com.bjsxt.pojo.Student" scope="prototype"></bean>
</beans>
(六)Spring對於Mybatis的整合案列
1.service層和mapper層的解耦
問題一:傳統方式是在service層直接通過mybatis對象完成數據的操作,業務層和mapper層耦合性非常高
解決一:使用SpringIOC將service層和Mapper層進行解耦
public UserServiceImpl implements UserService{
private UserMapper userMapper;
public void login(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
userMapper= (userMapper) ac.getBean("userMapper");
}
}
<!-- 配置mapper掃描bean -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<!--
mapper掃描會掃描com.bjsxt.mapper下的全部mapper.xml文件
並根據相應的mapper接口創建接口對象,存儲在spring容器中
mapper對象的健名爲mapper接口名首字母小寫
-->
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
2.servlet層和service層的解耦
問題二:如果Servlet層中直接new創建業務層對象,雖然可以正常使用,但是會造成servlet層和業務層的耦合性較高,不易於後期的迭代升級
解決二:使用SpringIOC將servlet層和service層進行解耦
public class UserServlet extends HttpServlet{
UserService userService;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
userService = (UserService) ac.getBean("userService");
}
}
<bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl"></bean>
3.Spring容器的合理創建方式
問題三:在以上兩個問題的代碼中,我們在Servlet中創建了一次Spring容器對象,在Service中又創建了一次Spring容器對象,Spring容器對象在一個線程中會被創建兩次,這樣造成在高併發環境中佔據的內存過多。
解決三:
⑴在service層中我們使用Spring容器對象獲取Mapper接口對象,Mapper接口對象本身就在Spring容器中,但是我們把Service也配置成bean了,那麼是不是可以使用依賴注入呢。直接在Spring的配置文件中通過依賴注入將Mapper對象注入給service對象。
⑵使用init方法,當服務器啓動時就創建Spring容器對象,當請求到來時,所有配置成bean的對象都已經生成好了,可以大大提高效率,而不用等請求到來時再創建。實現這個方式,除了將創建Spring容器的代碼寫到init方法外,還需要web.xml文件中,配置<load-on-startup>1</load-on-startup>。(load-on-startup元素標記容器是否在啓動的時候就加載這個servlet(實例化並調用其init()方法)。其中的參數1爲優先級,而非啓動延遲時間)。
public class UserServiceImpl implements UserService{
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper umuserMapper{
this.userMapper= userMapper;
}
@Override
public void login(){}
}
public class UserServlet extends HttpServlet{
private UserService userService;
@Override
public void init() throws ServletException{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
<!-- 配置Servlet -->
<servlet>
<servlet-name>user</servlet-name>
<servlet-class>com.bjsxt.servlet.UserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>user</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
4.Spring配置文件路徑與代碼的解耦
問題四:Spring容器的獲取路徑在init方法中與代碼耦合性較高
解決四:將Spring容器相關路徑信息配置到web.xml中
public class UserServlet extends HttpServlet{
UserService userService;
@Override
public void init() throws ServletException{
//優化方式
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
userService = (UserService) ac.getBean("userService");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
<!--配置Spring文件路徑 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext.xml</param-value>
</context-param>
<!--配置監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
5.整合代碼
(1)所需jar包
asm-3.3.1.jar
cglib-2.2.2.jar
commons-logging-1.1.1.jar
commons-logging-1.1.3.jar
gson-2.2.4.jar
javassist-3.17.1-GA.jar
jstl-1.2.jar
log4j-1.2.17.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
mybatis-spring-1.2.3.jar
mysql-connector-java-5.1.30.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
spring-aop-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
spring-jdbc-4.1.6.RELEASE.jar
spring-tx-4.1.6.RELEASE.jar
spring-web-4.1.6.RELEASE.jar
standard-1.1.2.jar
(2)配置applicationcontext.xml
通過配置文件說明哪些對象需要讓Spring容器幫組我們進行創建和解耦。data:配置數據源bean,存儲數據連接相關參數的對象。factory:配置工程bean,用來生產SqlSession對象的。mapper:配置mapper掃描,使用工廠對象生產SqlSession並完成對mapper包的掃描。service:配置業務層bean,實現業務層和servlet層的解耦。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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
">
<!-- 配置數據源bean -->
<bean id="data" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--
在spring的配置文件中,data的屬性數據庫驅動與我們以前在db配置文件中的健名不同
在Spring中,是driverClassName,而不是driver。
-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="bjsxt"></property>
</bean>
<!-- 配置工廠bean -->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="data"></property>
</bean>
<!-- 配置mapper掃描bean -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<!--
mapper掃描會掃描com.bjsxt.mapper下的全部mapper.xml文件
並根據相應的mapper接口創建接口對象,存儲在spring容器中。
-->
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
<!-- 配置業務層bean -->
<bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl">
<!--
前面mapper將mapper對象創建完成,在這裏使用屬性依賴注入
給userServvice中的屬性userMapper賦值
mapper掃描出來的mapper對象的鍵值爲,接口名首字母小寫
-->
<property name="userMapper" ref="userMapper"></property>
</bean>
</beans>
(3)配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
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>
<!--配置監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置Servlet -->
<servlet>
<servlet-name>user</servlet-name>
<servlet-class>com.bjsxt.servlet.UserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>user</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
</web-app>
(4)在servlet中創建Spring容器對象
public class UserServlet extends HttpServlet{
UserService userService;
@Override
public void init() throws ServletException{
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
userService = (UserService) ac.getBean("userService");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
(5)創建service接口和service實現類
public interface UserService {
void login();
}
public class UserServiceImpl implements UserService{
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper umuserMapper{
this.userMapper= userMapper;
}
@Override
public void login(){
userMapper.login(name,pwd);
}
}
(6)創建mapper接口和mapper.xml
public interface UserMapper {
User login(String uname,String pwd);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjsxt.mapper.UserMapper">
<!-- 用戶登錄SQL配置 -->
<select id="selUser" resultType="com.bjsxt.pojo.User">
select * from t_user where uname=#{0} and pwd=#{1}
</select>
</mapper>
二、SpringAOP
(一)AOP
AOP(Aspect Oriented Programing,面向切面編程),可以說是OOP(Object Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行爲的一個集合。當我們需要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力,也就是說,OOP允許你定義從上到下的管理,但並不適合定義從左到右的管理。例如日誌功能,日誌代碼往往水平地散佈在所有對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(crosscutting)代碼,在OOP設計中,它導致了大量代碼冗餘,而不利於各個模塊的重用。
而AOP技術則恰恰相反,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲"Aspect",即切面。所謂切面,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係,如果說"對象"是一個空心的圓柱體,其中封裝的是對象的屬性和行爲,那麼面向方便編程的方法,就彷彿一把利刃,將這些空心圓柱體拋開,以獲得其內部的消息。而剖開的切面,也就是所謂的切面了。然後它又以巧奪天工的妙手將這些剖開的切面復原,不留痕跡。
使用橫切技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處都基本相似。比如權限認證、日誌、事務處理。AOP的作用在於分離系統中的各個關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案架構師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯與對其提供支持的通用服務進行分離”。
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行,也被稱爲運行時增強;二是採用靜態織入的方式,引入特定的語法創建切面,從而使得編譯器可以在編譯期間織入有關切面的代碼,也稱爲編譯時增強。
切面(Aspect):一個關注點的模塊化,這個關注點實現可能另外橫切多個對象。事務管理是J2EE應用中一個很好的橫切關注點例子。切面用Spring的Advisor攔截器實現。
連接點(Joinpoint):程序執行過程中明確的點,如方法的調用或特定的異常被拋出。
通知(Adivce):在特定的連接點,AOP框架執行的動作。許多AOP框架包括Spring都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈。Spring中定義了四個advice:BeforAdvice,AfterAdvice,ThrowAdvice和DynamicIntroductionAdiv-ce。
切點(Pointcut):指定一個通知將被引發的一系列連接點的集合(也就是被增強的方法)。AOP框架必須允許開發者指定切點。例如,使用正則表達式。Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解,MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上。
引入(Introduction):添加方法或字段到被通知的類。Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現IsModified接口,來簡化緩存。Spring中要使用Introduction,可以通過DelegatingIntroductionInterce-ptor來實現通知,通過DefaultIntrodutionAdvisor來配置Advice和代理類要實現的接口。
目標對象(Target Object):包含連接點的對象(真實對象)。也被稱作被通知或被代理對象。POJO
AOP代理(AOP Proxy):AOP框架創建的對象(代理對象),包含通知。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
織入(Weaving):組裝切面來創建一個被通知對象的過程。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純JavaAOP框架一樣,在運行時完成織入。
AOP使用場景:AOP用來封裝橫切關注點,具體可以在下面的場景中使用:Authentication權限、Caching緩存、Context passing內容傳遞、Error handling錯誤處理、Lazy loading懶加載、Debugging調試、logging tracing profiling and monitoring記錄跟蹤優化校準、Performance optimization性能優化、Persistence持久化、Resource pooling資源池、Synchronization同步、Transactions事務。
(二)代理模式
在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種類型的設計模式屬於結構型模式。在代理模式中,我們創建具有現有對象的對象,並執行現有對象的相關方法,以便向外界提供功能接口。一般代理模式主要解決直接訪問對象不合適的情形。比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由於某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層,即使用代理模式。
代理模式的應用實例:windows裏面的快捷方式,spring aop,遠程代理,虛擬代理,防火牆代理等。
代理是一種常用的設計模式,其目的就是爲其他對象提供一個代理以控制對某個對象的訪問。代理類負責爲委託類預處理消息,過濾消息並轉發消息,以及進行消息被委託類執行後的後續處理。
在代理設計模式中,雖然可以實現在不修改真實方法的源碼基礎上完成功能的擴展,但是我需要改變調用方式由直接調用真實對象的真實方法,變成調用代理對象的代理方法。爲了方便我們進行替換,一般我們會將代理方法和真實方法設計得方法名和形參和返回值一樣,爲了規範代碼的編寫,由如下兩種實現:
(1)真實對象和代理對象實現相同的接口
(2)代理對象繼承真實對象。
1.動態代理相關類的介紹
在java的動態代理機制中,有兩個重要的類和接口。一個是InvocationHandler(Interface)、另一個則是Proxy(Class),這一個類和接口是實現動態代理所必須用到的。轉至https://blog.csdn.net/xiaokang123456kao/article/details/77679848
(1)InvocationHandler
每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯了一個handler,當我們動態代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由InvocationHandler這個接口的invoke方法來進行調用:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ }
proxy:指代JDK動態生成的最終代理對象
method:指代的是我們所需調用真實對象方法的Method對象
args:指代的是調用真實對象方法時接收的參數
(2)Proxy
Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
loader:一個ClassLoader對象,定義了由哪個ClassLoader來對生成的代理對象進行加載
interfaces:一個Interfaces對象的數組,表示的是我將要給我需要代理的對象提供一組聲明接口,如果我提供了一組接口給他,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHand-ler對象上
其實我們所說的DynamicProxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interfce給它,然後該class就宣稱它實現了這些interface。如此一來,我們可以把該class的實例當做這些interface中的任何一個來用。當然,這個DynaicProxy其實就是一個Proxy,它不會做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管時機的工作。
2.基於接口的JDK動態代理
//需要動態代理的接口
public interface Subject {
public void hello(String name);
public String bye();
}
//被代理類
public class RealSubject implements Subject{
@Override
public void hello(String name) {
System.out.println("hello "+name);
}
@Override
public String bye() {
System.out.println("bye");
return "bye";
}
}
//每次生成動態代理類對象時都需要指定一個實現了InvocationHandler接口的調用處理器對象
public class InvocationHandlerImpl implements InvocationHandler {
private Object subject; // 這個就是我們要代理的真實對象,也就是真正執行業務邏輯的類
public InvocationHandlerImpl(Object subject) {// 通過構造方法傳入這個被代理對象
this.subject = subject;
}
/**
* 該方法負責集中處理動態代理類上的所有方法調用
* 調用處理器根據這三個參數進行預處理或分派到委託類實例上反射執行
* @param proxy 最終生成的代理類實例
* @param method 被調用的方法對象
* @param args 調用上面method時傳入的參數
* @return method對應的方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("可以在調用實際方法前做一些事情");
System.out.println("當前調用的方法是" + method.getName());
// 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
result = method.invoke(subject, args);// 需要指定被代理對象和傳入參數
System.out.println(method.getName() + "方法的返回值是" + result);
System.out.println("可以在調用實際方法後做一些事情");
return result;// 返回method方法執行後的返回值
}
}
public class Test {
public static void main(String[] args) {
// 被代理的對象
Subject realSubject = new RealSubject();
/**
* 通過InvocationHandlerImpl的構造器生成一個InvocationHandler對象,
* 需要傳入被代理對象作爲參數
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
// 需要指定類裝載器、一組接口及調用處理器生成動態代理類實例
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("動態代理對象的類型:" + subject.getClass().getName());
subject.hello("Tom");
subject.bye();
}
}
3.基於繼承的CGLIB動態代理
前面我們詳細的介紹了JDK自身的AOP所繫統的一種動態代理的實現,它的實現相對而言是簡單的,但是卻有一個非常致命性的缺陷,就是隻能爲接口中的方法完成代理,而委託類自己的方法或者父類中的方法都不可能被代理。
CGLB應運而生,它是一個高性能的,底層基於ASM框架的一個代碼生成框架,它完美的解決了JDK版本的動態代理只能爲藉口方法代理的單一性不足問題。
//父類
public class Father{
public void sayHello(){
System.out.println("this is father");
}
}
//父接口
public interface Rerson{
void speack();
void run();
}
//委託類
public class Student extends Father implements Person{
public void study(){
System.out.println("i can study,,");
}
@Override
public void speak(){
System.out.println("i am a student,i can speak..");
}
@Override
public void run(){
System.out.println("i am a student,i can run..");
}
}
public class MyMethodInterceptor implements MethodInterceptor{
public Object intercept(Object obj,Method method,Object[] arg,MethodInterceptor proxy){
System.out.println("Before:"+method);
Object object = proxy.invokeSuper(obj,arg);
System.out.println("After:"+method);
return object;
}
}
4.Spring底層的代理模式
SpringAOP默認的代理模式爲JDK動態代理,可以在Spring的配置文件中配置代理模式爲cglib代理。
<!--設置代理模式爲Cglib -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
如果使用默認的JDK代理,則獲取切點bean的時候使用接口來接受。如果設置成爲Cglib代理,則實現真實對象類型來接收代理對象。
public class test{
public static void main(String[] args){
//Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
//Object object = proxy.invokeSuper(obj,arg);
}
}
(三)SpringAOP
1.基於Schem-based的AOP
(1)需要導入的jar包
aopalliance.jar
aspectjweaver.jar
commons-logging-1.1.3.jar
spring-aop-4.1.6.RELEASE.jar
spring-aspects-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
(2)創建Person實體類
public class Person {
public String pDemo(){
//int i=5/0;
System.out.println("Person.pDemo()");
return "abc";
}
}
(3)創建通知對象
①前置通知,實現MethodBeforAdvice,重寫befor(真實方法對象,代理方法實參數組,真實對象)
public class MyBefore implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("我是前置通知");
}
}
②後置通知,實現AfterReterningAdvice,重寫afterReturning(真實方法的返回值,真實方法對象,代理方法實參數組,真實對象)
public class MyAfter implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("我是後置通知:"+arg0);
}
}
③環繞通知,實現MethodInterceptor,重寫invoke(封裝了:真實方法對象,代理方法實參數組,真實對象,放行)
/**
* 注意:
* 需要放行,如果不放行則會造成真實方法沒有執行。
* 環繞通知的聲明在applicationcontext.xml中和配置順序相關。
*/
public class MyRound implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("環繞前");
//放行
Object obj = arg0.proceed();
System.out.println("環繞後:"+obj);
return null;
}
}
④異常通知:實現了ThrowAdvice,重寫afterThrowing(拋出異常的類型)
public class MyThrow implements ThrowsAdvice{
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("我是異常通知");
}
}
(4)配置applicationcontext.xml
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置數據源bean -->
<!-- 配置工廠bean -->
<!-- 配置mapperbean -->
<!-- 配置業務層bean -->
<!-- Schema-based方式 -->
<!-- 配置切點bean -->
<bean id="persion" class="com.bjsxt.pojo.Person"></bean>
<!-- 配置前置通知bean -->
<bean id="before" class="com.bjsxt.advice.MyBefore"></bean>
<!-- 配置後置通知bean -->
<bean id="after" class="com.bjsxt.advice.MyAfter"></bean>
<!-- 配置環繞通知bean -->
<bean id="round" class="com.bjsxt.advice.MyRound"></bean>
<!-- 配置異常通知 -->
<bean id="throw" class="com.bjsxt.advice.MyThrow"></bean>
<!-- 聲明切面 -->
<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.*.Person.pDemo(..))" id="mp" /><!-- 聲明切點 -->
<!-- 環繞通知 -->
<aop:advisor advice-ref="round" pointcut-ref="mp" />
<!-- 前置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="mp" />
<!-- 後置通知 -->
<aop:advisor advice-ref="after" pointcut-ref="mp" />
<!-- 異常通知 -->
<aop:advisor advice-ref="throw" pointcut-ref="mp" />
</aop:config>
</beans>
(5)執行
public class Test {
public static void main(String[] args) {
//SpringAOP-SchemaBased方式:
//獲取Spring容器對象
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationcontext.xml");
//獲取對象
Person p=(Person) ac.getBean("person");
p.pDemo();
}
}
2.基於AspectJ的AOP
(3)創建通知對象
public class MyAdvice {
//前置方法
public void before(String name,int age){
System.out.println("我是前置通知"+name+age);
}
//後置方法
public void after(){
System.out.println("我是後置通知");
}
//環繞通知
public Object myRound(ProceedingJoinPoint p) throws Throwable{
System.out.println("環繞前");
Object obj = p.proceed();
System.out.println("環繞後");
return obj;
}
//異常通知
public void myThrow(){
System.out.println("我是異常通知");
}
}
(4)配置applicationcontext.xml
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- AspectJ方式 -->
<!-- 配置真實對象bean -->
<bean id="persion" class="com.bjsxt.pojo.Person"></bean>
<!-- 配置通知bean -->
<bean id="advice" class="com.bjsxt.advice.MyAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置AspectJ的切面 -->
<aop:aspect ref="advice">
<!-- 配置切點 -->
<aop:pointcut expression="execution(* com.bjsxt.pojo.Person.pDemo(String,int)) and args(name,age)" id="mp">
<!-- 前置通知 -->
<aop:before method="before" arg-names="name,age" pointcut-ref="mp">
<!-- 後置通知:是否出現異常都會執行 -->
<aop:after method="after" pointcut-ref="mp"/>
<!-- 後置通知:出現異常不執行 -->
<aop:after-returning method="after" pointcut-ref="mp"/>
<!-- 環繞通知 -->
<aop:around method="myRound" pointcut-ref="mp"/>
<!-- 異常通知 -->
<aop:after-throwing method="myThrow" pointcut-ref="mp"/>
</aop:aspect>
</aop:config>
</beans>
3.基於AspectJ註解的AOP
註解的配置是基於AspectJ的,也就是我們可以使用註解來替換AspectJ在applicationcontext.xml中的配置信息。
(1)需要在applicaitoncontext.xml文件中聲明註解掃描,設置代理模式爲cglib,作用:告訴Spring容器哪些位置使用了註解。
(2)在真實對象上使用註解:@Compent在類上聲明,相當於bean標籤。@Pointcut在切面方法上聲明,表明該方法爲切點
(3)在通知類上使用直接:@Compent在類上聲明,相當於bean標籤。@Aspect在類上聲明,表明該類的對象爲通知對象,用來進行功能擴展的。@Befor、@After、@Around、@AfterThrowing。
(3)創建通知對象
@Component
@Aspect
public class MyAdvice {
//前置方法
@Before("com.bjsxt.pojo.Person.pDemo()")
public void before(){
System.out.println("我是前置通知");
}
//後置方法
@After("com.bjsxt.pojo.Person.pDemo()")
public void after(){
System.out.println("我是後置通知");
}
//環繞通知
@Around("com.bjsxt.pojo.Person.pDemo()")
public Object myRound(ProceedingJoinPoint p) throws Throwable{
System.out.println("環繞前");
Object obj = p.proceed();
return obj;
}
//異常通知
@AfterThrowing("com.bjsxt.pojo.Person.pDemo()")
public void myThrow(){
System.out.println("我是異常通知");
}
}
(4)配置applicationcontext.xml
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 註解方式 -->
<!--聲明註解掃描 -->
<context:component-scan base-package="com.bjsxt.advice,com.bjsxt.pojo"></context:component-scan>
<!--設置代理模式 -->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
</beans>
三、Spring加載properties文件的兩種方式
問題:在Spring的配置文件中數據源的相關參數是直接配置在裏面的,這樣造成數據源的參數和Spring配置文件之間耦合過高,造成Spring的配置文件的通用性降低。
解決:使用Spring加載外部properties文件讀取value值
實現:①創建屬性配置文件db.properties。②在Spring配置文件中聲明屬性文件掃描。③在bean標籤的屬性注入中使用${屬性文件健名}的方式獲取數據注入給bean。
(一)通過xml方式加載properties文件
使用context:property-placeholder標籤:
<!-- 掃描db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- spring啓動時則會去db.properties中找 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
使用PropertyPlaceholderConfigurer對象:用<bean>標籤來代替<context:property-placeholder location=" " />,可讀性更強:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!--
PropertyPlaceholderConfigurer類中有個locations屬性
接收的是一個數組,即我們可以在下面配好多個properties文件
以上方式只能加載單個properties文件
-->
<property name="locations">
<array>
<value>classpath:conn.properties</value>
</array>
</property>
</bean>
注意:屬性配置文件掃描注入級別低於依賴注入,屬性注入低於屬性配置文件掃描注入,我們需要將Factory注入Mapper的級別設置成屬性注入級別。
<!-- 以前的注入方式 -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
<!-- 換成如下方式 降低factory的注入級別 -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
(二)通過註解方式加載properties文件
在java代碼中使用Value註解來加載配置文件中的值,需要現在applicationcontext.xml中做如下配置:
<!-- 第二種方式是使用註解的方式注入,主要用在java代碼中使用註解注入properties文件中相應的value值 -->
<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<!-- 這裏是PropertiesFactoryBean類,它也有個locations屬性,也是接收一個數組,跟上面一樣 -->
<property name="locations">
<array>
<value>classpath:myProperty.properties</value>
</array>
</property>
</bean>
在java代碼中使用直接來獲取值:@Value表示去applicationcontext.xml文件中找id="prop"的bean,它是通過註解的方式讀取properties配置文件的,然後去相應的配置文件中讀取key=filePath的對應的value值。注意要有set方法才能被注入進來,註解寫在set方法上即可。
public class FileUploadUtil implements FileUpload {
private String filePath;
@Value("#{prop.filePath}")
public void setFilePath(String filePath) {
System.out.println(filePath);
}
}
總結:以上就是Spring加載properties配置文件的兩種方式。實際上,上面基於xml方式中的PropertyPlaceholderCon-figur-er類和這裏基於註解方式的PropertiesFactoryBean類都是繼承PropertiesLoaderSupport,都是用來加載properties配置文件的。
四、Spring的註解
註解的作用:替換配置文件的配置信息,提升開發效率。使用註解可以達到和配置文件相同的效果。Spring的註解的使用:①必須在Spring配置文件中聲明註解掃描。使用註解替換XML配置。
(一)bean註解
@compent:聲明在普通類上,相當於配置爲bean對象,一般用在實體類上。
@service:聲明在業務層對象上,相當於配置爲bean對象。
@controller:聲明在控制器類上,相當於配置爲bean對象。
注意:默認類名首字母小寫爲註解方式聲明的bean對象的ID
(二)依賴注入註解
@AutoWire:聲明在要被依賴注入的引用類型的屬性上,默認使用byType方式注入。
@Resuorce:聲明在要被依賴注入的引用類型的屬性上,先使用byName,然後使用byType
(三)屬性文件注入註解(properties)
@value():聲明在實體類的屬性上。
@Value("${user.uname}")
private String uname;
五、Spring的事務管理
(一)事務
1.事務的概念
什麼是事務?事務是邏輯上的一組操作,要麼都執行,要麼都不執行。
事務的特性(ACID):
- 原子性:事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要不完全不起作用
- 一致性:執行事務前後,數據保持一致
- 隔離性:併發訪問數據庫時,一個用戶的事務不被其他事務所幹擾,各併發事務之間數據庫是獨立的
- 持久性:一個事務被提交之後,它對數據庫中的數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響
2.Spring事務管理的相關接口
- PlatformTransactionManager:(平臺)事務管理器
- TransactionDefinition:事務定義信息(事務隔離級別、傳播行爲、超時、只讀、回滾規則)
- TransactionStatus:事務運行狀態
所謂事務管理,其實就是“按照給定的事務規則來執行提交或者回滾操作”。
(1)PlatformTransactionManager接口介紹
Spring並不直接管理事務,而是提供了多種事務管理器。他們將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。Spring事務管理器的接口是:org.springframework.transaction.PlatformTransactionM-anager通過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。
接口中定了三個方法:
Public interface PlatformTransactionManager()...{
// Return a currently active transaction or create a new one, according to the specified propagation behavior
//(根據指定的傳播行爲,返回當前活動的事務或創建一個新事務。)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// Commit the given transaction, with regard to its status(使用事務目前的狀態提交事務)
Void commit(TransactionStatus status) throws TransactionException;
// Perform a rollback of the given transaction(對執行的事務進行回滾)
Void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager根據不同持久層框架所對應的接口去實現類,幾個比較常見的如下圖所示:
事務 | 說明 |
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或者iBatis進行持久化數據時使用 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用Hibernate3.0版本進行持久化數據時使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA進行數據持久化時使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用JTA實現來管理事務,在一個事務跨越多個資源時使用 |
比如我們在使用JDBC或者Mybatis進行數據持久化操作時,我們的xml配置通常如下:
<!-- 事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 數據源 -->
<property name="dataSource" ref="dataSource" />
</bean>
(2)TransactionDefinition接口介紹
事務管理器接口PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到一個事務,這個方法裏面的參數是TransactionDefinition,TransactionDefinition接口中定義了5個方法以及一些表示事務屬性的常量(隔離級別、傳播行爲、回滾規則、是否只讀、事務超時)。
public interface TransactionDefinition {
//返回事務的傳播行爲
int getPropagationBehavior();
//返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據
int getIsolationLevel();
//返回事務的名字
String getName();
//返回事務必須在多少秒內完成
int getTimeout();
//返回是否優化爲只讀事務。
boolean isReadOnly();
}
(3)TransactionStatus接口介紹
TransactionStatus接口用來記錄事務的狀態:該接口定義了一組方法,用來獲取或判斷事務的相應狀態信息。
PliatformTransactionManager.getTransaction()方法返回一個TransactionStatus對象。返回的TransactionStatus對象可能代表一個新的或已經存在的事務(如果在當前調用堆棧有一個符合條件的事務)。
public interface TransactionStatus{
//是否是新的事務
boolean isNewTransaction();
//是否有恢復點
boolean hasSavepoint();
//設置爲只回滾
void setRollbackOnly();
//是否爲只回滾
boolean isRollbackOnly();
//是否已完成
boolean isCompleted;
}
3.事務隔離級別
事務隔離級別定義了一個事務可能受其他併發事務影響的程度。在典型的應用程序中,多個事務併發進行,經常會操作相同的數據來完成各自的任務(多個用戶對同一數據進行操作),可能會導致以下的問題:
髒讀(Dirty read) |
當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。 |
丟失修改(Lost to modify) |
指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那麼在第一個事務中修改了這個數據後,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱爲丟失修改。 |
不可重複讀(Unrepeatableread) |
指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個十五中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這樣就發生了一個事務內兩次讀到的數據時不一樣的,因此稱爲不可重複讀。 |
幻讀(Phantom read) |
幻讀與不可重複讀類似。它發生在一個事務讀取了幾行數據,接着另一個併發事務插入了一些數據時,在隨後的查詢中,第一個事務就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。(不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。) |
TransactionDefinition接口中定義了五個表示隔離級別的常量:
TransactionDefinition.ISOLATION_DEFAULT:使用後端數據庫默認的隔離級別,Mysql默認採用的是REPEATABLE_READ隔離界別,Oracle默認採用的是READ_COMMITTED隔離界別 |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔離級別,運毒讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複 |
TransacitonDefinition.ISOLATION_READ_COMMITTED:允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生 |
TransactionDefinition.ISOLATION_REPEATALBLE_READ:對同一字段的多次讀取結果都是一致的,除非數據時被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生 |
TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。 |
4.事務傳播行爲
爲了解決業務層方法之間互相調用的事務問題。當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:
支持當前事務的情況: |
TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務,如果當前沒有事務,則創建一個新的事務 |
TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務,如果當前沒有事務,則以非事務的方式繼續運行 |
TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務,如果當前沒有事務,則拋出異常 |
不支持當前事務的情況: |
TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起 |
TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常 |
其他情況: |
TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行,如果當前沒有事務,則該取值等於TransactionDefinition.PROPAGATION_REQUIRED |
這裏需要指出的是,前面六種事務傳播行爲是Spring從EJB中引入的,他們共享相同的概念。而PROPAGATION_NEST-ED是Spring所特有的。以PROPAGATION_NESTED啓動的事務內嵌於外部事務中,此時,內嵌事務並不是一個獨立地事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。外部事務的回滾也會導致嵌套子事務的回滾。
5.事務超時屬性
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在TransactionDefinition中以int的值來表示超時時間,其單位是秒。
6.事務只讀屬性
事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如數據源、JMS資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌爲只讀的,以提高事務處理的性能。在TransactionDefinition 中以boolean類型來表示該事務是否只讀。
7.回滾規則
這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時纔會回滾,而在遇到檢查型異常時不會回滾。但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行時異常哪樣回滾。同樣,你還可以聲明事務遇到特定的異常不會滾,即使這些異常是運行期異常。
(二)Spring的編程式和聲明式事務
Spring支持兩種方式的事務管理:
(1)編程式事務管理:通過TransactionTemplate手動管理事務,實際應用中很少使用。
(2)使用XML配置聲明式事務:推薦使用(代碼侵入性最小),實際是通過AOP實現。
實現聲明式事務的四種方式:
(1)基於TransactionInterceptor的聲明式事務:Spring聲明式事務的基礎,通常也不建議使用這種方式,但是與前面一樣,瞭解這種方式對理解Spring聲明式事務有很大作用。
(2)基於TransactionProxyFactoryBean的聲明式事務:第一種方式的改進版本,簡化的配置文件的書寫,這是Spring早期推薦的聲明式事務管理方式,但是在Spring2.0中已經不推薦了。
(3)基於<tx>和<aop>命名空間的聲明式事務管理:目前推薦的方式,其最大特點是與SpringAOP結合緊密,可以充分利用切點表達式的強大支持,使的管理事務更加靈活。
(4)基於@Transactional的全註解方式:將聲明式事務管理簡化到了極致。開發人員只需在配置文件中加上一行啓用相關後處理Bean的配置,然後在需要實施事務管理的方法或者類上使用@Transactional指定事務規則即可實現事務管理,而且功能也不必其他方式遜色。
1.基於TransactionTemplate的編程式事務管理
①業務層方法:
public class OrdersService{
//注入dao層對象
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
//注入TransactionTemplate對象
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate){
this.transactionTemplate = transactionTemplata;
}
//業務邏輯,寫轉賬業務,調用dao的方法
public void accountMoney(){
tranactionTemplate.execute(new TranactionCallback<Object>(){
@Override
public Object doInTransaction(TransactionStatus status){
Object result = null;
try{
ordersDao.addMoney();
/*
* 轉賬業務中一個賬戶需要減少錢,另一個賬戶需要加錢
* 按照以往的方式,如果不做事務管理,如果從減錢業務到加錢業務之間出現了錯誤,那麼減錢業務執行成功
* 修改了數據,但是加錢業務卻沒有執行,那麼數據就會出錯
* 我們希望對這兩個業務進行統一管理,如果1個失敗則全回滾,所以需要用到事務管理
* 這裏我們製造一個0爲除數的異常使用TransactionStatus在catch中做回滾處理
*/
int i = 10/0;
oredersDao.reduceMoney();
}catch(Exception e){
status.setRollbackOnly();
result = false;
System.out.println("Transfer Error!");
}
return result;
}
});
}
}
②applicationcontext.xml配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--配置數據源-->
<!--配置工廠-->
<!--配置mapper掃描-->
<!--編程式事務管理-->
<!--配置事務管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務管理器模板-->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<!--注入真正進行事務管理的事務管理器,name必須爲transactionManager否則無法注入-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
<!--對象生成及屬性注入-->
<bean id="ordersSerivce" class="com.bjsxt.service.orderService">
<proeprty name="ordersDao" ref="ordersDao"></property>
<!--注入事務管理的模板-->
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
2.基於AspectJ<tx>和<aop>命名空間的聲明式事務管理
①業務層方法:
public class OrdersService{
private OrdersService ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
//業務邏輯,寫轉賬業務,調用dao的方法
public void sccountMoney(){
ordersDao.addMoney();
int i = 10/0;
ordersDao.reduceMoney();
}
}
②applicationcontext.xml配置文件
<!--配置數據源-->
<!--配置工廠-->
<!--配置mapper掃描-->
<!-- 第一步:配置事務管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步:配置事務增強 -->
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<!--做事務操作-->
<tx:attributes>
<!--
設置進行事務操作的方法匹配規則
propagetion:事務傳播行爲
isolation:事務隔離級別
read-only:是否只讀
rollback-for:發生哪些異常時回滾
timeout:事務過期時間
-->
<tx:method name="ins*" propagetion="REQUIRED" />
<tx:method name="up*" isolation="DEFAULT" />
<tx:method name="del*" rollback-for="" timeout="-1" />
<tx:method name="sel*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切面 -->
<aop:config>
<!-- 切點 -->
<aop:pointcut expression="execution(* com.*.service.OrderService.*(..))" id="poincut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="poincut" />
</aop:config>
3.基於@Transactional的全註解聲明式事務管理
①業務層方法:
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,timeout=-1)
public class OrderService{
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
public void accountMoney(){
ordersDao.addMoney();
int i = 10/0;
ordersDao.reduceMoney();
}
}
②applicationcontext.xml配置文件
<!--配置數據源-->
<!--配置工廠-->
<!--配置mapper掃描-->
<!--第一步:配置事務管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--第二步:開啓事務註解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
<!--第三步:在方法所在類上加註解-->
<!-注入對象->