目录
1.2、AspectJAutoProxyBeanDefinitionParser类parse()方法
1.3、注册AspectJAnnotaionAutoProxyCreator实例
2.2、postProcessAfterInitialization
前言:在工作中我们常常会遇到给多个业务方法增加公共行为的业务问题,比如统计服务层的调用时间,添加打印日志的功能等等,在没有面向切面编程概念出现之前,我们的修改方式是在所有的业务代码中添加一份重复的代码片段(打印日志,获取统计时间等),这样做会导致业务代码和非业务代码强耦合(打印日志,统计调用时间暂将其称之为非业务代码)后期变更不好维护,对业务代码有很强的侵入性。这里AOP就应运而生了,它很好的解决如上的问题,它以切面的形式根据切入点表达式,注解等方式在我们业务层横切(动态增强)如一些公共功能的非业务代码,则业务代码和非业务代码可以分别开发互不影响。下面我们就来分析一下AOP的原理。
一、AOP的基础概念
1、相关名词解释
- 切面(Aspect):由切点和增强组成,既包含了横切逻辑的定义。也包含了连接点的定义。
- 通知(Advice):是切面的详细实现。以目标方法为參照点,依据放置的地方不同,可分为前置通知(Before)、后置返回通知(AfterReturning)、后置异常通知(AfterThrowing)、后置终于通知(After)与围绕通知(Around)5种。在实际应用中一般是切面类中的一个方法。详细属于哪类通知。相同是在配置中指定的。
- 连接点(Joinpoint):程序运行的某个特定的位置。比方类初始化前,初始化后。方法调用前。方法调用后等等
- 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常须要切入到不同的连接点上,这样的精准的匹配是由切入点的正則表達式来定义的。
- 目标对象(Target):增强逻辑的织入目标类。
- 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。能够简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共同拥有功能。代理对象对于使用者而言是透明的。是程序执行过程中的产物。
- 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程能够发生在编译期、类装载期及执行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话。就要求有一个支持这样的AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;仅仅有发生在执行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
- 增强:织入到目标类连接点上的一段代码
2、相关示例
此为一个日志打印为AOP的示例
2.1、切面类
这里不使用xml详细配置切面的而是直接使用@Aspect然后在xml总设置aop自动扫描。这个也是我们日常开发中常用的模式。
/**
* 日志打印切面
*/
@Aspect
@Component
public class LogAspect {
//在进入切点业务代码未执行之前执行
@Before("execution(* com.soecode.lyf.service.impl.*.*(..))")
public void logBefore(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
System.out.println("日志打印开始 class info : "+className+", method info : "+methodName+", args info: "+argsInfo);
}
//在执行完切点后执行
@After("execution(* com.soecode.lyf.service.impl.*.*(..))")
public void logAfter(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
System.out.println("日志打印结束 class info : "+className+", method info : "+methodName+", args info: "+argsInfo);
}
}
//符合切面类中的切入点表达式对应的服务组件
@Service
public class DemoServiceImpl implements DemoService {
public Book getById(Integer bookId) {
Book book = new Book();
book.setName("四世同堂");
book.setAuthor("老舍");
return book;
}
}
2.2、xml配置
有关aop的配置,这里使用了spring提供的自定义标签<aop:aspectj-autoproxy /> 自动扫描所有的@Aspect修饰的类将其注入到spring容器中(默认是后置处理器bean实例)
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描service包下所有使用注解的类型 -->
<context:component-scan base-package="com.soecode.lyf.service" />
<bean id="logAspect" class="com.soecode.lyf.aop.LogAspect" />
<!--
设置自动扫描aop 自动代理
proxy-target-class 为true 强制使用CGLIB 创建代理对象
expose-proxy 为true 设置代理增强,所谓代理增强是指
//一个服务组件 spring声明式的事务本身也是使用AOP原理来实现的
public class UserServiceImpl implements UserService{
@Transactional(propagation = Propagation.REQUIRED)
public void a(){
//a方法的内部调用b方法
this.b();
}
@Transactional(propagation = Propagation.REQUIRED_NEW)
public void b(){
System.out.println("b has been called");
}
}
方法a和方法b都使用了事务,但是方法a中调用了方法b,aop的代理增强是针对方法的,不针对代码块
所有在这种情况下调用方法a 只应用了方法a上声明的事务,方法a中对方法b的调用不会使用方法b上的事务
但是如果声明expose-proxy="true" 则调用方法a的时候会进入方法a和方法b的两个事务中
-->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true">
<!-- 除了自动扫描外 可以在该标签中显示的指定切面实例 -->
<!--<aop:include name="otherAspect" />-->
</aop:aspectj-autoproxy>
</beans>
2.3、测试
//指定在单元测试启动的时候创建spring的工厂类对象
@ContextConfiguration(locations = {"classpath:spring/spring-service.xml"})
//RunWith的value属性指定以spring test的SpringJUnit4ClassRunner作为启动类
//如果不指定启动类,默认启用的junit中的默认启动类
@RunWith(value = SpringJUnit4ClassRunner.class)
public class BookDaoTest extends BaseTest {
@Autowired
private DemoService demoService;
@Test
public void testQueryById() throws Exception {
int bookId = 2446;
Book book = demoService.getById(bookId);
System.out.println("书籍信息为:"+book);
}
}
测试结果如下 我们看到服务层的调用都
如上便是一个建档的使用aop为业务服务添加日志打印操作。如果读者想了解更多的aop用法,参考spring的AOP了解以及应用。
二、AOP原理分析
上面一中对aop的使用做了简要的描述,下面我们就来分析其背后的原理实现。首先从xml配置相关AOP入手。
1、自定义标签解析
在xml中使用 <aop:aspectj-autoproxy /> 就可以就可以实现切面类的解析和实现了。对此结合spring自定义标签解析,可以找到spring aop对应的源码报下的AopNamespaceHandler的类,该类就是我们进行<aop /> 自定义标签的解析处理类,话不多说让我们来看看这个类做了什么吧。
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
1.1、AopNamespaceHandler类
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
//注册 aspectj-autoproxy对应的BeandefinitionParser解析类
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());
}
在AopNamespaceHandler的init()方法中发现注册了多个BeanDefintionParser实例,这些实例的主要作用是将一个bean实例的配置转换为BeanDefinition类。其他的我们暂时不去关注这里只关注aspectj-autoproxy注册AspectJAutoProxyBeanDefinitionParser实例,BeanDefintionParser的核心方法是parse()方法,下面我们分析一下该类对应的parse()方法。
1.2、AspectJAutoProxyBeanDefinitionParser类parse()方法
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//注册AspectJAnnotationAutoProxyCreator实例
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary
(parserContext, element);
//解析<aop:include>子标签 并注册对应的属性
extendBeanDefinition(element, parserContext);
return null;
}
1.3、注册AspectJAnnotaionAutoProxyCreator实例
//注册AspectJAnnotationAutoProxyCreatorIfNecessary 对应的beanDefinition实例
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
//创建BeanDefintion对象并在在spring容器中的beanDefintionMap中注册同时返回
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
//解析proxy-target-class和expose-proxy属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
//将对应的beanDefintion 作为组件Component 注册到ParseContext中
registerComponentIfNecessary(beanDefinition, parserContext);
}
上面的方法主要分成三部分
- 创建BeanDefinition对象注册到spring容器中并返回该beanDefintion对象
- 解析<aop:aspectj-autoproxy />中的proxy-target-class和expose-proxy属性
- 将对应beanDefinition注册为spring的Component组件
1.4、创建beanDefinition对象并注册
//创建beanDefintion 并进行注册
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//先判断是否已经注册了AnnotationAwareAspectJAutoProxyCreator
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
//如果注册了 从容器中获取对应的beanDefintion
BeanDefinition apcDefinition =
registry.getBeanDefinition(AUTO_PROXY_CRE