文章目录
【探索Spring底层】容器的实现
1. BeanFactory实现的特点
BeanFactory提供的功能没有太丰富,它的一些功能的实现都是由BeanFactory后处理器和Bean后处理器提供的
下面就是模拟一下BeanFactory实现Bean的定义,以及@Autowired @Resource@Bean注解的实现
首先创建一个BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
这是新创建的BeanFactory,里面什么都没有,我们需要进行bean 的定义(class, scope, 初始化, 销毁)
下面是模拟的Config、Bean1和Bean2是模拟的对象,然后接下来将会将Config定义到BeanFactory中
@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的定义
使用BeanDefinitionBuilder的genericBeanDefinition方法将Config定义Bean,然后设置Bean标签范围为singleton(单例),接着使用beanFactory的registerBeanDefinition方法将config这个Bean注册到BeanFactory中
AbstractBeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
运行就可以发现有个config的bean,但是观察上面代码可以发现Config用@Bean注解也将Bean1以及Bean2也定义到BeanFactory中,但是这里输出却没有,这是因为@Bean以及@Configuration并没有生效!!!
因为解析@Bean以及@Configuration的功能BeanFactory并没有,因此需要给 BeanFactory 添加一些常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
添加BeanFactory 后处理器之后,就能发现BeanFactory 中多了几个后处理器。
BeanFactoryPostProcessor
- internalConfigurationAnnotationProcessor对应的类是ConfigurationClassPostProcessor,它是用来对 @Controller @Component @Bean @Configuration @Service @ComponentScan@PropertySource @Import @ImportResource 进行注解扫描
BeanPostProcessor
- internalAutowiredAnnotationProcessor 对应的类是AutowiredAnnotationBeanPostProcessor,它是用来对**@Autowired @Value**进行注解扫描,实现属性填充
- internalCommonAnnotationProcessor 对应的类是 CommonAnnotationBeanPostProcessor,它是用来对**@Resource @PreDestory @PostConstruct** 进行扫描的,实现属性填充
接下来就可以通过BeanFactoryPostProcessor来对BeanFactory补充一下bean的定义
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
//执行bean工厂处理器,执行到注解的时候,
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});
这时候就可以看到bean1和bean2了
然后尝试一下从bean1中获取@Autowired注入的Bean2,发现为null
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
这是因为@Autowired注解没有生效,使用 BeanPostProcessor 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
这时候再获取Bean2就有值了
这些Bean的实例化,只有在getBean的时候才会实例化,如果想要提前实例化号,可以使用preInstantiateSingletons,提前准备好实例
beanFactory.preInstantiateSingletons(); // 准备好所有单例(预先实例化,否则只有在getBean才会实例化)
1.1 同时使用@Autowired 和 @Resource 注解会发生什么?
首先先改装一下上面的Bean1
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;
}
@Autowired
private Inter bean;
public Inter getInter() {
return bean3;
}
}
static class Bean3 implements Inter {
}
static class Bean4 implements Inter {
}
interface Inter {
}
这样的话,肯定报错,因为Bean3和Bean4都实现了Inter接口,Spring无法得知要注入的是Bean3还是Bean4
但是我们把private Inter bean改为private Inter bean3,这样@Autowired在发现Bean3和Bean4都实现了Inter的前提下,会先查找名字一样的,然后就会找到Bean3
那如果加了Resource注解呢?
@Autowired
@Resource(name = "bean4")
private Inter bean3;
这样这个实例化的是Bean3还是Bean4???
System.out.println(beanFactory.getBean(Bean1.class).getInter());
emmm这里实例化的是Bean3,这是为什么呢?
那是因为Bean后处理会有排序逻辑,哪个后处理器的优先级高,那么就谁先解析这个注解
在前面添加bean后处理器的地方输出bean后处理器
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
可以发现第一个bean后处理器是用来解析@Autowired,所以先解析@Autowired,注入的就是Bean3了
那这个顺序可以改变吗?当然可以,通过比较器即可
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.sorted(beanFactory.getDependencyComparator())
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
现在就变成了先解析@Resource,所以注入的是Bean4
那么比较器是怎么设置的呢???
在前面添加BeanFactory的时候,AnnotationConfigUtils里面有一个Set< BeanDefinitionHolder >方法会给这些BeanFactory设置比较器
// 给 BeanFactory 添加一些常用的后处理器(还未运行)
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
那么Bean后处理器的顺序哪里来呢??
在每个Bean后处理器中都会有相应的get/set方法
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}
在这里就设置了顺序,明显可以看出AutowiredAnnotationBeanPostProcessor比较前,因为它只是-2,而CommonAnnotationBeanPostProcessor是减3
2. ApplicationContext的常见实现和用法
ApplicationContext有几个较为经典的容器
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- AnnotationConfigApplicationContext
- AnnotationConfigServletWebServerApplicationContext
下面是一些准备数据
@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;
}
}
2.1 ClassPathXmlApplicationContext
该容器是基于 classpath 下 xml 格式的配置文件来创建,用来读取xml文件
不过这种读取xml获得bean的方法用得很少了,因为现在大多是SpringBoot开发,这些都自动装配好了
private static void testClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("a02.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
<?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>
2.2 FileSystemXmlApplicationContext
FileSystemXmlApplicationContext基于磁盘路径下 xml 格式的配置文件来创建
用法和ClassPathXmlApplicationContext一样,就是读取的是相对路径和绝对路径
private static void testFileSystemXmlApplicationContext() {
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(
"src\\main\\resources\\a02.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
2.3 结合BeanFactory
可以创建一个新的BeanFactory
利用XmlBeanDefinitionReader读取xml文件中的Bean,构建参数为BeanFactory对象
然后将xml中的Bean加载到BeanFactory中
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("读取之前...");
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println("读取之后...");
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
2.4 AnnotationConfigApplicationContext
这种也是经典容器之一,是基于Java配置类创建的
@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;
}
}
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());
}
读取配置Config类,即可获取到其中的bean
这其中的一些Bean后处理器,也会自动添加上,这就是ApplicationContext比BeanFactory的好处
不需要手动人为添加后处理器
假如使用FileSystemXmlApplicationContext和ClassPathXmlApplicationContext就需要我们手动添加这些Bean后处理器
在SSM学习阶段,我们都需要在xml文件中假如以下标签
<context:annotation-config/>
这个标签就是帮助我们将这些后处理器加上,所以学习SSM的时候才没感觉那么麻烦
2.5 AnnotationConfigServletWebServerApplicationContext
AnnotationConfigServletWebServerApplicationContext也是基于Java配置类来创建的,主要用于Web环境
我们可以内嵌容器,像传统的SSM开发,必须依赖Tomcat服务程序才可以跑起来,而SpringBoot项目的话,已经内嵌容器了,所以不需要依赖外部的Tomcat也能将程序跑起来
代码中的ServletWebServerFactory就是内嵌容器Tomcat
而DispatcherServlet是前端控制器,也就是,浏览器的请求和响应都是经过前端控制器来处理的
除了这两个组件还是不够的
还需要DispatcherServletRegistrationBean注册DispatcherServlet到tomcat服务器
有了这三个基本的组件,一个内嵌服务的web服务就算完成了
除此之外,Controller一般通过@Controller来实现,这里通过org.springframework.web.servlet.mvc.Controller这个接口来实现原生的接口,直接用@Bean注解来实现,这里@Bean有个规定,就是以/开头,/后面的名称作为这个接口的访问路径,当访问hello就会进行controller1方法里面的逻辑
因为Controller接口是函数式接口,所以可以简写
@Configuration
static class WebConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
@Bean("/hello")
public Controller controller1() {
return (request, response) -> {
response.getWriter().print("hello");
return null;
};
}
}
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94994.html