Spring AOP 面向切面編程基本配置

LOGO

參考資料:https://lfvepclr.gitbooks.io/spring-framework-5-doc-cn/content/

1. AOP 原理

AOP Aspect Oriented Programing,面向切面編程。
AOP 採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼(性能監視、事務管理、安全檢查、緩存)
Spring AOP 使用純 Java 實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼

AOP底層原理:就是代理機制

動態代理:

  • 特點:字節碼隨用隨創建,隨用隨加載

  • 作用:不修改源碼的基礎上對方法增強

分類:

  • 基於接口的動態代理 - JDK 動態代理
  • 基於繼承的動態代理 - CGLib 動態代理

1.1 JDK 動態代理

  • 接口:UserDao
public interface UserDao {
	public void add();
	public void update();
}
  • 實現類:UserDaoImpl
public class UserDaoImpl implements UserDao {
	public void add() {
		System.out.println("add...");
	}
	public void update() {
		System.out.println("update...");
	}
}
  • JDK 動態代理機制(實現 InvocationHandler 接口,重寫 invoke() 方法)
public class JDKProxy implements InvocationHandler{
	private UserDao userDao;

	public JDKProxy(UserDao userDao) {
		this.userDao = userDao;
	}

	public UserDao createProxy() {
		UserDao proxy = (UserDao) Proxy.newProxyInstance(
				userDao.getClass().getClassLoader(),
				userDao.getClass().getInterfaces(),
				this); // this : 類的實例對象,即 new 出來的對象
		return proxy;
	}

	// 調用目標對象的任何一個方法 都相當於調用invoke();
    @Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if("update".equals(method.getName())){
			// 記錄日誌:
			System.out.println("日誌記錄...");
			Object result = method.invoke(userDao, args);
			return result;
		}
		return method.invoke(userDao, args);
	}
}
  • JDK 動態代理機制(匿名內部類直接調用返回代理對象,與實現 InvocationHandler 接口等價)
public class TestProxy {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        //動態代理設計模式:匿名內部類對象
        UserDao p = (UserDao) Proxy.newProxyInstance(
                userDao.getClass().getClassLoader(),
                userDao.getClass().getInterfaces(),
                //Lambda表達式,等價於 new InvocationHandler(){ invoke(){...} }
                (proxy, method, argss) -> {
                    String methodName = method.getName();
                    Object result;
                    //方法名比較:只增強指定的需要增強的方法
                    if ("add".equals(methodName)) {
                        System.out.println("日誌記錄...");
                        result = method.invoke(userDao, argss);
                    } else {
                        result = method.invoke(userDao, argss);
                    }
                    return result;
                });
        p.add();
        p.update();
}
  • 測試
import org.junit.Test;
public class SpringDemo1 {
	@Test
	public void test1(){
		UserDao userDao = new UserDaoImpl();
		userDao.add();
		userDao.update();
	}
	@Test
	public void test2(){
		// 被代理對象
		UserDao userDao = new UserDaoImpl();
		// 創建代理對象的時候傳入被代理對象.
		UserDao proxy = new JDKProxy(userDao).createProxy();
		proxy.add();
		proxy.update();
	}
}

test2 結果中打印了增強方法的信息:日誌記錄… add… update…

1.2 CGLib 動態代理

  • 普通類:ProductDaoImpl
public class ProductDaoImpl {
	public void add(){
		System.out.println("add...");
	}
	public void update(){
		System.out.println("update...");
	}
}
  • CGLib 動態代理機制(實現 MethodInterceptor 接口,重寫 intercept() 方法)
/**
 * 使用CGLib生成代理對象
 */
public class CGLibProxy implements MethodInterceptor{
	private ProductDaoImpl productDao;

	public CGLibProxy(ProductDaoImpl productDao) {
		this.productDao = productDao;
	}
	
	public ProductDaoImpl createProxy(){
		// 使用CGLIB生成代理:
		// 1.創建核心類
		Enhancer enhancer = new Enhancer();
		// 2.爲其設置父類
		enhancer.setSuperclass(productDao.getClass());
		// 3.設置回調
		enhancer.setCallback(this);
		// 4.創建代理
		return (ProductDaoImpl) enhancer.create();
	}

    @Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if("add".equals(method.getName())){
			System.out.println("日誌記錄...");
			Object obj = methodProxy.invokeSuper(proxy, args);
			return obj;
		}
		return methodProxy.invokeSuper(proxy, args);
	}
}
  • 測試
import org.junit.Test;
public class SpringDemo2 {
	
	@Test
	public void test1(){
		ProductDaoImpl productDao = new ProductDaoImpl();
		productDao.add();
		productDao.update();
	}
	@Test
	public void test2(){
		ProductDaoImpl productDao = new ProductDaoImpl();
		ProductDaoImpl proxy = new CGLibProxy(productDao).createProxy();
		proxy.add();
		proxy.update();
	}
}

test2 結果中打印了增強方法的信息:日誌記錄… add… update…

1.3 Spring AOP 代理方式

  • JDK 動態代理:被代理對象必須要實現接口,才能產生代理對象,如果沒有接口將不能使用動態代理技術。
  • CGLib 代理機制:第三方代理技術,CGLib 代理,可以對任何類生成代理。代理的原理是對目標對象進行繼承代理。 如果目標對象被 final 修飾,那麼該類無法被 CGLib 代理。

【結論】

Spring 框架中:

  • 如果類實現了接口,就使用 JDK 的動態代理生成代理對象
  • 如果這個類沒有實現任何接口,使用 CGLib 生成代理對象

image-20200618165834904

image-20200618165909739

2. AOP 術語

  • Joinpoint(連接點) : 所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。
  • Pointcut(切入點) : 所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。
  • Advice(通知/增強) : 所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知,通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(切面要完成的功能)。
  • Introduction(引介) : 引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方法或 Field。——類級別的增強。
  • Target(目標對象) : 代理的目標對象
  • Weaving(織入) : 是指把增強應用到目標對象來創建新的代理對象的過程,spring 採用動態代理織入,而AspectJ 採用編譯期織入和類裝載期織入。
  • Proxy(代理) : 一個類被 AOP 織入增強後,就產生一個結果代理類
  • Aspect(切面) : 是切入點和通知(引介)的結合

image-20200618170028865

3. AOP 實現

  1. 依賴配置
    在 pom.xml 添加 aop 依賴:aspectjweaver
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.8.13</version>
</dependency>
  1. 創建通知類
  • 前置通知(before):目標方法運行之前調用
  • 最終通知(after):在目標方法運行之後調用 (無論是否出現異常)
  • 後置通知(after-returning):在目標方法運行之後調用 (未出現異常就會調用)
  • 異常攔截通知(after-throwing):在目標方法運行之後調用(如果出現異常就調用
  • 環繞通知(around):在目標方法之前和之後都調用 (ProceedingJoinPoint對象 -->> 調用proceed方法)
public class MyAdvice {
    public void before() {
        System.out.println("前置通知(執行目標方法之前執行)");
    }

    public void after() {
        System.out.println("後置通知 目標方法之後執行(無論是否發生異常都會執行)");
    }

    public void after_returning() {
        System.out.println("後置通知 目標方法之後執行(未發生異常才執行)");
    }

    public void after_throwing() {
        System.out.println("異常通知(發生異常才執行)");
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("環繞通知(執行目標方法之前)");
        Object proceed = joinPoint.proceed();
        System.out.println("環繞通知(執行目標方法之後)");
        return proceed;
    }
}

3.1 XML 配置 AOP

創建 applicationContext.xml,添加 aop 約束

<aop:config>
	<!-- 配置切入點 切入點表達式的寫法:execution(表達式)
      public void com.abyg.service.UserServiceImpl.save() 精確查找 save 方法
      void com.qf.service.UserServiceImpl.save()  其他修飾符無返回值的save空參方法
      * com.qf.service.UserServiceImpl.save()  有或者無返回值的save空參方法
      * com.qf.service.UserServiceImpl.*()  有或者無返回值的所有空參方法
      * com.qf.service.*ServiceImpl.*(..)  有或者無返回值的所有有參或者空參方法
      * com.qf.service..*ServiceImpl.*(..)  一般不用,service包下子和孫包以ServiceImpl結尾的類中的方法
	-->
	<aop:pointcut id="pc" expression="execution(* com.qf.service.*ServiceImpl.*(..))" />
	<aop:aspect ref="myAdvice" >
		<!-- 指定名爲before方法作爲前置通知 -->
		<aop:before method="before" pointcut-ref="pc" />
		<!-- 後置 -->
		<aop:after method="after" pointcut-ref="pc"/>
		<!-- 後置 -->
		<aop:after-returning method="after_returning" pointcut-ref="pc" />
		<!-- 異常攔截通知 -->
		<aop:after-throwing method="after_throwing" pointcut-ref="pc"/>
		<!-- 環繞通知 -->
		<aop:around method="around" pointcut-ref="pc" />
	</aop:aspect>
</aop:config>

3.2 註解 配置 AOP

Spring中的註解配置AOP:

//通知類: 表示該類是一個通知類
@Aspect
public class MyAdvice {
    //自己設置一個切點,管理重複代碼
	@Pointcut("execution(* com.qf.service.*ServiceImpl.*(..))")
	public void pc(){}
    
	//前置通知 : 指定該方法是前置通知,並制定切入點
	@Before("MyAdvice.pc()")
    public void before() {
        System.out.println("前置通知(執行目標方法之前執行)");
    }
    
	//後置通知
	@After("execution(* com.qf.service.*ServiceImpl.*(..))")
    public void after() {
        System.out.println("後置通知 目標方法之後執行(無論是否發生異常都會執行)");
    }
    
	//後置通知
	@AfterReturning("execution(* com.qf.service.*ServiceImpl.*(..))")
    public void after_returning() {
        System.out.println("後置通知 目標方法之後執行(未發生異常才執行)");
    }

    //異常通知
	@AfterThrowing("execution(* com.qf.service.*ServiceImpl.*(..))")
    public void after_throwing() {
        System.out.println("異常通知(發生異常才執行)");
    }
    
	//環繞通知
	@Around("execution(* com.qf.service.*ServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("環繞通知(執行目標方法之前)");
        Object proceed = joinPoint.proceed();
        System.out.println("環繞通知(執行目標方法之後)");
        return proceed;
    }
}
<!-- applicationContext.xml -->
<?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.qf.service.UserServiceImpl" />
	<!-- 2.配置通知對象 -->
	<bean name="myAdvice" class="com.qf.annotation_aop.MyAdvice" />
	<!-- 3.開啓使用註解完成織入 -->
	<aop:aspectj-autoproxy />

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