1. 程式人生 > >Spring入門學習(AOP返回通知&異常通知&環繞通知&切面的優先順序) 第十六節

Spring入門學習(AOP返回通知&異常通知&環繞通知&切面的優先順序) 第十六節

Spring入門學習(AOP返回通知&異常通知&環繞通知)

返回通知

使用`@AfterReturning`註解,在方法正常結束後執行的通知,它是可以獲得方法的返回值的。
  • LoggingAspect類中新增如下方法:
    /**
     * 在方法正常結束後執行的程式碼
     * 返回通知是可以訪問到方法的返回值的
     * @param joinPoint
     */
    @AfterReturning(value="execution(* *.*(..))",returning=
    "result") public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method "+methodName+ " ends with "+result); }
    使用@AfterReturning註解,註解中的returning獲取返回值。
    測試結果:
    com.sun.proxy.$Proxy10
    The methodadd begins...[3, 6]
    The method add ends...
    The method add ends with 9
    result:9
    The methoddiv begins...[6, 3]
    The method div ends...
    The method div ends with 2
    result:2
    

異常通知

使用`@AfterThrowing`註解,在目標方法出現異常時會執行的程式碼,可以訪問到異常物件;且可以指定在出現特定異常時在執行通知程式碼
  • LoggingAspect類中新增afterThrowing方法:
    /**
     * 在目標方法出現異常時會執行的程式碼
     * 可以訪問到異常物件;且可以指定在出現特定異常時在執行通知程式碼
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value="execution(* *.*(..))",throwing="ex")
    public void afterThrowing
    (JoinPoint joinPoint, Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method "+methodName+ " occurs exception: "+ex); }
  • 測試:
    測試方法:
    public class Main {
    
    	public static void main(String[] args) {
    		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
    		ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
    		System.out.println(arithmeticCalculator.getClass().getName());
    		
    		int result = arithmeticCalculator.add(3, 6);
    		System.out.println("result:" + result);
    	
    		result = arithmeticCalculator.div(6, 3);
    		System.out.println("result:" + result);
    		
    		result = arithmeticCalculator.div(6, 0);
    		System.out.println("result:" + result);
    	}
    }
    
    測試結果:
    com.sun.proxy.$Proxy11
    The methodadd begins...[3, 6]
    The method add ends...
    The method add ends with 9
    result:9
    The methoddiv begins...[6, 3]
    The method div ends...
    The method div ends with 2
    result:2
    The methoddiv begins...[6, 0]
    The method div ends...
    The method div occurs exception: java.lang.ArithmeticException: / by zero
    Exception in thread "main" java.lang.ArithmeticException: / by zero
    	at com.fafa.spring.aop.impl.ArithmeticCalculatorImpl.div(ArithmeticCalculatorImpl.java:35)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:606)
    	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    	at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    	at com.sun.proxy.$Proxy11.div(Unknown Source)
    	at com.fafa.spring.aop.impl.Main.main(Main.java:19)
    

環繞通知

  • 使用@Around註解;
  • 環繞通知需要攜帶ProceedingJoinPoint型別的引數;
  • 環繞通知類似於動態代理的全過程:ProceedingJoinPoint這個型別的引數可以決定是否執行目標方法;
  • 且環繞通知必須有返回值,返回值即為有目標方法的返回值。
  1. 註釋掉LoggingAspect類中的其它的方法,新增如下方法:
    	/**
    	 * 環繞通知需要攜帶ProceedingJoinPoint型別的引數。
    	 * 環繞通知類似於動態代理的全過程:ProceedingJoinPoint這個型別的引數可以決定是否執行目標方法。
    	 * 且環繞通知必須有返回值,返回值即為有目標方法的返回值
    	 * @param proceedingJoinPoint
    	 */
    	@Around(value="execution(* *.*(..))")
    	public Object aroundMethd(ProceedingJoinPoint pjd){
    		String methodName = pjd.getSignature().getName();
    		Object result = null;
    		try {
    			// 前置通知
    			System.out.println("The method "+methodName+ " begins");
    			result = pjd.proceed();
    			// 返回通知
    			System.out.println("The method "+methodName+" ends with "+result);
    		} catch (Throwable e) {
    			// 異常通知
    			System.out.println("The method "+methodName+ " occurs exception: "+e);
    			e.printStackTrace();
    		}
    		// 後置通知
    		System.out.println("The method "+methodName+ " ends ");
    		return result;
    	}
    
  2. 測試(去掉除0的):
    com.sun.proxy.$Proxy8
    The method add begins
    The method add ends with 9
    The method add ends 
    result:9
    The method div begins
    The method div ends with 2
    The method div ends 
    result:2
    
    可見環繞通知和動態代理是非常類似的,可以控制到方法是否執行,方法的返回值。

切面的優先順序

  1. 此時我們再定義一個切面類ValidationAspect:
    @Component
    @Aspect
    public class ValidationAspect {
    
    	@Before("execution(public int com.fafa.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    	public void validateArgs(JoinPoint joinPoint){
    		System.out.println("-->validate:"+Arrays.asList(joinPoint.getArgs()));
    	}
    }
    
  2. 註釋掉LoggingAspect類中的aroundMethd方法
  3. 執行main方法:
    com.sun.proxy.$Proxy10
    The methodadd begins...[3, 6]
    -->validate:[3, 6]
    The method add ends...
    The method add ends with 9
    result:9
    The methoddiv begins...[6, 3]
    -->validate:[6, 3]
    The method div ends...
    The method div ends with 2
    result:2
    
    從結果看兩個切面類中@Before都執行了,但是我們如果要控制它們的順序該怎麼辦;
  4. 此時可以在切面類上使用@Order註解指定切面的優先順序,值越小優先順序越高:
    @Component
    @Aspect
    @Order(1)
    public class ValidationAspect {
    	@Before("execution(public int com.fafa.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    	public void validateArgs(JoinPoint joinPoint){
    		System.out.println("-->validate:"+Arrays.asList(joinPoint.getArgs()));
    	}
    }
    
    // 把這個類宣告為一個切面: 需要把該類放入到IOC容器中,再宣告為一個切面
    @Component
    @Aspect
    @Order(2)	//使用Order指定切面的優先順序,值越小優先順序越高
    public class LoggingAspect {
    
    // 宣告該方法是一個前置通知:在目標方法開始之前執行
    //	@Before("execution(public int com.fafa.spring.aop.impl.ArithmeticCalculator.add(int, int))")
    	@Before("execution(* com.fafa.spring.aop.impl.*.*(int, int))")
    	public void beforeMethod(JoinPoint joinPoint) {
    		String methodName = joinPoint.getSignature().getName();
    		List<Object> args = Arrays.asList(joinPoint.getArgs());
    		System.out.println("The method" + methodName +" begins..." + args);
    	}
    	
    	// 後置通知就是在目標方法執行後(無論是否發生異常),都會執行的通知
    	// 在後置通知中還不能訪問目標方法執行的結果。
    	@After("execution(* com.fafa.spring.aop.impl.*.*(..))")
    	public void afterMethod(JoinPoint joinPoint) {
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("The method "+methodName+" ends...");
    	}
    	
    	/**
    	 * 在方法正常結束後執行的程式碼
    	 * 返回通知是可以訪問到方法的返回值的
    	 * @param joinPoint
    	 */
    	@AfterReturning(value="execution(* *.*(..))",returning="result")
    	public void afterReturning(JoinPoint joinPoint, Object result){
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("The method "+methodName+ " ends with "+result);
    	}
    	
    	/**
    	 * 在目標方法出現異常時會執行的程式碼
    	 * 可以訪問到異常物件;且可以指定在出現特定異常時在執行通知程式碼
    	 * @param joinPoint
    	 * @param ex
    	 */
    	@AfterThrowing(value="execution(* *.*(..))",throwing="ex")
    	public void afterThrowing(JoinPoint joinPoint, Exception ex){
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("The method "+methodName+ " occurs exception: "+ex);
    	}
    	
    	/**
    	 * 環繞通知需要攜帶ProceedingJoinPoint型別的引數。
    	 * 環繞通知類似於動態代理的全過程:ProceedingJoinPoint這個型別的引數可以決定是否執行目標方法。
    	 * 且環繞通知必須有返回值,返回值即為有目標方法的返回值
    	 * @param proceedingJoinPoint
    	 */
    	/*@Around(value="execution(* *.*(..))")
    	public Object aroundMethd(ProceedingJoinPoint pjd){
    		String methodName = pjd.getSignature().getName();
    		Object result = null;
    		try {
    			// 前置通知
    			System.out.println("The method "+methodName+ " begins");
    			result = pjd.proceed();
    			// 返回通知
    			System.out.println("The method "+methodName+" ends with "+result);
    		} catch (Throwable e) {
    			// 異常通知
    			System.out.println("The method "+methodName+ " occurs exception: "+e);
    			e.printStackTrace();
    		}
    		// 後置通知
    		System.out.println("The method "+methodName+ " ends ");
    		return result;
    	}*/
    }
    
  5. 執行測試結果返回如下:
    com.sun.proxy.$Proxy11
    -->validate:[3, 6]
    The methodadd begins...[3, 6]
    The method add ends...
    The method add ends with 9
    result:9
    -->validate:[6, 3]
    The methoddiv begins...[6, 3]
    The method div ends...
    The method div ends with 2
    result:2