创建用于拦截的 bean
public class TestAopBean { private String testStr = "testStr"; public void testAop() { // 被拦截的方法,简单打印 System.out.println("I am the true aop bean"); } }
创建 Advisor
@Aspect 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
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启 AOP 功能--> <aop:aspectj-autoproxy /> <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
定位功能,找到解析器注册的地方:
- 按住 `ctrl`,定位标签对应的 `xsd` 文件
- 根据命名文件,在 `META-INF` 目录下找到了 `spring.handlers` 文件
- 在处理器文件中发现了处理器 `AopNamespaceHandler`
public class AopNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // In 2.0 XSD as well as in 2.1 XSD. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); // 注释 8.1 自定义注解,注册解析器,元素名是 aspectj-autoproxy registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); } }
处理器继承自 NamespaceHandlerSupport
,在加载过程中,将会执行 init
初始化方法,在这里,会注册 aspectj-autoproxy
类型的解析器 AspectJAutoProxyBeanDefinitionParser
如何注册自定义解析器之前也了解过了,所以接下来直接来看看,遇到 aspectj-autoproxy
类型的 bean
,程序是如何解析的。
注册 AnnotationAwareAspectJAutoProxyCreator
来看下解析时,它的入口方法如下:
public BeanDefinition parse(Element element, ParserContext parserContext) { // aop 注解的解析入口,注册 AnnotationAwareAspectJAutoProxyCreator AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); // 对注解中子类的处理 extendBeanDefinition(element, parserContext); return null; }
入口方法一如既往的简洁,交代了要做的事情,然后具体复杂逻辑再交给工具类或者子类继续实现,所以接下来要看的是如何注册 AnnotationAwareAspectJAutoProxyCreator
。
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) { // 通过工具类,注册或升级 AspectJAnnotationAutoProxyCreator BeanDefinition 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 类型是 AnnotationAwareAspectJAutoProxyCreator return 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(AUTO_PROXY_CREATOR_BEAN_NAME)) { // 如果在 registry 已经存在自动代理创建器,并且传入的代理器类型与注册的不一致,根据优先级判断是否需要修改 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); 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.internalAutoProxyCreator registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }
这个步骤中,实现了自动注册 AnnotationAwareAspectJAutoProxyCreator
类,同时能看到涉及到优先级的概念和注册名一直都是 AUTO_PROXY_CREATOR_BEAN_NAME
。
处理 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(PROXY_TARGET_CLASS_ATTRIBUTE)); if (proxyTargetClass) { // 处理 proxy-target-class 属性 // 与代码生成方式有关,在之后步骤中决定使用 jdk 动态代理 或 cglib AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE)); if (exposeProxy) { // 处理 expose-proxy 属性 // 扩展增强,有时候目标对象内部的自我调用无法实施切面中的增强,通过这个属性可以同时对两个方法进行增强 AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }
关于 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
方法,它是一个属性设置的过程,如果解析到的属性为 true
,将它们加入代理注册器的属性列表中,这里不细说下去。
将这两个属性分开熟悉:
proxy-target-class
Spring AOP
部分使用 JDK
动态代理 (Proxy + InvocationHandler),或者 CGLIB
(Code Generation LIB)来为目标对象创建代理。书中提到,推荐使用的是 JDK
动态代理。
如果被代理的目标对象实现了至少一个接口,则会使用 JDK
动态代理。所有该目标类型实现的接口都将被代理。
若该目标对象没有实现任何接口,则创建一个 CGLIB
代理。如果希望代理目标对象的所有方法,而不只是实现自接口的方法,可以通过该属性 proxy-target-class
开启强制使用 CGLIB
代理。
但是强制开启 CGLIB
会有以下两个问题:
- 无法同时(advise)Final 方法,因为他们不能被覆写
- 需要将 CGLB 二进制发行包放在 classpath 下面
如果考虑好上面两个方面,那就可以通过以下两个地方来强制开启 CGLIB
代理:
<!-- one --> <aop:config proxy-target-class="true">...</aop:config> <!-- two --> <aop:aspectj-autoproxy proxy-target-class="true"/>
其中有关 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
来实现:
<!-- one --> <aop:config expose-proxy="true">...</aop:config> <!-- two --> <aop:aspectj-autoproxy expose-proxy="true"/>
注册组件并通知
emmmm,这个方法内部逻辑如名字一样清晰,所以不细说啦。