動態代理
代理模式是常用的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接口進行描述,它使用類和方法作爲連接點的查詢條件。
切面就是一個帶有 @Aspect 註解的 Java 類.
切面需要在IOC容器中,@Component
通知是標註有某種註解的簡單的 Java 方法.
AspectJ支持 5 種類型的通知註解:
–@Before: 前置通知, 在方法執行之前執行
–@After: 後置通知, 在方法執行之後執行
–@AfterRunning: 返回通知, 在方法返回結果之後執行
–@AfterThrowing: 異常通知, 在方法拋出異常之後
–@Around: 環繞通知, 圍繞着方法執行
可以用@Order(1)指定不同切面的優先級(也就是執行順序),數字越小優先級越高
在 AspectJ切面中, 可以通過 @Pointcut註解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的
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())); } }