Spring原理学习(一):BeanFactory和ApplicationContext的原理和实现

目录

一、BeanFactory和ApplicationContext的关系

二、BeanFactory的功能

三、ApplicationContext的功能

3.1 MessageSource

3.2 ResourcePatternResolver

3.3  EnvironmentCapable

3.4 ApplicationEventPublisher

3.4.1 ApplicationEventPublisher功能体验

 3.4.2 事件有什么用

四、BeanFactory的实现

扫描二维码关注公众号,回复: 14710313 查看本文章

4.1 DefaultListableBeanFactory

         4.2 BeanFactory的后处理器

        4.3 Bean的后处理器

        4.4 总结

五、ApplicationContext的实现

5.1 ClassPathXmlApplicationContext

5.1.1 使用

        5.1.2 原理

 5.2 FileSystemXmlApplicationContext

5.2.1 使用

5.2.2 原理

5.3 AnnotationConfigApplicationContext

5.3.1 使用

5.4 AnnotationConfigServletWebServerApplication


一、BeanFactory和ApplicationContext的关系

        先看下springboot的引导类:

@SpringBootApplication
public class A01 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        SpringApplication.run(A01.class, args);
    }
}

        run方法的返回值是springboot容器,我们来看一下:

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

        那么ConfigurableApplicationContext是什么呢?咱们来看看类图:

        可以看到,ConfigurableApplicationContext是ApplicationContext的子类,而ApplicationContext又间接继承了BeanFactory。

        BeanFactory才是Spring的核心容器,主要的ApplicationContext实现都“组合”了BeanFactory的功能。

二、BeanFactory的功能

        先看下BeanFactory有哪些接口:

image-20220323145908937

         从表面上看,似乎只有getBean方法,但实际上我们还需要看他的实现类:控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能, 都由它的实现类提供。

        那么,想要看实现类的功能,我们该从何找起呢?

        我们先查看一下springboot中默认的ConfigurableApplicationContext类中的BeanFactory的实际类型,代码如下:

ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());

        从打印结果可以了解到实际类型为DefaultListableBeanFactory(详见4.1),所以这里以BeanFactory的一个实现类DefaultListableBeanFactory作为出发点,进行分析。他的类图:

image-20220323150712761

        我们先看看DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry,看看他的源码:

image-20220323150929451

         可以看到有个成员变量singletonObjects,其实这个变量里面保存了springboot所有的单例,我们可以通过反射拿到singletonObjects后将其打印出来,就能看到所有的单例了。

三、ApplicationContext的功能

        我们已经了解到,ApplicationContext是BeanFactory的子类,并且我们已经了解到了BeanFactory的功能,那么,我们将着重看看ApplicationContext比BeanFactory多了哪些功能。

image-20220324115647260

         可以看到,ApplicationContext除了继承自BeanFactory之外,还继承了以下四个类:

  • MessageSource:国际化功能,支持多种语言
  • ResourcePatternResolver:通配符匹配资源路径
  • EnvironmentCapable:环境信息,系统环境变量,*.properties、*.application.yml等配置文件中的值
  • ApplicationEventPublisher:发布事件对象

        下面我们来分别研究一下这四个类

3.1 MessageSource

       MessageSource拥有国际化功能,支持多种语言。

       与MessageSource有关的国际化功能的文件在springboot中默认放在message打头的文件中,我们先建好这些文件:

        然后在这些文件里面定义同名的key。比如在message_en.properties中定义hi=hello,在messages_ja.propertes中定义hi=こんにちは,在messages_zh中定义hi=你好,这样在代码中就可以根据这个key hi和不同的语言类型**获取不同的值了。 

        编写好代码后,运行起来之后就能看到结果:

image-20220324181409040

        Locale.CHINA、Locale.ENGLISH等值在实际项目中会由前端解析到界面所用的语言后传过来。

3.2 ResourcePatternResolver

        ResourcePatternResolver可以通过通配符来匹配资源路径。

        例1:获取类路径下的messages开头的配置文件:

Resource[] resources = context.getResources("classpath:messages*.properties");
for (Resource resource : resources) {
    System.out.println(resource);
}

         例2:获取spring相关jar包中的spring.factories配置文件:

resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
    System.out.println(resource);
}

3.3  EnvironmentCapable

        EnvironmentCapable可以获取系统环境信息或系统环境变量里的值,比如环境变量、*.properties、*.application.yml等配置文件中的值。

//获取系统环境变量中的java_home
System.out.println(context.getEnvironment().getProperty("java_home"));
//获取项目的application.yml中的server.port属性
System.out.println(context.getEnvironment().getProperty("server.port"));

3.4 ApplicationEventPublisher

        ApplicationEventPublisher可以用来发布事件。

3.4.1 ApplicationEventPublisher功能体验

        想要试试发布事件的功能,我们需要准备三个部分:事件发送类、事件接收(监听)类、事件类。

        先看事件类,他继承自ApplicationEvent:

public class UserRegisteredEvent extends ApplicationEvent {
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}

        再定义一个事件接受(监听)类,用于监听用户注册事件。类上需要加@Component注解,将该类交给spring管理。spring中任意个容器都可以作为监听器。然后定义一个处理事件的方法,参数类型为事件类的对象,方法头上需要加上@EventListener注解。

@Component
@Slf4j
public class UserRegisteredListener {
    @EventListener
    public void userRegist(UserRegisteredEvent event) {
        System.out.println("UserRegisteredEvent...");
        log.debug("{}", event);
    }
}

        再定义一个发送事件的类,就是使用ApplicationEventPublisher的实例对象调用pubulishEvent方法发送,传入的参数是我们刚刚定义好的事件类:

@Component
@Slf4j
public class UserService {
    @Autowired
    private ApplicationEventPublisher context;
    public void register(String username, String password) {
        log.debug("新用户注册,账号:" + username + ",密码:" + password);
        context.publishEvent(new UserRegisteredEvent(this));
    }
}

        然后在主启动类中调用一下就可以了:

UserService userService = context.getBean(UserService.class);
userService.register("张三", "123456");

 3.4.2 事件有什么用

        事件最主要的功能就是解耦。

        譬如我们使用事件来做一个用户注册的功能,功能里有用户注册类UserService,用来发送事件,也有用户注册监听类UserRegisteredListener,用于接收事件。由于用户注册后我们有多种后续操作,比如给用户发短信、给用户发邮件或给用户发微信公众号提醒。这样就要求我们的系统有良好的可扩展性,UserService类和UserRegisteredListener类不能耦合在一起,而我们使用事件(如上),就能够实现UserService类和UserRegisteredListener类的解耦:用户注册类UserService发送事件后,我们可以用不同的监听类来接收,不同的监听类做不同的事情;比如我们可以用UserRegisteredListener1来给用户发短信,用UserRegisteredListener2来给用户发邮件。

        使用事件进行解耦是一种新的解耦方式,他与AOP方式有什么不同呢?这个值得我们思考。

四、BeanFactory的实现

4.1 DefaultListableBeanFactory

        BeanFactory的实现类十分多,我们需要抓住一个重点的实现类去看,这个类就是DefaultListableBeanFactory。 

         接二(BeanFactory的功能)中所言,Spring底层创建实体类就是依赖于DefaultListableBeanFactory,所以,他是BeanFactory的实现类中最重要的一个。我们有必要使用一下这个类,来模拟Spring使用DefaultListableBeanFactory创建其他实体类对象的过程。

public class TestBeanFactory {
    public static void main(String[] args) {

        //先创建bean工厂,刚创建的时候是没有任何bean的,我们需要往里面添加bean的定义
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // bean 的定义(即bean的一些描述信息,包含class:bean是哪个类,scope:单例还是多例,初始化、销毁方法等)
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        //把beanDefinition这个bean定义注册进bean工厂,第一个参数是给它起的名字
        beanFactory.registerBeanDefinition("config", beanDefinition);

        
        // 打印BeanFactory中Bean
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

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

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

    // bean1依赖于bean2
    @Slf4j
    static class Bean1 {
        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }

        public Bean1() {
            log.debug("构造 Bean1()");
        }
    }

    @Slf4j
    static class Bean2 {
        public Bean2() {
            log.debug("构造 Bean2()");
        }
    }

    interface Inter {

    }
}

        这个时候打印出bean工厂中有多少bean,结果只有一个,就是我们刚刚注册进去的config。

        那么问题来了,我们在spring实战的知识中得知:当加上@Configuration和@Bean时,容器中会注册这些bean;换句话说,我们此时打印bean工厂的所有bean,理应看到bean1和bean2,而不是只有config。此时只有一个解释能成立:@Configuration和@Bean都没有被解析。那么解析这些注解的功能由谁提供呢?

         4.2 BeanFactory的后处理器

        BeanFactory本身实现的功能并不多,他的许多功能都是由BeanFactory的后处理器进行扩展的。

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        使用上面的工具类可以为bean工厂添加一些常用的后处理器,此时我们打印bean工厂里的所有bean:

        可以看到现在多了一些后处理器从名字也可以大致猜出,他们是处理@Configuration的、@Autowired的……,现在只是把他们加进了bean工厂,还需要让他们工作起来。

public class TestBeanFactory {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // bean 的定义(class, scope, 初始化, 销毁)
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        beanFactory.registerBeanDefinition("config", beanDefinition);

        // 给 BeanFactory 添加一些常用的后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

    beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });

        // 打印BeanFactory中Bean
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

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

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

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

        public Bean1() {
            log.debug("构造 Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }
    }

    static class Bean2 {
        private static final Logger log = LoggerFactory.getLogger(Bean2.class);

        public Bean2() {
            log.debug("构造 Bean2()");
        }
    }
}

        此时再打印所有的bean,就可以看到bean1和bean2已经出现了。

        4.3 Bean的后处理器

        在4.2中我们添加了一些后处理器,比如internalConfigurationAnnotationProcessor是处理@Configuration的,它属于BeanFactory的后处理器;而internalAutowiredAnnotationProcessor和internalCommonAnnotationProcessor就属于bean的后处理器,他们是针对 bean 的生命周期的各个阶段提供扩展, 例如 internalAutowiredAnnotationProcessor用于解析@Autowired、internalCommonAnnotationProcessor用于解析@Resource。

        如果我们需要让@Autowired 和 @Resource注解也发挥作用,则需要:

// Bean 后处理器, 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource ...
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
                .sorted(beanFactory.getDependencyComparator())
                .forEach(beanPostProcessor -> {
            System.out.println(">>>>" + beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });

        这样我们的@Autowired 和 @Resource注解就发挥作用了。请注意,我们这里getBeansOfType()方法传递的参数是BeanPostProcessor.class,是bean的后处理器。

        4.4 总结

        BeanFactory是一个比较基础的类,他本身并没有特别多的功能,这些事情它不会去做:

  • 不会主动调用BeanFactory后处理器
  • 不会主动添加Bean后处理器
  • 不会主动初始化单例
  • 不会解析#{}、${}等

五、ApplicationContext的实现

        先看看ApplicationContext的实现类有哪些:

image-20220325174005606

        今天我们来介绍四个比较重要的实现类:

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigServletWebServerApplication

5.1 ClassPathXmlApplicationContext

        较为经典的容器, 基于 classpath(类路径)下 xml 格式的配置文件来创建ApplicationContext.

5.1.1 使用

        创建一个测试类 

private static void testClassPathXmlApplicationContext() {
    ClassPathXmlApplicationContext context = 
                                    new ClassPathXmlApplicationContext("a02.xml");

    //看一下ApplicationContext中有多少bean
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    //看看bean2中有没有成功注入bean1
    System.out.println(context.getBean(Bean2.class).getBean1());
}

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

         创建xml配置文件,并在文件中定义bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 控制反转, 让 bean1 被 Spring 容器管理 -->
    <bean id="bean1" class="com.itheima.a02.A02.Bean1"/>

    <!-- 控制反转, 让 bean2 被 Spring 容器管理 -->
    <bean id="bean2" class="com.itheima.a02.A02.Bean2">
        <!-- 依赖注入, 建立与 bean1 的依赖关系 -->
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>

        运行结果:

  

5.1.2 原理

        我们模拟一下加载xml文件的过程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通过 XmlBeanDefinitionReader 到xml文件中读取bean的配置信息,将这些bean加载到bean工厂中。 

public static void main(String[] args) {
        //先实现DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("读取之后...");
        //然后到xml文件中读取bean的定义信息
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

 5.2 FileSystemXmlApplicationContext

        基于磁盘路径下 xml 格式的配置文件来创建ApplicationContext。

5.2.1 使用

        编写一个测试类:

private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                        "src\\main\\resources\\a02.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        //看看bean2中有没有成功注入bean1
        System.out.println(context.getBean(Bean2.class).getBean1());
    }

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

        xml文件与5.1.1中为同一个。运行结果也同5.1.1 

5.2.2 原理

        我们模拟一下加载xml文件的过程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通过 XmlBeanDefinitionReader 到xml文件中读取bean的配置信息,将这些bean加载到bean工厂中。  

public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("读取之后...");
        //然后到xml文件中读取bean的定义信息
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

5.3 AnnotationConfigApplicationContext

5.3.1 使用

        较为经典的容器, 基于 java 配置类来创建ApplicationContext。 

private static void testAnnotationConfigApplicationContext() {
    AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}

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

    @Bean
    public Bean2 bean2(Bean1 bean1) {
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

        运行结果:

         可以看到,结果与5.1.1和5.2.1有不同,因为我们的配置类Config也默认为一个bean注入进了bean工厂;除此之外,AnnotationConfigApplicationContext 还自动帮我们加了五个后处理器。

5.4 AnnotationConfigServletWebServerApplication

        较为经典的容器, 基于 java 配置类来创建ApplicationContext, 用于 web 环境。

        我们创建一个测试类来使用它:

    private static void testAnnotationConfigServletWebServerApplicationContext() {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class WebConfig {
        //tomcat容器
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        //前控制器
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }
        //让前控制器运行在Tomcat容器中
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }
        //控制器
        @Bean("/hello")
        public Controller controller1() {
            return (request, response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }

        可以看到后台打印出很多的bean。

        从这里可以了解到:

  • springboot内嵌了Tomcat,我们无需手动添加Tomcat容器,也可以运行bean
  • springboot的所有请求都要经过dispatchServlet(前控制器),然后再走到我们自己的控制器中
  • 通过DispatcherServletRegistrationBean 可以将DispatcherServlet 注册到Tomcat中

猜你喜欢

转载自blog.csdn.net/m0_49499183/article/details/129900726