Spring還可以這麼學–AOP
上一篇文章Spring還可以這麼學–IoC(控制反轉) / DI(依賴注入)理解
1. 什麼是AOP?
AOP(Aspect Oriented Programming),即面向切面編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。
2. AOP的作用?
利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。
首先我們來看沒有AOP時,如果我們要做日誌處理,就得在每個方法中添加
但大多數日誌處理代碼都是相同的,所以我們將日誌處理抽離成一個新的方法,但是儘管這樣,我們還得手動插入這些方法。
但這樣代碼的耦合度很高,當我們要更改這個功能時,就得一個個更改
使用AOP後
爲了在指定位置執行這些橫向的功能,需要知道指定的是什麼地方
例如上圖,方法級別的aop實現,在一個程序執行鏈條中,把method2稱爲切點,也就是說在method2執行時會執行橫切的功能,那麼是在method2之前還是之後呢,又是執行什麼呢?這些都由advice(通知)來指定。
Spring 方面可以使用下面提到的五種通知工作:
3. AOP的核心概念
- 橫切關注點:對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點
- 切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象
- 連接點(joinpoint):被攔截到的點,因爲Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器
- 切入點(pointcut):對連接點進行攔截的定義
- 通知(advice):所謂通知指的就是指攔截到連接點之後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類
- 目標對象:代理的目標對象
- 織入(weave):將切面應用到目標對象並導致代理對象創建的過程
- 引入(introduction):在不修改代碼的前提下,引入可以在運行期爲類動態地添加一些方法或字段
###4. 實現方式
####Spring AOP的XML實現方式
Employee.java文件
package com.wangc;
public class Employee {
private String name;
private int age;
public String getName() {
System.out.println("name = "+name);
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
System.out.println("age = "+age);
return age;
}
public void setAge(int age) {
this.age = age;
}
public void printThrowException() {
System.out.println("發生異常");
throw new IllegalArgumentException();
}
}
Logging.java文件
package com.wangc;
public class Logging {
//在一個方法執行之前,執行通知。
public void beforeAdvice(){
System.out.println("執行employee的方法之前執行.");
}
//在一個方法執行之後,不考慮其結果,執行通知。
public void afterAdvice(){
System.out.println("執行employee的方法之後執行.");
}
//在一個方法執行之後,只有在方法成功完成時,才能執行通知。
public void afterReturningAdvice(Object retVal){
System.out.println("返回:" + retVal.toString() );
}
//在一個方法執行之後,只有在方法退出拋出異常時,才能執行通知。
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("拋出了一個異常: " + ex.toString());
}
}
Beans.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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:config>
<!-- 聲明一個aspect,logging將被依賴注入 -->
<aop:aspect id="log" ref="logging">
<!-- 聲明一個切入點 ,該切入點將與 com.wangc 包下的Logging類的方法相匹配,這裏的“*”是通配符-->
<aop:pointcut id="all" expression="execution(* com.wangc.*.*(..))"/>
<!-- 定義一個前置通知 -->
<aop:before pointcut-ref="all" method="beforeAdvice"/>
<!-- 定義一個後置通知 -->
<aop:after pointcut-ref="all" method="afterAdvice"/>
<!-- 定義一個返回後通知 -->
<aop:after-returning pointcut-ref="all" returning="retVal" method="afterReturningAdvice"/>
<!-- 定義一個拋出異常後通知 -->
<aop:after-throwing pointcut-ref="all" throwing="ex" method="AfterThrowingAdvice"/>
</aop:aspect>
</aop:config>
<bean id="employee" class="com.wangc.Employee">
<property name="name" value="zhangsan" />
<property name="age" value="28" />
</bean>
<bean id="logging" class="com.wangc.Logging"/>
</beans>
MyApp.java文件
package com.wangc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Employee employee = (Employee) context.getBean("employee");
employee.getName();
employee.getAge();
employee.printThrowException();
}
}
執行結果:
執行employee的方法之前執行.
name = zhangsan
執行employee的方法之後執行.
返回:zhangsan
執行employee的方法之前執行.
age = 28
執行employee的方法之後執行.
返回:28
執行employee的方法之前執行.
發生異常
執行employee的方法之後執行.
拋出了一個異常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......
注意:上面的例子只有一個橫切關注點,如果有多個橫切關注點,可以使用aspect裏的order屬性來控制橫切關注點的順序。
......
<aop:config>
<aop:aspect id="log1" ref="logging1" order="1">
<aop:pointcut id="addTime" expression="execution(* com.wangc.*.*(..))"/>
<aop:before pointcut-ref="addTime" method="beforeAdvice"/>
</aop:aspect>
<aop:aspect id="log2" ref="logging2" order="2">
<aop:pointcut id="printLog" expression="execution(* com.wangc.*.*(..))"/>
<aop:before pointcut-ref="printLog" method="beforeAdvice"/>
</aop:aspect>
</aop:config>
......
Spring AOP 的 @AspectJ實現方式
這裏只需要在上面的基礎上修改以下兩個文件就可實現,修改後的文件如下:
Beans.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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 自動掃描被@Aspect標註的切面 -->
<aop:aspectj-autoproxy />
<bean id="employee" class="com.wangc.Employee">
<property name="name" value="zhangsan" />
<property name="age" value="28" />
</bean>
<bean id="logging" class="com.wangc.Logging"/>
</beans>
Logging.java文件
package com.wangc;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Logging {
@Pointcut("execution(* com.wangc.*.*(..))")
private void all() {
}
//在一個方法執行之前,執行通知。
@Before("all()")
public void beforeAdvice(){
System.out.println("執行employee的方法之前執行.");
}
//在一個方法執行之後,不考慮其結果,執行通知。
@After("all()")
public void afterAdvice(){
System.out.println("執行employee的方法之後執行.");
}
//在一個方法執行之後,只有在方法成功完成時,才能執行通知。
@AfterReturning(pointcut = "all()", returning="retVal")
public void afterReturningAdvice(Object retVal){
System.out.println("返回:" + retVal.toString() );
}
//在一個方法執行之後,只有在方法退出拋出異常時,才能執行通知。
@AfterThrowing(pointcut = "all()", throwing="ex")
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("拋出了一個異常: " + ex.toString());
}
}
這裏做個簡單的說明: 用@Aspect的註解來標識切面,注意不要把它漏了,否則Spring創建代理的時候會找不到它,@Pointcut註解指定了切點,@Before、@After、@AfterReturning和@AfterThrowing指定了運行時的通知。
運行結果:
執行employee的方法之前執行.
name = zhangsan
執行employee的方法之後執行.
返回:zhangsan
執行employee的方法之前執行.
age = 28
執行employee的方法之後執行.
返回:28
執行employee的方法之前執行.
發生異常
執行employee的方法之後執行.
拋出了一個異常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......