spring框架之AOP(面向切面編程)

一、AOP簡述

AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護(增強方法)的一種技術。

AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間耦合度降低,提高程序的可重用性同時提高了開發的效率

AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼

經典應用:事務管理性能監視、緩存、日誌,權限管理

Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼

AspectJ是一個基於Java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入

二、AOP實現原理

aop底層將採用代理機制進行實現。(動態代理模式)

 動態代理它可以直接給某一個目標對象生成一個代理對象,而不需要代理類存在。

動態代理與代理模式(靜態代理)原理是一樣的,只是它沒有具體的代理類,直接通過反射生成了一個代理對象。

①:jdk的動態代理方式(spring的aop底層默認使用的是jdk動態代理)

要求:接口 + 實現類

spring底層默認使用該方式創建代理對象

②: cglib方式

要求:實現類(給目標類創建一個子類)

spring 可以使用cglib字節碼增強實現aop。

三、AOP術語

1.target:目標類,需要被代理的類。例如:UserService的實現類UserServiceImpl

2.Joinpoint(連接點):所謂連接點是指那些可能被攔截到的方法。例如:UserServiceImpl所有的方法

3.PointCut 切入點:已經被增強的連接點。例如:addUser()

4.advice 通知/增強,增強代碼。例如:after、before

5. Weaving(織入):是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.

6.proxy 代理類(類似中介)

7. Aspect(切面): 是切入點pointcut和通知advice的結合

四、實現AOP的方式

1、JDK動態代理

 JDK動態代理對“裝飾者”設計模式簡化。使用前提:必須有接口

目標類:接口 + 實現類(被代理的類需要增強的)

通知類:用於存通知 MyAdvice

工廠類:編寫工廠生成代理

 

主要的兩個方法:

Proxy.newProxyInstance():產生代理類的實例。僅能代理實現至少一個接口的類

       ClassLoader:類加載器。固定寫法,和被代理類使用相同的類加載器即可。

       Class[] interface:代理類要實現的接口。固定寫法,和被代理類使用相同的接口即可。

       InvocationHandler:策略(方案)設計模式的應用。

 

InvocationHandler中的invoke方法:調用代理類的任何方法,此方法都會執行   

         Object proxy:代理對象本身的引用。一般用不着。

         Method method:當前調用的方法。

         Object[] args:當前方法用到的參數

 

主要的代碼:

public interface ZuFangZi {
  public void lookHouse();
  public void getMoney(double money);
}

FangDong.java

package com.tf.staticproxy;
public class FangDong implements ZuFangZi {
	@Override
	public void lookHouse() {
		System.out.println("看房東的房子");
	}
	@Override
	public void getMoney(double money) {
	System.out.println("房東收到的房租:"+money);
	}
}

CreateZhongjie.java

package com.tf.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//創建代理對象
public class CreateZhongJie {
	
public static ZuFangZi createZhongJie(){
	//創建被代理對象(目標類)
     FangDong fangDong = new FangDong();
	/*創建代理 對象
	 * loader:類加載器
	 * interfaces:被代理對象實現的接口列表
	 * h:調用代理對象方法的處理器
	 * */
	return (ZuFangZi) Proxy.newProxyInstance(CreateZhongJie.class.getClassLoader(), fangDong.getClass().getInterfaces(), new InvocationHandler() {
	/*proxy - 在其上調用方法的代理實例   一般用不到
      method - 對應於在代理實例上調用的接口方法的 Method 實例。Method 對象的聲明類將是在其中聲明方法的接口,
                                      該接口可以是代理類賴以繼承方法的代理接口的超接口。
      args - 包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則爲 null。
                               基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。 
	 * */
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("中介開始:"+method.getName());
		//擴展代理類
		Double zhongjieMoney = 3000.0;
		Object obj = null;
		if(method.getName().equals("getMoney")){
			Double money = (Double)args[0];
			System.out.println("中介收取:"+zhongjieMoney+"費");
			//通過反射調用目標類的方法
			method.invoke(fangDong, new Double[]{money-zhongjieMoney});
		}else{
			 obj  = method.invoke(fangDong, args);
		}
		   System.out.println("中介開始:"+method.getName()+"結束");
			return obj;
		}
	});	
}
}

Test.java

public class Test {
	public static void main(String[] args) {
		//獲得創建的中介類
		ZuFangZi zhongjie = CreateZhongJie.createZhongJie();
		zhongjie.lookHouse();
		zhongjie.getMoney(10000);
	}
} 

 

2、CGLIB字節碼增強

當沒有接口時,還想生成代理類。

①:沒有接口,只有實現類。

②:採用字節碼增強框架 cglib,在運行時創建目標類的子類,從而對目標類進行增強。

                                                           目標類:  BokServiceImpl.java

public class BookServiceImpl  {
	public  void add() {
		//開啓事務
		try{
		System.out.println("add");
	 }catch (Exception e) {
		//回滾事務
	}
	}
	public  void delete() {
		//開啓事務
		try {
		System.out.println("delete");
		//提交事務
		}catch (Exception e) {
			//回滾事務
		}
	}
	}

                                                                         通知類:MyAdvice.java

public class MyAdvice {
	public   void before() {
		System.out.println("前置通知---開啓事務");
	}
	public void after() {
		System.out.println("後置通知-----提交事務");
	}
	public void exceptionAdvice() {
		System.out.println("異常通知----回滾事務");
	}
}

                                              工廠類 :MyCglibProxyFactory.java

public class MyCglibProxyFactory {
public static BookServiceImpl createProxy(){
	//1:創建目標類的對象
	BookServiceImpl bookServiceImpl = new BookServiceImpl();
   // 2 創建cglib的核心對象
	Enhancer enhancer  =new Enhancer();
	//3 創建增強類的 對象
	MyAdvice advice = new MyAdvice();
	//4 創建enhancer 父類 
	enhancer.setSuperclass(bookServiceImpl.getClass());
	//5 設置回調函數  通過代理對象調用方法時會執行回調函數中的 MethodInterceptor方法
	enhancer.setCallback(new MethodInterceptor(){
		@Override
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			advice.before();
			Object o  = null;
			try {
			//通過反射調用目標類的方法
			o = method.invoke(bookServiceImpl, args);
			advice.after();
		} catch (Exception e) {
			advice.exceptionAdvice();
		}	 
	     return o;//目標類的返回值
		}
	});
    //6 創建這個代理對象
	return (BookServiceImpl) enhancer.create();
}
}

                                                                         測試類:TestCglib.java

//cglib創建對象,不需要有接口 只要有一個類
public class TestCglib {
public static void main(String[] args) {
	BookServiceImpl b = MyCglibProxyFactory.createProxy();
	b.add();
	b.delete();
   //被增強的方法爲切入點
}
}

五、SpringAOP聯盟通知類型

  1. AOP聯盟爲通知Advice定義了org.aopalliance.aop.Advice接口
  2. Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類
    • 前置通知 org.springframework.aop.MethodBeforeAdvice
    • 在目標方法執行前實施增強
    • 後置通知org.springframework.aop. AfterReturningAdvice
      • 在目標方法執行後實施增強
    • 環繞通知org.aopalliance.intercept.MethodInterceptor
      • 在目標方法執行前後實施增強
    • 異常拋出通知org.springframework.aop.ThrowsAdvice
      • 在方法拋出異常後實施增強
    • 引介通知 org.springframework.aop.IntroductionInterceptor

在目標類中添加一些新的方法和屬性

環繞通知,必須手動執行目標方法放行

try{

   //前置通知

   //執行目標方法

   //後置通知

} catch(){

   //拋出異常通知

}

 

六、spring編寫代理:半自動(瞭解)

  1. 讓spring 創建代理對象,從spring容器(配置文件)中手動的獲取代理對象

                                                          增強類

public class MyAdvice implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		/*環繞通知 調用目標方法前後都會執行
		 * 是否放行(調用)*/
		System.out.println("環繞通知   ---  後置通知");
		//調用目標方法
	    Object o =	invocation.proceed();
		System.out.println("環繞通知   --- 後置通知");
		return o;
	}
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("afterReturning 後置通知");
	}
	@Override
	public void before(Method returnValue, Object[] args, Object target) throws Throwable {
		System.out.println("前置通知");
		
	}
}

                                          spring-di.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- spring的半自動 -->
<!-- 目標類 -->
<bean id="bookService" class="com.tf.aop.banzidong.BookServiceImpl"></bean>
<!-- 通知類的對象 -->
<bean id="myAdvice" class="com.tf.aop.banzidong.MyAdvice"></bean>
<!-- 使用spring的ProxyFactoryBean類創建代理對象 -->
<bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.tf.aop.banzidong.BookService"></property>
<property name="target" value="bookService"></property>
<property name="interceptorNames" value="myAdvice"></property>
</bean>
</beans>

注意: 這三個名字不能變

interfaces: 被代理對象實現的列表

 target:目標對象的引用

 inteceptorNames:增強類的引用

optimize :強制使用cglib

                <property name="optimize" value="true"></property>

底層機制

            如果目標類有接口,採用jdk動態代理(默認)

            如果沒有接口,採用cglib字節碼增強

            如果聲明 optimize = true ,無論是否有接口,都採用cglib

                                                                             測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/tf/aop/banzidong/spring-di.xml")
public class TestBanZiDong {
//spring幹一半,我們自己做一半
//提供接口實現類 、通知類
    @Autowired
	private BookService bookService;
	@Test
	public void test() {
    System.out.println(bookService);
	}
}

七、spring aop編程:全自動【掌握】

  1. 從spring容器獲得目標類,如果配置aop,spring將自動生成代理。
  2.  要確定目標類,aspectj 切入點表達式、
  3. 導包

    aspectjweaver-1.8.2.jar

    spring-aspects-4.3.5.RELEAxcxcSE.jar

        使用<aop:config>進行配置

                proxy-target-class="true" 聲明時使用cglib代理

            <aop:pointcut>切入點,從目標對象獲得具體方法

            <aop:advisor>特殊的切面,只有一個通知和一個切入點

                advice-ref通知引用

                pointcut-ref切入點引用   

                                                                      增強類


public class MyAdvice implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		/*環繞通知 調用目標方法前後都會執行
		 * 是否放行(調用)*/
		System.out.println("環繞通知   ---  後置通知");
		//調用目標方法
	    Object o =	invocation.proceed();
		System.out.println("環繞通知   --- 後置通知");
		return o;
	}
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("afterReturning 後置通知");
	}
	@Override
	public void before(Method returnValue, Object[] args, Object target) throws Throwable {
		System.out.println("前置通知");
		
	}
}

                                                                                             spring-di.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:aop="http://www.springframework.org/schema/aop"
   
     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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- spring的全自動 -->
<bean id="bookService" class="com.tf.aop.auto.BookServiceImpl"></bean>
<bean id="myAdvice" class="com.tf.aop.auto.MyAdvice"></bean>
<!-- 全自動配置 
解析到aop節點 會爲目標類自動創建代理對象那個
-->
<aop:config>
  <aop:pointcut expression="execution(* com.tf.aop.auto.*Impl.*(..))" id="mypointcut"/>
  <aop:advisor advice-ref="myAdvice" pointcut-ref="mypointcut"/>
</aop:config>
</beans>

                                                                                Test測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/tf/aop/auto/spring-di.xml")
public class TestAuto {
	@Resource
	private BookService bookService;
	@Test
	public void test(){
		bookService.add();
	}
}

7、2 切入點表達式

.execution()  用於描述方法   @annotation()  描述註解

       語法:execution(修飾符返回值包.類.方法名(參數) throws異常

              修飾符,一般省略

                     public            公共方法

                     *                   任意

              返回值,不能省略

                     void               返回沒有值

                     String            返回值字符串

                     *                   任意

              包,[省略]

                     com.qf.crm                   固定包

                     com.qf.crm.*.service     crm包下面子包任意包下的service包(例如:com.qf.crm.staff.service)

                     com.qf.crm..                 crm包下面的所有子包(含自己)

                     com.qf.crm.*.service..   crm包下面任意子包,固定目錄service,service目錄任意包

                     com.qf.crm.*

              類,[省略]

                     UserServiceImpl                  指定類

                     *Impl                                  以Impl結尾

                     User*                                  以User開頭

                     *                                        任意

              方法名,不能省略

                     addUser                               固定方法

                     add*                                          以add開頭

                     *Do                                    以Do結尾

                     *                                        任意

              (參數)

                     ()                                        無參

                     (int)                                    一個整型

                     (int ,int)                              兩個

                     (..)                                      參數任意

              throws ,可省略,一般不寫。

比如execution(* com.tf.aop.auto.*Impl.*(..))    :com.tf.aop.auto包下以Impl結尾的類的任意方法

八、AspectJ介紹

  1. AspectJ是一個基於Java語言的AOP框架
  2. Spring2.0以後新增了對AspectJ切點表達式支持
  3. @AspectJ 是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面

    新版本Spring框架,建議使用AspectJ方式來開發AOP

  1. 主要用途:自定義開發

8、1 AspectJ 通知類型

  1. aspectj 通知類型,只定義類型名稱。已知方法格式。
  2. 個數:6種(環繞比較重要)。

       before:前置通知(應用:各種校驗)

              在方法執行前執行,如果通知拋出異常,阻止方法運行

       afterReturning:後置通知(應用:常規數據處理)

              方法正常返回後執行,如果方法中拋出異常,通知無法執行

              必須在方法執行後才執行,所以可以獲得方法的返回值。

       around:環繞通知(應用:十分強大,可以做任何事情)

              方法執行前後分別執行,可以阻止方法的執行

              必須手動執行目標方法

       afterThrowing:拋出異常通知(應用:包裝異常信息)

              方法拋出異常後執行,如果方法沒有拋出異常,無法執行

       after:最終通知(應用:清理現場)

              方法執行完畢後執行,無論方法中是否出現異常

環繞

 

try{

     //前置:before

    //手動執行目標方法

    //後置:afterRetruning

} catch(){

    //拋出異常 afterThrowing

} finally{

    //最終 after

}

 

代碼:

                                                  增強類(通知名稱任意(方法名任意

/*JoinPoint 可以得到目標類的相關信息
 * ProceedingJoinPoint  可以控制是否調用目標方法 只能用到環繞通知方法中
 * */
public class MyAdvice {

	public void before(JoinPoint joinpoint){
		System.out.println("前置通知:"+joinpoint.getTarget());
	}
	public void afterReturn(JoinPoint joinpoint){
		System.out.println("後置通知:"+joinpoint.getSignature());
	}
	public void after(){
		System.out.println("最終增強");
	}
	public void around(ProceedingJoinPoint pjo){
		System.out.println("環繞=-=後置通知:"+pjo.getSignature().getName());
		try{
			Object o =pjo.proceed();
			System.out.println("環繞---"+o);
		}catch(Throwable e){
			e.printStackTrace();
		}
		
		System.out.println("環繞===後置通知:"+pjo.getSignature().getName());
	}
	public void throwsAdvice(Throwable throwable){
		System.out.println("異常通知"+throwable.getMessage());
	}
}

                                                 spring-di.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- aspectj方式實現aop -->
<bean id="bookService" class="aspectj.BookServiceImpl"></bean>
<!-- 配置增強類 -->
<bean id="myAdvice" class="aspectj.MyAdvice"></bean>
<aop:config>
    <aop:pointcut expression="execution(* aspectj.*Impl.*(..))" id="mypointcut"></aop:pointcut>
    <aop:aspect ref="myAdvice">
    <aop:before method="before" pointcut-ref="mypointcut"/>
    <aop:after method="after" pointcut-ref="mypointcut"/>
    <aop:around method="around" pointcut-ref="mypointcut"/>
    <aop:after-throwing method="throwsAdvice" pointcut-ref="mypointcut" throwing="throwable"/>
    <aop:after-returning method="afterReturn" pointcut-ref="mypointcut"/>
     </aop:aspect>
</aop:config>
</beans>

                                                 測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aspectj/spring-di.xml")
public class TestAspectj {
	@Autowired
private BookService bookService;
@Test
public void test(){
	bookService.add();
}
}

總結:如果是環繞增強的方法必須傳遞參數:ProccedingJoinpoint

IIII.spring的全自動和aspectj方式實現的aop

     1)spring全自動的增強類需要實現aop聯盟提供的幾個接口:MethodInterceptor(環繞),MethodBeforeAdvice(前置),AfterReturningAdvice(後置)

   spring配置文件使用的是:<aop:advisor>節點配置

   2)aspectj方式

   增強類不需要實現任何接口,只需要提供幾個用於增強的方法即可

   注意:如果是環繞增強的方法必須傳遞參數:ProccedingJoinpoint

        其他增強可傳入JoinPoint參數(aspect包下的),該參數可獲得目標方法的基本信息

在spring配置文件中使用<aop:aspect>節點配置

 8、2 基於註解

  增強類:

@Aspect   //該註解等價於 xml<bean id="myAdvice" class="aspectj.MyAdvice"></bean>
@Component
public class MyAdvice {
 @Pointcut(value="execution(* aspectj.annotation.*Impl.*(..))")
	public void myPointcut(){
		
	}
	@Before(value="myPointcut()")
	public void before(JoinPoint joinpoint){
		System.out.println("前置通知:"+joinpoint.getTarget());
	}
	@AfterReturning("myPointcut()")
	public void afterReturn(JoinPoint joinpoint){
		System.out.println("後置通知:"+joinpoint.getSignature());
	}
	@After("myPointcut()")
	public void after(){
		System.out.println("最終增強");
	}
	@Around("myPointcut()")
	public void around(ProceedingJoinPoint pjo){
		System.out.println("環繞=-=後置通知:"+pjo.getSignature().getName());
		try{
			Object o =pjo.proceed();
			System.out.println("環繞---"+o);
		}catch(Throwable e){
			e.printStackTrace();
		}
		
		System.out.println("環繞===後置通知:"+pjo.getSignature().getName());
	}
	//異常:添加:throwing="throwable"  值爲參數名稱
	@AfterThrowing(value="myPointcut()",throwing="throwable")
	public void throwsAdvice(Throwable throwable){
		System.out.println("異常通知"+throwable.getMessage());
	}
}

spring-di.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
    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
        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
        ">
<!-- 掃描帶有註解的類 -->
<context:component-scan base-package="aspectj.annotation"></context:component-scan>
<!-- aop自動代理 
proxy-target-class="false" 默認jdk創建代理對象
                 true :cglib創建代理對象
-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>

測試類:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/aspectj/annotation/spring-di.xml")
public class TestAnnotaiont {
	@Resource
	private BookService bookService;
	@Test
	public void test(){
		//System.out.println(bookService);
		bookService.add();
	}
}

BookServiceImpl不要忘記加註解@Service

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章