深入理解 Spring

以下所有内容并不是深入 spring 源码,而是理解 spring 的运行机制,大致结构,设计思想等

本文参考自【黑马程序员】spring高级49讲教程整理而来,教程链接 黑马程序员Spring视频教程,深度讲解spring5底层原理_哔哩哔哩_bilibili

容器接口

BeanFactory & ApplicationContext

org.springframework.beans.factory.BeanFactoryorg.springframework.context.ApplicationContext 都是 Spring 中最核心的接口

其中BeanFactory 是用于访问 spring bean 容器的根接口,而ApplicationContext间接继承了BeanFactory,如下类图所示

通过这种继承关系,说明ApplicationContext是对BeanFactory的功能扩展,而 spring bean 容器的基础功能依然在BeanFactory中定义

BeanFactory 功能

BeanFactory接口中,主要定义的都是getBean()相关的方法,如下

但实际上,控制反转,基本的依赖注入,生命周期等,都由其子实现类org.springframework.beans.factory.support.DefaultListableBeanFactory提供了,如下图

官方文档对此类的定义

Spring的可配置列表的默认实现可以是一个工厂和 bean 定义注册表接口:一个基于bean定义元数据的成熟的bean工厂,可通过后处理器进行扩展。
典型的用法是在访问bean之前,首先注册所有bean定义(可能从bean定义文件中读取)。因此,按名称查找Bean是本地Bean定义表中的一种廉价操作,它对预先解析的Bean定义元数据对象进行操作。
注意,特定bean定义格式的读取器通常是单独实现的,而不是作为bean工厂子类:参见例如org.springframework.beans.factory.xml.XmlBeanDefinitionReader
对于org.springframework.beans.factory的另一个替代实现。看看静态列表成为工厂,它管理现有的bean实例,而不是基于bean定义创建新的实例。

如上类图所示,DefaultListableBeanFactory又间接继承了org.springframework.beans.factory.support.DefaultSingletonBeanRegistry,这是 spring 中所有单例 bean 容器所属的类,官方文档定义其为:共享 bean 实例的通用注册表

spring 中单例 bean 的容器在此类中被作为属性,定义如下

/** Cache of singleton objects: bean name to bean instance. */
// 通过 bean 名称对应 bean 实例的形式缓存所有的单例 bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

而在ApplicationContext的实现类org.springframework.context.support.GenericApplicationContext中,又通过组合的方式,持有一个DefaultListableBeanFactory的实例,如下

private final DefaultListableBeanFactory beanFactory;

这说明ApplicationContext既具备了BeanFacorygetBean()的能力,同时又通过组合DefaultListableBeanFactory的方式具备了管理所有 bean 实例的能力

spring 中通过组合、继承、实现等方式,将不同的功能交给不同的类扩展和复用,遵循了开闭原则,达到解耦的效果

ApplicationContext 功能

org.springframework.context.ApplicationContext通过继承的方式,包含了 spring 中许多的重要功能

ApplicationContext的继承体系如下

通过继承父接口,ApplicationContext主要扩展了以下功能

  • 国际化相关,在其父接口org.springframework.context.MessageSource中定义
  • 事件发布相关,在其父接口org.springframework.context.ApplicationEventPublisher中定义
  • 资源解析相关,在其父接口org.springframework.core.io.support.ResourcePatternResolver中定义
  • 环境信息(例如系统环境变量)相关,在其父接口org.springframework.core.env.EnvironmentCapable中定义

针对上述功能,使用 spring-boot 进行简单演示


国际化

// spring-boot 完全启动后, 会返回 ConfigurableApplicationContext 类型的应用容器, 此类也继承了 ApplicationContext
// 因此可以直接通过 context 来调用 ApplicationContext 的扩展功能
ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);

// 会输出指定的 key 对应配置的值, 达到翻译的效果, 例如 hi=你好, hi=hello 等不同语言的配置
// 前提是在 maven 工程的 resources 目录下, 存在 messages.properties(必须) 以及 messages_zh_CN.properties(可选) 这种不同语言对应的配置文件
System.out.println(context.getMessage("hi", null, Locale.CHINA));

资源解析

// 解析类路径下的文件资源, classpath 代表类路径, 加上 * 代表匹配外部 jar 包中的类路径资源, 否则的话只能解析对其项目的类路径
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
    System.out.println(resource);
}

环境

// 获取当前主机的系统环境变量 
context.getEnvironment().getSystemEnvironment().forEach((k, v) -> System.out.println(k + "===" + v));

事件发布

定义事件对象

import org.springframework.context.ApplicationEvent;

/**
 * 事件对象
 *
 * @author dhj
 * @date 2022/10/3
 */
public class OrderEvent extends ApplicationEvent {

    /**
     * @param source 指定事件的发布源,也就是谁发的事件
     */
    public OrderEvent(Object source) {
        super(source);
    }
}

定义对应事件的事件监听器

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 事件监听器 , spring 中任意组件都可作为事件的监听器
 *
 * @author dhj
 * @date 2022/10/3
 */
@Component
public class OrderListener {

    /**
     * 接收事件的方法
     *
     * @param orderEvent 事件对象
     */
    @EventListener // 标识此方法作为事件监听的方法(如果有多个接收相同事件对象的事件监听器,届时都会同时收到此事件)
    public void listenerOrder(OrderEvent orderEvent) {
        System.out.println("1:" + orderEvent);
    }
}

发布事件

ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);
// 发布事件,同时指定事件源
context.publishEvent(new OrderEvent(context));

这里为了方便,直接使用 context 对象来发布事件,如果在其他 spring 组件中,可以直接注入事件发布器来发布事件,如下

@Autowired
private ApplicationEventPublisher publisher;

小结

BeanFactoryApplicationContext之间并不只是简单的继承关系,ApplicationContext通过组合的形式复用了BeanFactory中定义的功能,并且又通过多继承其他接口的方式,扩展了BeanFactory

BeanFactory 实现

BeanFactoryPostProcessor

BeanFactory的实现有很多,其中最主要的是org.springframework.beans.factory.support.DefaultListableBeanFactory实现类

下面是DefaultListableBeanFactory的简单使用演示

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 * @author dhj
 * @date 2022/10/5
 */
public class PlainMainTest {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        /*
         spring 中通过 bean 的定义(class,scope,初始化方法,销毁方法等)作为创建 bean 实例的依据
         所以需要先将 bean 定义信息提供给 bean 工厂
         */

        // 创建一个 bean 定义实例
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(Config.class) // bean 的类型
                .setScope("singleton") // scope(单例 bean)
                .getBeanDefinition(); // 返回 bean 定义实例
        
        // 注册 bean 定义信息到 beanFactory,其内部通过 Map 结构维护多个 bean 定义信息
        beanFactory.registerBeanDefinition("config", beanDefinition);
        
        // 打印当前 beanFactory 中的所有 bean 定义信息
        Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {
        public Bean1() {
            System.out.println("Bean1()");
        }
    }

    static class Bean2 {
        public Bean2() {
            System.out.println("Bean2()");
        }
    }
}

最终的打印结果为config,也就是说只注册了配置类,但是配置类中通过@Bean注解标识的组件的【bean 定义信息】没有被注册到BeanFactory中,这意味着后续也无法获取到对应的实例

这是因为原始的BeanFactory并没有诸如解析配置类,解析注解的能力,这需要通过后处理器来实现,通过如下代码为BeanFactory提供功能增强

// 为 beanFactory 添加一些后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

再次运行,打印结果如下

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

可以看到,当前的BeanFactory中多出了几个 bean 定义信息,这些就是对BeanFactory进行功能增强的后处理器

但目前这些后处理器还未起作用,还需要手动的应用后处理器的增强方法,最终代码如下

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
/*
 spring 中通过 bean 的定义(class, scope, 初始化方法, 销毁方法)作为创建 bean 实例的依据
 所以需要先将 bean 定义信息提供给 bean 工厂
 */

// 创建一个 bean 定义实例
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
    .genericBeanDefinition(Config.class) // bean 的类型
    .setScope("singleton") // scope(单例 bean)
    .getBeanDefinition(); // 返回 bean 定义实例

// 注册 bean 定义信息, 内部通过 Map 结构维护多个 bean 定义信息
beanFactory.registerBeanDefinition("config", beanDefinition);

// 为 beanFactory 添加一些后处理器, 可以用于 beanFactory 的功能增强
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

// BeanFactoryPostProcessor 后处理器, 主要是补充一些 bean 定义信息, 如 @Bean @Configuration
// 这里直接执行其中的后处理方法, 使用其提供的增强功能
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryProcessor -> {
    beanFactoryProcessor.postProcessBeanFactory(beanFactory);
});

// 打印当前 beanFactory 中的所有 bean 定义信息
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);

BeanPostProcessor

通过以上方式注册 bean 之后,就可以通过BeanFactory中的getBean()根据beanNamebeanType来获取 bean 实例了,但目前 bean 实例化的过程中,并没有处理 bean 之间的依赖关系,如下代码

@Configuration
static class Config {
    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

static class Bean1 {

    // Bean1 依赖 Bean2
    @Autowired
    private Bean2 bean2;

    public Bean1() {
        System.out.println("Bean1()");
    }
}

static class Bean2 {
    public Bean2() {
        System.out.println("Bean2()");
    }
}

通过beanFactory.getBean(Bean1.class).bean2获取的 bean2 此时为null,这说明在Bean1中通过@Autowired申明的依赖关系没有生效,换句话说,当前的BeanFactory没有【依赖注入】的功能

这是因为针对当前的BeanFactory,并没有为其手动的扩展【依赖注入】相关的后处理器,需要通过如下代码进行扩展

// BeanPostProcessor 后处理器, 针对 bean 生命周期的各个阶段提供扩展功能
// 这里是通过获取 BeanPostProcessor 类型的 bean 实例, 将其添加到 beanFactory 中
// 后续通过 beanFactory 调用 getBean() 时, 便会应用这些后处理器逻辑, 其中就会包含【依赖注入】
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

// 可以设置提前把单例创建好, 否则默认会在使用到 getBean() 时, 才会去创建对应的 bean
beanFactory.preInstantiateSingletons();
System.out.println(beanFactory.getBean(Bean1.class).bean2);

小结

截至目前,可以了解到的是,BeanFactory只具备基本的getBean()功能,而 spring 中一系列的依赖注入,配置解析等能力,主要通过针对BeanFactory进行功能增强的不同类型的后处理器实现

例如BeanFactoryPostProcessor,主要用于补充 bean 的定义信息;BeanPostProcessor主要针对 bean 生命周期的各个阶段进行功能扩展

而这些功能扩展目前都是手动添加的,在ApplicationContext中,则对这些扩展功能进行了封装

后处理器排序

针对原始的BeanFactory,添加的后处理器有多个,如果不进行排序操作,默认是按照添加的顺序执行的,例如在调用AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);注册后处理器时,部分源码如下

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}

    	// LinkedHashSet 能够维护元素添加的顺序
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

    	// Autowired 注解的后处理器 bean 定义信息先被添加
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

    	// CommonAnnotationBeanPostProcessor 后处理器, 主要用于处理 @Resource 注解
		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		........

可以看出,AutowiredAnnotationBeanPostProcessor的定义信息在CommonAnnotationBeanPostProcessor之前被添加,实际生效的顺序也排在前面,因为在实际添加后置处理器实例时,也是通过遍历的方式顺序添加的,例如

// 顺序遍历添加后处理器
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

考虑如下注册 bean 的结构

@Configuration
static class Config {

    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public Teacher teacher() {
        return new Teacher();
    }

    @Bean
    public Student student() {
        return new Student();
    }
}

static class Bean1 {

    @Autowired
    private Person person;
}

interface Person {

}

static class Student implements Person {

}

static class Teacher implements Person {

}

此时的 bean 容器中,Person类型的 bean 有多个,如果只是单纯的声明@Autowired,那么Bean1会创建失败,因为容器中相同类型的 bean 有多个,spring 无法确定到底注入哪一个Person类型的 bean,可如果将变量名称改为与@Bean对应的方法名称一致,如下

 @Autowired
 // @Qualifier("student") 或者使用 Qualifier 指定要注入的 bean 的名称
 private Person student; // 变量名称与注入的 bean 名称保持一致

spring 会在找到多个相同类型的 bean 实例时,根据变量的名称与 bean 实例的名称进行匹配,选择合适的进行注入,可见@Autowired会先通过类型查找对应的 bean,如果有多个结果,再根据名称来决定,可如果连对应类型都没有查找到,后续也不会根据名称进行匹配

@Resource注解则恰恰相反,此注解优先对 bean 名称匹配的实例进行注入,尽管此实例类型与变量的类型不一致;以下代码在注入时会抛出异常

@Bean
public Teacher teacher() {
    return new Teacher();
}

@Bean
public Integer student() {
    return Integer.valueOf("1");
}
@Resource
private Person student;

哪怕student()返回值类型为Integer@Resource也会在变量名称与方法名称一致的情况下尝试进行注入,最后导致抛出异常

@Autowire在遇到这种情况时,会更加智能一些,它会察觉到student()方法名称虽然和变量名称一致,但返回值类型并不符合,因此会选择注入teacher()方法返回的实例

现在考虑一下极端情况,注入的变量上同时被标注了@Autowired@Resource注解,如下

@Bean
public Teacher teacher() {
    return new Teacher();
}

@Bean
public Student student() {
    return new Student();
}
@Autowired
@Resource(name = "teacher")
private Person student;

这实际上就是后处理器执行先后的问题了,如果是AutowiredAnnotationBeanPostProcessor后处理器先被执行,那么最终注入的实例类型为Student,否则的话会注入Teacher类型

spring 中,组件可以通过实现org.springframework.core.Ordered接口,来标识自身的优先级,此外,通过org.springframework.core.annotation.Order注解也可以指定当前组件的优先级,Ordered接口中定义了getOrder()方法,其返回的值越小(int 类型),代表优先级越高

AutowiredAnnotationBeanPostProcessor后处理器定义了一个order属性定义自身的优先级,如下

private int order = Ordered.LOWEST_PRECEDENCE - 2;

CommonAnnotationBeanPostProcessor也在构造方法中通过setOrder设置了优先级

public CommonAnnotationBeanPostProcessor() {
    setOrder(Ordered.LOWEST_PRECEDENCE - 3); // 优先级设置
    ......
}

可以看出AutowiredAnnotationBeanPostProcessor的优先级要比CommonAnnotationBeanPostProcessor的优先级低,因为其order值更大

那么只要在添加后处理器时,先排序,就可以调整后处理器添加的顺序,如下代码

beanFactory.getBeansOfType(BeanPostProcessor.class).values()
    .stream()
    .sorted(beanFactory.getDependencyComparator()) // 根据 order 值排序(升序)
    .forEach(beanFactory::addBeanPostProcessor);

经过排序后,CommonAnnotationBeanPostProcessor就会先执行,最终注入的结果为Teacher类型

ApplicationContext 实现

ApplicationContext的实现类依旧众多,最经典的有如下几个

  • org.springframework.context.support.ClassPathXmlApplicationContext 基于 classpath 下 xml 格式的配置文件创建 bean
  • org.springframework.context.support.FileSystemXmlApplicationContext 基于磁盘路径下 xml 格式的配置文件创建 bean
  • org.springframework.context.annotation.AnnotationConfigApplicationContext 基于注解配置来创建 bean
  • org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext基于注解配置来创建 bean,用于 web 环境

ClassPathXmlApplicationContext & FileSystemXmlApplicationContext

下面对ClassPathXmlApplicationContextFileSystemXmlApplicationContext这两个实现的基本使用进行演示,从演示中可以大概窥探出 spring 通过 xml 配置文件解析 bean 定义信息的大致原理

先定义 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bean1" class="com.example.spring.test.ApplicationContextMainText1.Bean1">
        <property name="bean2" ref="bean2"/>
    </bean>

    <bean id="bean2" class="com.example.spring.test.ApplicationContextMainText1.Bean2"/>
</beans>

分别使用ClassPathXmlApplicationContextFileSystemXmlApplication加载配置文件并获取 bean 实例

// ClassPathXmlApplicationContext 基于类路径的 xml 配置 bean
public static void testClassPathXmlApplication() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    Bean1 bean = context.getBean(Bean1.class);
    System.out.println(bean.bean2);
}
// FileSystemXmlApplication 基于文件系统的 xml 配置 bean
public static void testFileSystemXmlApplicationContext() {
    ApplicationContext context =
        new FileSystemXmlApplicationContext("D:\\projectCode\\spring-understand\\src\\main\\resources\\bean1.xml");
    Bean1 bean = context.getBean(Bean1.class);
    System.out.println(bean.bean2);
}

而这两个类之所以能够通过配置文件就能创建出 bean 实例,依靠的不仅仅是xml解析技术,bean 的实例化过程实际上是由BeanFactory提供的,其内部执行流程大致如下

// BeanFactory 的实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 通过 XmlBeanDefinitionReader 来解析 xml
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 将 xml 文件中的解析出的 bean 定义信息注册到 beanFactory 中
xmlBeanDefinitionReader.loadBeanDefinitions("bean1.xml");

Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);

上述代码可以体现出,ClassPathXmlApplicationContextFileSystemXmlApplicationContext只是解析了 xml 配置文件,如果想要注册 bean 定义信息,至少得提供 BeanFactory 的实例,而这在二者的父类org.springframework.context.support.AbstractRefreshableApplicationContext中,则通过组合的方式持有了一个BeanFactory的实例,定义如下

/** Bean factory for this context. */
@Nullable
private volatile DefaultListableBeanFactory beanFactory;

这样一切都说得通了,ClassPathXmlApplicationContextFileSystemXmlApplicationContext自身提供了解析 xml 配置的能力

再通过组合BeanFactory的方式,执行后续的 bean 定义注册,bean 实例化逻辑,这其中BeanFactory又扩展一系列后处理器的增强逻辑

AnnotationConfigApplicationContext

此实现类可以通过注解配置类的形式来配置 bean,如下代码

注解配置类

@Configuration
static class Config {
    @Bean
    public Bean1 bean1(Bean2 bean2) {
        Bean1 bean1 = new Bean1();
        bean1.setBean2(bean2);
        return bean1;
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

static class Bean1 {
    private Bean2 bean2;

    public void setBean2(Bean2 bean2) {
        this.bean2 = bean2;
    }
}

static class Bean2 {

}

使用 AnnotationConfigApplicationContext 加载注解配置类

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Bean1 bean = context.getBean(Bean1.class);
System.out.println(bean.bean2);

AnnotationConfigServletWebServerApplicationContext

此实现类主要用于 web 环境中的主要 bean 配置,通过此类就可以搭建起基本的 web 环境,这也是 spring boot 帮我们做的事情,使用演示如下

web 配置类

@Configuration
static class WebConfig {
    // 配置 web 容器
    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    // 配置前控制器
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 注册【前控制器】到 web 容器
    @Bean
    public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
        // "/" 匹配所有请求
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }

    // 手动注册一个控制器,这相当于使用 @Controller 注解注册了一个控制器
    @Bean("/hello") // 如果 bean 名称以 / 开头, 那么此控制器的访问路径就为完整的 bean 名称
    public Controller controllerOne() {
        return (request, response) -> {
            response.getWriter().println("hello");
            return null;
        };
    }
}

使用 AnnotationConfigServletWebServerApplicationContext 加载 web 配置类

ApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

容器启动完成后,内嵌的tomcat默认会在8080端口监听,可以访问localhost:8080/hello测试配置是否加载成功

Bean 生命周期

生命周期及后处理器

在 spring 中,一个 bean 通常都会经过【实例化】➡【依赖注入】➡【初始化】➡【销毁】这几个生命周期,如下代码

/**
 * @author dhj
 * @date 2022/10/7
 */
@Component
public class LifeBean {
    private static final Logger log = LoggerFactory.getLogger(LifeBean.class);

    public LifeBean() {
        log.info("实例化");
    }

    @Autowired
    public void autowire(@Value("${JAVA_HOME}") String javaHome) {
        log.info("依赖注入: {}", javaHome);
    }

    @PostConstruct
    public void init() {
        log.info("初始化");
    }

    @PreDestroy
    public void destroy() {
        log.info("销毁");
    }
}

启动容器后关闭,以演示 bean 销毁的生命周期

ConfigurableApplicationContext context = SpringApplication.run(SpringDemoApplication.class, args);
context.close();

打印结果如下

实例化
依赖注入: D:\JDK\jdk-8\jdk8
初始化
销毁

而在这几个生命周期中,又穿插了各种后处理器,在之前,有关BeanFactory的后处理器BeanFactoryPostProcessor,主要是向 BeanFactory中提供一些 bean 定义的补充信息

而针对 bean 生命周期的后处理器BeanPostProcessor,主要是提供和 bean 相关的一些扩展增强功能,下面是自定义的后处理器,用以观察后处理器相对于生命周期执行的时机

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * 自定义的 bean 后处理器
 *
 * @author dhj
 * @date 2022/10/7
 */
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.info("销毁之前执行, 例如被 @PreDestroy 标识的方法");
        }
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.info("实例化之前执行, 这里返回的对象会替换掉原本的 bean");
        }
        // 返回 null 则不会替换
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.debug("实例化之后执行, 如果返回 false 会跳过依赖注入的环节");
            //return false;
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.info("依赖注入阶段执行, 如 @Autowired @Value @Resource");
        }
        return pvs;
    }


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.info("初始化之前执行, 这里的返回的对象会替换掉原本的 bean, 如 @PostConstruct @ConfigurationProperties");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("lifeBean".equals(beanName)) {
            LOGGER.info("初始化之后执行, 这里返回的对象会替换掉原本的 bean, 例如返回代理后的对象");
        }
        return bean;
    }
}

打印结果

实例化之前执行, 这里返回的对象会替换掉原本的 bean
实例化
依赖注入阶段执行, 如 @Autowired @Value @Resource
依赖注入: D:\JDK\jdk-8\jdk8
初始化之前执行,这里的返回的对象会替换掉原本的 bean, 如 @PostConstruct @ConfigurationProperties
初始化
初始化之后执行, 这里返回的对象会替换掉原本的 bean, 例如返回代理后的对象
销毁之前执行, 例如被 @PreDestroy 标识的方法
销毁

可以看到,在 bean 生命周期各个阶段的前后,都可以通过后处理器穿插各种逻辑,这也是 spring 能够提供各种增强功能的基础

模板方法

通过BeanFactory调用getBean()时,不同的后处理逻辑会在 bean 生命周期的各个阶段被执行,这些逻辑不可能全部耦合到getBean()中,而是使用模板方法的设计模式来扩展不同的后处理逻辑,因为 bean 生命周期的几个阶段是固有的逻辑,变化的只是后处理器,将这些后处理的行为抽象出来,交给子类去实现不同的后处理器,然后只需要在getBean()中统一调用抽象接口,就可以实现不同后处理逻辑的调用,如下代码

/**
 * @author dhj
 * @date 2022/10/8
 */
@Slf4j
public class TemplateMethod {
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactory();

        //  添加扩展的后处理逻辑
        beanFactory.addBeanPostProcessor(new BeanFactory.BeanPostProcessor() {
            @Override
            public void postProcessBeforeInitialization(Object bean) {
                log.info("postProcessor:解析 @Autowire,解析 @Resource...");
            }
            @Override
            public void postProcessAfterInitialization(Object bean) {
                log.info("postProcessor:初始化后的后处理逻辑...");
            }
        });

        String bean = beanFactory.getBean(String.class);
        System.out.println(bean);
    }

    static class BeanFactory {
        private final List<BeanPostProcessor> processorList = new ArrayList<>(5);

        public <T> T getBean(Class<T> clazz) {
            try {
                // 不变的部分
                log.info("实例化");
                T bean = clazz.getDeclaredConstructor().newInstance();
                log.info("依赖注入");

                // 变化和扩展的部分, 通过 processorList 统一维护, 动态扩展【processorList.add()】而无需修改原始代码
                this.processorList.forEach(processor -> {
                    processor.postProcessBeforeInitialization(bean);
                });
                log.info("初始化");
                this.processorList.forEach(processor -> {
                    processor.postProcessAfterInitialization(bean);
                });
                return bean;
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.error("bean 初始化失败");
            return null;
        }

        // 外部调用, 添加扩展的逻辑
        public void addBeanPostProcessor(BeanPostProcessor processor) {
            this.processorList.add(processor);
        }

        // 定义抽象的需要扩展的部分
        interface BeanPostProcessor {
            void postProcessBeforeInitialization(Object bean);

            void postProcessAfterInitialization(Object bean);
        }
    }
}

常见 Bean 后处理器

前面已经了解了 spring 如何通过模板方法模式,将不同的 bean 后处理器用于 bean 生命周期的各个阶段,这里继续演示 spring 中的几个常见后处理器及其作用

有如下代码结构

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

/**
 * @author dhj
 * @date 2022/10/8
 */
public class BeanPostProcessorMainTest {
    public static void main(String[] args) {
        // 一个纯粹的 bean 容器, 没有额外的功能
        GenericApplicationContext context = new GenericApplicationContext();
        
        // 注册 bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);

        // 初始化容器
        context.refresh();

        // 关闭容器
        context.close();
    }


    static class Bean1 {
        private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);

        private Bean2 bean2;

        private Bean3 bean3;

        private String javaHome;

        @Autowired
        public void setBean2(Bean2 bean2) {
            LOGGER.info("@Autowire 生效");
            this.bean2 = bean2;
        }

        @Resource
        public void setBean3(Bean3 bean3) {
            LOGGER.info("@Resource 生效");
            this.bean3 = bean3;
        }

        @Autowired
        public void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {
            LOGGER.info("@Value 生效");
            this.javaHome = javaHome;
        }

        @PostConstruct
        public void init() {
            LOGGER.info("@PostConstruct 生效");
        }

        @PreDestroy
        public void destroy() {
            LOGGER.info("@PreDestroy 生效");
        }
    }

    static class Bean2 {

    }

    static class Bean3 {

    }
}

以上是一个最基本的 spring 容器的启动过程,但因为使用了GenericApplicationContext,此类中没有注册任何后处理器,所以各项功能如【依赖诸如】【bean 生命周期钩子】等功能都没有生效,现在分别来逐步添加各种后处理器,就可以很明显的观察到 spring 中不同后处理器产生的效果了

AutowiredAnnotationBeanPostProcessor

// 添加 @Qualifier @Value @Lazy 等注解的解析器, 否则 @Value 的值注入会失败,
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加 @Autowire, @Value 注解的后处理器 AutowiredAnnotationBeanPostProcessor 到 context 容器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);

AutowiredAnnotationBeanPostProcessor 主要用于解析@Autowired以及@Value注解,完成依赖注入,关于ContextAnnotationAutowireCandidateResolver,可参考 Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析


CommonAnnotationBeanPostProcessor

// 添加 CommonAnnotationBeanPostProcessor 到 context 容器
context.registerBean(CommonAnnotationBeanPostProcessor.class);

CommonAnnotationBeanPostProcessor 主要用于解析@Resource @PostConstruct @PreDestroy注解


ConfigurationPropertiesBindingPostProcessor

// 添加 @ConfigurationProperties 注解的后处理器到 context 容器
ConfigurationPropertiesBindingPostProcessor.register(context);

主要用于解析@ConfigurationProperties注解

Autowired Bean 后处理器

用于处理@Autowired注解的处理器比较常用,下面单独进行分析,代码如下

类结构

static class Bean1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);

    private Bean2 bean2;

    private Bean3 bean3;

    private String javaHome;

    public Bean1() {
        System.out.println("bean1 实例化");
    }

    @Autowired
    public void setBean2(Bean2 bean2) {
        LOGGER.info("@Autowire 生效,bean2:{}", bean2);
        this.bean2 = bean2;
    }

    @Resource
    public void setBean3(Bean3 bean3) {
        LOGGER.info("@Resource 生效,bean3:{}", bean3);
        this.bean3 = bean3;
    }

    @Autowired
    public void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {
        LOGGER.info("@Value 生效,value:{}", javaHome);
        this.javaHome = javaHome;
    }

    @PostConstruct
    public void init() {
        LOGGER.info("@PostConstruct 生效");
    }

    @PreDestroy
    public void destroy() {
        LOGGER.info("@PreDestroy 生效");
    }
}

static class Bean2 {
    public Bean2() {
        System.out.println("bean2 实例化");
    }
}

static class Bean3 {
    public Bean3() {
        System.out.println("bean3 实例化");
    }
}

通过后处理器完成依赖注入

// 创建 beanFactory 实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

// 先注册 bean1 依赖的 bean 实例, 方便测试
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

// 添加占位符解析器, 如 ${}
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

// @Autowired @Value 注解解析的后处理器
AutowiredAnnotationBeanPostProcessor autowiredBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();

// 设置后处理器的 beanFactory 引用, 因为 autowiredBeanPostProcessor 需要从 beanFactory 中获取实例化好的 bean, 用于依赖注入
autowiredBeanPostProcessor.setBeanFactory(beanFactory);

// 创建需要被依赖注入的目标 bean 实例, 此实例中的成员变量, 方法参数存在被 @Autowired @Value 修饰的情况
Bean1 bean1 = new Bean1();

/*
【针对 bean1 执行依赖注入的后处理逻辑】
参数一 pvs 代表【使用指定的值注入】, 这里不需要
参数二 代表需要被注入的目标 bean
参数三 代表目标 bean 的名称
*/
autowiredBeanPostProcessor.postProcessProperties(null, bean1, "bean1");
System.out.println(bean1.javaHome);

关键在于postProcessProperties()方法,AutowiredAnnotationBeanPostProcessor只通过此方法就完成对目标 bean 的依赖注入,此方法源码如下

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // findAutowiringMetadata() 查找需要依赖注入的数据
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

findAutowiringMetadata()方法会查找目标 bean 中被@Autowired @Value标识的属性或方法入参,将其封装为一个InjectionMetadata类型的对象,如下

InjectionMetadata中通过集合的形式保存了目标 bean 中所有的需要依赖注入的【方法】或【属性】

postProcessProperties中的依赖注入代码使用手动方式实现如下

// 创建 beanFactory 实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

// 先注册 bean1 依赖的 bean 实例, 方便测试
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

// 添加占位符解析器, 如有 ${}
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

// @Autowired @Value 注解解析的后处理器
AutowiredAnnotationBeanPostProcessor autowiredBeanPostProcessor
    = new AutowiredAnnotationBeanPostProcessor();

// 设置后处理器的 beanFactory 引用, 因为 autowiredBeanPostProcessor 需要从 beanFactory 中获取实例化好的 bean, 用于依赖注入
autowiredBeanPostProcessor.setBeanFactory(beanFactory);

// 创建需要被依赖注入的目标 bean 实例, 此实例中的成员变量, 方法参数存在被 @Autowired @Value 修饰的情况
Bean1 bean1 = new Bean1();

// =================== 手动依赖注入 =========================
Class<AutowiredAnnotationBeanPostProcessor> autowiredAnnotationBeanPostProcessorClass
    = AutowiredAnnotationBeanPostProcessor.class;
Method postProcessPropertiesMethod = autowiredAnnotationBeanPostProcessorClass
    .getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);

postProcessPropertiesMethod.setAccessible(true);
InjectionMetadata meta = (InjectionMetadata) postProcessPropertiesMethod.invoke(autowiredBeanPostProcessor, "bean1", Bean1.class, null);

// 对 bean1 执行依赖注入
meta.inject(bean1, "bean1", null);

System.out.println(bean1.javaHome);

总结

spring 中的@Autowired @Value等注解的解析和自动注入功能,由AutowiredAnnotationBeanPostProcessor提供,只需要调用其postProcessProperties()方法就可以完成依赖注入的后处理逻辑

postProcessProperties()内部又分为两个步骤来完成依赖注入

  • 解析目标 bean 中标注了@Autowired@Value注解的属性或方法,保存到集合中
  • 遍历集合,针对这些属性和方法所需的依赖,根据 beanFactory 中的 bean,,环境变量信息,配置文件等,执行注入

常见工厂后处理器

相对于BeanPostProcessor,工厂后处理器BeanFactoryPostProcessor主要用于对BeanFactory进行的组件扫描、注册提供增强

现有如下类结构,都位于com.example.spring.bean.scan包下

import org.slf4j.LoggerFactory;

/**
 * @author dhj
 * @date 2022/10/15
 */
public class Bean1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {
        LOGGER.info("{}-实例化", Bean1.class.getName());
    }
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author dhj
 * @date 2022/10/15
 */
@Component
public class Bean2 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Bean2.class);

    public Bean2() {
        LOGGER.info("{}-实例化", Bean2.class.getName());
    }
}

配置类

@Configuration
@ComponentScan(basePackages = "com.example.spring.bean.scan") // 扫描包路径
static class Config {

    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("111111");
        return druidDataSource;
    }
}

演示代码

import com.alibaba.druid.pool.DruidDataSource;
import com.example.spring.bean.scan.Bean1;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * 常见 BeanFactoryPostProcessor 演示
 *
 * @author dhj
 * @date 2022/10/15
 */
public class BeanFactoryPostProcessorMainTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(BeanFactoryPostProcessorMainTest.class);

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 注册配置类
        context.registerBean("config", Config.class);
        
        // 初始化容器
        context.refresh();

        // 打印容器中的 bean 定义
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

        // 容器销毁
        context.close();
    }
}

以上代码的打印结果中,只会包含config,也就是我们手动注册的配置类,但配置类上指定的包扫描以及其中通过@Bean注册的组件通通都没有生效,因为这些功能是由BeanFactoryPostProcessor提供的

要想使Config类中标注的注解生效,需要在容器初始化之前注册org.springframework.context.annotation.ConfigurationClassPostProcessor类型的BeanFactoryPostProcessor,如下

// 注册 BeanFactoryPostProcessor
// @Configuration @Bean @ComponentScan 等注解的解析
context.registerBean(ConfigurationClassPostProcessor.class);

// 初始化容器
context.refresh();

这样就能够将Config配置类所配置的相关 bean 注册到 BeanFactory 中,这种扩展功能,正是由ConfigurationClassPostProcessor提供

再举一个例子,spring 整合 mybatis 时,需要指定@Mapper注解的扫描包路径,之后此包之下标注了@Mapper注解的 bean 定义信息都会被注册到 beanFactory 中,但因为Mapper都是接口类型,容器中的组件实际上是Mapper接口对应的代理实现类,这不是重点

重点是扫描Mapper接口所在包以及注册 bean 定义信息的org.mybatis.spring.mapper.MapperScannerConfigurer,也是一个BeanFactoryPostProcessor,其类定义如下

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    .....

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这说明只需要在初始化容器之前,注册此类到BeanFactory中,即可实现Mapper接口的扫描和注册,这也是@MapperScan注解所做的事,如下

准备一个 Mapper 接口

/**
 * @author dhj
 * @date 2022/10/15
 */
@Mapper
public interface Mapper1 {
   Logger LOGGER = LoggerFactory.getLogger(Mapper1.class);
}

手动注册 Mapper 接口扫描的 BeanFactoryPostProcessor

// MyBatis 的 Mapper 注解扫描 BeanFactoryProcessor
context.registerBean(MapperScannerConfigurer.class, customBeanDefinition -> {
    customBeanDefinition.getPropertyValues()
        .add("basePackage", "com.example.spring.mapper"); // 自定义的 BeanDefinition 属性, 这里是指定扫描的包路径
});

获取注册的 Mapper 接口实例

Mapper1 mapperProxy = context.getBean(Mapper1.class);

通过 debug 可以看到,最终获取的Mapper接口对应的实例,是一个由 mybatis 提供的代理对象

【组件扫描 && 注册】模拟实现

为了更好的理解BeanFactoryPostProcessor的运行原理,下面对BeanFactoryPostProcessor的组件扫描 && 注册功能进行一个模拟实现

spring 中对组件资源的加载主要分为扫描和注册

  • 首先是根据@ComponentScan注解,加载指定package(类路径)下的.class资源,再检测每个资源是否被标识为了一个 spring 组件
  • 如果是被@Component直接或间接标识,那么会将其封装为BeanDefinition,注册到 beanFactory 中
  • 容器在刷新时,则会将 beanFactory 中注册的BeanDefinition实例化为可用的组件

组件资源扫描 && 注册

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);

// 查找 Config 类上标注的 ComponentScan 注解
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
Assert.notNull(componentScan, "not null");
String[] packages = componentScan.basePackages();
// 用于读取 Resource 的元信息
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (String packageStr : packages) {
    // 将扫描注解上指定的包路径转化为 classpath*:/xxx/xxx/xxx/**/*.class 资源路径用于扫描
    String resourcePath = "classpath*:" + packageStr.replace(".", "/") + "/**/*.class";

    // 加载指定类路径下的资源,实际会将 .class 文件加载为 IO 输入流
    Resource[] resources = context.getResources(resourcePath);

    // 组件名称的生成器,可以根据 BeanDefinition 生成 beanName
    AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

    for (Resource resource : resources) {
        // 拿到一个 metaData 的读取器
        MetadataReader metadataReader = factory.getMetadataReader(resource);

        // 读取读取 class 的注解元数据,判断其是否加了 @Component 注解(只能是 @Component 注解)
        boolean directComponent = metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName());

        // 判断是否加了 @Component 的派生注解,例如 @Controller
        boolean indirectComponent = metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName());

        // 直接或间接的加了 @Component 注解
        if (directComponent || indirectComponent) {
            // 根据组件的 className 创建对应的 BeanDefinition
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
                .getBeanDefinition();

            // 生成 beanName
            String beanName = beanNameGenerator.generateBeanName(beanDefinition, beanFactory);

            // 注册 beanDefinition
            context.getDefaultListableBeanFactory()
                .registerBeanDefinition(beanName, beanDefinition);
        }
    }
}

// 容器刷新
context.refresh();

// 打印被扫描出的组件名称
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

实际上,上述代码就是 spring 中组件扫描对应的BeanFactoryPostProcessor执行的基本逻辑

@Bean 模拟实现

有时候我们所定义的组件不是通过@ComponentScan指定扫描的包路径来自动注册到 spring 容器的,因为可能需要自己手动创建一个实例注册到 spring 容器中(例如数据库源对象的创建,需要指定 url username password 等参数)

此时就可以通过Configuration声明一个配置类,在配置类中使用@Bean标识某个方法, 被标识方法的返回值实例则会被添加到 spring 容器中,例如

@Bean
public DruidDataSource dataSource() {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
    druidDataSource.setUsername("root");
    druidDataSource.setPassword("111111");
    return druidDataSource;
}

这实际上是工厂方法模式的体现,配置类作为工厂,@Bean注解标识的方法则作为工厂方法

下面对 spring 中解析@Bean注解,注册组件到容器的过程进行一个模拟,代码如下

// @Bean 模拟实现
private static void beanTest() throws Exception {
    GenericApplicationContext context = new GenericApplicationContext();
    // 注册配置类
    context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);

    // 用于读取指定资源的元数据
    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();

    // 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
    MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());

    // 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
    Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());

    // 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
    methods.forEach(method -> {
        // 创建一个 BeanDefinitionBuilder
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        // 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
        // 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
        builder.setFactoryMethodOnBean(method.getMethodName(), BeanFactoryPostProcessorMainTestConfig.class.getName());

        // 拿到 BeanDefinition
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        // 注册 BeanDefinition
        context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), beanDefinition);
    });

    // 初始化容器
    context.refresh();
}

上述代码在初始化容器时会报错 No bean named 'com.example.spring.config.BeanFactoryPostProcessorMainTestConfig' available,这是因为在setFactoryMethodOnBean()调用时,需要指定

  • method.getMethodName()工厂方法的名称
  • BeanFactoryPostProcessorMainTestConfig.class.getName()调用工厂方法的 bean 名称

问题就在 bean 名称这里,这个 bean 实际上就是配置类注册到容器中的名称,而配置类我们在一开始就已经注册过了:context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);

所以setFactoryMethodOnBean()方法的第二个参数,应该为config

此问题解决后,初始化容器时继续报错:Error creating bean with name 'sqlSessionFactoryBean': Unsatisfied dependency expressed through method 'sqlSessionFactoryBean

sqlSessionFactoryBean的工厂方法相较于其他普通工厂方法,多了方法的入参,如下

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    return sqlSessionFactoryBean;
}

针对于工厂方法的入参以及构造方法的入参,如果需要自动注入,可以指定自动注入的模式为AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR

最终代码如下

private static void beanTest() throws Exception {
    GenericApplicationContext context = new GenericApplicationContext();
    // 注册配置类
    context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);

    // 用于读取指定资源的元数据
    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();

    // 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
    MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());

    // 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
    Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());

    // 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
    methods.forEach(method -> {
        // 创建一个 BeanDefinitionBuilder
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        // 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
        // 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
        builder.setFactoryMethodOnBean(method.getMethodName(), "config");
        // 设置自动注入的模式,在 @Bean 标识的方法有入参时,可以自动注入参数实例,默认为 AUTOWIRE_NO(不自动注入)
        // 而对于构造方法和工厂方法的注入,都使用的是 AUTOWIRE_CONSTRUCTOR 模式
        builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);

        // 拿到 BeanDefinition
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        // 注册 BeanDefinition
        context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), beanDefinition);
    });

    // 初始化容器
    context.refresh();
}

实际上,还可以将此逻辑封装为一个BeanFactoryPostProcessor,如下代码

import com.example.spring.config.BeanFactoryPostProcessorMainTestConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;

import java.util.Set;

public class AtBeanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(AtBeanBeanFactoryPostProcessor.class);

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

        try {
            // 用于读取指定资源的元数据
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();

            // 读取 BeanFactoryPostProcessorMainTestConfig 配置类的元数据
            MetadataReader reader = factory.getMetadataReader(BeanFactoryPostProcessorMainTestConfig.class.getName());

            // 通过配置类的元信息拿到配置类中标注了 @Bean 注解的方法元数据集合
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());

            // 遍历方法元数据集合(实际上就是遍历配置类中所有被标注了 @Bean 注解的方法)
            methods.forEach(method -> {
                // 创建一个 BeanDefinitionBuilder
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                // 设置工厂方法(也就是当前遍历到的被 @Bean 标识的方法)
                // 底层实际上是设置了 BeanDefinition 的 factoryMethodName 属性以及 factoryBeanName 属性
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                // 设置自动注入的模式,在 @Bean 标识的方法有入参时,可以自动注入参数实例,默认为 AUTOWIRE_NO(不自动注入)
                // 而对于构造方法和工厂方法的注入,都使用的是 AUTOWIRE_CONSTRUCTOR 模式
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);

                // 拿到 BeanDefinition
                AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
                // 注册 BeanDefinition
                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
                beanFactory.registerBeanDefinition(method.getMethodName(), beanDefinition);
            });
        } catch (Exception e) {
            LOGGER.error("异常:", e);
        }
    }
}

使用时,直接注册AtBeanBeanFactoryPostProcessor到容器中,就可实现@Bean的功能,如下代码

GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", BeanFactoryPostProcessorMainTestConfig.class);
// 注册自定义的 @Bean 工厂后处理器
context.registerBean(AtBeanBeanFactoryPostProcessor.class);

// 初始化容器
context.refresh();

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);

存在的问题

  • 没有处理initMethod属性
  • 配置类被写死为BeanFactoryPostProcessorMainTestConfig,应该扫描所有标注了@Configuration注解的配置类

@Mapper 模拟实现

在之前,直接通过MapperScannerConfigurer完成了扫描标注@Mapper注解标识的接口,注册其代理对象的实例到容器中的功能,这是借助了mybatis-spring依赖提供的封装好的BeanFactoryPostProcessor而实现的,下面来手动实现其功能

首先需要了解的是最基本的Mapper接口注入,有如下配置类

import com.alibaba.druid.pool.DruidDataSource;
import com.example.spring.mapper.Mapper1;
import com.example.spring.mapper.Mapper2;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class MapperConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("111111");
        return druidDataSource;
    }

    // Mapper 对象的创建工厂 Bean, 这里是指定创建 Mapper1 类型的对象实例
    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

    // Mapper 对象的创建工厂 Bean ,这里是指定创建 Mapper2 类型的对象实例
    @Bean
    public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }
}

MapperFactoryBean,顾名思义,也就是生产 Mapper 对象实例的工厂,这里向容器中注入了 Mapper 对象工厂,在获取 Mapper 实例时,则通过对应的 Mapper 对象工厂来创建

注册配置类,启动容器

GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类
context.registerBean("config", MapperConfig.class);
// 自定义的 @Bean 工厂后处理器
context.registerBean(AtBeanBeanFactoryPostProcessor.class);
context.refresh();
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
Mapper1 bean = context.getBean(Mapper1.class);
System.out.println(bean);
context.close();

需要注意的是,这里使用了自定义的@Bean工厂后处理器AtBeanBeanFactoryPostProcessor,这里面的配置类目前是写死的,需要手动将AtBeanBeanFactoryPostProcessor@Bean涉及到的相关配置类修改为context.registerBean("config", MapperConfig.class);中注册的配置类:MapperConfig

注册的配置类和AtBeanBeanFactoryPostProcessor实际使用的配置类,二者需要保持一致,使得AtBeanBeanFactoryPostProcessor会扫描Mappconfig中的@Bean,加载对应的组件到容器中

因为AtBeanBeanFactoryPostProcessor中,有两处关键位置使用到了对应的配置类

  • factory.getMetadataReader(MapperConfig.class.getName());加载配置类的元数据信息,目的是为了得到其中标注了@Bean的工厂方法(通过 class 找到对应的配置类)
  • builder.setFactoryMethodOnBean(method.getMethodName(), "config");设置创建@Bean申明的组件时,其调用的对应的工厂方法(通过指定配置类注册时的名称:config,找到对应的配置类,)

针对以上两种情况,如果注册时的配置类与实际使用时的配置类不一致,则可能会出现当前得到的工厂方法名称,在另一个配置类中不存在等异常情况

自定义 @Mapper 工厂后处理器

首先是定义 Mapper 配置类,其中包含 Mapper 实例创建所必须的组件,如下

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@MapperScan(basePackages = "com.example.spring.mapper")
@Configuration
public class MapperScanConfig {
    @Bean(initMethod = "init")
    public DruidDataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xlf_db");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("111111");
        return druidDataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}

接下来便是实现自定义的 MapperBeanFactoryProcessor,如下代码

import com.example.spring.config.MapperScanConfig;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.Assert;

/**
 * 自定义的 Mapper 工厂后处理器
 */
public class MapperBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // 资源解析器
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // 元数据读取器
            CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
            MapperScan mapperScan = AnnotationUtils.findAnnotation(MapperScanConfig.class, MapperScan.class);
            AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
            Assert.notNull(mapperScan, "not null");

            String[] basePackages = mapperScan.basePackages();
            for (String basePackagePath : basePackages) {
                String resourcePath = "classpath*:" + basePackagePath.replace(".", "/") + "/**/*.class";
                // 加载类路径下的资源
                Resource[] resources = resolver.getResources(resourcePath);
                for (Resource resource : resources) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    ClassMetadata classMetadata = metadataReader.getClassMetadata();
                    boolean hasMapper = metadataReader
                        .getAnnotationMetadata()
                        .hasAnnotation(Mapper.class.getName());
                    boolean isInterface = classMetadata.isInterface();
                    // 标注了 @Mapper 注解并且为接口类型,才可将其作为 Mapper 接口
                    if (hasMapper && isInterface) {
                        /*
                         在容器中为对应的 Mapper 接口,创建其对应的 MapperFactoryBean
                         */
                        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                            .genericBeanDefinition(MapperFactoryBean.class)
                                // 设置当前 MapperFactoryBean 对应的 Mapper
                                .addConstructorArgValue(classMetadata.getClassName())
                                // 设置当前 MapperFactoryBean 对应的装配模式
                                // 因为 MapperFactoryBean 实例创建时,还需要其他
                                // 实例参数,这里通过自动装配的方式进行设置
                                .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                                .getBeanDefinition();

                        /*
                        注意,这里如果直接使用当前的 beanDefinition 生成 beanName
                        会出现名称重复的情况(因为一直使用的是 MapperFactoryBean.class 来生成的 beanDefinition)
                        最终导致容器中只会注入一个 beanDefinition
                        spring 的解决办法为使用当前 Mapper 接口生成一个 beanDefinition
                        使用此 beanDefinition 专门来生成 beanName,
                        这样每个 Mapper 接口都有其对应的 MapperFactoryBean 了
                         */
                        BeanDefinition mapperBd = BeanDefinitionBuilder
                            .genericBeanDefinition(classMetadata.getClassName())
                                .getBeanDefinition();
                        String beanName = beanNameGenerator.generateBeanName(mapperBd, registry);
                        // 注册 MapperFactoryBean 到容器
                        registry.registerBeanDefinition(beanName, beanDefinition);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

Aware && InitializingBean

Aware 接口

org.springframework.beans.factory.Aware 接口主要用于注入一些与容器相关的信息,官方文档对其定义如下

一个标记性的超接口,表明 Bean 有资格被 Spring 容器通过回调式方法通知到某个特定的框架对象。实际的方法签名由各个子接口决定,但通常应该只由一个接受单一参数的无返回值的方法组成

例如Aware接口的子接口org.springframework.beans.factory.BeanNameAware,通过实现此接口,当前组件便可以通过其回调方法得到自身在 spring 容器中的名字,如下代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MyBean implements BeanNameAware {

    @Override
    public void setBeanName(String name) {
        log.info("---{}", name);
    }
}

再比如org.springframework.beans.factory.BeanFactoryAware接口,则可以将BeanFactory容器注入到当前组件中来

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MyBean implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("---{}", beanFactory.toString());
    }
}

除此之外,还有如下一些常见的Aware接口,包括

org.springframework.context.ApplicationContextAware接口,用于注入ApplicationContext容器

org.springframework.context.EmbeddedValueResolverAware接口,用于注入${}等表达式的解析器

InitializingBean 接口

关于org.springframework.beans.factory.InitializingBean接口,官方文档对其定义如下

由需要在 BeanFactory 设置所有属性后做出反应的 Bean 实现的接口:例如,执行自定义的初始化,或者仅仅是检查所有必须的属性是否已经被设置。

关键在于,InitializingBean接口中定义的方法,是在所有的属性设置完成后执行的,此时当前组件所有的属性已经准备完成了,利用这一条件,可以继续定义一些增强逻辑,也就是在正式使用组件之前,执行的一些初始化操作

例如凭借InitializingBean接口,将配置文件中解析出的值赋给静态变量,示例代码如下

首先在配置文件中定义一个值

server.host=127.0.0.1

现在想要通过静态变量的方式,直接拿到配置文件中的值,可能会有如下写法

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MyBean {
    @Value("${server.host}")
    public static String HOST;
}

但最终HOST的值为null,因为 spring 并不支持对静态字段使用@Autowired注入,某些情况下,还可能打印类似错误信息Autowired annotation is not supported on static fields

此时便可以借助InitializingBean接口,来实现静态变量从配置文件中取值的效果,如下代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MyBean implements InitializingBean {
    @Value("${server.host}")
    private String hostValue;
    
    public static String HOST;

    @Override
    public void afterPropertiesSet() throws Exception {
        HOST = this.hostValue;
    }
}

首先hostValue是普通的成员变量,通过@Value注解能够正常得到值,到目前为止,都可以看作 bean 生命周期的【依赖注入】阶段

InitializingBean接口则保证在其提供的afterPropertiesSet()执行时(初始化阶段),所有的属性都已经设置完毕,这时就可以自然的将hostValue的值赋给HOST,当整个【初始化阶段】执行完成,组件才可以正常使用,不会存在赋值之前就调用导致拿到空值的情况

内置功能

实际上,不论是Aware还是InitializingBean接口,都有其对应的替代方式,例如Aware接口的注入功能,使用@Autowired便可以满足大部分的场景且功能更加丰富

InitializingBean接口在初始阶段执行的逻辑,使用@PostConstruct来标注相同的逻辑的方法,也能达到同样的效果

@Autowired@PostConstruct注解提供的功能,都属于 spring 的 扩展功能(需要各种后处理器来实现),AwareInitializingBean则属于 spring 的 内置功能,最大的区别在于,内置功能是不会失效的,而扩展功能在某些情况下则会失效

@Autowired 失效分析

前面已经说过,spring 的内置功能在某些情况下可能会失效,下面则是@Autowired失效情况的演示

定义配置类

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
@Slf4j
public class MyConfig {

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.info("application bean-----{}", applicationContext);
    }

    @PostConstruct
    public void init() {
        log.info("myConfig init-------");
    }
}

一个基本的 spring 容器启动流程

import com.example.spring.config.MyConfig;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

import java.util.Arrays;

/**
 * Autowired 失效测试
 */
public class AutoWiredInvalidTest {

    public static void main(String[] args) {
        test1();
    }

    public static void test1() {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myConfig", MyConfig.class);
        // 对 @Autowired、@Value 注解提供支持
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        // 对 @PostConstruct 注解提供支持
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        // 对 @Configuration、@Bean 等注解提供支持
        context.registerBean(ConfigurationClassPostProcessor.class);

        context.refresh();

        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
    }
}

观察控制台的打印信息如下,不出意外,此时的@Autowired@PostConstruct已经生效

application bean---org.springframework.context.support.GenericApplicationContext@768b970c
com.example.spring.config.MyConfig - myConfig init-------
myConfig
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor

修改配置类

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
@Slf4j
public class MyConfig {

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.info("application bean-----{}", applicationContext);
    }

    @PostConstruct
    public void init() {
        log.info("myConfig init-------");
    }
    
    @Bean
    public BeanFactoryPostProcessor processor1() {
        return beanFactory -> {
            log.info("processor1 exec----");
        };
    }
}

再次执行,发现新增的BeanFactoryPostProcessor确实生效了,但是@Autowired@PostConstruct注解相关的功能以及对应的后处理器提供的扩展功能却失效了,要想探究失效的具体原因,需要先对 spring 启动过程中的refresh()流程有一个基本了解,这里分为两种情况,如下

配置类中不包含BeanFactoryPostProcessor时,配置类的创建情况

Java配置类BeanPostProcessorBeanFactoryPostProcessorApplicationContextJava配置类BeanPostProcessorBeanFactoryPostProcessorApplicationContext1. 执行 BeanFactoryPostProcessor2. 注册 BeanPostProcessor3. 创建和初始化3.1 依赖注入扩展逻辑执行(如 @Value 和 @Autowired)3.2 初始化扩展逻辑执行(如 @PostConstruct)3.3 执行 Aware 及 InitializingBean3.4 创建成功

配置类中包含BeanFactoryPostProcessor时,配置类的创建情况,如下

...... 省略
@Bean
public BeanFactoryPostProcessor processor1() {
    return beanFactory -> {
        log.info("processor1 exec----");
    };
}
...... 省略
Java配置类BeanPostProcessorBeanFactoryPostProcessorApplicationContextJava配置类BeanPostProcessorBeanFactoryPostProcessorApplicationContext3. 创建和初始化(配置类提前创建)3.1 执行 Aware 及 InitializingBean3.2 创建成功(配置类提前创建完成)1. 执行 BeanFactoryPostProcessor(触发配置类提前创建,到第3步)2. 注册 BeanPostProcessor

此时的配置类中通过@Bean向容器中注入了BeanFactoryPostProcessor,而BeanFactoryPostProcessor想要创建成功,就先要 提前 创建配置类,注意这里配置类的创建被 提前 到了refresh()流程中的【执行 BeanFactoryPostProcessor】阶段,而此时还没有到@Autowired@PostConstruct等对应的 扩展功能 执行的阶段

最终导致:当配置类中存在BeanFactoryPostProcessor注入时,当前配置类中其余后执行的扩展逻辑失效

但就算是配置类被提前创建,假如配置类实现了AwareInitializingBean接口,在配置类创建之前,其中对应的AwareInitializingBean的回调方法还是会被正常执行(如上图)

这便是Aware等内置扩展的优势,它总是会被执行,不用担心失效的问题,这也是 spring 框架的内部类常用的注入和初始化方式

初始化 && 销毁

初始化

spring 中提供了多种 bean 初始化的方式,如下代码

import com.example.spring.bean.init.Bean1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

    @Bean(initMethod = "init3") // 方式一:通过 @Bean 的 initMethod 指定初始化方法(方法由 Bean1 自身提供)
    public Bean1 bean1() {
        return new Bean1();
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;

@Slf4j
public class Bean1 implements InitializingBean {

    // 方式二:被 @PostConstruct 标识的方法可以作为初始化方法
    @PostConstruct
    public void init1() {
        log.info("PostConstruct-----");
    }

    // 方式三:实现 InitializingBean 的 afterPropertiesSet 方法,也会被作为初始化逻辑执行
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("afterPropertiesSet-----");
    }

    public void init3() {
        log.info("init3-----");
    }
}

至于这三种初始化方式的执行顺序,通过如下打印信息可以得出

PostConstruct-----
afterPropertiesSet-----
init3-----

销毁

多种销毁方式的示例代码如下

import com.example.spring.bean.destroy.Bean1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

    @Bean(destroyMethod = "destroyMethod") // 方式一:通过 @Bean 注解的 destroyMethod 指定销毁方法(方法由 Bean1 自身提供)
    public Bean1 bean1() {
        return new Bean1();
    }
}

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;

import javax.annotation.PreDestroy;

@Slf4j
public class Bean1 implements DisposableBean {
    // 方式二:被 @PreDestroy 标识的方法可以作为销毁方法
    @PreDestroy
    public void method1() {
        log.info("PreDestroy-----");
    }

    // 方式三:实现 DisposableBean 的 destroy 方法,也会被作为销毁逻辑执行
    @Override
    public void destroy() throws Exception {
        log.info("DisposableBean-----");
    }

    public void destroyMethod() {
        log.info("destroyMethod-----");
    }
}

三种销毁方式的执行顺序如下

PreDestroy-----
DisposableBean-----
destroyMethod-----

Scope

scope 简介

spring 中的 scope,指的是 bean 的作用域,《Spring 揭秘》对其的解释如下

scope 用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的 scope 之前生成并装配这些对象,在该对象不再处于这些 scope 的限定之后,容器通常会销毁这些对象

简单来说,scope 就是 bean 的有效范围,超出该范围,bean 将不再可用或被销毁

spinng 目前主要有五种 scope,如下

  • singleton:单例模式,全局只有一个 bean,可通过ConfigurableBeanFactory.SCOPE_SINGLETON得到此模式对应的常量值,singleton也是 spring 中默认的 scope;singleton scope下的 bean 会在 spring 容器关闭时被销毁
  • prototype:原型模式,每次获取 bean 时,都会返回一个新的实例,可通过ConfigurableBeanFactory.SCOPE_PROTOTYPE得到此模式对应的常量值

以下三种,都针对 web 应用程序

  • request:此模式下,每次 http 请求,都会产生一个新的 bean 且仅在当前 http request 内有效,请求结束后,旧的 bean 会被销毁;可通过WebApplicationContext.SCOPE_REQUEST得到此模式的常量值
  • session:此模式下的 bean,在当前 session 会话内有效,每次新的会话都会产生新的 bean,如果 session 超时(可通过setMaxInactiveInterval()设置 session 超时时间),则旧的 bean 会被销毁;可通过WebApplicationContext.SCOPE_SESSION得到此模式的常量值
  • application:application 作用域,用于将 bean 的生命周期绑定到当前 web 应用程序(在当前 web 应用程序内有效,除非 web 应用重启);可以通过WebApplicationContext.SCOPE_SESSION得到此模式的常量值,或者直接通过@ApplicationScope设置当前的 bean 为 application scope

scope 失效

在 spring 中,一个 singleton 的 bean,想要注入其他 scope 的 bean,是会存在问题的,如下代码

一个 singleton scope 的 bean

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
    // 在 singletion scope 的 bean 中注入其他 scope 的 bean
    @Autowired
    private PrototypeBean prototypeBean;
    
    public PrototypeBean getPrototypeBean() {
        return prototypeBean;
    }
}

一个 prototype scope 的 bean

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeBean {
}

启动 spring 容器,查看打印结果

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext("com.example.spring.bean.scope.invalid");

        for (int i = 0; i < 2; i++) {
            SingletonBean singletonBean = context.getBean(SingletonBean.class);
            System.out.println(String.valueOf(i + 1) + "----" + singletonBean.getPrototypeBean());
        }
    }
}
1----com.example.spring.bean.scope.invalid.PrototypeBean@176d53b2
2----com.example.spring.bean.scope.invalid.PrototypeBean@176d53b2

可以看到,多此获取一个prototype scope的 bean,返回的却是相同的地址值,PrototypeBean的 scope 失效了

原因在于SingletonBean本身是单例的,它的依赖注入只会发生一次,其依赖的PrototypeBean也只会被注入一次,后续没有其他地方再用到PrototypeBeanPrototypeBean也不会再重新生成,所以每次获取的都是相同的PrototypeBean

scope 失效解决

@Lazy

@Lazy用于指示是否要延迟初始化 bean,使用@Lazy注解标识一个成员变量或者该成员变量的set(),后续每次使用到该成员变量时,都会通过代理创建新对象,达到prototype scope的效果

proxyMode

@Scope注解中,指定proxyMode,也可以配置当前 bean 使用代理,如下

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class PrototypeBean {
}

ScopedProxyMode.TARGET_CLASS,创建基于类的代理(使用CGLIB)

ObejctFactory

通过注入一个ObjectFactory来获取目标 bean,每次获取,ObjectFactory都会返回一个新的目标 bean,如下代码

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
    // 通过泛型指定要通过 ObjectFactory 获取的目标 bean 类型
    @Autowired
    private ObjectFactory<PrototypeBean> factory;

    public PrototypeBean getPrototypeBean() {
        // 每次返回一个新的目标 bean
        return factory.getObject();
    }
}

ApplicationContext

直接通过 spring 容器获取目标 bean,因为每次都重新获取,不会导致 scope 失效,如下代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonBean {
    @Autowired
    private ApplicationContext context;

    public PrototypeBean getPrototypeBean() {
        return context.getBean(PrototypeBean.class);
    }
}

Aop

aop 实现之 aspectJ 增强

AOP 是 Aspect Oriented Programming 的缩写,即面向切面编程;AOP 的作用在于将公共逻辑进行抽取,在目标方法执行的各个阶段(切入点),执行指定的逻辑,达到代码复用,提升一定的开发效率

目前最常见的 aop 实现方式为【代理】,但实际上还有其他几种方式来实现,首先是 aspectJ 代理增强,aspectJ 直接在编译阶段修改 class 文件织入增强逻辑,属于静态 AOP 框架,使用如下

aspectJ 静态织入的实现,需要对应的 aspectJ 编译器支持,可以通过 maven 依赖和插件进行引入

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
</dependency>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>8</source>
        <target>8</target>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
        <Xlint>ignore</Xlint>
        <encoding>UTF-8</encoding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <!-- use this goal to weave all your main classes -->
                <goal>compile</goal>
                <!-- use this goal to weave all your test classes -->
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

切面类

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspectJ {

    @Before("execution(* com.example.aop.aspectj_demo.MyService.hello(..))")
    public void before() {
        System.out.println("before");
    }
}

业务类

public class MyService {

    public void hello() {
        System.out.println("hello");
    }
}

测试

public class MainTest {
    public static void main(String[] args) {
        MyService service = new MyService();
        service.hello();
    }
}

注意,如果使用 idea 运行,可能 aspectJ 插件不会生效,使用 maven compile 命令手动编译后再执行即可

观察编译后的MyService的 class 文件,如下

public class MyService {
    public MyService() {
    }

    public void hello() {
        MyAspectJ.aspectOf().before();
        System.out.println("hello");
    }
}

aspectJ 直接将增强的逻辑织入进了 class 文件中,而不是通过代理对象的方式进行增强

同时 aspectJ 还支持对静态方法进行增强,如下代码

在业务类中新增静态方法

public class MyService {

    public void hello() {
        System.out.println("hello");
    }

    public static void hi(){
        System.out.println("hi");
    }
}

修改切入点表达式,匹配 MyService 中的所有方法

@Before("execution(* com.example.aop.aspectj_demo.MyService.*(..))")

测试

public class MainTest {
    public static void main(String[] args) {
        MyService service = new MyService();
        service.hello();
        service.hi();
    }
}

注意静态方法需要通过对象来调用,否则无法进行增强且会抛异常

aop 实现之 agent 类加载

aspectJ 增强是在编译阶段通过修改 class 文件达到增强效果;而 agent 类加载则是在类加载阶段就开始进行 aop 增强,使用演示如下

首先需要去除 pom 文件中,acpectJ 的编辑器插件

准备切面类

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspectJ {

    @Before("execution(* com.example.aop.aspectj_demo.MyService.*(..))")
    public void before() {
        System.out.println("before");
    }
}

准备业务类

public class MyService {

    public void hello() {
        System.out.println("hello");
        // 注意:这里调用了内部的 yes(), 如果是代理实现的 AOP, yes() 不会被增强
        yes();
    }

    public void yes() {
        System.out.println("yes");
    }

    public static void hi() {
        System.out.println("hi");
    }
}

测试类

public class MainTest {
    public static void main(String[] args) {
        MyService service = new MyService();
        service.hello();
        service.hi();
    }
}

在 resource 目录或者是项目编译后的 classes 目录下添加META-INF目录,在其中编写aop.xml文件

<aspectj>
    <aspects>
        <aspect name="com.example.aop.aspectj_demo.MyAspectJ"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.example.aop.aspectj_demo.MyService"/>
            <include within="com.example.aop.aspectj_demo.MyAspectJ"/>
        </weaver>
    </aspects>
</aspectj>

这是用于类加载时,读取相关的切面类以及切入点信息

在 idea 的 vm options 选项中,添加啊如下运行参数(又或者直接通过java -javaagent...来运行)

# 路径需要根据当前运行环境进行替换
-javaagent:C:\Users\18781\software\MavenWork\maven_repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar

查看打印结果

before
hello
before
yes
before
hi

通过 arthas 反编译 MyService 后的字节码

这也解释了为什么yes()内部调用时能被增强,因为yes()内部被直接织入了增强逻辑,而非代理模式下,由代理对象来提供增强逻辑

可见,采用类加载的方式实现的 AOP,就算是内部方法的调用,也能织入增强逻辑,这是代理模式的 AOP 所不具备的

aop 代理增强-jdk

使用 jdk 自带的Proxy类,能够针对接口实现代理增强,代码如下

MyService 接口

public interface MyService {
    void hello();
}

MyServiceImpl 接口实现

public class MyServiceImpl implements MyService {
    @Override
    public void hello() {
        System.out.println("Hello");
    }
}

jdk 代理演示

import java.lang.reflect.Proxy;

/**
 * jdk 的动态代理演示
 */
public class JdkProxyMainTest {
    public static void main(String[] args) {
        // 目标对象
        MyService target = new MyServiceImpl();

        // 拿到类加载器,用于加载运行期间动态生成的代理类字节码
        // 因为代理类实际上没有源代码,通过直接生成字节码来执行增强逻辑,而字节码的加载,需要类加载器
        ClassLoader loader = JdkProxyMainTest.class.getClassLoader();
        MyService serviceProxy = (MyService) Proxy.newProxyInstance(loader, new Class[]{MyService.class}, (proxy, methods, params) -> {
            // 执行代理对象的前置增强逻辑
            System.out.println("before");
            // 反射执行目标方法并且返回其执行结果
            return methods.invoke(target,params);
        });
        serviceProxy.hello();
    }
}

此时,目标对象target与代理对象serviceProxy之间为平级关系,因为二者都实现了MySevice接口

jdk 的代理是通过接口实现的

aop 代理增强-cglib

cglib 代理演示如下


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class CglibProxyMainTest {
    static class Target {
        public void hello() {
            System.out.println("hello");
        }
    }

    public static void main(String[] args) {
        Target target = new Target();
        // 这里的 proxy 对象,实际上是 Target 的子类实例
        // 因为 Target 没有实现或继承其他的接口或类,这里的 proxy 类型只能是 Target 的子类才可以强转成功
        Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (o, method, objects, methodProxy) -> {
            System.out.println("before");
            // 反射调用目标方法
            return method.invoke(target,objects);
        });
        proxy.hello();
    }
}

cglib 的代理对象proxy与目标对象target之间为继承关系,这说明 cglib 通过继承目标类,实现代理增强

这也限制了目标类不能为final的,因为final类不能被继承,也就无法创建代理对象;同理,目标对象中被增强的方法如果是final的,那么也不能被增强,因为final修饰的方法无法被子类重写,代理对象的增强逻辑无法织入

cglib 还提供了通过methodProxy调用目标方法的形式(spring 中也使用这种方式),可以避免反射调用目标方法,如下

// 不使用反射
methodProxy.invoke(target,objects);

jdk 代理实现原理

jdk 代理主要通过创建与目标对象实现同一接口的代理对象,使用代理对象执行目标方法,实现代理增强,下面是模拟 jdk 代理实现的示例代码

创建接口

public interface MyService {
    void hello();

    void hi(String msg);
}

因为代理对象中具体的增强逻辑以及需要调用的目标方法都是不确定的,所以需要定义一个接口将其抽象出来,在创建代理对象时,传入此接口的默认实现,后续代理对象便可直接调用执行接口实现中自定义的增强逻辑

import java.lang.reflect.Method;

/**
 * 代理对象调用目标方法的处理程序抽象接口
 */
public interface InvocationHandler {

    /**
     * 通过此接口方法的具体实现,传入代理增强的逻辑以及调用的目标方法反射对象及其参数
     * 因为接口中可能有多个目标方法,需要通过传入 Method 准确调用
     *
     * @param proxy  代理对象本身
     * @param method 被增强的目标方法
     * @param params 目标方法参数
     * @return 返回目标方法调用后的返回值
     */
    Object invoke(Object proxy, Method method, Object... params);
}

手动定义代理对象(实际上 jdk 的代理对象是在底层动态生成的字节码)

import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * 模拟实现 jdk 代理对象,与目标对象一样,都实现了 MyService 接口
 */
public class $Proxy0 implements MyService {
    /**
    * 目标方法调用处理程序
    */
    private final InvocationHandler handler;
    /**
     * 方法反射对象,申明为 static,在类加载时初始化一次
     * 后续每次调用代理方法时,无需再次创建
     */
    private static final Method hiMethod;

    private static final Method helloMethod;

    static {
        try {
            hiMethod = MyService.class.getDeclaredMethod("hi", String.class);
            helloMethod = MyService.class.getDeclaredMethod("hello");
        } catch (NoSuchMethodException e) {
            // 需要转化为运行时异常抛出
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建代理对象时,通过 handler 传入具体的代理增强逻辑
     *
     * @param handler 目标方法调用处理程序
     */
    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public String hello() {
        try {
            // 拿到目标方法的反射对象
            return (String) handler.invoke(this, helloMethod);
        } catch (RuntimeException | Error e) {
            throw new RuntimeException(e);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public String hi(String msg) {
        try {
            // 调用指定的目标方法并且传入参数
            return (String) handler.invoke(this, hiMethod, msg);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

演示

/**
 * 模拟 jdk 动态代理的实现原理
 */
public class MainDemo {

    /**
     * 目标对象实现了 MyService 接口
     */
    static class Target implements MyService {
        @Override
        public String hello() {
            System.out.println("hello");
            return "hello";
        }

        @Override
        public String hi(String msg) {
            System.out.println(msg);
            return msg;
        }
    }

    public static void main(String[] args) {
        // 被代理的目标对象
        Target targetObj = new Target();
        // 创建代理对象并且自定义代理增强逻辑
        $Proxy0 proxy = new $Proxy0((p, method, params) -> {
            System.out.println("before");
            try {
                return method.invoke(targetObj, params);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        // 通过代理对象调用目标方法
        proxy.hi("hi!");
    }
}

实际上,除了上述手动实现 handler 接口的细节方式外,自定义的代理对象,也可以直接继承java.lang.reflect.Proxy,这是 jdk 提供的代理类且提供了对应的 handler 接口以及构造函数,如下

public class Proxy implements java.io.Serializable {
    private static final long serialVersionUID = -2222568056686623797L;

    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
    { InvocationHandler.class };

    /**
     * a cache of proxy constructors with
     * {@link Constructor#setAccessible(boolean) accessible} flag already set
     */
    private static final ClassLoaderValue<Constructor<?>> proxyCache =
        new ClassLoaderValue<>();

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

    /**
     * Prohibits instantiation.
     */
    private Proxy() {
    }

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param  h the invocation handler for this proxy instance
     *
     * @throws NullPointerException if the given invocation handler, {@code h},
     *         is {@code null}.
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    
    // ......

这就无需我们手动去实现 handler 接口了,使用演示如下

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 继承 Proxy
public class $Proxy1 extends Proxy implements MyService {

    // 对父类的 handler 接口赋值
    public $Proxy1(InvocationHandler h) {
        super(h);
    }

    private static final Method hiMethod;

    private static final Method helloMethod;

    // 初始化目标方法反射对象
    static {
        try {
            hiMethod = MyService.class.getDeclaredMethod("hi", String.class);
            helloMethod = MyService.class.getDeclaredMethod("hello");
        } catch (NoSuchMethodException e) {
            // 需要转化为运行时异常抛出
            throw new RuntimeException(e);
        }
    }

    @Override
    public String hello() {
        try {
            // 通过父类中的 handler 接口实现调用目标方法
            return (String) h.invoke(this, helloMethod, new Object[]{});
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String hi(String msg) {
        try {
            return (String) h.invoke(this, hiMethod, new Object[]{msg});
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        MainDemo.Target target = new MainDemo.Target();
        $Proxy1 proxy1 = new $Proxy1((proxy, method, params) -> {
            System.out.println("before");
            return method.invoke(target, params);
        });
        proxy1.hi("hi");
    }
}

jdk 代理源码分析

前面已经梳理了 jdk 代理的实现原理,但实际上 jdk 代理动态生成的代理类的具体源码,我们无法直接查看到,需要借助arthas工具进行查看

arthas需要对应的 java 程序保持运行,通过如下代码使 main 方法执行后,继续在控制台等待读取指令

try {
    int b = System.in.read();
} catch (IOException e) {
    throw new RuntimeException(e);
}

运行arthas-boot.jar,输入指令jad com.sun.proxy.$Proxy0,便可查看对应的代理类反编译的字节码文件,如下

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  com.example.aop.proxy.MyService
 */
package com.sun.proxy;

import com.example.aop.proxy.MyService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
extends Proxy
implements MyService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    // 初始化目标方法的反射对象,jdk 实现的代理中,额外还初始化了 equals hashCode 等方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.aop.proxy.MyService").getMethod("hello", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void hello() {
        try {
            // 通过 handler 的 invoke() 调用目标方法 m3
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

cglib 代理实现原理

cglib 代理的实现与 jdk 代理实现大同小异,都是将代理逻辑抽象为接口,在代理对象中通过接口调用具体的代理逻辑,如下代码

定义业务类

public class MyService {
    public void save() {
        System.out.println("save()");
    }

    public void save(int i) {
        System.out.println("saveInt-" + i);
    }

    public void save(long i) {
        System.out.println("saveLong-" + i);
    }
}

定义代理类

import org.springframework.cglib.proxy.MethodInterceptor;

import java.lang.reflect.Method;

// 代理对象通过继承的方式实现代理
public class Proxy0 extends MyService {

    private final MethodInterceptor methodInterceptor;

    private static final Method saveMethod0;
    private static final Method saveMethod1;
    private static final Method saveMethod2;

    // 初始化目标代理对象中的反射方法对象
    static {
        try {
            saveMethod0 = MyService.class.getDeclaredMethod("save");
            saveMethod1 = MyService.class.getDeclaredMethod("save", int.class);
            saveMethod2 = MyService.class.getDeclaredMethod("save", long.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public Proxy0(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    @Override
    public void save() {
        try {
            // 通过 MethodInterceptor 的实现来调用代理方法
            methodInterceptor.intercept(this, saveMethod0, new Object[]{}, null);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, saveMethod1, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void save(long i) {
        try {
            methodInterceptor.intercept(this, saveMethod2, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

测试

public class MainTest {
    public static void main(String[] args) {
        MyService myService = new MyService();
        Proxy0 proxy0 = new Proxy0((o, method, objects, methodProxy) -> {
            System.out.println("before");
            return method.invoke(myService, objects);
        });
        proxy0.save(5);
    }
}

spring 代理选择规则

在 spring 中,不论是 jdk 代理还是 cglib 代理都有所使用,那么什么时候使用 jdk 代理,什么时候使用 cglib,在 spring 中都有一定的规则,如下代码

定义接口

public interface MyIService {
    void hello();
}

定义实现

public class MyService implements MyIService{
    @Override
    public void hello() {
        System.out.println("hello");
    }
}

测试类

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class MainDemo {
    public static void main(String[] args) {
        test1();
    }

    public static void test1() {
        // 创建切点对象
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 设置切点表达式
        pointcut.setExpression("execution(* hello())");

        // 创建通知对象
        MethodInterceptor advice = methodInvocation -> {
            System.out.println("before");
            Object result = methodInvocation.proceed(); // 调用目标
            System.out.println("after");
            return result;
        };

        // 创建切面,由一个切点加一个通知构成
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 创建代理
        ProxyFactory factory = new ProxyFactory();
        // 设置目标对象
        factory.setTarget(new MyService());
        // 添加切面
        factory.addAdvice(advice);

        // 拿到代理对象(内部决定使用 cglib 或则是 jdk 代理增强,通过查看代理对象 proxy 的类型可知)
        MyIService proxy = (MyIService) factory.getProxy();
        proxy.hello();
    }
}

上述代码为 spring 中底层创建代理对象时采用的方式,具体使用 cglib 还是 jdk 代理,需要查看factory.getProxy()方法的源码,如下

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        // jdk 动态代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // cglib 动态代理
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        // jdk 动态代理
        return new JdkDynamicAopProxy(config);
    }
}

其中config.isProxyTargetClass()hasNoUserSuppliedProxyInterfaces主要用于确定是否指定了目标类所实现的接口,这在创建代理工厂时可以设置,如下代码

factory.setInterfaces(MyService.class.getInterfaces());

如果指定了目标类实现的接口,此时proxyTargetClass的默认值为false,但hasNoUserSuppliedProxyInterfaces返回值为true,最终会使用 jdk 的动态代理

如果没有指定目标类实现的接口,在proxyTargetClass默认值为false的情况下,会使用cglib的动态代理

如果在创建ProxyFactory时,通过factory.setProxyTargetClass(true);直接指定了proxyTargetClass的值为true,那么无论是否设置了目标类的接口,最终都会使用 cglib 动态代理

切点匹配

在 spring 中,通过定义通知对象,指定代理增强的具体逻辑;而通过创建切点对象,指定增强逻辑执行的具体位置;切点和通知相结合,构成了切面,这就好比用一把刀,找准了一个位置(切点),一刀切下去

想要在不同的类的方法中精确的插入增强逻辑,需要指定一个具体规则,这就是切点表达式,spring 提供了多种切入点匹配表达式,比较常用的有execution() 用于匹配方法@annotation() 用于匹配注解,使用都较为简单;同时 spring 还支持自定义切点匹配逻辑,只需要实现org.springframework.aop.MethodMatcher接口即可,如下代码演示了 spring 中匹配@Transactional注解的大致原理

定义辅助类

static class T1 {
    @Transactional
    public void save() {

    }

    public void query() {

    }
}

@Transactional
static class T2 {
    public void save() {

    }

    public void select() {

    }
}

自定义 @Transactional 匹配规则

try {
        // 通过实现 MethodMatcher 接口,自定义注解匹配规则
        StaticMethodMatcherPointcut pt = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                // MergedAnnotations 能够在指定的范围上搜索注解
                MergedAnnotations fromMethod = MergedAnnotations.from(method);
                // 检查方法上是否存在 Transactional 注解
                if (fromMethod.isPresent(Transactional.class)) {
                    return true;
                }
                // 检查类上是否标注了 Transactional 注解(同时直接搜索策略为:TYPE_HIERARCHY,这会同时检查本类,父类以及接口上是否标注了对应注解)
                MergedAnnotations fromClass = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                return fromClass.isPresent(Transactional.class);
            }
        };
        // 匹配指定 Class 中的指定方法是否标注了 Transactional
        boolean t1SaveIsTransactional = pt.matches(T1.class.getDeclaredMethod("save"), T1.class);
        boolean t1QueryIsTransactional = pt.matches(T1.class.getDeclaredMethod("query"), T1.class);
        System.out.println(t1QueryIsTransactional);
        System.out.println(t1SaveIsTransactional);

        boolean t2SaveIsTransactional = pt.matches(T2.class.getDeclaredMethod("save"), T2.class);
        boolean t2SelectIsTransactional = pt.matches(T2.class.getDeclaredMethod("select"), T2.class);
        System.out.println(t2SaveIsTransactional);
        System.out.println(t2SelectIsTransactional);
    } catch (Exception e) {
        e.printStackTrace();
    }

Aspect && Advisor 切面

aspect 切面因为使用方便,称之为高级切面,advisor 则为低级切面,这并非指 advisor 的功能更弱,只不过 advisor 更适合框架内部使用,实际上最终 spring 也会把 aspect 切面转化为 advisor 切面进行处理

以下代码演示了 spring 中如何确定对象是否需要创建代理的基本过程

首先同时定义高级切面和低级切面

@Aspect // 定义高级切面
public static class Aspect1 {
    @Before("execution(* save())")
    public void before() {
        System.out.println("aspect before");
    }

    @After("execution(* save())")
    public void after() {
        System.out.println("aspect after");
    }
}

@Configuration
static class Config {
    // 定义通知
    @Bean
    public MethodInterceptor advice() {
        return invocation -> {
            System.out.println("before");
            Object proceed = invocation.proceed();
            System.out.println("after");
            return proceed;
        };
    }

    // 定义低级切面
    @Bean
    public Advisor advisor(MethodInterceptor advice) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* query())");
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}

// 目标类
public static class Service1 {
    public void save() {
        System.out.println("save");
    }
}

public static class Service2 {
    public void query() {
        System.out.println("query");
    }
}

对于低级切面,一个通知对应一个低级切面;而对于高级切面中定义的多个通知,如上述代码中通过@Before@After定义的两个通知,spring 最终会将其转换为对应的两个低级切面

启动容器

package org.springframework.aop.framework.autoproxy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类(包含低级切面的 bean 配置)
context.registerBean("config", Config.class);
// 注册高级切面 aspectj
context.registerBean("aspect1", Aspect1.class);
// 注册配置类解析的后处理器
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

context.refresh();

AnnotationAwareAspectJAutoProxyCreator creator =
        context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);

// 根据传入的 class,查找所有符合要求的 advisor【低级切面】(前提是容器中已经存在对应的 advisor)
List<Advisor> service1Advisors =
        creator.findEligibleAdvisors(Service1.class, "service1");
service1Advisors.forEach(System.out::println);

/*
 wrapIfNecessary 会根据 findEligibleAdvisors 调用的情况
 确定是否为传入的 bean 创建代理,在 findEligibleAdvisors 返回的
 集合为空的情况下,不会创建代理
 */
Object wrap1 = creator.wrapIfNecessary(new Service1(), "service1", "service1");
System.out.println(wrap1.getClass()); // MainTest$Service1$$EnhancerBySpringCGLIB$$20cd8f4b
((Service1) wrap1).save();

注意这里的包路径,为了使用 AnnotationAwareAspectJAutoProxyCreator 中protected修饰的方法,当前的测试类被刻意放在了自建的org.springframework.aop.framework.autoproxy包下

实际上,org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator会在getBean()调用流程中,自动完成如wrapIfNecessary()findEligibleAdvisors()等相关调用,创建代理对象返回,因为AnnotationAwareAspectJAutoProxyCreator的实际类型为一个BeanPostProcessor,其是在 bean 依赖注入或初始化之后进行的功能增强,其父接口的描述如下

/**
BeanPost处理器的子接口,它添加了实例化之前的回调和实例化之后但在设置显式属性或发生自动连接之前的回调。通常用于抑制特定目标bean的默认实例化,例如创建具有特殊目标源(池化目标、延迟初始化目标等)的代理,或者实现额外的注入策略,如现场注入。注:此接口是一个专用接口,主要供框架内部使用。建议尽可能地实现简单的豆后处理器接口,或者从InstantiationAwareBeanPostProcessorAdapter派生,以避免对该接口的扩展。
 */
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

因此上述代码又可以简化如下

GenericApplicationContext context = new GenericApplicationContext();
// 注册配置类(包含低级切面的 bean 配置)
context.registerBean("config", Config.class);
// 注册高级切面 aspectj
context.registerBean("aspect1", Aspect1.class);
// 注册配置类解析的后处理器
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
// 注册目标对象
context.registerBean(Service1.class);

context.refresh();
// 获取目标对象(实际返回代理对象)
Service1 bean = context.getBean(Service1.class);
bean.save();

代理的创建时机

spring 中代理的创建时机主要有两种

其一

@Aspect
static class Aspect1{
    @Before("execution(* save())")
    public void before(){

    }
}

@Component
static class Bean1 {
    public void save(){

    }
}

@Component
static class Bean2 {
    @Autowired
    Bean1 bean1;
}

上述代码中的 Bean1 被 Aspect1 织入了增强逻辑,理应为其创建代理对象,同时 Bean2 也依赖注入了 Bean1(实际需要注入 Bean1 的代理对象)

此时的 Bean1 和 Bean2 之间并没有相互依赖关系,因此 Bean1 会按照正常的流程,在其初始化之后,由AnnotationAwareAspectJAutoProxyCreator创建出代理对象并依赖注入给 Bean2

其二

如果 Bean1 和 Bean2 之间存在依赖关系,如下

@Aspect
static class Aspect1{
    @Before("execution(* save())")
    public void before(){

    }
}

@Component
static class Bean1 {
    @Autowired
    Bean2 bean2;
    
    public void save(){

    }
}

@Component
static class Bean2 {
    @Autowired
    Bean1 bean1;
}

因为循环依赖的存在,此时 Bean1 和 Bean2 都先进行了实例化;Bean2 如果想要继续实例化(由Bean1 依赖注入 Bean2触发),那么 Bean1 的代理对象就必须提前创建(二级缓存),而不是等到初始化之后再创建

当 Bean1 的代理对象创建完成后,Bean2 才能顺利完成依赖注入及其后续流程;先前 Bean1 依赖注入 Bean2 的动作也得以继续,这说明在循环依赖的情况下,代理对象的创建是在目标对象的依赖注入之前发生的(因为需要确保其他依赖了目标对象的 bean 能够依赖注入得到代理对象)

切面调用流程

通过@Aspect注解声明的切面,实际上是通过以下流程转化为,才得到最终的调用逻辑

高级切面转低级切面

以下代码主要演示了 spring 将@Aspect注解标识的高级切面转化为低级切面的基本流程

定义高级切面 & 目标类

@Aspect
public static class Aspect1 {
    @Before("execution(* save())")
    public void before() {
    }

    @After("execution(* save())")
    public void after() {

    }
}

static class Target {
    public void save() {
        System.out.println("save");
    }
}

转为低级切面

import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.*;
import org.springframework.aop.support.DefaultPointcutAdvisor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

List<Advisor> advisors = new ArrayList<>();
// 创建切面对象的工厂,传入当前的切面对象实例
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect1());
// 遍历高级切面类 Aspect1 的所有方法
for (Method method : Aspect1.class.getDeclaredMethods()) {
    // 判断方法上是否有指定通知注解
    if (method.isAnnotationPresent(Before.class)) {
        // 创建切点对象
        String beforeExecution = method.getAnnotation(Before.class).value();
        AspectJExpressionPointcut beforePointcut = new AspectJExpressionPointcut();
        beforePointcut.setExpression(beforeExecution);

        // 创建 before 通知实例
        AspectJMethodBeforeAdvice beforeAdvice = new AspectJMethodBeforeAdvice(method, beforePointcut, factory);

        // 创建低级切面
        DefaultPointcutAdvisor beforeAdvisor = new DefaultPointcutAdvisor(beforePointcut, beforeAdvice);
        advisors.add(beforeAdvisor);
    }

    // after 通知的解析与 before 一致
    if (method.isAnnotationPresent(After.class)) {
        // 创建切点对象
        String afterExecution = method.getAnnotation(After.class).value();
        AspectJExpressionPointcut afterPointcut = new AspectJExpressionPointcut();
        afterPointcut.setExpression(afterExecution);

        // 创建 after 通知实例
        AspectJAfterAdvice afterAdvice =
                new AspectJAfterAdvice(method, afterPointcut, factory);

        // 创建低级切面
        DefaultPointcutAdvisor afterAdvisor = new DefaultPointcutAdvisor(afterPointcut, afterAdvice);
        advisors.add(afterAdvisor);
    }
}
advisors.forEach(System.out::println);

统一转换为环绕通知

对于一个目标对象,存在多个通知的情况很常见,如果只是单纯的将所有涉及到的通知收集起来简单调用,则不能达到前置或后置通知的效果,因此 spring 会将所有的通知都统一转换为环绕通知,由外及内,再由内到外的调用,如下图

而所谓环绕通知,实际上就是实现了MethodInterceptor接口的通知类,本身就实现了此接口的通知类型,将不会被转换,例如AspectJAroundAdviceAspectJAfterAdviceAspectJAfterThrowingAdvice这几个通知本身就是环绕通知

统一转换为环绕通知,可以借助ProxtFactory实现,代码如下

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target());
// 需要事先设置一个 ExposeInvocationInterceptor 通知(高优先级),这用于在所有环绕通知调用前将
// MethodInvocation 实例设置进当前线程(ThreadLocal),方便链式调用时获取
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
// 设置通知集合
proxyFactory.addAdvisors(advisors);
// 统一转换为环绕通知,返回转换后的集合
List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(
        Target.class.getMethod("save"),
        Target.class
);

通过打印集合信息可以得知,统一转换后的非环绕通知@AfterReturning@Before被分别转换为了AfterReturningAdviceInterceptorMethodBeforeAdviceInterceptor两种类型,实际上就是对应通知的包装类,例如@Before的环绕通知包装类MethodBeforeAdviceInterceptor的源码如下

// 依旧实现了 MethodInterceptor 接口
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

	// 内部组合了一个 MethodBeforeAdvice 实例
    private final MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

    // 实际调用时,在目标方法之前调用前置通知的之前逻辑
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        // 链式调用(责任链模式)
		return mi.proceed();
	}

}

转换为环绕通知后,便可以利用环绕通知集合,创建MethodInterceptor实例,实现链式调用

// 通过创建 MethodInvocation 实例,实现链式调用
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
        null,
        new Target(),
        Target.class.getMethod("save"),
        new Object[]{},
        Target.class,
        methodInterceptorList // 环绕通知列表
);
try {
    methodInvocation.proceed();
} catch (Throwable e) {
    throw new RuntimeException(e);
}

模拟通知调用链

为了更好的理解不同类型通知的调用逻辑,下面将模拟实现通知调用逻辑,代码如下

定义目标类

public class Target {
    public String save() {
        System.out.println("save");
        // 模拟异常情况,用于观察异常通知逻辑
        //int x = 10 / 0;
        return "success";
    }
}

实现自定义的 MethodInvocation

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;

public class CustomMethodInvocation implements MethodInvocation {
    // 通知集合
    private final List<MethodInterceptor> interceptors;
    // 目标对象
    private final Object target;
    // 目标方法参数
    private final Object[] args;
    // 目标方法
    private final Method targeMethod;

    // 当前调用次数统计,也是 interceptors 的调用指针
    private int count;

    public CustomMethodInvocation(List<MethodInterceptor> interceptors,
                                  Object target,
                                  Method targeMethod,
                                  Object[] args) {
        this.interceptors = interceptors;
        this.target = target;
        this.targeMethod = targeMethod;
        this.args = args;
    }

    @Override
    public Method getMethod() {
        return this.targeMethod;
    }

    @Override
    public Object[] getArguments() {
        return this.args;
    }

    @Override
    public Object proceed() throws Throwable {
        if (count > interceptors.size() - 1) {
            // 目标方法调用
            return targeMethod.invoke(target, args);
        }
        MethodInterceptor interceptor = interceptors.get(count);
        count++;
        // 调用下一个 interceptor,实际上是通过 this 间接的递归调用当前实例的 proceed()
        return interceptor.invoke(this);
    }

    @Override
    public Object getThis() {
        return this.target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return this.targeMethod;
    }
}

实现不同通知类型的自定义 MethodInterceptor

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 自定义实现后置通知
 */
public class AfterMethodInterceptor implements MethodInterceptor {
    InterceptorExec exec;

    public AfterMethodInterceptor(InterceptorExec exec) {
        this.exec = exec;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        } finally {
            // 始终在目标方法调用完后执行通知逻辑(不管是否有异常)
            exec.exec();
        }
    }
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 自定义实现前置通知
 */
public class BeforeMethodInterceptor implements MethodInterceptor {

    InterceptorExec exec;

    public BeforeMethodInterceptor(InterceptorExec exec) {
        this.exec = exec;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 始终在目标方法调用之前执行通知逻辑
        exec.exec();
        return invocation.proceed();
    }
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 自定义实现返回通知(在目标方法返回前被调用)
 */
public class ReturningMethodInterceptor implements MethodInterceptor {
    InterceptorExec exec;

    public ReturningMethodInterceptor(InterceptorExec exec) {
        this.exec = exec;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            // 只在没有异常且目标方法调用返回后,执行 returning 通知逻辑
            Object result = invocation.proceed();
            exec.exec();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 自定义实现异常通知(在目标方法异常时调用)
 */
public class ThrowMethodInterceptor implements MethodInterceptor {

    InterceptorExec exec;

    public ThrowMethodInterceptor(InterceptorExec exec) {
        this.exec = exec;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        } catch (Exception e) {
            // 只在目标方法调用异常时执行通知逻辑
            exec.exec();
            throw e;
        }
    }
}

定义函数式接口,方便通过构造传入具体执行的通知逻辑

/**
 * 自定义的通知逻辑
 */
@FunctionalInterface
public interface InterceptorExec {
    void exec();
}

测试类

public class MainTest {
    public static void main(String[] args) {
        AfterMethodInterceptor afterItr = new AfterMethodInterceptor(() -> System.out.println("after1"));
        BeforeMethodInterceptor beforeItr = new BeforeMethodInterceptor(() -> System.out.println("before1"));
        BeforeMethodInterceptor beforeItr2 = new BeforeMethodInterceptor(() -> System.out.println("before2"));
        ReturningMethodInterceptor returnItr = new ReturningMethodInterceptor(() -> System.out.println("return1"));
        ThrowMethodInterceptor throwItr = new ThrowMethodInterceptor(() -> System.out.println("throw1"));
        
        // 同一类型的通知调用顺序,由添加到集合时的顺序决定(如 before2 在 before1 前被添加)
        // 但不同通知类型的调用顺序不变(before 肯定会在 after 前调用) 这是由 invoke() 方法中,对 proceed() 方法不同时机的递归调用决定的
        // 例如 before 通知,总是在 proceed() 前调用通知逻辑,那么它总是会在其余的 proceed() 还在递归调用时,就已经执行了
        // 再比如 after 通知,总是在 proceed() 方法递归调用返回后执行,那么它也肯定在其他通知逻辑之后执行
        List<MethodInterceptor> interceptors = List.of(afterItr, returnItr, throwItr, beforeItr2, beforeItr);
        try {
            CustomMethodInvocation invocation = new CustomMethodInvocation(
                    interceptors, new Target(), Target.class.getMethod("save"),
                    new Object[]{}
            );
            Object result = invocation.proceed();
            System.out.println(result);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

Mvc

DispatcherServlet 初始化

初始化时机

借助 spring mvc 中的相关类,可以使用内嵌 tomcat 的形式快速启动一个 web 服务,如下代码

配置文件

server.port=8888
spring.mvc.servlet.load-on-startup=1

编写配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;

import java.util.Objects;

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties") // 加载类路径下的 properties 配置文件
public class WebConfig {
    @Autowired
    private Environment environment;

    // 配置 web 容器
    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
        int port = Integer.parseInt(Objects.requireNonNull(environment.getProperty("server.port")));
        tomcatServletWebServerFactory.setPort(port);
        return new TomcatServletWebServerFactory();
    }

    // 配置前控制器
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 注册【前控制器】到 web 容器
    @Bean
    public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // 设置 spring 容器启动时便初始化,参数的值越小,在多个 servlet 的情况下,初始化的优先级越高
        int startUp = Integer.parseInt(Objects.requireNonNull(environment.getProperty("spring.mvc.servlet.load-on-startup")));
        registrationBean.setLoadOnStartup(startUp);
        // "/" 匹配所有请求
        return registrationBean;
    }

    // 手动注册一个控制器,这相当于使用 @Controller 注解注册了一个控制器
    @Bean("/hello") // 如果 bean 名称以 / 开头, 那么此控制器的访问路径就为完整的 bean 名称
    public Controller controllerOne() {
        return (request, response) -> {
            response.getWriter().println("hello");
            return null;
        };
    }
}

配置加载

import com.example.mvc.config.WebConfig;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;

public class WebTest1 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

注意,目前只是将 DispatcherServlet 的实例,通过 spring 初始化到容器中,再未指定初始化时机时,DispatcherServlet 真正的初始化,是在首次访问 web 服务时,由 tomcat 执行其初始化,初始化的打印信息如下

3月 11, 2023 11:59:49 上午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
11:59:49.609 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'

11:59:49.630 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - Detected 1 mappings in 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'

11:59:49.923 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: none

11:59:49.968 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: none

11:59:49.986 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data

11:59:49.986 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 377 ms

11:59:49.999 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/hello", parameters={}

11:59:50.003 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - Mapped to com.example.mvc.config.WebConfig$$Lambda$163/0x0000000800222040@71913952

11:59:50.008 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

11:59:50.159 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/favicon.ico", parameters={}

11:59:50.167 [http-nio-8080-exec-2] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /favicon.ico

11:59:50.167 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND

初始化操作

DispatcherServlet 的初始化代码逻辑,在其内部的onRefresh()方法中声明,执行的是一系列初始化策略,部分源码如下

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    // 初始化文件上传解析器
    initMultipartResolver(context);
    // 初始本地化解析器,语言,国际化信息等
    initLocaleResolver(context);
    initThemeResolver(context);
    // 初始化请求和请求处理器之间的映射关系
    initHandlerMappings(context);
    // 初始化请求处理器的适配器(这里的 handler 可以理解为具体处理请求的逻辑,例如 controller 中的具体逻辑)
    // 在 spring 中,处理请求的形式是多种多样的,通过定义一个 HandlerAdapter 接口,来定义统一的请求处理方式(屏蔽不同请求处理方式之间的差异)
    // 具体的请求处理逻辑,交给接口实现(适配器模式)
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

DispatcherServlet 的初始化一共分为九个步骤,每个步骤都是去初始化某一类的组件

posted @   告别时光  阅读(597)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.