AOP(Aspect-Oriented Programming,面向切面編程)
切面(Aepect):橫切關注點(跨越應用程序多個模塊的功能)被模塊化的對象;
通知(Advice):切面必須要完成的工作;
目標(Target):被通知的對象;
代理(Proxy):像目標對象應用通知之后創建的對象;
連接點(Joinpoint):程序執行的某個特殊位置,如類某個方法調用前、調用后、方法拋出異常后等。連接點由兩個信息確定:方法表示的程序執行點;想對點表示的方位。
切點(pointcut):每個類都擁有多個連接點,即連接點是程序類中客觀存在的事務;
AOP通過切點定位到特定的連接點。
AOP的主要編程對象是切面(aopect),而切面模塊化橫切關注點。
在應用AOP編程時,仍需要定義公共功能,但可以明確這個功能在哪里,以什么方式應用,並且不必修改受影響的類,這樣的話橫切關注點就被模塊化到特殊的對象(切面)里。
AOP的好處:
每個事物邏輯位於一個位置,代碼不分散,便於維護和升級;
業務模塊更簡潔,值包含核心業務代碼
1、基於AspectJ注解
(1)Maven引入依賴包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency>
(2)在spring配置文件中添加aop的命名空間,同時配置使得項目支持Aop,配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--使Spring支持自動檢測組件,如注解的Controller --> <context:component-scan base-package="com.parry.redisCluster.*" /> <!--*************** 支持aop **************** --> <aop:aspectj-autoproxy proxy-target-class="true" /> </beans>
(3)實現一個日志切面
- 切面首先是IOC容器中的一個bean,即在切面類前加上標簽@Component標簽
- 切面還需要添加標簽@Aspect標簽,聲明該類是一個切面
- 實現需要的通知,在方法之前添加如下的通知類型:
- @Before:前置通知,在方法前通知;
- @After :后置通知,在方法執行后通知;
- @AfterRunning:返回通知,在方法返回結果之后通知;
- @AfterThrowing:異常通知,在方法拋出異常之后通知;
- @Around:環繞通知,圍繞着方法執行;
- 切入點表達式的書寫:
- execution(* com.cn21.redisCluster.controller.*(..)) :第一個*表示任意的修飾符(public/private/protected)及任意的返回值(void/Object);第二個*表示任意的方法,‘..’表示任意數量的參數;
- execution(public * com.cn21.redisCluster.controller.*Controller.*(..)):表示com.cn21.redisCluster.controller包下+以Controller結尾的+公共的方法(public)的方法;
- execution(public void com.cn21.redisCluster.controller.*Controller.*(..)):表示com.cn21.redisCluster.controller包下+以Controller結尾的+公共的方法(public)+返回類型是void的方法;
- execution(public void com.cn21.redisCluster.controller.*Controller.*(int,..)):表示com.cn21.redisCluster.controller包下+以Controller結尾+公共的方法(public)+返回類型是void的類+第一個參數是int的方法;
- execution(public void com.cn21.redisCluster.controller.*Controller.*(int,int)):表示com.cn21.redisCluster.controller包下+以Controller結尾+公共的方法(public)+返回類型是void的類+第一個參數是int+第二個參數是int的方法;
- 可以在方法中聲明一個類型為JoinPoint的參數,然后就可以訪問鏈接細節,如方法名稱和參數值;
import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect //該標簽把LoggerAspect類聲明為一個切面 @Order(1) //設置切面的優先級:如果有多個切面,可通過設置優先級控制切面的執行順序(數值越小,優先級越高) @Component //該標簽把LoggerAspect類放到IOC容器中 public class LoggerAspect { /** * 定義一個方法,用於聲明切入點表達式,方法中一般不需要添加其他代碼 * 使用@Pointcut聲明切入點表達式 * 后面的通知直接使用方法名來引用當前的切點表達式;如果是其他類使用,加上包名即可 */ @Pointcut("execution(public * com.parry.redisCluster.controller.*Controller.*(..))") public void declearJoinPointExpression(){} /** * 前置通知 * @param joinPoint */ @Before("declearJoinPointExpression()") //該標簽聲明次方法是一個前置通知:在目標方法開始之前執行 public void beforMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("this method "+methodName+" begin. param<"+ args+">"); } /** * 后置通知(無論方法是否發生異常都會執行,所以訪問不到方法的返回值) * @param joinPoint */ @After("declearJoinPointExpression()") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("this method "+methodName+" end."); } /** * 返回通知(在方法正常結束執行的代碼) * 返回通知可以訪問到方法的返回值! * @param joinPoit */ @AfterReturning(value="declearJoinPointExpression()",returning="result") public void afterReturnMethod(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("this method "+methodName+" end.result<"+result+">"); } /** * 異常通知(方法發生異常執行的代碼) * 可以訪問到異常對象;且可以指定在出現特定異常時執行的代碼 * @param joinPoint * @param ex */ @AfterThrowing(value="declearJoinPointExpression()",throwing="ex") public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("this method "+methodName+" end.ex message<"+ex+">"); } /** * 環繞通知(需要攜帶類型為ProceedingJoinPoint類型的參數) * 環繞通知包含前置、后置、返回、異常通知;ProceedingJoinPoin 類型的參數可以決定是否執行目標方法 * 且環繞通知必須有返回值,返回值即目標方法的返回值 * @param joinPoint */ @Around(value="declearJoinPointExpression()") public Object aroundMethod(ProceedingJoinPoint point){ Object result = null; String methodName = point.getSignature().getName(); try { //前置通知 System.out.println("The method "+ methodName+" start. param<"+ Arrays.asList(point.getArgs())+">"); //執行目標方法 result = point.proceed(); //返回通知 System.out.println("The method "+ methodName+" end. result<"+ result+">"); } catch (Throwable e) { //異常通知 System.out.println("this method "+methodName+" end.ex message<"+e+">"); throw new RuntimeException(e); } //后置通知 System.out.println("The method "+ methodName+" end."); return result; } }
2、基於XML配置的AOP
(1)在spring配置文件中添加aop的命名空間,配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--使Spring支持自動檢測組件,如注解的Controller --> <context:component-scan base-package="com.parry.redisCluster.*" /> <!-- 配置切面的Bean --> <bean id="validationAspect" class="com.cn21.redisCluster.aspect.ValidationAspect"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切點表達式 --> <aop:pointcut id="pointcut" expression="execution(!void com.parry.redisCluster.controller.*.*(..))" /> <!-- 配置切面及配置 --> <aop:aspect order="3" ref="validationAspect"> <!-- 前置通知 --> <aop:before method="beforMethod" pointcut-ref="pointcut"/> <!-- 后置通知 --> <aop:after method="afterMethod" pointcut-ref="pointcut"/> <!-- 返回通知 --> <aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut" returning="result"/> <!-- 異常通知 --> <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> </aop:config> </beans>
(2)實現切面類:
package com.parry.redisCluster.aspect; import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; public class ValidationAspect { /** * 前置通知 * @param joinPoint */ public void beforMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("ValidationAspect this method "+methodName+" begin. param<"+ args+">"); } /** * 后置通知(無論方法是否發生異常都會執行,所以訪問不到方法的返回值) * @param joinPoint */ public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("ValidationAspect this method "+methodName+" end."); } /** * 返回通知(在方法正常結束執行的代碼) * 返回通知可以訪問到方法的返回值! * @param joinPoit */ public void afterReturnMethod(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("ValidationAspect this method "+methodName+" end.result<"+result+">"); } /** * 異常通知(方法發生異常執行的代碼) * 可以訪問到異常對象;且可以指定在出現特定異常時執行的代碼 * @param joinPoint * @param ex */ public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("ValidationAspect this method "+methodName+" end.ex message<"+ex+">"); } }