Spring學習之AOP(面向切面編程)

動態代理

    代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務

    動態代理好比如影星和經紀人,實際演戲的是影星,經紀人爲影星處理好拍戲前後的事情.實際演戲的是影星.影公司找影星拍戲前需要先找到經紀人,但最終拍戲還是得找影星執行這個動作. 

 public interface Dinner {
        //吃晚飯的方法
        public void haveDinner();
}
//委託類
public class MyDinner implements Dinner{
         @Override
         public void haveDinner() {
         System.out.println("媽媽做的晚飯真好吃....");
         }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//代理類
public class MyDinnerProxy implements InvocationHandler {
         private Object originalObject; //被代理的原始對象
 
         //綁定被代理對象,返回一個代理對象
         public Object getProxy(Object obj)
         {
              this.originalObject = obj;
          //返回一個代理對象
              return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
                  obj.getClass().getInterfaces(), this);
         } 
         @Override
         public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
              Object result = null;
              System.out.println("吃飯之前洗手保持個人衛生...");
              result = method.invoke(this.originalObject, args);
              System.out.println("吃飯之後洗碗保持廚房衛生....");
              return result;
         } 
}
//測試類
public class MyDinnerProxyDemo {
     public static void main(String[] args) {
          Dinner din = new MyDinner();
          //不是使用代理對象的效果
          //din.haveDinner();
          MyDinnerProxy proxy = new MyDinnerProxy();
          //返回了一個代理對象
          din = (Dinner)proxy.getProxy(din);
          //執行代理對象的方法
          din.haveDinner();
     }
}

AOP簡介 

AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充.

AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點.

在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裏, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏.

AOP 的好處:

–每個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級

–業務模塊更簡潔, 只包含核心業務代碼.

 

 

切面(Aspect):  橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象

通知(Advice):  切面必須要完成的工作

目標(Target): 被通知的對象

代理(Proxy): 向目標對象應用通知之後創建的對象

連接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的連接點,執行點爲 ArithmethicCalculator#add(); 方位爲該方法執行前的位置

切點(pointcut):每個類都擁有多個連接點:例如 ArithmethicCalculator的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務。AOP 通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過 org.springframework.aop.Pointcut接口進行描述,它使用類和方法作爲連接點的查詢條件。  wKioL1ZXxsjCBUUQAACe8RLetc8184.png 

切面就是一個帶有 @Aspect 註解的 Java 類.

切面需要在IOC容器中,@Component

通知是標註有某種註解的簡單的 Java 方法.

AspectJ支持 5 種類型的通知註解:

–@Before: 前置通知, 在方法執行之前執行

–@After: 後置通知, 在方法執行之後執行

–@AfterRunning: 返回通知, 在方法返回結果之後執行

–@AfterThrowing: 異常通知, 在方法拋出異常之後

–@Around: 環繞通知, 圍繞着方法執行

 可以用@Order(1)指定不同切面的優先級(也就是執行順序),數字越小優先級越高

 在 AspectJ切面中, 可以通過 @Pointcut註解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的

wKiom1ZYCP6yIc1VAAElhgpGawU540.png

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
 * AOP 的 helloWorld
 * 1. 加入 jar 包
 * 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
 * spring-aspects-4.0.0.RELEASE.jar
 * 
 * 2. 在 Spring 的配置文件中加入 aop 的命名空間。 
 * 
 * 3. 基於註解的方式來使用 AOP
 * 3.1 在配置文件中配置自動掃描的包: <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
 * 3.2 加入使 AspjectJ 註解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 使@Before和@After起作用
 * 爲匹配的類自動生成動態代理對象. 
 * 
 * 4. 編寫切面類: 
 * 4.1 一個一般的 Java 類
 * 4.2 在其中添加要額外實現的功能. 
 *
 * 5. 配置切面
 * 5.1 切面必須是 IOC 中的 bean: 添加 @Component 註解
 * 5.2 聲明是一個切面: 添加 @Aspect
 * 5.3 配置切面優先級@Order(1)
 * 5.3.1 給方法配置通知註解.
前置通知: @Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")

 * 6. 在通知中訪問連接細節: 可以在通知方法中添加 JoinPoint 類型的參數, 從中可以訪問到目標方法的簽名和方法的參數. 
 * 
 */
//通過添加 @Aspect 註解聲明一個 bean 是一個切面!
@Order(2)
@Aspect
@Component
public class LoggingAspect {
/**
  * 定義一個方法, 用於聲明切入點表達式. 一般地, 該方法中再不需要添入其他的代碼. 
  * 使用 @Pointcut 來聲明切入點表達式. 
  * 後面的其他通知直接使用方法名來引用當前的切入點表達式. 
  */
 @Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
 public void declareJointPointExpression(){}
    //聲明是一個前置通知,在目標方法之前執行
 @Before("declareJointPointExpression()")
 public void beforeMethod(JoinPoint joinPoint){
  String methodName = joinPoint.getSignature().getName();
  Object [] args = joinPoint.getArgs();
  
  System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
 }
//聲明是一個後置通知,在目標方法執行後(發生異常後)執行,還不能訪問目標方法的執行結果
 
 @After("execution(* com.atguigu.spring.aop.*.*(..))")
 public void afterMethod(JoinPoint joinPoint){
  String methodName = joinPoint.getSignature().getName();
  System.out.println("The method " + methodName + " ends");
 }
 /**
  * 在方法法正常結束受執行的代碼
  * 返回通知是可以訪問到方法的返回值的!
  */
 @AfterReturning(value="declareJointPointExpression()",
   returning="result")
 public void afterReturning(JoinPoint joinPoint, Object result){
  String methodName = joinPoint.getSignature().getName();
  System.out.println("The method " + methodName + " ends with " + result);
 }
 
 /**
  * 在目標方法出現異常時會執行的代碼.
  * 可以訪問到異常對象; 且可以指定在出現特定異常時在執行通知代碼
  */
 @AfterThrowing(value="declareJointPointExpression()",
   throwing="e")
 public void afterThrowing(JoinPoint joinPoint, Exception e){
  String methodName = joinPoint.getSignature().getName();
  System.out.println("The method " + methodName + " occurs excetion:" + e);
 }
 
 /**
  * 環繞通知需要攜帶 ProceedingJoinPoint 類型的參數. 
  * 環繞通知類似於動態代理的全過程: ProceedingJoinPoint 類型的參數可以決定是否執行目標方法.
  * 且環繞通知必須有返回值, 返回值即爲目標方法的返回值
  */
 /*
 @Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
 public Object aroundMethod(ProceedingJoinPoint pjd){
  
  Object result = null;
  String methodName = pjd.getSignature().getName();
  
  try {
   //前置通知
   System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
   //執行目標方法
   result = pjd.proceed();
   //返回通知
   System.out.println("The method " + methodName + " ends with " + result);
  } catch (Throwable e) {
   //異常通知
   System.out.println("The method " + methodName + " occurs exception:" + e);
   throw new RuntimeException(e);
  }
  //後置通知
  System.out.println("The method " + methodName + " ends");
  
  return result;
 }
}
//另一個切面
package com.atguigu.spring.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(1)
@Aspect
@Component
public class VlidationAspect {
//重用另一個包的一個類裏的切入點表達式
 @Before("com.atguigu.spring.aop.LoggingAspect.declareJointPointExpression()")
 public void validateArgs(JoinPoint joinPoint){
  System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
 }
 
}

 

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