前言
Spring 的 IoC 和 AOP 称之为 Spring 框架的两个核心. AOP 是什么? AOP 原理是什么? 本章节开始, 我们就来看看 SpringAOP 到底是怎么玩转起来的?
AOP 是什么?
定义
Aspect Oriented Programming, 面向切面编程, 是一种编程范例, 旨在通过分离横切关注点来增加模块性, 它通过在不修改代码本身的情况下向现有代码添加其他行为来实现. 动态的将代码切入到类的指定方法或指定位置上的编程思想, 就是面向切面编程.
使用
在系统中, 肯定存在一些公共逻辑模块. 比如日志的记录, 事务的管理, 请求的校验等. 如果把这种逻辑模块的代码收到写到业务模块中, 代码重复度就非常之高. 这还不是唯一的问题, 关键如果公共逻辑模块的代码要修改, 必须要全部修改. 这个根本不符合码农的科学发展观. AOP, 可以帮助我们解决这些问题.
实现
AOP 本身并不能解决这些问题, AOP 就是一种思想, 而解决问题依靠的是 AOP 具体的实现, 也就是我们本章节所说的 Spring AOP. 不过, 值得注意的是, 在 Spring2.0 之后, 开始集成 aspectj. 所以, 我们所说的 Spring AOP, 其实就是 Spring 加 Aspectj 这种方式.
概念性知识
要熟悉 Spring AOP, 里面有些概念一定要先搞搞清楚才行.
Aspect 切面, 将横切关注点设计为独立可重用的对象, 这些对象称为切面. 实际上就是一些功能增强的类或者对象的代表, 比如: 日志管理, 事务管理, 异常控制等.
Joinpoint 连接点, 切面在应用程序执行时加入对象的业务流程中的特定点, 称为连接点. 它用来定义在目标程序的哪里通过 AOP 加入新的逻辑. 通俗讲, 就是对应的具体的被代理的方法 , 比如 saveUser().Joinpoint 跟我们具体的被代理的方法一一对应
Pointcut 切点, 匹配连接点的断言. 通知和一个切入点表达式关联, 并在满足这个切入点的连接点上运行. 它是 joinpoint 的集合.
Advice 通知 / 增强, 在切面的某个特定的连接点上执行的动作. 可以理解为它是一段程序代码, 在代理类上的上面或者下面增加一些代码来实现增强. 比如事务管理 AOP, 通知 / 增强对应的就是开启事务, 关闭事务这些具体代码上的操作.
Advisor Advice 和 Pointcut 组成的独立的单元, 用来定义只有一个通知和一个切入点的切面. 再通俗点来说, 它是将 Advice 注入到程序中的 Pointcut 位置. Spring 中的事务管理使用的就是 advisor.
Introduction 引入, 通过引入, 可以在一个对象中加入新的方法和属性, 而不用修改它的程序. 这种方式很少用, 基本也不太推荐用. 自己定义的通知必须要实现 MethodInterceptor.
实例
了解到上面的知识后, 我们通过 xml 的配置方式具体来看一下 Spring AOP 的应用.
首先, 定义一个切面的类.
- public class UserAspect {
- public void beforeAdvice() {
- System.out.println("前置通知");
- }
- public void afterAdvice() {
- System.out.println("后置通知");
- }
- public void afterReturnAdvice() {
- System.out.println("返回通知");
- }
- public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("环绕通知之前");
- Object result = joinPoint.proceed();
- System.out.println("环绕通知之后");
- return result;
- }
- }
其次, 在 Spring 配置文件中先将这个类注册成 Bean. 再通过 AOP 的标签关联到一起.
- <bean id="userAspect" class="com.viewscenes.netsupervisor.aspect.UserAspect"></bean>
- <aop:config>
- <aop:aspect id="userAspect" ref="userAspect">
- <aop:pointcut id="userPointcut" expression="(execution(*
- com.viewscenes.netsupervisor.service..*.*(..)))" />
- <aop:before method="beforeAdvice" pointcut-ref="userPointcut"/>
- <aop:after method="afterAdvice" pointcut-ref="userPointcut"/>
- <aop:after-returning method="afterReturnAdvice" pointcut-ref="userPointcut"/>
- <aop:around method="aroundAdvice" pointcut-ref="userPointcut"/>
- </aop:aspect>
- </aop:config>
最后, 我们通过调用 UserService 中的方法来测试一下.
前置通知
环绕通知之前
---------- 根据 ID 删除用户信息 ------------
环绕通知之后
返回通知
后置通知
xml 标签的解析
不知诸位可否还有印象, Spring 是怎么解析配置文件中的标签的呢? 如果不记得, 可以到 Spring 源码分析 (一)Spring 的初始化和 xml 解析 https://www.jianshu.com/p/baa1d48e7f57 回顾一下.
这里, 我们直接来到 ConfigBeanDefinitionParser.parse() 方法. 它位于 org.springframework.aop.config 包. 大概可以分为两个步骤, 注册入口类和解析子节点.
注册入口类
parse 方法的开始就注册了一个类, AspectJAwareAdvisorAutoProxyCreator. 这个类相当重要, 它是 AOP 的入口类. 注册的过程就是把它封装成 BeanDefinition 对象, 添加到 beanDefinitionNames 容器中. 这个容器, 我们已经很熟悉了, 就是循环它来进行实例化和依赖注入.
- //cls 就是 AspectJAwareAdvisorAutoProxyCreator.class
- private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls,
- BeanDefinitionRegistry registry, Object source) {
- RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
- beanDefinition.setSource(source);
- beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
- beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- // 注册 beanDefinition 将 beanName 加入到 beanDefinitionNames 容器中
- registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
- return beanDefinition;
- }
解析子节点
接下来是解析配置文件标签的地方, 获取 < aop:config > 下的子标签. 它的子标签只有三类:<aop:pointcut>,<aop:advisor>,<aop:aspect>. 下面的源码也正对应这三种类型.
- List<Element> childElts = DomUtils.getChildElements(element);
- for (Element elt: childElts) {
- String localName = parserContext.getDelegate().getLocalName(elt);
- if (POINTCUT.equals(localName)) {
- parsePointcut(elt, parserContext);
- }
- else if (ADVISOR.equals(localName)) {
- parseAdvisor(elt, parserContext);
- }
- else if (ASPECT.equals(localName)) {
- parseAspect(elt, parserContext);
- }
- }
pointcut 的解析
pointcut 解析其实很简单, 把 id 和 expression 拿到, 封装成 BeanDefinition 对象, 它的类是 AspectJExpressionPointcut, 把表达式放入 beanDefinition 对象的 propertyValues 属性, 最后同样是注册到 beanDefinitionNames 容器中.
- private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
- String id = pointcutElement.getAttribute(ID);
- String expression = pointcutElement.getAttribute(EXPRESSION);
- AbstractBeanDefinition pointcutDefinition = null;
- try {
- pointcutDefinition = createPointcutDefinition(expression);
- String pointcutBeanName = id;
- if (StringUtils.hasText(pointcutBeanName)) {
- // 注册到 beanDefinitionNames 容器, id 为 beanName
- parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
- }
- }
- return pointcutDefinition;
- }
- protected AbstractBeanDefinition createPointcutDefinition(String expression) {
- RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
- beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
- beanDefinition.setSynthetic(true);
- beanDefinition.getPropertyValues().add(EXPRESSION, expression);
- return beanDefinition;
- }
aspect 的解析
aspect 是一个切面. 切面里面包含切入点和通知. 引入类型先略过不表.
advice
获取 aspect 节点下的所有子节点, 先过滤 advice 节点. 然后解析生成 AspectJPointcutAdvisor 类的 BeanDefinition 对象.
- // 获取 aspect 节点的子节点
- NodeList nodeList = aspectElement.getChildNodes();
- boolean adviceFoundAlready = false;
- for (int i = 0; i <nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- // 判断是不是 advice 节点.
- if (isAdviceNode(node, parserContext)) {
- if (!adviceFoundAlready) {
- adviceFoundAlready = true;
- //aspectName 就切面的 ref,Bean 的名字
- beanReferences.add(new RuntimeBeanReference(aspectName));
- }
- // 解析 advice 生成 AspectJPointcutAdvisor 类的 BeanDefinition 对象.
- AbstractBeanDefinition advisorDefinition = parseAdvice(
- aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
- beanDefinitions.add(advisorDefinition);
- }
- }
- private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
- String name = parserContext.getDelegate().getLocalName(aNode);
- return (BEFORE.equals(name) || AFTER.equals(name) ||
- AFTER_RETURNING_ELEMENT.equals(name) ||
- AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
- }
parseAdvice 方法注册很多类, 最后串联到一块来, 一个一个来看.
首先, 创建了方法工厂 bean. 注册了 MethodLocatingFactoryBean 类, 往 propertyValues 中添加了两个属性, targetBeanName 切面的 Bean,methodName 通知的方法名.
- RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
- //aspectName 切面类的 Bean methodName 方法名称 比如 before
- methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
- methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
- methodDefinition.setSynthetic(true);
然后, 创建实例工厂的定义. 注册了 SimpleBeanFactoryAwareAspectInstanceFactory 类, 这个类实现了 BeanFactoryAware 接口. 这样的话, 在实例化的时候会调用到 setBeanFactory 方法, 可以拿到 BeanFactory. 有个 getAspectInstance 方法, 根据切面名字就可以拿到切面类的实例.
- // 注册 SimpleBeanFactoryAwareAspectInstanceFactory 实例的 BeanDefinition
- RootBeanDefinition aspectFactoryDef =
- new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
- aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
- // 类的属性和方法
- public class SimpleBeanFactoryAwareAspectInstanceFactory implements
- AspectInstanceFactory, BeanFactoryAware {
- private String aspectBeanName;
- private BeanFactory beanFactory;
- public void setAspectBeanName(String aspectBeanName) {
- this.aspectBeanName = aspectBeanName;
- }
- public void setBeanFactory(BeanFactory beanFactory) {
- this.beanFactory = beanFactory;
- if (!StringUtils.hasText(this.aspectBeanName)) {
- throw new IllegalArgumentException("'aspectBeanName' is required");
- }
- }
- public Object getAspectInstance() {
- return this.beanFactory.getBean(this.aspectBeanName);
- }
- }
其次, 注册切入点. 它把上面这两个 BeanDefinition 当做参数传了过去, 最后放入新建的 BeanDefinition 对象中. 这个新建的 BeanDefinition 对象, 是根据 advice 类型而创建的, 当然了, 也是五个类型, 对应五个类的实例. 下面还有三个步骤: 设置 propertyValues, 解析 advcie 里的 pointcut 属性, 设置 bean 的参数列表.
- private AbstractBeanDefinition createAdviceDefinition(
- Element adviceElement, ParserContext parserContext, String aspectName, int order,
- RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
- List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
- //getAdviceClass 根据 advice 的类型创建不同类型的 BeanDefinition
- //BEFORE 前置通知 AspectJMethodBeforeAdvice.class
- //AFTER 后置通知 AspectJAfterAdvice.class
- //AFTER_RETURNING_ELEMENT 返回后通知 AspectJAfterReturningAdvice.class
- //AFTER_THROWING_ELEMENT 异常通知 AspectJAfterThrowingAdvice.class
- //AROUND 环绕通知 AspectJAroundAdvice.class
- RootBeanDefinition adviceDefinition = new RootBeanDefinition(
- getAdviceClass(adviceElement, parserContext));
- adviceDefinition.setSource(parserContext.extractSource(adviceElement));
- //1, 设置 propertyValues
- adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
- adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
- //2, 解析 advcie 里的 pointcut 属性
- //pointcut 分为两种. 一种是 pointcut-ref 引用类型, 一种是 pointcut 表达式类型
- // 如果是引用类型, 返回字符串
- // 如果是表达式类型, 则创建 AspectJExpressionPointcut 类型的 Bean, 将表达式放入 propertyValues 属性.
- Object pointcut = parsePointcutProperty(adviceElement, parserContext);
- if (pointcut instanceof BeanDefinition) {
- cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
- beanDefinitions.add((BeanDefinition) pointcut);
- }
- else if (pointcut instanceof String) {
- RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
- cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
- beanReferences.add(pointcutRef);
- }
- //3, 设置 bean 的参数列表. adviceDefinition 对象有一个构造函数参数值, 放入了三个属性
- ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
- cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
- cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
- cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
- return adviceDefinition;
- }
最后, 配置 advisor. 创建 AspectJPointcutAdvisor 类实例的 BeanDefinition 对象, 还是那个构造函数参数值, 把上一步返回的 adviceDefinition 当做参数放入 genericArgumentValues.
- RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
- advisorDefinition.setSource(parserContext.extractSource(adviceElement));
- // 构造函数参数值 adviceDef 就是上一步返回的 adviceDefinition
- advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
最后的最后, 注册 advisorDefinition 到容器中并返回.
- parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
- return advisorDefinition;
一定要记得, 这一系列操作都是在循环体里完成的. 所以, 有几个通知的类型, 就会生成几个 advisorDefinition 对象. 处理完, 添加到循环体开头定义的 List 中.
pointcut
刚才在解析 advice 已经解析了 pointcut, 这里又有一个呢? advice 里的 pointcut 是独立使用的, 只能作用于当前的 advice. 但是在 aspect 里面也可以单独定义 pointcut, 可以作用于所有的 advice. 解析过程是一样的, 不再赘述.
advisor 的解析
advisor 可以理解为是只有一个通知和一个切入点的切面. 它的解析也比较简单. 创建一个 DefaultBeanFactoryPointcutAdvisor 类实例的 BeanDefinition 的对象, 把通知的 BeanName 和 Order 放入 propertyValues, 再把这个 BeanDefinition 对象注册到容器中. 然后解析 pointcut, 过程一样.
来源: https://juejin.im/post/5c7ba9015188252d56428e87