SpringAOP源码解析之基础设施注册(一)

写在最前

AspectJ和Spring AOP是两种实现AOP(面向切面编程)的不同方式,它们在实现机制和使用方式上存在一些区别。

  1. AspectJ是一种独立的AOP框架,它提供了比Spring AOP更强大和更灵活的功能。AspectJ可以在编译时或者运行时织入切面,它使用自己的切点表达式语言来定义切点和通知,并且可以实现更细粒度的切面编程。AspectJ支持静态织入和动态织入,以及多种织入方式(编译时织入、类加载时织入、运行时织入等)。

  2. Spring AOP是Spring框架提供的一种轻量级AOP解决方案,它集成在Spring框架中,使用动态代理实现AOP功能。Spring AOP提供了基于代理的运行时织入,它使用AspectJ的切点表达式语言来定义切点,并支持常见的通知类型(前置通知、后置通知、环绕通知等)。相比于AspectJ,Spring AOP更注重于简化配置和集成,提供了更方便的声明式AOP编程方式。

下面是一些AspectJ和Spring AOP之间的区别:

  • 功能强大程度:AspectJ提供了更多的AOP功能和更细粒度的控制,例如引入(introduction)和复杂的切点定义。而Spring AOP提供了基本的AOP功能,适用于大多数常见的AOP需求,但相对更简单和易于使用。

  • 织入方式:AspectJ支持静态织入和动态织入,可以在编译时或者运行时将切面织入目标代码。而Spring AOP使用动态代理,在运行时通过代理对象实现切面功能。

  • 集成与配置:AspectJ是一个独立的AOP框架,需要单独配置和使用。而Spring AOP与Spring框架集成在一起,可以直接使用Spring的IoC容器和其他功能,通过简单的配置即可使用AOP功能。

  • 性能:AspectJ的织入是在编译时或者类加载时完成的,因此在性能上通常比Spring AOP更高效。Spring AOP的运行时代理会引入额外的开销,但对于大多数应用场景来说,性能差异可能并不显著。

综上所述,AspectJ适用于需要更高级别、更精细控制的AOP需求,而Spring AOP适用于那些希望在Spring应用程序中使用简单、轻量级AOP的场景。选择使用哪种方式取决于你的具体需求和项目背景。

AOP概念

让我们首先定义一些核心的AOP概念和术语。这些术语不是Spring特有的,所以SpringAOP并没有单独定义一套自己的术语,而是使用的通用的AOP术语。

  • 切面(Aspect):跨越多个类的关注点的模块化。事务管理是企业级Java应用程序中一个典型的横切关注点。在Spring AOP中,切面可以通过普通类(基于Schema的方式)或带有@Aspect注解的普通类(@AspectJ风格)来实现。

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

  • 通知(Advice):切面在特定连接点上执行的操作。不同类型的通知包括"around"、"before"和"after"通知(后面会讨论通知类型)。许多AOP框架,包括Spring,在模型中将通知视为拦截器,并维护围绕连接点的拦截器链。

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

  • 引入(Introduction):代表类型声明附加方法或字段。Spring AOP允许您为任何被通知的对象引入新的接口(以及相应的实现)。例如,您可以使用引入使一个Bean实现IsModified接口,以简化缓存操作(在AspectJ社区中,引入被称为inter-type声明)。

  • 目标对象(Target object):被一个或多个切面通知的对象。也称为"被通知对象"。由于Spring AOP是通过运行时代理实现的,因此这个对象总是一个被代理的对象。

  • AOP代理(AOP proxy):AOP框架创建的对象,用于实现切面的契约(例如,通知方法的执行)。在Spring框架中,AOP代理可以是JDK动态代理或CGLIB代理。

  • 织入(Weaving):将切面与其他应用程序类型或对象连接起来,创建一个被通知的对象。这可以在编译时(例如使用AspectJ编译器)、加载时或运行时进行。Spring AOP与其他纯Java AOP框架一样,在运行时进行织入。

下面是一个使用Spring AOP的示例,以更清楚地说明这些概念:

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.*.*(..))")
    public void loggableMethods() {}
    
    @Before("loggableMethods()")
    public void beforeMethodExecution(JoinPoint joinPoint) {
        System.out.println("Before method execution: " + joinPoint.getSignature().getName());
    }
    
    @After("loggableMethods()")
    public void afterMethodExecution(JoinPoint joinPoint) {
        System.out.println("After method execution: " + joinPoint.getSignature().getName());
    }
}

在上述示例中:

  • LoggingAspect是一个切面,通过@Aspect注解标识。
  • loggableMethods()是一个切点,通过@Pointcut注解定义。它匹配所有com.example包下的方法。
  • beforeMethodExecution()afterMethodExecution()是通知,分别在loggableMethods()切点匹配的方法执行前和执行后执行。
  • JoinPoint是连接点,它表示方法的调用或执行的具体位置。

总结:

  • 切面定义了在何处以及何时应用通知。
  • 连接点是程序执行过程中的特定点,例如方法调用或执行。
  • 通知是切面在连接点上执行的具体行为,可以是前置通知、后置通知、返回通知、异常通知或环绕通知。
  • 切点定义了在应用程序中哪些连接点上应用通知,使用切点表达式进行匹配。

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

1、前置通知(Before advice):在连接点之前执行的通知,但它无法阻止执行流程继续到连接点(除非抛出异常)。

2、返回通知(After returning advice):在连接点正常完成后执行的通知,例如方法返回而没有抛出异常。

3、异常通知(After throwing advice):在连接点通过抛出异常而退出时执行的通知。

4、最终通知(After (finally) advice):无论连接点以哪种方式退出(正常返回或异常返回),都会执行的通知。

5、环绕通知(Around advice):环绕连接点(例如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到连接点执行还是通过返回自己的返回值或抛出异常来终止被通知方法的执行。

源码分析

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy用于启用处理使用AspectJ的@Aspect注解标记的组件的支持,类似于Spring的aop:aspectj-autoproxy XML元素的功能。

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

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 * 用于指定使用CGLIB代理还是JDK动态代理,如果目标对象没有实现任何接口,则必须使用CGLIB代理,否则会抛出异常。如果目标对象实现了
	 * 接口,则默认使用JDK动态代理。如果设置为true,则强制使用CGLIB动态代理
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 * 是否将代理对象暴露给ThreadLocal中的AopContext.currentProxy()方法。默认为false。如果设置为true
	 * 则可以通过方法获取到当前的代理对象。
	 * 需要注意的是,将exposeProxy设置为true的时候可能会带来性能和内存方面的开销,并且可能导致AopContext被意外的暴露到非信任的代码中。
	 */
	boolean exposeProxy() default false;

}

@Import(AspectJAutoProxyRegistrar.class)

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar。

@EnableAspectJAutoProxy
@ComponentScan(value = {"com.qhyu.cloud.**"})
public class AopConfig {

当AopConfig类上加了这个注解的时候,AopConfig配置类在ConfigurationClassPostProcessor被调用postProcessBeanDefinitionRegistry方法的时候查看是否有实现ImportBeanDefinitionRegistrars,毫无疑问AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,所以将会调用AspectJAutoProxyRegistrar的registerBeanDefinitions方法。

这里我需要讲一个题外话,ConfigurationClassParser在解析候选配置类的时候会处理@Import注解,在Spring中,处理@Import注解的逻辑涉及到不同的方式和时机。

SpringAOP源码解析之基础设施注册(一)_第1张图片

上面这个截图位置已经非常清楚了。

  1. 基本的@Import注解处理:
    当解析配置类时,Spring会检查其中是否存在@Import注解。如果存在,它将解析注解值并加载被导入的类或配置类。这些被导入的类或配置类将成为应用上下文中的Bean,可以用于依赖注入和组件扫描。

  2. ImportSelector接口:
    如果@Import注解的值是实现了ImportSelector接口的类,那么Spring将调用该类的selectImports()方法。ImportSelector接口允许根据特定条件选择要导入的类或配置类。selectImports()方法返回一个字符串数组,其中包含要导入的类或配置类的全限定名。

  3. ImportBeanDefinitionRegistrar接口:
    如果@Import注解的值是实现了ImportBeanDefinitionRegistrar接口的类,那么Spring将调用该类的registerBeanDefinitions()方法。ImportBeanDefinitionRegistrar接口允许以编程方式向Spring容器注册更多的Bean定义。

  4. 配置类处理:
    如果@Import注解的值既不是ImportSelector的实现类,也不是ImportBeanDefinitionRegistrar的实现类,那么Spring将将其视为普通的配置类,并对其进行处理。这意味着被导入的类或配置类将被加载到应用上下文中,成为可用的Bean。

可以根据不同的情况选择使用不同的@Import注解方式,以实现不同的导入逻辑和条件选择。这样可以更灵活地配置Spring应用程序的组件和依赖关系。

回到正题,这个@Import主要是注册这个类的beanDefinition信息。

@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 注册AnnotationAwareAspectJAutoProxyCreator
		// bean的名称为 org.springframework.aop.config.internalAutoProxyCreator
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		// 用于获取@EnableAspectJAutoProxy的注解属性值
		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

AbstractAutoProxyCreator

AbstractAutoProxyCreator是一个抽象类,实现了Spring框架中的BeanPostProcessor接口。BeanPostProcessor接口定义了在Bean初始化过程中的前后处理方法,其中包括postProcessAfterInitialization方法。

AbstractAutoProxyCreator的作用是在Bean初始化完成之后,通过postProcessAfterInitialization方法创建AOP代理对象。它会检查目标Bean是否符合AOP代理的条件,例如是否标记了特定的注解或者实现了特定的接口。如果目标Bean满足条件,AbstractAutoProxyCreator会使用适当的代理工具(如JDK动态代理或CGLIB)创建代理对象,并将其替换原始的目标Bean。

通过代理对象,AbstractAutoProxyCreator能够在目标Bean的方法执行前后插入额外的逻辑,例如执行切面的通知方法。这样可以实现AOP的功能,例如方法拦截、事务管理等。

需要注意的是,AbstractAutoProxyCreator是一个抽象类,具体的AOP代理创建逻辑由其子类实现。常见的子类包括AnnotationAwareAspectJAutoProxyCreator和InfrastructureAdvisorAutoProxyCreator等。

总结起来,AbstractAutoProxyCreator的作用是在Bean初始化完成后,通过postProcessAfterInitialization方法创建AOP代理对象,以实现AOP功能。

@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			//根据给定的bean的class和name构建出一个key,格式beanclassName_beanName
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// 真正进行处理的地方,里面有代码很明显是用来创建代理对象的
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

你可能感兴趣的:(Spring专题,java,spring,spring,cloud,后端,spring,boot)