概述
面向切面编程(AOP)是面向对象编程(OOP)的补充,它提供了另一种关于程序结构的思考方式。OOP中模块化的关键单元是类,而在AOP中,模块化单元是切面。切面支持跨多个类型和对象的切点(如事务管理)的模块化。
Spring AOP 是 Spring 框架的关键组件之一。Spring IOC 容器不依赖于AOP组件,如果不要我们项目中不需要 AOP 功能那么就可以不加载这个模块。AOP 补充了 Spring IOC,以提供一个非常强大的中间件解决方案。
Spring Aop
Spring Aop 核心概念
- Aspect: 切面是包含了多个切点,事务管理器(Transaction management)就是一个很好的例子,通常在 spring aop 中通过 @Aspect 来申明
- Join point: 连接点,表示具体的方式执行的一个点,异常处理或者方法执行。
- Advice: 通知,在特定的连接点才去操作,比如在通知之前,通知之后,通知过程中增加自己的逻辑。
- Pointcut: 切点,表示我们想再那个具体的方法,那个具体或者匹配一类 "select" 开头的方法。这里支持 EL 表达式进行匹配。
- Introduction: 引入,表示 AOP 可以在代理过程中,将一个已有的类引入新的接口。
- Target object: 当前对象,其实也是原始对象,如果发生 AOP 代理的时候,此时返回的始终是 AOP 代理对象。
- AOP proxy: AOP 创建的代理对象。
- Weaving: 切面植入时机,可以是运行时,运行前,运行后。
Spring AOP 包括以下几个类型的通知(Advice):
前置通知:在连接点之前运行的通知,但是它不能阻止执行流程前进到连接点(除非它引发异常)。
后置通知:在连接点正常完成之后要运行的通知(例如,如果方法返回而没有引发异常)。
异常通知:如果方法因引发异常而退出,则运行通知。
建议(最终)之后:无论连接点退出的方式如何(正常或特殊收益),均应执行通知。
环绕通知:围绕连接点的通知,例如方法调用。这是最有力的通知。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是返回连接点还是通过返回其自身的返回值或引发异常来结束方法的执行
环绕通知是最通用的通知。由于 Spring AOP 与 AspectJ 一样,提供了各种通知类型,建议使用功能最弱的建议类型,以实现所需的行为。例如,如果您只需要使用方法的返回值更新缓存,则最好使用后置通知而不是环绕通知,尽管环绕通知可以完成相同的事情。使用最具体的通知类型可以提供更简单的编程模型,并减少出错的可能性。例如,您不需要调用用于around通知的proceed() 方法JoinPoint,因此不会导致主业务失败。
所有建议参数都是具体的类型,因此您可以使用适当类型(例如,方法执行的返回值的类型)而不是Object数组的建议参数。
切入点匹配的连接点的概念是 AOP 的关键,它与仅提供拦截功能的旧技术有所不同。切入点使建议的目标独立于面向对象的层次结构。例如,可以将提供声明性事务管理的环绕建议应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。
Spring Aop 实现概述
Spring AOP默认将标准JDK动态代理用于AOP代理。这使得可以代理任何接口(或一组接口)。
Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口是必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于最好是对接口而不是对类进行编程,因此业务类通常实现一个或多个业务接口。在某些情况下(可能极少数),当您需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法时,可以强制使用 CGLIB。
总结: Spring AOP 默认是采用 JDK 动态代理,通常情况下,如果一个类没有实现接口那么就是用 GCLIB 代理
Spring Aop 常用注解
1. 启动 @AspectJ支持
要在Spring配置中使用@AspectJ切面,您需要启用Spring支持以基于@AspectJ方面配置Spring AOP,并基于这些切面是否触发通知对Bean进行自动代理。通过自动代理,我们的意思是,如果Spring确定一个或多个切面通知使用bean,它将自动为该bean生成一个代理以拦截方法调用并确保按需运行建议。
可以使用XML或Java样式的配置来启用@AspectJ支持。无论哪种情况,您都需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径(版本1.8或更高版本)上。
代码例子:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
2. 声明一个切面
启用@AspectJ支持后,@Aspect Spring会自动检测到在应用程序上下文中使用@AspectJ方面(具有注释)的类定义的任何bean,并用于配置Spring AOP。下面是一个声明切面的例子
代码例子:
@Aspect
public class LogAspect {
}
3. 声明一个切入点
切入点确定了拦截的连接点,从而使我们能够控制运行建议的时间。Spring AOP仅支持Spring Bean的方法执行连接点,因此您可以将切入点视为与Spring Bean上的方法执行相匹配。切入点声明由两部分组成:一个包含名称和任何参数的签名,以及一个切入点表达式,该切入点表达式精确地确定我们感兴趣的方法执行。在AOP的@AspectJ批注样式中,常规方法定义提供了切入点签名。 并通过使用@Pointcut 注解定义切入点表达式(用作切入点签名的方法必须具有void返回类型)。
一个例子:
@Pointcut("execution(* cn.edu.xxx.service.*.*(..))")
public void serviceOperation() {
}
Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符:
- execution:用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。
- within:将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。
- this:限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。
- target:在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。
- args:将匹配限制为连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例。
- @target:在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。
- @args:限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。
- @within:将匹配限制为具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释的类型中声明的方法的执行)。
- @annotation:将匹配点限制为连接点的主题(在Spring AOP中运行的方法)具有给定注释的连接点。
4. 定义通知
议与切入点表达式关联,并且在切入点匹配的方法执行之前,之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。
在同一个定义的通知方法@Aspect的类,需要在同一连接点运行基于分配优先级上按以下顺序他们的通知类型,从最高到最低的优先级:@Around,@Before,@After, @AfterReturning,@AfterThrowing。但是请注意,由于Spring的实现风格,在同一方面中的any或advice方法之后AspectJAfterAdvice,@Afteradvice方法将被有效地调用。@AfterReturning 和 @AfterThrowing
当在同一类中@After定义的两个相同类型的建议(例如,两个建议方法)@Aspect都需要在同一连接点上运行时,其顺序是不确定的(因为无法通过以下方式检索源代码声明顺序) Javac编译类的反射)。请考虑将此类建议方法折叠为每个@Aspect类中每个连接点的一个建议方法,或将这些建议重构为单独的@Aspect类,您可以通过Ordered或 在方面级别进行排序@Order。
定义通知的例子:
@Before("cn.edu.cqvie.aspect.LogAspect.serviceOperation()")
public void doServiceCheck() {
System.out.println("doServiceCheck .....");
}
@After("cn.edu.cqvie.aspect.LogAspect.serviceOperation()")
public void doReleaseLock() {
System.out.println("doReleaseLock .....");
}
@AfterReturning(
pointcut="cn.edu.cqvie.aspect.LogAspect.serviceOperation()",
returning="retVal")
public void doServiceCheck(Object retVal) {
System.out.println("doServiceCheck ....." + retVal);
}
5. 引入
简介(在AspectJ中称为类型间声明)使方面可以声明建议对象实现给定的接口,并代表那些对象提供该接口的实现。
您可以使用 @DeclareParents 进行引入。此批注用于声明匹配类型具有新的父类(因此具有名称)。
Spring Aop 核心原理
前面我们分析了 Spring AOP 的使用方法和基础概念,下面我们来继续分析一下 Spring AOP 的原理。
AnnotationAwareAspectJAutoProxyCreator AOP 后置处理器
Spring 再初始化 bean 的过程中,完成初始化过后,就执行 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization 来执行代理,所以,代理的时机是在 Spring 初始化完成之后,并且在将 Bean 完整对象放入 Spring Bean 容器之前其目的就是将 Bean 的原始对象 BeanWapper 转换为一个代理对象。执行的过程如下:
- 先判断当前的 bean 是不是要进行AOP,比如当前的Bean的类型是 Pointcut, Advice, Advisor 等那些就不需要 AOP。
- 如果匹配到 Advisors 不为 null, 那么进行代理并且返回代理对象。
- 判断代理实现如果实现如果没有设置为 GCLIB 并且实现了接口,那么就采用 JDK 代理,否则使用 GCLIB。
- 执行代理的代码。
ProxyFactory 代理工厂
ProxyFactory 是一个代理工厂类,我们在使用 AOP 代理之前首先需要通过代理工厂获取一个具体的 AOP 代理对象 AopProxy 的实例,Spring 的源码如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// config 就是 ProxyFactory 对象
// optimize为 true,或 proxyTargetClass 为 true,或用户没有给 ProxyFactory 对象添加 interface
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.");
}
// targetClass是接口,直接使用Jdk动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用Cglib
return new ObjenesisCglibAopProxy(config);
}
else {
// 使用Jdk动态代理
return new JdkDynamicAopProxy(config);
}
}
在这里我们需要关注两个类 JdkDynamicAopProxy, ObjenesisCglibAopProxy
JdkDynamicAopProxy
下面是生成代理对象的代码:
// 获取代理对象
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
// 查找所有需要实现的接口
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 创建代理对象
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
我们再来看看当我们调用目标对象的方法时候,就会来调用 JdkDynamicAopProxy#invoke 方法来实现代理对象。其实核心的原理就是生成了一个代理类,然后去查找关联的 MethodInterceptor r然后在代理方法中去把这些串起来实现代理。下面我们来看看 inovke 的具体实现:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
// 目标没有实现自己的 equals 方法
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
// 目标没有实现自己的 hashCode 方法
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
// 根据代理对象的配置调用服务,如果是Advised接口的实现类,则直接调用
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
// 有可能为null.尽可能减少拥有目标对象的时间,在这种情况下对象来自于对象池
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
// 获得这个方法的拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
// 如果没有拦截器链,则直接调用目标对象
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
// 构造一个方法调用
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 调用连接点的拦截器链
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
// 必须来自TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
// 保存旧的代理对象
AopContext.setCurrentProxy(oldProxy);
}
}
}
总结一下: 这个过程其实可以分为三步:
- 获取原始对象和原始对象的类型,原始对象再
targetSource中获取到target, 然后再通过target获取targetClass - 通过当前对象的
ProxyFactory所添加的并匹配的Advisor封装成MethodInterceptor的拦截器链chain - 如果chain为空,则直接执行target对应的当前方法
- 如果chain不为空,则会依次执行chain中的MethodInterceptor。 执行的顺序,会按照通知的设定执行,如果存在相同类型的通知,会按照排序进行执行。
ObjenesisCglibAopProxy
gclib 的代理过程和 jdk 代理很多类似目前我就不再这里详细描述,下面是 gclib 代理对象创建的过程:
- 创建Enhancer
- 设置Enhancer的superClass为通过ProxyFactory.setTarget()所设置的对象的类
- 设置Enhancer的interfaces为通过ProxyFactory.addInterface()所添加的接口,以及SpringProxy、Advised接口
- 设置Enhancer的Callbacks为DynamicAdvisedInterceptor
- 最后通过Enhancer创建一个代理对象
查看生成的代理类代码
//该设置用于输出cglib动态代理产生的类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
//该设置用于输出jdk动态代理产生的类 默认在com/sun/proxy 目录下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Spring Aop 使用方式
通过实现PointcutAdvisor接口
- BeanNameAutoProxyCreator
@Bean
public BeanNameAutoProxyCreator creator(){
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setBeanNames("userService");
beanNameAutoProxyCreator.setInterceptorNames("myAdvisor");
return beanNameAutoProxyCreator;
}
定义的这个bean,相当于一个“自动代理”器,有了这个Bean之后,可以自动的对setBeanNames中所对应的bean进行代理,代理逻辑为所设置的interceptorNames
- DefaultAdvisorAutoProxyCreator
// 定义了 Advisor 过后 DefaultAdvisorAutoProxyCreator
// 回自动去查找 BeanPostProcessor
@Bean
public DefaultAdvisorAutoProxyCreator creator1(){
DefaultAdvisorAutoProxyCreator creator
= new DefaultAdvisorAutoProxyCreator();
return creator;
}
定义 Advisor
@Component
public class MyAdvisor implements PointcutAdvisor {
@Override
public Pointcut getPointcut() {
NameMatchMethodPointcut methodPointcut = new NameMatchMethodPointcut();
methodPointcut.setMappedName("test");
return methodPointcut;
}
@Override
public Advice getAdvice() {
return new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("方法执行之前");
}
};
}
@Override
public boolean isPerInstance() {
return false;
}
}
在 AbstractAutoProxyCreator#postProcessAfterInitialization 后置处理器方法执行的时候。
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
// wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
//当前这个 Bean 不用被代理
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//1.先判断当前的 bean 是不是要进行AOP,比如当前的Bean的类型是 Pointcut, Advice, Advisor 等那些就不需要 AOP
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
//2.如果匹配到 Advisors 不为 null, 那么进行代理并且返回代理对象
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) { //需要动态代理
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//3. 基于 bean 对象和 advice 创建代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
//存一个代理对象的类型
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
后面会调到 AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean 然后通过 findEligibleAdvisors 方法查找到所有的 Advisor。 后面再对 Bean 和 Advisor 进行绑定。
通过@Aspect、@Pointcut、@Before等注解
注解方式实现,再前面概念描述的时候已经提到本处不在详细描述。
Spring 源码解析
- Spring 启动过程
- Spring Bean 的生命周期
- Spring 属性注入
- Spring 循环依赖
- Spring Aop
- Spring 事务处理
- Spring 整合 MyBatis
- Spring 常见问题