我们在业务开发中,使用得最多的是面向对象编程(OOP),因为它的代码逻辑直观,从上往下就能查看完整的执行链路。
在这个基础上延伸,出现了面向切面编程(AOP),将可以重复性的横切逻辑抽取到统一的模块中。
例如日志打印、安全监测,如果按照 OOP 的思想,在每个方法的前后都要加上重复的代码,之后要修改的话,更改的地方就会太多,导致不好维护。所以出现了 AOP 编程, AOP 所关注的方向是横向的,不同于 OOP 的纵向。
所以接下来一起来学习 AOP 是如何使用以及 Spring 容器里面的处理逻辑~
创建用于拦截的 beanpublic class TestAopBean {
private String testStr = "testStr";
public void testAop() {
// 被拦截的方法,简单打印
System.out.println("I am the true aop bean");
}
}
创建 Advisor
@Aspectpublic class AspectJTest {
public class AspectJTest {
@Pointcut("execution(* *.testAop(..))")
public void test() {
}
@Before("test()")
public void beforeTest() {
System.out.println("before Test");
}
@After("test()")
public void afterTest() {
System.out.println("after Test");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint joinPoint) {
System.out.println("around Before");
Object o = null;
try {
// 调用切面的方法
o = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around After");
return o;
}
}
首先类打上了 @Aspect 注解,让 Spring 认识到这个是一个切面 bean,在方法打上 @Pointcut("execution(* *.testAop(..))"),表示这是一个切点方法,execution() 内部的表达式指明被拦截的方法,Before 、After、Around 分别表示在被拦截方法的前、后已经环绕执行。
创建配置文件 aop.xml
<bean id="aopTestBean" class="aop.TestAopBean"/>
<bean class="aop.AspectJTest" />
</beans>
测试 Demo
public class AopTestBootstrap {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
TestAopBean bean = (TestAopBean) context.getBean("aopTestBean");
bean.testAop();
// 输出内容 看输出顺序,了解到增强方法的执行顺序 :
// Around proceed 之前 -> Before -> Around proceed 之后 -> After
//around Before
//before Test
//I am the true aop bean
//around After
//after Test
}
}
根据上面的启动例子,发现在自己写的核心业务方法 testAop() 上,明明只是简单打印了 I am the true aop bean,但执行结果输出了其它内容,说明这个类被增强了,在不修改核心业务方法上,我们对它进行了扩展。证明了 AOP 可以使辅助功能独立于核心业务之外,方便了程序的扩展和解耦。
使用起来很方便,接下来一起来看看 Spring 是如何实现 AOP 功能的吧~
动态 AOP 自定义标签之前在介绍自定义标签时,提到了 AOP 的实现也借助了自定义注解,根据自定义标签的思想:每个自定义的标签,都有对应的解析器,然后借助强大的开发工具 IDEA 定位功能,找到解析器注册的地方:
640?wx_fmt=png
按住 ctrl
,定位标签对应的 xsd
文件
根据命名文件,在 META-INF
目录下找到了 spring.handlers
文件
在处理器文件中发现了处理器 AopNamespaceHandler
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Overridepublic void init() {// In 2.0 XSD as well as in 2.1 XSD.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());// 注释 8.1 自定义注解,注册解析器,元素名是 aspectj-autoproxyregisterBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());// Only in 2.0 XSD: moved to context namespace as of 2.1registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());}}处理器继承自 NamespaceHandlerSupport,在加载过程中,将会执行 init 初始化方法,在这里,会注册 aspectj-autoproxy 类型的解析器 AspectJAutoProxyBeanDefinitionParser
如何注册自定义解析器之前也了解过了,所以接下来直接来看看,遇到 aspectj-autoproxy 类型的 bean,程序是如何解析的。
注册 AnnotationAwareAspectJAutoProxyCreator来看下解析时,它的入口方法如下:
public BeanDefinition parse(Element element, ParserContext parserContext) {
// aop 注解的解析入口,注册 AnnotationAwareAspectJAutoProxyCreatorAopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);// 对注解中子类的处理extendBeanDefinition(element, parserContext);return null;}入口方法一如既往的简洁,交代了要做的事情,然后具体复杂逻辑再交给工具类或者子类继续实现,所以接下来要看的是如何注册 AnnotationAwareAspectJAutoProxyCreator。
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) {
// 通过工具类,注册或升级 AspectJAnnotationAutoProxyCreatorBeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));// 处理 proxy-target-class 以及 expose-proxy 属性useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);// 注册组件并通知,让监听器进行处理registerComponentIfNecessary(beanDefinition, parserContext);}可以看到这个方法内部有三个处理逻辑,所以我们来一个一个去分析了解:
注册或者升级 AnnotationAwareAspectJAutoProxyCreator对于 AOP 的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成,它可以根据 @Point 注解定义的切点来自动代理相匹配的 bean。
由于 Spring 替我们做了很多工作,所以开发 AOP 业务时才可以这么简单,连配置也简化了许多,所以来看下 Spring 是如何使用自定义配置来帮助我们自动注册 AnnotationAwareAspectJAutoProxyCreator。
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) {
// 实际注册的 bean 类型是 AnnotationAwareAspectJAutoProxyCreatorreturn registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);}
private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");if (registry.containsBeanDefinition(AUTOPROXYCREATORBEANNAME)) {// 如果在 registry 已经存在自动代理创建器,并且传入的代理器类型与注册的不一致,根据优先级判断是否需要修改BeanDefinition apcDefinition = registry.getBeanDefinition(AUTOPROXYCREATORBEANNAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {// 根据优先级选择使用哪一个int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {// 传进来的参数优先级更大,修改注册的 beanName,使用传进来的代理创建器apcDefinition.setBeanClassName(cls.getName());}}// 因为已经存在代理器,不需要之后的默认设置,直接返回return null;}RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);beanDefinition.setSource(source);// 默认的是最小优先级beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 自动代理创建器的注册名字永远是 org.springframework.aop.config.internalAutoProxyCreatorregistry.registerBeanDefinition(AUTOPROXYCREATORBEANNAME, beanDefinition);return beanDefinition;}这个步骤中,实现了自动注册 AnnotationAwareAspectJAutoProxyCreator 类,同时能看到涉及到优先级的概念和注册名一直都是 AUTOPROXYCREATORBEANNAME。
处理 proxy-target-class 以及 expose-proxy 属性private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {// 这方法作用挺简单的,就是解析下面两个属性,如果是 true,将它们加入代理注册器的属性列表中// definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE)boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXYTARGETCLASS_ATTRIBUTE));if (proxyTargetClass) {// 处理 proxy-target-class 属性// 与代码生成方式有关,在之后步骤中决定使用 jdk 动态代理 或 cglibAopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSEPROXYATTRIBUTE));if (exposeProxy) {// 处理 expose-proxy 属性// 扩展增强,有时候目标对象内部的自我调用无法实施切面中的增强,通过这个属性可以同时对两个方法进行增强AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}}关于 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); 方法,它是一个属性设置的过程,如果解析到的属性为 true,将它们加入代理注册器的属性列表中,这里不细说下去。
将这两个属性分开熟悉:
proxy-target-classSpring AOP 部分使用 JDK 动态代理 (Proxy InvocationHandler),或者 CGLIB (Code Generation LIB)来为目标对象创建代理。书中提到,推荐使用的是 JDK 动态代理。
如果被代理的目标对象实现了至少一个接口,则会使用 JDK 动态代理。所有该目标类型实现的接口都将被代理。
若该目标对象没有实现任何接口,则创建一个 CGLIB 代理。如果希望代理目标对象的所有方法,而不只是实现自接口的方法,可以通过该属性 proxy-target-class 开启强制使用 CGLIB 代理。
但是强制开启 CGLIB 会有以下两个问题:
如果考虑好上面两个方面,那就可以通过以下两个地方来强制开启 CGLIB 代理:
expose-proxy有时候目标对象内部的自我调用将无法实施切面中的增强。
例如两个方法都加上了事务注解 @Transactional 但是事务类型不一样:
public interface TestService {
void a();void b();}
public class TestServiceImpl implements TestService {
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
System.out.println("Hello world");
}
}
此处的 this 指向了目标对象, this.b() 方法将不会执行 b 事务的切面,即不会执行事务增强。
为了解决这个问题,使 a() 和 b() 方法同时增强,可以通过 expose-proxy 来实现:
创建 AOP 代理前面主要围绕着自动代理器 AnnotationAwareAspectJAutoProxyCreator 的注册流程来讲解,接下来看自动代理器做了什么来完成 AOP 的操作。
下面是 AnnotationAwareAspectJAutoProxyCreator 的继承体系:
640?wx_fmt=png
在图片右上角,发现它实现了 BeanPostProcessor 接口,之前文章提到过,它是一个后处理器,可以在 bean 实例化前后进行扩展。查看了实现了该接口的两个方法,postProcessBeforeInitialization 没有做处理,直接返回该对象。
实际进行处理的是 postProcessAfterInitialization 方法,在 bean 实例化之后的处理,在这一步中进行里代理增强,所以来看下这个方法:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {// 组装 keyObject cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 如果适合被代理,则需要封装指定的 beanreturn wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果已经处理过if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 不需增强if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 给定的 bean 类是否代表一个基础设施类,基础设置类不应代理 || 配置了指定 bean 不需要代理if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 如果存在增强方法则创建代理Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DONOTPROXY) {// 增强方法不为空this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理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;}来提取一下核心流程:
获取增强方法或者增强器我们刚才写的 @Before 、@After 之类的,就是增强方法,AOP 处理时,要先找出这些增强方法。
根据获取的增强进行代理找到增强方法后,需要对这些增强方法进行增强代理,实际上这个 bean 已经不完全是原来的类型了,会变成代理后的类型。
获取增强方法或者增强器入口方法在这里:
protected Object[] getAdvicesAndAdvisorsForBean(
Class
beanClass, String beanName, @Nullable TargetSource targetSource) {// 寻找符合的切面List
protected List
由于逻辑太多,所以接下来贴的代码不会太多,主要来了解它的大致流程,有需要的可以跟着源码工程的注释跟踪完整的流程~:
寻找对应的增强器 findCandidateAdvisorsprotected List
List
主要流程如下:
获取所有 beanName,会将之前在 beanFactory 中注册的 bean 都提取出来。
遍历前一步骤提取出来的 bean 列表,找出打上 @AspectJ 注解的类,进行进一步处理
继续对前一步提取的 @AspectJ 注解的类进行增强器的提取
将提取结果加入缓存中
可以查询代码中的注释,从 [注释 8.3] 到 [注释 8.8 根据切点信息生成增强器] 都是这个方法的处理逻辑
※※在这个流程的最后一步中,会将识别到的切点信息(PointCut)和增强方法(Advice)进行封装,具体是由 Advisor 的实现类 InstantiationModelAwarePointcutAdvisorImpl 进行统一封装。
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
// 简单赋值this.declaredPointcut = declaredPointcut;...
if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
this.pointcut = new PerTargetInstantiationModelPointcut(
this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
this.lazy = true;
}
else {
// A singleton aspect.
this.pointcut = this.declaredPointcut;
this.lazy = false;
// 初始化增强器
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
}
封装体前半部分逻辑只是简单赋值。关键是这个方法 instantiateAdvice(this.declaredPointcut),在这一步中,对不同的增强(Before/After/Around)实现的逻辑是不一样的。在 ReflectiveAspectJAdvisorFactory#getAdvice 方法中区别实现了根据不同的注解类型封装不同的增强器。
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
...// 注释 8.7 根据不同的注解类型封装不同的增强器switch (aspectJAnnotation.getAnnotationType()) {case AtPointcut:
}
return null;
case AtAround:
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
default:
}
}
最后切点方法通过解析和封装成 Advisor,提取到的结果加入到缓存中。细心的你可能会发现除了普通的增强器外,还有另外两种增强器:同步实例化增强器和引介增强器。由于用的比较少,所以我看到源码中这两个分支处理没有深入去学习,感兴趣的同学请继续深入学习这两种增强器~
获取匹配的增强器 findAdvisorsThatCanApply在前面流程中,已经完成了所有增强器的解析,但是对于前面解析到的增强器,并不一定都适用于当前处理的 bean,所以还需要通过一个方法来挑选出合适的增强器。
protected List
ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {// 在这一步中进行过滤增强器return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}}可以看到,具体实现过滤操作的是工具类方法 AopUtils.findAdvisorsThatCanApply:
public static List
if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List
创建代理通过前面的流程,获取到了所有对应 bean 的增强器后,可以开始代理的创建。
protected Object createProxy(Class
beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
ProxyFactory proxyFactory = new ProxyFactory();// 拷贝,获取当前类中的相关属性proxyFactory.copyFrom(this);// 决定对于给定 bean 是否应该使用 targetClass 而不是他的接口代理if (!proxyFactory.isProxyTargetClass()) {// 检查 proxyTargetClass 设置以及 preserveTargetClass 属性if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {// 添加代理接口evaluateProxyInterfaces(beanClass, proxyFactory);}}// 这一步中,主要将拦截器封装为增强器Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);// 定制代理customizeProxyFactory(proxyFactory);// 用来控制代理工厂被配置之后,是否含允许修改通知// 缺省值为 false,不允许修改代理的配置proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 生成代理,委托给了 ProxyFactory 去处理。return proxyFactory.getProxy(getProxyClassLoader());}对于代理类的创建和处理, Spring 委托给了 ProxyFactory 去处理,在上面贴出的函数主要是对 ProxyFactory 的初始化操作,进而对真正的创建代理做准备,主要流程如下:
获取当前类的属性
添加代理接口
封装 Advisor 并加入到 ProxyFactory
设置要代理的类
为子类提供定制的函数 customizeProxyFactory,子类通过该方法对 ProxyFactory 进行进一步的封装
进行获取代理操作
比较关键的是第三个步骤和第六个步骤,其中在第三个步骤中,进行的是拦截器包装,详细代码流程请查 [注释 8.9 为给定的bean创建AOP代理] 和 [注释 8.10 包装拦截器,封装成 Advisor]。
接着,完成了所有增强器的封装过程,到了解析的最后一步,进行代理的创建和获取。
public Object getProxy(@Nullable ClassLoader classLoader) { return createAopProxy().getProxy(classLoader);}return createAopProxy().getProxy(classLoader);}创建代理 createAopProxy()定位到创建代理的代码:
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.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}从上面代码中能看出,根据了几个关键属性,判断创建的是哪种类型的 AopProxy,一种是 JDK 动态代理,另一种是 CGLIB 动态代理。
前面提到过的 proxy-target-class 属性和 targetClass 属性,在这里判断了应该创建哪一个代理。
获取代理 getProxy()640?wx_fmt=png
观察图片以及前面分析,可以知道有两种代理方式:[JDK 动态代理] 和 [CGLIB 动态代理]
同时先说下动态代理的含义:抽象类在编译期间是未确定具体实现子类,在运行时才生成最终对象。
JDK 动态代理JDK 代理是默认推荐的代理方式,使用的是 Proxy InvocationHandler。
可以通过以下方式实现:定义一个接口、实现类,和一个处理器继承于 InvocationHandler,然后重载处理器中的 invoke 方法,对代理对象进行增强。
JdkDynamicAopProxy.java
public Object getProxy(@Nullable ClassLoader classLoader) {
// 注释 8.11 JDK 动态代理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);}获取代理的核心步骤在 Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),第三个参数是 JdkDynamicAopProxy 本身,而且它实现了 InvocationHandler 接口,重载了 invoke 方法。
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 注释 8.12 jdk 动态代理重载的 invoke 方法MethodInvocation invocation;Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;try {Object retVal;if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}target = targetSource.getTarget();Class
targetClass = (target != null ? target.getClass() : null);// Get the interception chain for this method.// 获取此方法的拦截链List