Aspect-oriented Programming (AOP) 即面向切面编程。
简单来说,AOP 是一种编程范式,允许我们模块化地定义横跨多个对象的行为。AOP 可以帮助我们将应用程序的关注点分离,使得代码更加清晰、易于维护和扩展。
大白话:在方法执行前后运行指定代码,比如日志记录、事务开启/提交/回滚等。
AOP可以帮助我们解决在代码中耦合度高的问题,让我们的代码更加模块化和易于维护。
具体来说,AOP可以通过在运行时动态地将通用功能(例如日志记录、性能分析、事务管理)应用于多个模块,而无需修改它们的代码。这样可以避免代码重复和嵌套,提高代码的复用性和可维护性。
另外,在复杂的业务场景中,多个模块可能需要共享某些共同的功能,而AOP可以让这些功能从模块中抽离出来,以便更好地进行组织和重用。
总的来说,AOP可以让我们更好地实现代码的分离和聚合,从而获得更高效、更可靠的代码。
大白话:增强原方法的功能,解耦通用功能,透明化静默操作。
举例 事务切面切面:
增强原方法的功能:原本方法使用的是数据库连接默认的策略自动提交事务的,有了切面能够保证方法内同一事务了;
解耦通用功能:但是很多方法都需要做事务的控制,有了切面不需要我们每一个方法都加几行相同的代码;
透明化静默操作:方法本身需要知道我怎么开的事务?需要知道我什么时候多打印了日志吗?
@Override public UserPO findByUsername(@AutoTrim String username) { log.info("execute findByUsername by username={}", username); Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username)); Assert.isTrue(opt.isPresent(), "没有找到用户"); UserPO userPO = opt.get(); log.info("execute findByUsername by username={}; return {} ", username, userPO); return userPO; } @Override public UserPO findByEmail(@AutoTrim String email) { log.info("execute findByEmail by email={}", email); Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email)); Assert.isTrue(opt.isPresent(), "没有找到用户"); UserPO userPO = opt.get(); log.info("execute findByEmail by email={}; return {} ", email, userPO); return userPO; }
@Override public UserPO findByUsername(@AutoTrim String username) { Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username)); Assert.isTrue(opt.isPresent(), "没有找到用户"); return opt.get(); } @Override public UserPO findByEmail(@AutoTrim String email) { Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email)); Assert.isTrue(opt.isPresent(), "没有找到用户"); return opt.get(); }
此处代码演示 【part1】
演示内容:
UserServiceImpl
注入Bean,测试接口查看日志打印;UserServiceAopImpl
注入Bean,测试接口查看日志打印;Pointcut
为logPointcut2()
;@ConditionalOnProperty
,修改yml文件;例如:测试环境要开启日志,生产环境要关闭日志,通过配置灵活控制。PS:我们在此暂不考虑基于
XmlApplicationContext
系列;
AOP的实现依赖于多态和动态代理。
为了更好的理解,我们可以先举例一个静态代理的类来分析。
此处代码演示 【part2】
演示内容:
UserServiceStaticAopImpl
注入Bean,测试接口查看日志打印;UserServiceStaticAopAnyImpl
注入Bean,测试接口查看日志打印;PS:我们在此暂不考虑基于
XmlApplicationContext
系列;
静态代理的AOP结构(简单易理解版):
切面:指横跨多个类的一个关注点,它与不同类中相似的方法相对应。如保存数据时添加日志,可以新建一个切面配置日志记录逻辑。
大白话:一个模块化的切面程序,也可以理解为是一个实现切面功能的类;
用@Aspect定义的Bean Class,或者Spring xml配置里的aop:aspect标签:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config>
连接点:指在应用程序执行过程中的某个特定位置,如方法调用或异常处理等。
可以理解为要切面的对象类型,比如要加切面的目标是构造器,或者一个方法,或者是一个属性的赋值;
AspectJ中可以有很多种,详见AspectJ Join Points;
SpringAOP中只有一种,就是方法执行(Method execution);
通知:指在切面的某个连接点上执行的代码。通知有许多类型,包括“前置通知”、“后置通知”、“返回通知”、“异常通知”、“环绕通知”,其中“环绕通知”能够完全控制目标方法的执行。
切入点:指一个或多个连接点,通常定义在一个正则表达式中,描述哪些方法会被拦截。
参考:
Join Points and Pointcuts
Spring 之AOP AspectJ切入点语法详解
例:within(com.supalle.springaop..*.UserServiceAopImpl)
execution(* com.supalle.springaop.*.*(..))
within(com.supalle.springaop.TestController) && @annotation(com.supalle.springaop.Log)
详见:AOP Concepts
此处代码演示 【part3】
XmlApplicationContext
的应用需要开启<aop:aspectj-autoproxy />
AnnotationConfigApplicationContext
的应用需要开启@EnableAspectJAutoProxy
;SpringBoot环境下可以不用,在org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
里已经默认启用;
package com.supalle.springaop.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 日志切面 * * @author supalle * @see {@link https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts} */ @Slf4j @Aspect @Component //@ConditionalOnProperty(value = "log-execution", havingValue = "true") public class LogAspect { @Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl)") // the pointcut within *AopImpl private void logPointcut() { } @Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl) || within(com.zaxxer.hikari.HikariDataSource)") private void logPointcut2() { } @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { log.info("Before Advice 1 execute {}", joinPoint.getSignature().getName()); } // 同一个Aspect内,@Order排序无效,需要靠方法名排序,比如把当前方法改为beforeLog2,就能运行在beforeLog前面 @Before("logPointcut()") public void beforeLog2(JoinPoint joinPoint) { log.info("Before Advice 2 execute {}", joinPoint.getSignature().getName()); } @AfterReturning(value = "logPointcut()", returning = "returnValue") public void afterReturningLog(JoinPoint joinPoint, Object returnValue) { log.info("AfterReturning Advice execute {}; {} ", joinPoint.getSignature().getName(), returnValue); } @AfterThrowing(value = "logPointcut()", throwing = "throwable") public void afterThrowingLog(JoinPoint joinPoint, Throwable throwable) { log.info("AfterThrowing Advice execute {}; {}", joinPoint.getSignature().getName(), throwable.getMessage()); } @After("logPointcut()") public void afterLog(JoinPoint joinPoint) { log.info("After Advice execute {}", joinPoint.getSignature().getName()); } @Around("logPointcut()") public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable { Signature joinPointSignature = joinPoint.getSignature(); String name = joinPointSignature.getName(); Logger logger = LoggerFactory.getLogger(joinPointSignature.getDeclaringTypeName()); Object[] args = joinPoint.getArgs(); String[] argNames = ((MethodSignature) joinPointSignature).getParameterNames(); String argString = ""; if (args != null && args.length > 0) { argString = " by " + IntStream.range(0, args.length) .mapToObj(index -> argNames[index] + "=" + args[index]) .collect(Collectors.joining(" , ")); } logger.info("Around Advice execute {}{}", name, argString); Object returnValue = joinPoint.proceed(); logger.info("Around Advice execute {}{}; return {} ", name, argString, returnValue); return returnValue; } }
略
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
中会查找容器中所有的Advisor类型Bean,也属于AutoProxy的支持;
LogBeforeAdvice
:定义一个Advice,加@Component
注册为Beanpackage com.supalle.springaop.advice; import lombok.extern.slf4j.Slf4j; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry的支持 */ @Slf4j @Component public class LogBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { log.info("LogBeforeAdvice execute {}", method.getName()); } }
LogBeforeAdvisor
:定义一个Advisor,加@Component
注册为Beanpackage com.supalle.springaop.advisor; import com.supalle.springaop.advice.LogBeforeAdvice; import lombok.RequiredArgsConstructor; import org.aopalliance.aop.Advice; import org.springframework.aop.ClassFilter; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor public class LogBeforeAdvisor extends AbstractPointcutAdvisor { private final LogBeforeAdvice advice; @Override public Pointcut getPointcut() { Pointcut pointcut = new Pointcut() { @Override public ClassFilter getClassFilter() { return clazz -> "UserServiceAopImpl".equals(clazz.getSimpleName()); } @Override public MethodMatcher getMethodMatcher() { return MethodMatcher.TRUE; } }; return pointcut; } @Override public Advice getAdvice() { return advice; } }
运行代码后调试接口,看控制台打印输出:
: LogBeforeAdvice execute findByUsername
用的不多,可以参考Programmatic Creation of @AspectJ Proxies
动态代理是指在程序运行时动态生成代理类的技术,即不需要手工编写代理类的源代码,而是在程序运行期间通过反射等机制动态地生成。
动态代理模式可以帮助我们减少重复的代码,提高代码的可维护性和可扩展性。通常情况下,我们都是通过实现接口来创建代理对象,但是如果一个类没有实现任何接口,我们仍然可以通过动态代理来创建代理对象。动态代理模式适用于一些横切关注点(cross-cutting concerns)的处理,例如日志、安全、事务等功能。在 Java 中,动态代理模式主要有两种实现方式:
基于 JDK 动态代理(JDK Dynamic Proxy):JDK 提供了一个 java.lang.reflect.Proxy 类,可以动态地创建实现一组给定接口的代理类。要求被代理对象必须实现至少一个接口,并通过 Proxy 类的静态方法 newProxyInstance 来创建代理对象。
基于 CGLIB 动态代理:CGLIB(Code Generation Library) 是一个基于 ASM(Java 字节码操作框架)的高性能字节码生成库,可以在运行时动态生成字节码,并生成对应的代理类。要求被代理对象必须有默认构造函数,并通过 CGLIB 库提供的代理工厂(Enhancer 类)来创建代理对象。
总之,动态代理模式可以帮助我们在程序运行期间动态地生成代理类,从而达到增强功能、添加处理逻辑等目的。
Spring AOP 就是动态代理的一种落地实现,底层是通过JDK Proxy和Cglib&ASM策略来进行动态生成代理类;
Spring Native应用下实现AOP的话,刚出来的时候我看过一遍,因为无法动态生成字节码,所以是在AOT编译时,给应用中所有的类都生成了一个代理类,当这个类在实际运行时需要动态代理时,则加载这个代理类直接使用。(时过境迁,现在不知道实现方式变了没有)
AOT(Ahead-of-time )compiler:预先编译器;
JIT(Just-In-Time)compiler:即时编译器;
除了上文说的AOP基本概念在Spring中有对应的类外,Spring AOP APIs中还有其它几个核心类,用于具体的AOP实现。
Pointcut
+ Advice
的组合;
- JdkDynamicAopProxy
- ObjenesisCglibAopProxy
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
用UML画出他们的结构图如下:
以UserService
为例,Spring AOP生成的代理类大致结构:
仔细思考,上文举例的静态代理实现AOP的代码,是不够健全的,尤其是在Advice
的顺序上;
此处代码演示 【part2】:beforeAspects
切面中添加 System.out.println(1/0);使其抛出异常
引用Spring官方文档的原文:
Advice: Action taken by an aspect at a particular join point. Different types of advice include "around", "before", and "after" advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
这句“Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.”翻译过来就是“包括Spring在内的许多AOP框架都将Advice建模为拦截器,并在连接点周围维护拦截器链。”
很好,使用类似于javax.servlet.Filter
拦截器的方式,就可以很好的控制Advice
的执行顺序。
详见:
org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
时序图大致如下:
调用链的顺序,基于Advised中的advisors集合顺序,主要的排序策略如下:
Advisor
的order
值,没有则取Advice
的order
值,还没有则为null
,约等于最低优先级;@Aspect
类中定义多个Advice时,先按Around
, Before
, After
, AfterReturning
, AfterThrowing
排序,再按方法名排序;Advice
类实现多个Advice
类型时,按 MethodInterceptor
,BeforeAdvice
,AfterReturningAdvice
,AfterThrowsAdvice
排序;AbstractAdvisingBeanPostProcessor
的后置处理器添加的Advisor
,后置处理器可以通过beforeExistingAdvisors
属性控制是否排序在所有已经存在的Advisor
前;
- Spring官方文档:Advice Ordering
- 排序Advisors:
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#sortAdvisors
- 排序同一Aspect中的Advice:
org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethods
PS:如果Advisor/Advice是实现了Ordered接口的话,@Order注解是不生效的。
ProxyConfig
下分几个家族,都可以添加Advisor
,创建AOP代理类。
排序:Ordered=Ordered.HIGHEST_PRECEDENCE; // Integer.MIN_VALUE ;方法内写死,继承才可以修改
注意:AsyncAnnotationAdvisor
是在AsyncAnnotationBeanPostProcessor
后置处理器中创建和匹配,beforeExistingAdvisors=true;排序在所有已经存在的Advisor前。
Spring异步执行的AOP支持。
排序:Ordered=Ordered.HIGHEST_PRECEDENCE; // 继承自父类
AsyncExecutionInterceptor
Spring注解@Async
异步执行的AOP支持。
排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 继承自父类
AbstractPointcutAdvisor
,可set;
注意:RetryConfiguration
实现IntroductionAdvisor
,排序优先于同order的未实现IntroductionAdvisor
的Advisor。
Spring注解@Retryable
方法失败重试的AOP支持。
排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 继承自父类
AbstractPointcutAdvisor
,可set,
默认来自@EnableCaching
的order
属性,也是LOWEST_PRECEDENCE
,可以直接在注解里修改。
Spring注解缓存@EnableCaching
、@Cacheable
等的AOP支持。
排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 继承自父类
AbstractPointcutAdvisor
,可set,
默认来自@EnableTransactionManagement
的order
属性,也是LOWEST_PRECEDENCE
,可以直接在注解里修改。
Spring注解事务@Transactional
的AOP支持。
Spring AOP代理类创建的时机,主要是在Bean被引用之前,依靠BeanPostProcessor
来实现;
这就要靠ProxyProcessorSupport
家族的AbstractAdvisingBeanPostProcessor
和AbstractAutoProxyCreator
了。
AbstractAdvisingBeanPostProcessor
对单个Advisor
进行AOP切面的添加;AbstractAutoProxyCreator
对所有满足Pointcut
的Bean进行切面的添加;两者大部分情况会在postProcessAfterInitialization()
钩子方法中创建Bean的AOP代理对象,但也有一些特殊情况,比如自定义TargetSource
的时候,会在postProcessBeforeInstantiation()
钩子方法中创建代理对象;
还有一种情况是在getEarlyBeanReference()
钩子方法时创建,也会提前创建好AOP代理对象;
详见源码:
org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
另外注释一下:postProcessBeforeInstantiation()
钩子执行时,Bean已经完成了 创建对象、属性填充、依赖注入、初始化方法的执行;
其它自动创建的还有ProxyFactoryBean
、AbstractSingletonProxyFactoryBean
、ScopedProxyFactoryBean
的派生类也会在初始化Bean时自动创建;
Spring IOC容器在扫描所有的BeanDefinition
后,会先按顺序初始化完所有的BeanPostProcessor
,在初始化其它普通的Bean
;而AbstractAdvisingBeanPostProcessor
和AbstractAutoProxyCreator
这两个处理自动AOP的后置处理器排序都是 Ordered=LOWEST_PRECEDENCE
,因此,在这两后置处理器之前初始化的Bean
都无法被他们进行自动AOP代理。
Customizing Beans by Using a BeanPostProcessor
Spring官方提示:
BeanPostProcessor instances and AOP auto-proxying
Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessor instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessor instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.
源码详见:org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors
AbstractAutoProxyCreator
中,有三个Map
对象保存AOP代理类的创建信息,分别是 targetSourcedBeans
、advisedBeans
、earlyProxyReferences
,当三者内存在Bean的代理对象时,则不会重复创建;AbstractAdvisingBeanPostProcessor
中会判断 bean instanceof Advised
,如果已经是AOP代理对象,则不会再创建新的代理对象,直接在此代理对象上添加Advisor
;常说的Spring三级缓存指的是org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
类中的三个Map类型的属性,分别是:
Map<String, Object> singletonObjects; // 存放初始化完成的单例Bean Map<String, Object> earlySingletonObjects; // 存放getEarlyBeanReference()钩子创建的早期Bean Map<String, ObjectFactory<?>> singletonFactories; // 存放earlySingletonObject的工厂实例
IOC默认开启允许循环引用,但是在依赖注入时,但是Spring并不想把创建AOP代理的行为提前,因此设计了getEarlyBeanReference
钩子和singletonFactories
、earlySingletonObjects
,以支持循环引用。
在实例化Bean
后,先创建出earlySingletonObject
的工厂实例,以备后续依赖注入的其它Bean
引用当前Bean
,当真实存在循环依赖时,调用earlySingletonObject
的工厂的方法构建早期Bean
供其注入,并将这个早期Bean
存放到earlySingletonObjects
中,以备循环依赖不止一次;最终在当前Bean初始化完成后,移除早期Bean
,将最终的Bean
放入singletonFactories
中,成为正式暴露的Bean实例;org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
// Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName);
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
篇幅太长,略
Spring缓存切面主要依赖于AOP的实现,当对象内进行this.doSomething()调用时,因为没有经过代理对象,所以也没有缓存切面的功能;
与缓存切面一样,Spring事务切面主要依赖于AOP的实现,当对象内进行this.doSomething()调用时,因为没有经过代理对象,所以也没有事务切面的功能;
因为事务切面会在执行方法之前开启事务,之后再加锁,当锁住的代码执行完成后,在提交事务,
因此synchronized代码块执行是在事务之内执行的,可以推断在代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的库存数据不是最新的。
解决上面的方法,比较简单的可以在update方法之前加上synchronizedQ在还没有开事务之间就加锁,那么就可以保证线程同步