【Spring高级】第1讲:BeanFactory 与 ApplicationContext

两者关系

BeanFactoryApplicationContext都是Spring框架中非常重要的接口,它们都与Spring的IoC容器有关。

下面通过SpringApplication的引导类来说明上面两个接口。

看下面代码:

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
    }
    
}

上面代码中,通过调用SpringApplication.run()方法,返回的就是Spring的容器,也就是代码中的ConfigurableApplicationContext类型,他继承了ApplicationContext,是它的子接口。

下面可以看下类图:

在这里插入图片描述

可以看到ConfigurableApplicationContext继承了很多的接口,其中上层之一就有ApplicationContextApplicationContext又继承了很多其他接口,最顶层的之一就是BeanFactory

结论:ApplicationContext间接继承了BeanFactory,它在BeanFactory基础上又提供了很多功能扩展。

进入context的getBean方法,其实调用了父类AbstractApplicationContextgetBean方法,如下:

@Override
public Object getBean(String name) throws BeansException {
   assertBeanFactoryActive();
   return getBeanFactory().getBean(name);
}

可以看到,其实容器的getBean方法就是间接调用了beanFactorygetBean方法。

debug查看context的属性,如下:

在这里插入图片描述

可以看到容器context会有个beanFactory属性。另外,展开beanFactory属性,可以看到还有个singletonObjects属性,它就是用来存储容器中的单例Bean的一个Map集合。

总结:

BeanFactory是Spring框架中最底层的接口,是IoC最核心的接口。它定义了IoC的基本功能,其中最核心的是getBean方法,来获取Bean。控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供。

ApplicationContextBeanFactory的子接口,拥有BeanFactory的全部功能,并且扩展了很多高级特性,如国际化、事件传播、资源加载等。还提供了bean的实例缓存、生命周期管理、bean的实例代理、bean的发布等高级服务。

BeanFactory功能

BeanFactory本身的功能很简单,其源码如下:

public interface BeanFactory {

   /**
    * FACTORY_BEAN前缀
    */
   String FACTORY_BEAN_PREFIX = "&";


   /**
    * 根据名字获取bean
    */
   Object getBean(String name) throws BeansException;

   /**
    * 根据名称和类型获取bean
    */
   <T> T getBean(String name, Class<T> requiredType) throws BeansException;

   /**
    * 根据名字和构造参数或工厂方法参数获取bean
    */
   Object getBean(String name, Object... args) throws BeansException;

   /**
    * 根据类型获取bean
    */
   <T> T getBean(Class<T> requiredType) throws BeansException;

   /**
    * 根据类型和构造参数或工厂方法参数获取bean
    */
   <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

   /**
    * 特定bean的提供者
    */
   <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

   /**
    * 特定bean的提供者
    */
   <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

   /**
    * 是否含有bean
    */
   boolean containsBean(String name);

   /**
    * 是否单例
    */
   boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

   /**
    * 
    */
   boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

   /**
    * 是否原型
    */
   boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

   /**
    * 检查执行name的bean是否是指定类型
    */
   boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

   /**
    * 获取bean的类型
    */
   @Nullable
   Class<?> getType(String name) throws NoSuchBeanDefinitionException;

   /**
    * 获取bean的类型
    */
   @Nullable
   Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

   /**
    * 获取bean的别名
    */
   String[] getAliases(String name);

}

主要有以下几个功能:

  • getBean:获取bean
  • containsBean:是否含有bean
  • getType:bean的类型

除了以上基本功能,它还有大量的实现类,提供了其他丰富的功能,例如DefaultListableBeanFactory,其类图如下

在这里插入图片描述

可以看到,它继承了AbstractAutowireCapableBeanFactory,顶层继承了BeanFactory。实现了其他丰富的功能。

他存储着容器的所有的单例bean,其功能实现是在父类DefaultSingletonBeanRegistry中,有个属性为singletonObjects

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

存储着所有单例bean的名称和bean的map。

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
//        context.getBean("aaa");

        // 获取
        Field singletonObjectsField = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        singletonObjectsField.setAccessible(true);
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, Object> singletonObjects = (Map<String, Object>) singletonObjectsField.get(beanFactory);
        System.out.println(singletonObjects);
    }

}

ApplicationContext功能

回顾下ApplicationContext的类图:

在这里插入图片描述

上图中,ApplicationContext区别于BeanFactory的主要功能就是基于以下4个接口:

  • EnvironmentCapable:用于访问应用程序环境属性的接口,这些属性可能来自不同的源,如属性文件、系统属性、命令行参数等。
  • ApplicationEventPublisher:用于发布应用程序事件。当应用程序中的某些事情发生时(例如,一个bean被创建或一个特定的操作被执行),您可以发布一个事件,然后任何对该事件感兴趣的监听器都可以接收到通知并执行相应的操作。这是观察者模式的一个实现,允许组件之间进行解耦的通信。
  • ResourcePatternResolver:解析资源路径,并可能支持使用通配符来匹配多个资源。它通常用于加载配置文件、图片、模板等资源。
  • MessageSource:用于国际化(i18n)和本地化(l10n)的消息解析,提供程序的多语言支持。

下面一一演示。

  1. 国际化

国际化文件均在 src/resources 目录下

messages.properties(空)

messages_en.properties

hi=Hello

messages_ja.properties

hi=こんにちは

messages_zh.properties

hi=你好

注意

  • ApplicationContext 中 MessageSource bean 的名字固定为 messageSource
  • 使用 SpringBoot 时,国际化文件名固定为 messages
  • 空的 messages.properties 也必须存在

示例代码:

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
//        context.getBean("aaa");

        // 国际化
        System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
        System.out.println(context.getMessage("hi", null, Locale.CHINESE));
        System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
    }

}

结果如下:

Hello
你好
こんにちは
  1. 获取资源

示例:

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
//        context.getBean("aaa");

        // 获取资源
      	// 注意classpath后的路径如果是jar包里,需要加冒号
        Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
        for (Resource resource : resources) {
            System.out.println(resource);
        }
    }
}

结果如下:

URL [jar:file:/Users/fltech/.m2/repository/org/springframework/boot/spring-boot/2.6.13/spring-boot-2.6.13.jar!/META-INF/spring.factories]
URL [jar:file:/Users/fltech/.m2/repository/org/springframework/spring-beans/5.3.23/spring-beans-5.3.23.jar!/META-INF/spring.factories]
URL [jar:file:/Users/fltech/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.6.13/spring-boot-autoconfigure-2.6.13.jar!/META-INF/spring.factories]
  1. 获取环境属性

增加配置文件 application.properties,如下:

server.port=8080
logging.level.com.cys=debug

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=123456

server.servlet.session.timeout=30m

示例代码:

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
//        context.getBean("aaa");

        // 获取环境属性
        System.out.println(context.getEnvironment().getProperty("java.home"));
        System.out.println(context.getEnvironment().getProperty("server.port"));
    }
}

既可以拿到系统级的环境变量,也可以拿到配置文件里的配置,结果如下:

/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
8080
  1. 事件发布订阅

事件发布订阅需要事件和监听器两个角色。

首先需要自定义一个事件,继承接口ApplicationEvent

import org.springframework.context.ApplicationEvent;

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

定义一个监听器:

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

@Component
public class DemoListener {

    /**
     * 使用EventListener注解来监听
     *
     * @param event 注意参数要与事件的类型相同
     */
    @EventListener
    public void doListener(DemoEvent event) {
        System.out.println("监听到消息...");
    }

}

启用监听:

@SpringBootApplication
public class Demo01Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(Demo01Application.class, args);
//        context.getBean("aaa");

        // 事件发布
        context.publishEvent(new DemoEvent(context));
    }
}

启动后成功监听到消息。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>