Spring中的AOP
本節內容:
- AOP介紹
- Spring底層AOP的實現原理
- Spring的AOP名詞
- Spring中的AOP開發
一、AOP介紹
1. 什麽是AOP
在軟件業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
Spring 是解決實際開發中的一些問題:
- AOP 解決 OOP 中遇到的一些問題.是 OOP 的延續和擴展。
【註意】:Spring是提供了對對象進行aop編程的支持,並不是說Spring本身就能aop。Spring作為對象的容器,它能夠幫為容器中管理的對象生成動態代理對象。以前生成動態代理對象需要自己寫代碼,而Spring能夠幫我們生成代理對象,只需要配置文件配置下或者使用註解。
aop思想:橫向重復,縱向抽取。
aop是個思想,在各個業務中、領域中都會體現,最早是在filter中,Servlet解決亂碼。沒學filter之前,解決亂碼比較煩,每次接收參數之前都要解決參數亂碼,於是每個Servlet中都要設置解決亂碼。當我們使用了filter之後,請求到達Servlet之前先會經過filter,於是我們將解決亂碼的代碼放到了filter中,於是所有Servlet中的解決亂碼的代碼都可以舍棄了,見下圖。從系統架構圖來說,Filter中解決亂碼這個事,等於代碼加到了所有的Servlet類上,這樣切面就形成了,所謂的面向切面編程。
動態代理也使用過這個aop思想:使用動態代理技術將這個抽取出來的handler加到所有的service中,形成代理對象。
還有個攔截器示例,也用到了aop思想:
2. 為什麽學習 AOP
對程序進行增強:不修改源碼的情況下
- AOP 可以進行權限校驗,日誌記錄,性能監控,事務控制。
3. Spring 的 AOP 的由來
AOP 最早由 AOP 聯盟的組織提出的,制定了一套規範。Spring 將 AOP 思想引入到框架中,必須遵守 AOP 聯盟 的規範。
4. 底層實現
代理機制:Spring 的 AOP 的底層用到兩種代理機制:
- JDK 的動態代理:針對實現了接口的類產生代理。
- Cglib 的動態代理:針對沒有實現接口的類產生代理。應用的是底層的字節碼增強的技術生成當前類的子類對象。
二、Spring底層AOP的實現原理
代理機制:Spring 的 AOP 的底層用到兩種代理機制:
- JDK 的動態代理:針對實現了接口的類產生代理。
- Cglib 的動態代理:針對沒有實現接口的類產生代理。應用的是底層的字節碼增強的技術生成當前類的子類對象。
動態代理在使用時有個局限性,被代理對象必須要實現接口才能產生代理對象。如果代理對象沒有接口,將不能使用動態代理技術。於是為了保證每一個對象都能生成代理對象,所以Spring融入了一個第三方代理技術:cglib代理。該代理技術對任何類生成代理,其代理的原理是對目標對象進行繼承代理。如果目標對象被final修飾,那麽該類無法被cglib代理。
那麽Spring采用哪一種代理方式生成代理對象呢?
混合的。如果代理對象有接口,優先使用動態代理。如果沒有接口,使用cglib代理。
【示例】:使用動態代理和cglib代理分別完成對類的代理。(把事務代碼插入到需要事務的地方)
動態代理代碼示例:
package com.wisedu.service; /** * Created by jkzhao on 12/12/17. */ public interface UserService { void save(); void delete(); void update(); void find(); }接口UserService.java
package com.wisedu.service; /** * Created by jkzhao on 12/12/17. */ public class UserServiceImpl implements UserService { @Override public void save() { //System.out.println("打開事務"); System.out.println("保存用戶"); //System.out.println("提交事務"); } @Override public void delete() { System.out.println("刪除用戶"); } @Override public void update() { System.out.println("更新用戶"); } @Override public void find() { System.out.println("查找用戶"); } }被代理對象UserServiceImpl.java
package com.wisedu.proxy; import com.wisedu.service.UserService; import com.wisedu.service.UserServiceImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Created by jkzhao on 12/12/17. */ public class UserServiceProxyFactory implements InvocationHandler{ public UserServiceProxyFactory(UserService us) { this.us = us; } private UserService us; public UserService getUserServiceFactory(){ //返回UserService的代理對象 //生成動態代理返回 UserService usProxy = (UserService)Proxy.newProxyInstance( UserServiceProxyFactory.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), this); return usProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("打開事務"); Object invoke = method.invoke(us, args); //原有方法的調用 System.out.println("提交事務"); return invoke; } }代理對象類UserServiceProxyFactory.java
package com.wisedu.proxy; import com.wisedu.service.UserService; import com.wisedu.service.UserServiceImpl; import org.junit.Test; /** * Created by jkzhao on 12/13/17. */ public class demo { @Test public void fun1(){ UserService us = new UserServiceImpl(); //創建被代理對象 UserServiceProxyFactory factory = new UserServiceProxyFactory(us); UserService usProxy = factory.getUserServiceFactory(); //代理對象 usProxy.save(); } }測試代碼demo.java
cglib代理代碼示例:(Spring整合了cglib,不需要在導入其他包)
package com.wisedu.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import com.wisedu.service.UserService; import com.wisedu.service.UserServiceImpl; //觀光代碼=>cglib代理(cglib主要是對類進行繼承代理) public class UserServiceProxyFactory2 implements MethodInterceptor { public UserService getUserServiceProxy(){ Enhancer en = new Enhancer();//幫我們生成代理對象 en.setSuperclass(UserServiceImpl.class);//設置對誰進行代理(cglib主要是對類進行繼承代理,得告訴它它爹是誰) en.setCallback(this);//代理要做什麽 UserService us = (UserService) en.create();//創建代理對象 return us; } @Override public Object intercept(Object prxoyobj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable { //打開事務 System.out.println("打開事務!"); //調用原有方法 Object returnValue = methodProxy.invokeSuper(prxoyobj, arg); //提交事務 System.out.println("提交事務!"); return returnValue; } }UserServiceProxyFactory2.java
@Test public void fun2(){ UserServiceProxyFactory2 factory = new UserServiceProxyFactory2(); UserService usProxy = factory.getUserServiceProxy(); //代理對象 usProxy.save(); System.out.println(usProxy instanceof UserServiceImpl); }測試方法
動態代理針對的是接口,而cglib代理是對被代理對象進行繼承。
但是這些代碼意義不大,將來在使用Spring時知道怎麽去配置,不會讓我們自己去手寫。
三、Spring的AOP名詞
- Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點。
- Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。
- Advice(通知/增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。通知分為前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
- Target(目標對象):代理的目標對象
- Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程。spring 采用動態代理織入,而 AspectJ 采用編譯期織入和類裝在期織入
- Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類
- Aspect(切面): 是切入點和通知(引介)的結合
四、Spring中的AOP開發
1. 導包
4+2+2+2:4個Spring核心包,2個日誌包,2(spring的aop包:spring-aop-4.2.4.RELEASE.jar和spring-aspects-4.2.4.RELEASE.jar),2(spring需要的第三方aop包:com.springsource.org.aopalliance-1.0.0.jar和com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar)
2. 準備目標對象
以上面示例中的 UserServiceImpl 作為目標對象。
package com.wisedu.service; /** * Created by jkzhao on 12/12/17. */ public class UserServiceImpl implements UserService { @Override public void save() { //System.out.println("打開事務"); System.out.println("保存用戶"); //System.out.println("提交事務"); } @Override public void delete() { System.out.println("刪除用戶"); } @Override public void update() { System.out.println("更新用戶"); } @Override public void find() { System.out.println("查找用戶"); } }UserServiceImpl.java
3. 定義通知(增強)
在Spring當中如何定義通知呢?使用方法來定義通知。先建一個包,com.wisedu.springaop,然後在包裏新建一個類,將通知放於類中。如下:
package com.wisedu.springaop; import org.aspectj.lang.ProceedingJoinPoint; //通知類(通知:增強的代碼) public class MyAdvice { //前置通知 // |-目標方法運行之前調用 //後置通知(如果出現異常不會調用) // |-在目標方法運行之後調用 //環繞通知 // |-在目標方法之前和之後都調用 //異常攔截通知 // |-如果出現異常,就會調用 //後置通知(無論是否出現異常都會調用) // |-在目標方法運行之後調用 //---------------------------------------------------------------- //前置通知 public void before(){ System.out.println("這是前置通知!!"); } //後置通知 public void afterReturning(){ System.out.println("這是後置通知(如果出現異常不會調用)!!"); } //環繞通知(最特殊的一個通知,要控制目標方法的調用,也就是需要自己手動調用目標方法,在目標方法之前和之後加增強的代碼) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("這是環繞通知之前的部分!!"); Object proceed = pjp.proceed();//調用目標方法 System.out.println("這是環繞通知之後的部分!!"); return proceed; } //異常通知 public void afterException(){ System.out.println("出事啦!出現異常了!!"); } //後置通知 public void after(){ System.out.println("這是後置通知(出現異常也會調用)!!"); } }MyAdvice.java
4. 配置將通知織入目標對象
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd "> <!-- 準備工作: 導入aop(約束)命名空間 --> <!-- 1.配置目標對象 --> <bean name="userService" class="com.wisedu.service.UserServiceImpl" ></bean> <!-- 2.配置通知對象 --> <bean name="myAdvice" class="com.wisedu.springaop.MyAdvice" ></bean> <!-- 3.配置將通知織入目標對象 --> <aop:config> <!-- 配置切入點(目標對象哪些方法要增強),表達式多種寫法: public void com.wisedu.service.UserServiceImpl.save() void com.wisedu.service.UserServiceImpl.save() 默認值就是針對public方法,public可以省略 * com.wisedu.service.UserServiceImpl.save() 對該類下的返回值不做要求的save方法,把void改為* * com.wisedu.service.UserServiceImpl.*() 對方法不要求,但是要求方法空參 * com.wisedu.service.UserServiceImpl.*(..) 對參數不做要求,沒有也行,有也行 * com.wisedu.service.*ServiceImpl.*(..) 對類不做要求,尋找service包下以ServiceImpl結尾的類中的所有方法。貼近現實項目中的表達式 * com.wisedu.service..*ServiceImpl.*(..) service的子包下也會去找 --> <aop:pointcut expression="execution(* com.wisedu.service.*ServiceImpl.*(..))" id="pc"/> <!--id隨便取,就是為切入點起個名字,expression是重點--> <aop:aspect ref="myAdvice" > <!--通知的描述--> <!-- 指定名為before方法作為前置通知 --> <aop:before method="before" pointcut-ref="pc" /> <!-- 後置 --> <aop:after-returning method="afterReturning" pointcut-ref="pc" /> <!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pc" /> <!-- 異常攔截通知 --> <aop:after-throwing method="afterException" pointcut-ref="pc"/> <!-- 後置 --> <aop:after method="after" pointcut-ref="pc"/> </aop:aspect> </aop:config> </beans>applicationContext.xml
package com.wisedu.springaop; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.wisedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:com/wisedu/springaop/applicationContext.xml") public class Demo { @Resource(name="userService") private UserService us; //這裏獲得到的已經是userService的代理對象了 @Test public void fun1(){ us.save(); //如果想使得異常通知生效,需要save()方法裏需要出現異常,比如int i=0/1; } }測試Demo.java
5. Spring的aop註解配置(了解)
(1)導包
和上面一樣,4+2+2+2
(2)準備目標對象
和上面一樣
(3)準備通知
和上面一樣
(4)配置織入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd "> <!-- 準備工作: 導入aop(約束)命名空間 --> <!-- 1.配置目標對象 --> <bean name="userService" class="com.wisedu.service.UserServiceImpl" ></bean> <!-- 2.配置通知對象 --> <bean name="myAdvice" class="com.wisedu.annotationaop.MyAdvice" ></bean> <!-- 3.開啟使用註解完成織入 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>applicationContext.xml
package com.wisedu.annotationaop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; //通知類 @Aspect //表示該類是一個通知類 public class MyAdvice { @Pointcut("execution(* com.wisedu.service.*ServiceImpl.*(..))") public void pc(){} //前置通知 //指定該方法是前置通知,並制定切入點 @Before("MyAdvice.pc()") public void before(){ System.out.println("這是前置通知!!"); } //後置通知 @AfterReturning("execution(* com.wisedu.service.*ServiceImpl.*(..))") public void afterReturning(){ System.out.println("這是後置通知(如果出現異常不會調用)!!"); } //環繞通知 @Around("execution(* com.wisedu.service.*ServiceImpl.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("這是環繞通知之前的部分!!"); Object proceed = pjp.proceed();//調用目標方法 System.out.println("這是環繞通知之後的部分!!"); return proceed; } //異常通知 @AfterThrowing("execution(* com.wisedu.service.*ServiceImpl.*(..))") public void afterException(){ System.out.println("出事啦!出現異常了!!"); } //後置通知 @After("execution(* com.wisedu.service.*ServiceImpl.*(..))") public void after(){ System.out.println("這是後置通知(出現異常也會調用)!!"); } }MyAdvice.java
package com.wisedu.annotationaop; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.wisedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:com/wisedu/annotationaop/applicationContext.xml") public class Demo { @Resource(name="userService") private UserService us; @Test public void fun1(){ us.save(); } }測試Demo.java
Spring中的AOP