6、spring核心源码解析之aop概况流程1

前情

​ 前面写了五篇又过于spring ioc与DI的文章,并且都通过源码进行了一些分析。从前文中,我们可以看到,spring内部解析@Configuration、@Autowired等注解也是通过Bean的后置处理器进行解析的。由此可见spring的后置处理器是很重要的一个扩展点之一。

​ 基于此,我们可以大胆的猜测,spring内部扩展一个新模块,增加新的注解时,肯定是有相对的后置处理器进行注解解析。同理,以后如果我们想通过结合spring,扩展框架,增加一些新的注解时,也可以设计自己的BeanPostProcess来完成注解解析,bean的注入等功能。

​ 本文主要讲解的是spring aop 与 aspects,引用官网的一段话。

​ Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

​ One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP (meaning you do not need to use AOP if you don’t want to), AOP complements Spring IoC to provide a very capable middleware solution.

​ 翻译:面向切面的编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。 OOP中模块化的关键单元是类,而在AOP中模块化是切面。 切面使关注点(例如事务管理)的模块化可以跨越多种类型和对象。 (这种关注在AOP文献中通常被称为“跨领域”关注。)

​ Spring的关键组件之一是AOP框架。 尽管Spring IoC容器不依赖于AOP(这意味着您不需要的话就不需要使用AOP),但AOP是对Spring IoC的补充,以提供功能非常强大的中间件解决方案。

1.spring aop和aspects的基本概念

让我们首先了解一些主要的AOP概念和术语。这些术语不是特定于Spring的。不幸的是,AOP术语并不是特别直观。但是,如果使用Spring自己的术语,将会更加令人困惑。(想要知道更多可以查看官网,以下概念均来自spring官网

切面( Aspect ):涉及多个类别的关注点的模块化。事务管理是企业Java应用程序中横切关注的一个很好的例子。在Spring AOP中,切面是通过使用常规类(基于模式的方法)或使用@Aspect注释(@AspectJ样式)注释的常规类来实现的。

连接点( Join point ):在程序执行过程中的一点,例如方法的执行或异常的处理。在Spring AOP中,连接点始终代表方法的执行。

通知( Advice ):切面在特定的连接点处采取的操作。不同类型的通知包括“周围”,“之前”和“之后”通知。 (通知类型将在后面讨论。)包括Spring在内的许多AOP框架都将通知建模为拦截器,并在连接点周围维护一系列拦截器。

切入点( Pointcut ):与连接点匹配的谓词。通知与切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配的连接点的概念是AOP的核心,默认情况下,Spring使用AspectJ切入点表达语言。

简介( Introduction ):代表类型声明其他方法或字段。 Spring AOP允许您向任何通知对象引入新的接口(和相应的实现)。例如,您可以使用简介使Bean实现IsModified接口,以简化缓存。 (在AspectJ社区中,介绍被称为类型间声明。)

目标对象( Target object ):一个或多个切面通知的对象。也称为“通知对象”。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。

AOP代理( AOP proxy ):由AOP框架创建的一个对象,用于实现切面合同(通知方法执行等)。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。

编织( Weaving ):将切面与其他应用程序类型或对象链接以创建通知的对象。这可以在编译时(例如,使用AspectJ编译器),加载时或在运行时完成。像其他纯Java AOP框架一样,Spring AOP在运行时执行编织。

Spring AOP包括以下类型的通知:

在通知之前( Before advice ):在连接点之前运行的通知,但是它不能阻止执行流程继续进行到连接点(除非它引发异常)。

返回通知后( After returning ):在连接点正常完成后要运行的通知(例如,如果方法返回而没有引发异常)。

抛出通知后( After throwing advice ):如果方法因抛出异常而退出,则执行通知。

通知之后(最终)( After (finally) advice ):无论连接点退出的方式如何(正常或特殊返回),均应执行通知。

环绕通知( Around advice ):围绕连接点的通知,例如方法调用。这是最有力的通知。周围通知可以在方法调用之前和之后执行自定义行为。它还负责选择是返回连接点还是通过返回其自身的返回值或引发异常来捷径通知的方法执行。

环绕通知是最通用的通知。由于Spring AOP与AspectJ一样,提供了各种通知类型,因此我们通知您使用功能最弱的通知类型,以实现所需的行为。例如,如果您只需要使用方法的返回值更新缓存,则最好使用返回后的通知而不是周围的通知,尽管周围的通知可以完成相同的事情。使用最具体的通知类型可提供更简单的编程模型,并减少出错的可能性。例如,您不需要在用于周围通知的JoinPoint上调用proce()方法,因此,您不会失败。

所有通知参数都是静态类型的,因此您可以使用适当类型(例如,从方法执行返回的值的类型)而不是对象数组的通知参数。

切入点匹配的连接点的概念是AOP的关键,它与仅提供拦截功能的旧技术有所不同。切入点使通知的目标独立于面向对象的层次结构。例如,您可以将提供声明性事务管理的环绕通知应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。

​ 讲了这么多的概念,可以自己简单的通过画一张图来加深自己对于aop的理解,当然也可以参考我画的如下图所示。

在这里插入图片描述

讲了这么多概念,我们可以讲一个小案例,不涉及代理,了解aop的思想。

在我们刚开始学习jdbc时,肯定都做过以下几个步骤。但是在实际开发中,基本上看不到这种做法了,因为为了简化开发,我们把重复性代码,都交给了框架进行处理,我们只用关心核心业务即可。

此时spring jdbc框架做的就是利用aop的设计思想,将核心业务当成一个节点,在核心业务前后织入我们数据库的连接等操作,协助完成我们的jdbc操作。

在这里插入图片描述

2.aop设计一个统计方法的执行时间注解

采用aop的思想做一个简单的案例,日常业务开发中也用的着。通常我们要知道一个接口的调用时间时,只需要在接口方法上加一个注解,即可测试出该接口的调用时常。

2.1开启spring aop功能@EnableAspectJAutoProxy

spring aop功能的开启很简单,只需要在依赖中加入aspectj的相关依赖,同时在启动类上加@EnableAspectJAutoProxy注解即可。

//因为我是spring源码,采用的是gradle
dependencies {
    compile(project(":spring-core"))
    compile(project(":spring-context"))
    compile(project(":spring-aspects"))
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
@Configuration
@EnableAspectJAutoProxy //开启动态代理
public class MyApp {}

2.2编写切面

@Aspect
public class HandlingTimeAspect {
	//切点,切的是一个注解@HandlingTime
    @Pointcut("@annotation(com.upanda.app.test.annotaition.HandlingTime)")
    public void handlingTimePointcut() {}
	
    //环绕通知
    @Around("handlingTimePointcut()")
    public Object handlingTimeAround(ProceedingJoinPoint joinPoint){
        try {
            long startTime = System.currentTimeMillis();
			System.out.println("方法签名:"+joinPoint.getSignature());
			System.out.println("方法执行开始时间:{}"+startTime);
            Object proceed = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
			System.out.println("方法执行结束时间:"+endTime);
			System.out.println("方法耗时:"+(endTime-startTime));
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

}

注解@HandlingTime类

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlingTime {
}

2.3通过@EnbleLogger注解导入切面

之后以要通过注解导入切面,是因为我们可以在启动类上,通过是否加入了@EnbleLogger注解来实现该功能的可插拔。

@EnbleLogger注解类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HandlingTimeAspect.class)
public @interface EnableLogger {
}
  1. 使用及测试结果

    在指定方法上加@HandlingTime

    	@HandlingTime
    	public String getName() {
    		return name;
    	}
    

    测试结果

    > Task :spring-upanda:MyApplication.main()
    com.upanda.app.test.Person@5ab9e72c
    方法签名:String com.upanda.app.test.Person.getName()
    方法执行开始时间:1589704484958
    方法执行结束时间:1589704484970
    方法耗时:12
    coyhzx
    

3.spring之aop运行的整体流程分析

通过上文,我们可以大体的了解到,spring aop的一些概念,以及使用流程。

首先开启aop,使用@EnableAspectJAutoProxy即可。那么我们可以看看这个注解做了什么事情

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	//表示是否使用基于子类继承的CGLIB动态代理,默认是采用基于接口的jdk动态代理,
	boolean proxyTargetClass() default false;

	/**
	 * @since 4.3.1
	 */
	//表示代理应由AOP框架公开为一个线程变量{@code ThreadLocal} ,
	// 以便通过{@link org.springframework.aop.framework.AopContext}类进行检索。
	// *默认情况下为关闭,即不能保证{@code AopContext}访问将起作用。
	boolean exposeProxy() default false;

}

该注解导入了一个类AspectJAutoProxyRegistrar,该类实现了ImportBeanDefinitionRegistrar接口,该接口是用来向容器中注入bean的(具体用法可参考我的spring源码分析的第四篇文章4、spring核心源码解析之自定义bean三种方式@import、ImportSelector、ImportBeanDefinitionRegistrar)。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

该类调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法向我们容器中注入了一个aspectj组件自动代理创建器。

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}

AnnotationAwareAspectJAutoProxyCreator这个类是一个很复杂的类,可以先看一下它的类结构继承关系图。
在这里插入图片描述

从继承关系中,我们可以看到两个spring里经常使用的两个扩展点Aware接口,BeanPostProcessor接口。

BeanFactoryAware:实现该接口,可以将BeanFactory注入到我们的AbstractAutoProxyCreator抽象类中。

SmartInstantiationAwareBeanPostProcessor: 实现该接口,我们可以在bean的注册过程中,织入我们自己的业务逻辑。

基于前几篇有关于spring ioc文章的源码分享,其实我们可以大胆的猜测一下,spring是如何完成aop的功能的:

我猜想:spring aop动态代理实现的关键逻辑就在于AnnotationAwareAspectJAutoProxyCreator是如何实现了SmartInstantiationAwareBeanPostProcessor、InstantiationAwareBeanPostProcessor、BeanPostProcessor接口中的方法,搞清楚了这些,我觉得spring aop的运作流程就了然于心了。

这里面有很多的父子类继承关系,以及复杂逻辑,如果把自己看晕了就难了。但是其实长时间看spring代码之后,我们可以总结出一些小规律(即设计思想,后续可能分析如何去看spring源码)。

上一篇:5、spring核心源码解析之DI依赖注入、自动装配@Autowired和@Resource
[下一篇:7.未完待续]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章