Spring AOP以及事務控制總結
AOP和OOP區別:
AOP和OOP定義:
AOP:(Aspect Oriented Programming)面向切面編程
OOP:(Object Oriented Programming )面向對象編程
面向切面編程:基於OOP基礎之上新的編程思想,指在程序運行期間,將某段代碼動態的切入到指定方法的指定位置進行運行的編程方式
JAVA動態代理基本語法
動態代理的本質是實現了與被代理類同一組接口的類,所以要保證代理類和被代理類實現了同一組接口
舉例:
//接口
public interface Calculatorable {
Integer add(int a,int b);
Integer sub(int a,int b);
}
//被代理類
public class Calculator implements Calculatorable {
public Integer add(int a, int b) {
System.out.println("加法...");
return a+b;
}
public Integer sub(int a, int b) {
System.out.println("減法...");
return a-b;
}
}
//代理類
public class CalculatorProxy {
public static Calculatorable getProxy(final Calculatorable cal){
InvocationHandler ih = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = null;
try {
System.out.println("方法執行前...");
o= method.invoke(cal, args);
System.out.println("方法執行後");
} catch (Exception e) {
System.out.println("方法有異常");
} finally {
System.out.println("finally語句塊...");
}
return o;
}
};
return (Calculatorable) Proxy.newProxyInstance(cal.getClass().getClassLoader(), cal.getClass().getInterfaces(), ih);
}
}
//測試
@Test
public void test01(){
Calculatorable p = CalculatorProxy.getProxy(new Calculator());
p.add(4, 5);
}
打印結果:
方法執行前...
加法...
方法執行後
finally語句塊...
動態代理的缺點:代碼量多,且必須要實現一組接口
Spring簡化面向切面編程
1.AOP基本概念
1)專業術語:
連接點:每一個方法的每一個位置
橫切關注點:每一個方法的同一位置下的連接點
切入點:需要真正執行切入的方法的位置
切入點表達式:通過表達式篩選切入點
通知方法:每一個橫切關注點下的方法
切面類:橫切關注點+通知方法
2.需要導入的包
Spring AOP需要導入的包:
spring核心包:
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar:(Spring支持面向切面編程的包)
加強版的面向切面編程,即不用實現接口也能實現動態代理(使用cglib)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
maven工程的pom配置(加強版):
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
3.註解配置AOP過程
spring實現AOP其實就是依靠切面類,實現一個切面類,切面類由通知方法和切面表達式構成
1.spring中共有五種通知:
@Before:前置通知,在方法執行之前執行
@After:後置通知,在方法執行之後執行
@AfterRunning:返回通知,在方法返回結果之後執行
@AfterThrowing:異常通知,在方法拋出異常之後執行
@Around:環繞通知,圍繞着方法執行
2.五種通知的執行順序
方法正常運行:
環繞前置–前置通知–目標方法–環繞返回–環繞後置–後置通知–返回通知
方法異常運行:
環繞前置–前置通知–目標方法–環繞異常–環繞後置–後置通知–異常通知
執行順序:
try{
環繞前置
[普通前置]
環繞執行:目標方法執行
環繞返回
}catch(){
環繞出現異常
}finally{
環繞後置
}
[普通後置]
[普通方法返回/方法異常]
總之,就是環繞通知要優先比其他的通知先執行,而且執行順序是正常的,即返回通知在後置通知之前執行
3.多切面類下通知的執行順序
與Filter一樣,先進來的切面類後出去,且各個切面類的環繞通知相互獨立運行,互不影響。即只會優先本類的其他通知,但並不會影響其他切面類的通知。
4.切面表達式寫法
固定格式: execution(訪問權限符 返回值類型 方法全類名(參數表))
通配符:
-
*:
1)匹配一個或者多個字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int) ----MyMath*r表示類名爲以mymath開頭,以r結尾 2)匹配任意一個參數:第一個是int類型,第二個參數任意類型;(匹配兩個參數) execution(public int com.atguigu.impl.MyMath*.*(int, *)) 3)只能匹配一層路徑 4)訪問權限位置不能寫*;權限位置不寫就行就默認表示public
-
…:
1)匹配任意多個參數,任意類型參數 2)匹配任意多層路徑: execution(public int com.atguigu..MyMath*.*(..));
-
&&”、“||”、“!
&&:我們要切入的位置滿足這兩個表達式
MyMathCalculator.add(int,double)
execution(public int com.atguigu…MyMath*.(…))&&execution( .(int,int))
||:滿足任意一個表達式即可
execution(public int com.atguigu…MyMath*.(…))&&execution( .(int,int))
!:只要不是這個位置都切入
!execution(public int com.atguigu…MyMath*.*(…))
注意:不能以…開頭,如果要寫成最模糊匹配,則可寫成execution(* *.*(…)):表示任意包下任意類,如果以*開頭叫表示多層任意路徑
5.示例
目標類:
@Service
public class MyCalculator /*implements MyCalculatorable 可寫可不寫 */ {
public Integer add(int a, int b) {
System.out.println("------加法運行了------");
return a+b;
}
public Integer sub(int a, int b) {
System.out.println("------減法運行了------");
return a-b;
}
}
切面類A:
@Order(1) //如果有多切面類,可以指定順序,值越小順序越優先
@Aspect //表明爲切面類
@Component
public class AopTestClass {
//切面表達式可重用:切面表達式同一寫在一個無返回值的方法上,用@Pointcut標註
@Pointcut("execution(Integer com.proxytest.MyCalculator.*(..))")
public void pointCut(){};
@Before("pointCut()")
public void logBefore(JoinPoint jp){
System.out.println(jp.getSignature().getName()+"方法"+"前置通知開始執行");
}
@After("pointCut()")
public void logAfter(JoinPoint jp){
System.out.println(jp.getSignature().getName()+"方法"+"後置通知開始執行");
}
//在通知方法中寫的參數spring要能識別,所以不能寫spring不能識別的參數
//JoinPoint:封裝了當前目標方法的詳細信息
//throwing:告訴Spring哪個參數是用來接收異常
//returning:告訴Spring這個result用來接收返回值
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void logAfterThrowing(JoinPoint jp,Exception ex){
System.out.println(jp.getSignature().getName()+"方法"+"異常通知開始執行");
}
@AfterReturning(value = "pointCut()",returning = "res")
public void logAfterReturning(JoinPoint jp,Integer res){
System.out.println(jp.getSignature().getName()+"方法"+"返回通知開始執行"+"返回值爲"+res);
}
//如果在環繞通知中就把異常捕獲而不拋出,外界是感受不到異常的,所以一般需要拋出,同理,如果方法有返回值,也需要return
//注意此處的返回值不能寫在finally語句中,否則外界還是會將感受不到異常
@Around("pointCut()")
public Object logAround(ProceedingJoinPoint jp) {
Object res = null;
try {
System.out.println(jp.getSignature().getName()+"方法"+"環繞通知的前置通知開始執行.....");
res = (Integer) jp.proceed(jp.getArgs());
System.out.println(jp.getSignature().getName()+"方法"+"環繞通知的返回通知開始執行.....");
return res;
} catch (Throwable ex) {
System.out.println(jp.getSignature().getName()+"方法"+"環繞通知的異常通知開始執行.....");
throw new RuntimeException(ex);
}finally {
System.out.println(jp.getSignature().getName()+"方法"+"環繞通知的後置通知開始執行.....");
}
}
}
切面類B:
@Order(2)
@Aspect
@Component
public class AopTest02Class {
@Pointcut("execution(Integer com.proxytest.MyCalculator.*(..))")
public void pointCut() {}
@Before("pointCut()")
public void logBefore(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class前置通知開始執行");
}
@After("pointCut()")
public void logAfter(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class後置通知開始執行");
}
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void logAfterThrowing(JoinPoint jp, Exception ex) {
System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class異常通知開始執行");
}
@AfterReturning(value = "pointCut()", returning = "res")
public void logAfterReturning(JoinPoint jp, Integer res) {
System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class返回通知開始執行" + "返回值爲" + res);
}
}
方法正常運行控制檯打印結果:
add方法環繞通知的前置通知開始執行.....
add方法前置通知開始執行
add方法AopTest02Class前置通知開始執行
------加法運行了------
add方法AopTest02Class後置通知開始執行
add方法AopTest02Class返回通知開始執行返回值爲11
add方法環繞通知的返回通知開始執行.....
add方法環繞通知的後置通知開始執行.....
add方法後置通知開始執行
add方法返回通知開始執行返回值爲11
方法異常運行控制檯打印結果:
add方法環繞通知的前置通知開始執行.....
add方法前置通知開始執行
add方法AopTest02Class前置通知開始執行
------加法運行了------
add方法AopTest02Class後置通知開始執行
add方法AopTest02Class異常通知開始執行
add方法環繞通知的異常通知開始執行.....
add方法環繞通知的後置通知開始執行.....
add方法後置通知開始執行
add方法異常通知開始執行
測試類:
@Test
public void test02(){
ApplicationContext ac= new ClassPathXmlApplicationContext("spring.xml");
MyCalculator bean = (MyCalculator)ac.getBean(MyCalculator.class);
Integer i = bean.add(5,6);
}
開始AOP註解配置:
<context:component-scan base-package="com.*"/>
<aop:aspectj-autoproxy/>
注意:
從ioc容器中拿到目標對象時:
1)如果實現了接口
如果用類類型獲取實例,一定用他的接口類型,不要用它的實現類,也可以通過真實對象的id來獲取
MyCalculatorablebean = ioc.getBean(MyCalculatorable.class);
MyCalculatorbean = (MyCalculator) ioc.getBean(“myCalculator”);
2)如果直接寫的目標類,則根據目標類類型和id名稱獲取對象都可
MyCalculatorbean = (MyCalculator) ioc.getBean(MyCalculator.class);
MyCalculatorbean = (MyCalculator) ioc.getBean(“myCalculator”);
4.AOPxml配置
<!-- 1.配置切面類和目標類的Bean-->
<bean id="myCalculator" class="com.proxytest.MyCalculator"></bean>
<bean id="aopTestClass" class="com.aoptest.AopTestClass"></bean>
<bean id="aopTest02Class" class="com.aoptest.AopTest02Class"></bean>
<!-- 2.引入AOP名稱空間,配置切入點表達式 -->
<aop:config>
<aop:pointcut expression="execution(Integer com.proxytest.MyCalculator.*(..))" id="globalPoint"/>
<!-- 普通前置 ===== 目標方法 =====(環繞執行後置/返回)====s普通後置====普通返回 -->
<!-- 指定切面:@Aspect -->
<aop:aspect ref="aopTestClass" order="1">
<!-- 配置哪個方法是前置通知;method指定方法名,logStart@Before("切入點表達式")-->
<!-- 如果切入點定義在當前切面類中,就只能當前切面能用 -->
<aop:pointcut expression="execution(Integer com.proxytest.MyCalculator.*(..))" id="mypoint"/>
<aop:around method="logAround" pointcut-ref="mypoint"/>
<aop:before method="logBefore" pointcut="execution(Integer com.proxytest.MyCalculator.*(..))"/>
<aop:after-returning method="logAfterReturning" pointcut-ref="mypoint" returning="res"/>
<aop:after-throwing method="logAfterThrowing" pointcut-ref="mypoint" throwing="ex"/>
<aop:after method="logAfter" pointcut-ref="mypoint"/>
</aop:aspect>
<aop:aspect ref="aopTest02Class" order="3">
<aop:before method="logBefore" pointcut-ref="globalPoint"/>
<aop:after-returning method="logAfterReturning" pointcut-ref="globalPoint" returning="res"/>
<aop:after-throwing method="logAfterThrowing" pointcut-ref="globalPoint" throwing="ex"/>
<aop:after method="logAfter" pointcut-ref="globalPoint"/>
</aop:aspect>
<!-- 在切面類中使用五個通知註解來配置切面中的這些通知方法都何時何地運行 -->
</aop:config>
tips:注意,在xml中配置,環繞前置執行的位置不一定會比普通前置要前面,這取決於aop:around和aop:before標籤的位置順序,但是環繞後置一定比普通後置先
Spring事物控制
編程式事務:
TransactionFilter{
try{
//獲取連接
//設置非自動 提交
chain.doFilter();
//提交
}catch(Exception e){
//回滾
}finllay{
//關閉連接釋放資源
}
}
聲明式事務:
事務管理代碼的固定模式作爲橫切關注點,可以通過AOP方法模塊化,進而藉助Spring AOP框架實現聲明式事務管理,這個切面類Spring已經實現,這個切面類稱爲事物管理器
1.事物管理器註解配置
因爲事物控制依賴於切面編程,所以之前AOP導入的包,事物控制也需要
maven的POM配置:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
1)事物管理器註解配置內容
<context:component-scan base-package="com.*"></context:component-scan>
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:pro.properties" />
<!-- 配置數據源-->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!-- 事務控制 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住數據源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--2:開啓基於註解的事務控制模式;依賴tx名稱空間 -->
<tx:annotation-driven transaction-manager="tm"/>
<!--3:給事務方法加註解@Transactional,在service類中的某個方法上加入-->
2)@Transactional屬性
-
isolation:事物的隔離級別
事物出現的幾個問題:髒讀、幻讀、不可重複讀
分別對應以下幾個隔離級別:
Isolation.READ_UNCOMMITTED:讀出髒數據
Isolation.READ_COMMITTED:解決髒讀
Isolation.REPEATABLE_READ:解決髒讀、幻讀
Isolation.SERIALIZABLE:都解決,但效率低,不常用 -
異常回滾機制
運行時異常(非檢查異常):可以不用處理;默認都回滾;
編譯時異常(檢查異常):要麼try-catch,要麼在方法上聲明throws,默認不回滾;
noRollbackFor:哪些異常事務可以不回滾,可以讓原來默認回滾的異常給他不回滾
noRollbackFor={ArithmeticException.class,NullPointerException.class}
noRollbackForClassName:String全類名:
rollbackFor:哪些異常事務需要回滾,原本不回滾(原本編譯時異常是不回滾的)的異常指定讓其回滾
rollbackForClassName:全類名 -
readOnly-boolean:設置事務爲只讀事務:可以進行事務優化;
readOnly=true:加快查詢速度;不用管事務那一堆操作了 -
timeout-int(秒爲單位):超時:事務超出指定執行時長後自動終止並回滾
-
propagation-Propagation:事務的傳播行爲如下表,常用爲required和required_new,默認事物都是required
Tips:
1.如果任何處崩,已經執行的REQUIRES_NEW都會成功;
2.如果子事物是REQUIRED,事務的屬性都是繼承於大事務的。即在子事物上設置的如超時時間等無效,只有在超事物上設置對子事物纔有影響;如果子事物是REQUIRES_NEW則可以自定義事物屬性
2.事物管理器XML配置
<context:component-scan base-package="com.*"></context:component-scan>
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:pro.properties" />
<!-- 配置數據源-->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!-- 事務控制 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住數據源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--
基於xml配置的事務;依賴tx名稱空間和aop名稱空間
1)、Spring中提供事務管理器(事務切面),配置這個事務管理器
2)、配置出事務方法的屬性;
3)、告訴Spring哪些方法是事務方法;
(事務切面按照我們的切入點表達式去切入事務方法)
-->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!-- 事務建議;事務增強 advice-ref:指向事務管理器的配置 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事務管理器:事務建議又稱事務增強;配置的是事務屬性;
transaction-manager="transactionManager":指定是配置哪個事務管理器;-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事務屬性 -->
<tx:attributes>
<!-- 指明哪些方法是事務方法;切入點表達式只是說,事務管理器要切入這些方法,哪些方法加事務使用tx:method指定的 -->
<tx:method name="*"/> <!--可以把所有的切入方法都指定爲事物-->
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/><!--還可以另外配置某些具體的方法-->
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
對於重要的事物/通知方法用XML配置,不重要的用註解配置