Spring 拾遗 - AOP
概述
AOP(Aspect-Oriented Progarmming),面向切面编程 / 面向方面编程,是Spring框架提供的关键特性之一;使用AOP技术,可以将一些系统层面的问题分离出来独立编程实现,并在适当的时机织入系统,以增强目标实现,对于广大开发者而言,日常应用中主要用来解决的问题包括事务控制、权限控制、日志管理等;使用AOP特性,可以避免在业务逻辑中过多的掺杂公共问题的解决方案,故此,它也是OOP(Object-Oriented Programming)的有效补充;AOP思想在其它开源软件中也有广泛的应用,大家熟悉且比较经典的,莫过于Struts2的拦截器了。
术语定义
- Aspect:切面,通常是一个类,包含切入点和通知;
- Pointcut:切入点,在程序中,通常体现为切入点表达式;
- Advice:通知,在特定的切入点上执行的增强处理,通常是一个方法,定义增强实现的逻辑;
- Join Point:连接点,程序执行过程中一个明确的点,通常为方法的调用;
- Target Object:目标对象,即被通知的对象 / 被增强的对象 / 被代理的对象。
通知(Advice)类型
- @Before:前置通知,在目标方法执行之前做增强处理;
- @Around:环绕通知,在目标方法执行前后做增强处理,注意,编程核心为ProceedingJoinPoint类;
- @After:后置通知,在目标方法完成之后做增强处理(无论目标方法是正常完成,还是异常退出);
- @AfterReturning:返回后通知,在目标方法正常完成后做增强处理,区别于@After;注解可传入returning形参,用以表示目标方法的返回值;
- @AfterThrowing:异常时通知,在目标方法抛出异常时做增强处理;注解可传入throwing形参,用以表示目标方法抛出的异常。
实现原理
如果被代理对象实现了接口,使用JDK动态代理实现,如果被代理对象未实现接口,则使用CGLIB实现。通过阅读Spring实现AOP的源码可知,Spring默认使用JDK动态代理技术实现AOP。
@SuppressWarnings("serial") public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 有以下逻辑可知,Spring默认使用JDK动态代理技术实现AOP if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
配置示例
定义示例所需的基础类,源码清单如下:
UserServiceI.java
public interface UserServiceI { public void addUser(); }
UserServiceImpl.java
@Service("userService") public class UserServiceImpl implements UserServiceI { public void addUser() { System.out.println("UserServiceImpl.addUser()"); } }
UserServiceAspect.java
/** * 切面类,定义对目标方法的增强处理 * * @author */ @Component("userServiceAspect") public class UserServiceAspect { public void before() { System.out.println("Before Advice"); } public void around(ProceedingJoinPoint proceed) throws Throwable { System.out.println("Around Advice before"); proceed.proceed(); System.out.println("Around Advice after"); } public void after() { System.out.println("After Advice"); } public void afterReturning() { System.out.println("AfterReturning Advice"); } public void afterThrowing() { System.out.println("AfterThrowing Advice"); } }
基于xml文件的方式配置aop
<aop:config> <!-- 切入点定义,此处为UserServiceImpl类中的所有方法 --> <aop:pointcut expression="execution(* mysource.demo.spring_aop_xml.*Impl.*(..))" id="userPointcut"/> <!-- 切面配置,对 UserService 增强处理 --> <aop:aspect ref="userServiceAspect" id="userServiceAspect"> <aop:before method="before" pointcut-ref="userPointcut" /> <aop:around method="around" pointcut-ref="userPointcut" /> <aop:after method="after" pointcut-ref="userPointcut" /> <aop:after-returning method="afterReturning" pointcut-ref="userPointcut" /> <aop:after-throwing method="afterThrowing" pointcut-ref="userPointcut" /> </aop:aspect> </aop:config>
基于注解的形式配置aop
修改UserServiceAspect.java文件,添加@Aspect注解,使其具有AOP切面的特性;使用@Pointcut定义切入点;并分别使用注解定义通知方式,如下代码所示:
package mysource.busi.aop; 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.stereotype.Component; /** * 切面类,定义对目标方法的增强处理 * * @author */ @Component @Aspect public class UserServiceAspect { /** * 定义切入点,此处匹配 UserServiceI 类中的所有方法 */ @Pointcut("execution(* mysource..*.*(..))") public void pointcut() {} @Before("pointcut()") public void before() { System.out.println("Before Advice"); } @Around("pointcut()") public void around(ProceedingJoinPoint proceed) throws Throwable { System.out.println("Around Advice before"); // around 通知中,调用 proceed(),才会执行目标方法 proceed.proceed(); System.out.println("Around Advice after"); } @After("pointcut()") public void after() { System.out.println("After Advice"); } @AfterReturning(pointcut="pointcut()", returning="returning") public void afterReturning(Object returning) { System.out.println("AfterReturning Advice"); } @AfterThrowing(pointcut="pointcut()", throwing="error") public void afterThrowing(Throwable error) { System.out.println("AfterThrowing Advice"); } }
示例运行结果
调用UserServiceI.addUser()方法,输出如下,可以看出切面类对业务方法进行了织入增强:
通知执行优先级
有执行结果可知,先织入@Around通知,接着执行@Before通知,目标方法退出时,先织入@Around通知,接着执行@After通知,最后执行@AfterReturning通知。
AspectJ切点函数
切点表达式语法说明:* 表示任意返回类型,任意类名、方法名,任意参数类型;.. 连续两个点,表示0个或多个包路径,0个或多个参数;
execution,匹配满足模式定义的目标类方法;示例表达式,匹配mysource包及其子包中所有类方法:
@Pointcut("execution(* mysource..*.*(..))")
within,匹配指定包路径(类名也可以看作包名)的所有方法;示例表达式,匹配mysource.aop包中所有的类方法:
@Pointcut("execution(* mysource.aop.*)")