Spring--AOP
Posted Wayfo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring--AOP相关的知识,希望对你有一定的参考价值。
AspectJ:Java 社区里最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
1).加入jar包
Spring 中启用 AspectJ 注解支持
org.aspectjweaver-1.8.9.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
commons-logging-1.1.1.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
2).在配置文件中加入aop的命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
3).基于注解的方式
①.在配置文件中加入如下配置
<aop:aspectj-autoproxy/>
当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
②.创建切面类
切面类要被IOC容器管理,加入@Component注解;切面还需要加入@Aspect注解
用 AspectJ 注解声明切面,要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
③.在切面类中声明各种通知
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
i.前置通知:
在方法执行之前执行的通知
前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值
ii.后置通知
后置通知在目标方法执行后(无论是否发生异常),执行的通知
iii.返回通知
无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点正常返回的时候记录日志,应使用返回通知代替后置通知在返回通知中, 只要将 returning 属性添加到@AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称. 必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.
iiii.异常通知
只在连接点抛出异常时才执行异常通知
将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
iiiii.环绕通知
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
④.Demo
package yang.mybatis.test.AOP; public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
目标对象(Target)
package yang.mybatis.test.AOP; import org.springframework.stereotype.Component; @Component("calculator") public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
切面(Aspect)
package yang.mybatis.test.AOP; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 1. 加入 jar 包 * 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="..."></context:component-scan> * 3.2 加入使 AspjectJ 注解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy> * 为匹配的类自动生成动态代理对象. * * 4. 编写切面类: * 4.1 一个一般的 Java 类 * 4.2 在其中添加要额外实现的功能. * * 5. 配置切面 * 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解 * 5.2 声明是一个切面: 添加 @Aspect * 5.3 声明通知: 即额外加入功能对应的方法. * 5.3.1 前置通知: @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))") * @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体. * @Before 里面的是切入点表达式: * * 6. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数. * * 7. @After 表示后置通知: 在方法执行之后执行的代码. */ //通过添加 @Aspect 注解声明一个 bean 是一个切面! @Aspect @Component public class LoggingAspect { //利用方法签名编写 AspectJ 切入点表达式 @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))") //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值. 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(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); } //返回通知,无异常时执行,返回通知是可以访问到方法的返回值的 @AfterReturning(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))", returning = "result") public void afterReturnMethod(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with "+result); } //在目标方法出现异常时会执行的代码 //可以访问到异常对象;且可以指定在出现特定异常时执行通知代码 @AfterThrowing(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception with "+ex); } /** * 环绕通知需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法. * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ /* @Around("execution(public int public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))") 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; }*/ }
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml"); Calculator calculator = (Calculator) applicationContext.getBean("calculator"); System.out.println(calculator.getClass().getName()); int result = calculator.add(11, 12); System.out.println("result:" + result); result = calculator.div(21,1); System.out.println("result:" + result);
4).指定切面的优先级
在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
切面的优先级可以通过利用 @Order 注解指定.值越小, 优先级越高.
@Order(0) @Aspect @Component public class LoggingAspect { }
@Order(1) @Aspect @Component public class ValidateAspect { }
同一个连接点上LoggingAspect 切面先于ValidateAspect切面执行
5).切入点表达式和重用切入点
典型的切入点表达式时根据方法的签名来匹配各种方法:
execution * com.nchu.spring.Calculator.*(..): // 匹配 Calculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值.
第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名. execution public * ArithmeticCalculator.*(..): // 匹配 Calculator 接口的所有公有方法. execution public double ArithmeticCalculator.*(..): // 匹配 Calculator 中返回 double 类型数值的方法 execution public double ArithmeticCalculator.*(double, ..): //匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数 execution public double ArithmeticCalculator.*(double, double): //匹配参数类型为 double, double 类型的方法.
在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.
在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的. 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.其他通知可以通过方法名称引入该切入点.
改造后的切面
package yang.mybatis.test.AOP; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; //通过添加 @Aspect 注解声明一个 bean 是一个切面! @Order(0) @Aspect @Component public class LoggingAspect { /** * 该方法用于声明切入点表达式,一般的该方法中不需要添加其他代码 */ @Pointcut("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))") public void pointcut(){} /** * 利用方法签名编写 AspectJ 切入点表达式 * @param joinPoint */ @Before("pointcut()") //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值. 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)); } /** * 在后置通知中,因为方法可能会出异常,所以不能访问目标方法的执行的结果 * @param joinPoint */ @After("pointcut()") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); } /** * 返回通知,无异常时执行,返回通知是可以访问到方法的返回值的 * @param joinPoint * @param result */ @AfterReturning(value = "pointcut()", returning = "result") public void afterReturnMethod(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with "+result); } /** * 在目标方法出现异常时会执行的代码 *可以访问到异常对象;且可以指定在出现特定异常时执行通知代码 * @param joinPoint * @param ex */ @AfterThrowing(value = "pointcut()", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception with "+ex); } /** * 环绕通知需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法. * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ /* @Around("pointcut()") 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; }*/ }
6、基于XML的配置
将上文中的两个类中的配置移除,在applicationContext.xml文件中加入以下配置
<bean id="calculator" class="yang.mybatis.test.AOP.XML.CalculatorImpl"></bean> <bean id="loggingAspect" class="yang.mybatis.test.AOP.XML.LoggingAspect"></bean> <!--AOP配置--> <aop:config> <!-- 配置切点表达式 --> <aop:pointcut id="pointcut" expression="execution(public int yang.mybatis.test.AOP.XML.CalculatorImpl.*(..))"></aop:pointcut> <!-- 配置切面及通知 --> <aop:aspect ref="loggingAspect" order="0"> <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before> <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after> <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing> <aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning> <!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>--> </aop:aspect> <aop:aspect ref="vlidationAspect" order="1"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
以上是关于Spring--AOP的主要内容,如果未能解决你的问题,请参考以下文章