标题中的咖啡罐指的是Spring容器,容器里装的当然就是被称作Bean的豆子。本文我们会以一个最基本的例子来熟悉Spring的容器管理和扩展点。
阅读PDF版本

为什么要让容器来管理对象?

首先我们来聊聊这个问题,为什么我们要用Spring来管理对象(的生命周期和对象之间的关系)而不是自己new一个对象呢?大家可能会回答是方便,为了解耦。我个人觉得除了这两个原因之外,还有就是给予了我们更多可能性。如果我们以容器为依托来管理所有的框架、业务对象,那么不仅仅我们可以无侵入调整对象的关系,还有可能无侵入随时调整对象的属性甚至悄悄进行对象的替换。这就给了我们无限多的可能性,大大方便了框架的开发者在程序背后实现一些扩展。不仅仅Spring Core本身以及Spring Boot大量依赖Spring这套容器体系,一些外部框架也因为这个原因可以和Spring进行无缝整合。
Spring可以有三种方式来配置Bean,分别是最早期的XML方式、后来的注解方式以及现在最流行的Java代码配置方式。

Bean的回调事件

在前文parent模块(空的一个SpringBoot应用程序)的基础上,我们先来创建一个beans模块:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>me.josephzhu</groupId><artifactId>spring101-beans</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>spring101-beans</name><description></description><parent><groupId>me.josephzhu</groupId><artifactId>spring101</artifactId><version>0.0.1-SNAPSHOT</version></parent><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

然后来创建我们的豆子:

package me.josephzhu.spring101beans;import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;@Component
public class MyService implements InitializingBean, DisposableBean {public int increaseCounter() {this.counter++;return counter;}public int getCounter() {return counter;}public void setCounter(int counter) {this.counter = counter;}private int counter=0;public MyService(){counter++;System.out.println(this + "#constructor:" + counter);}public String hello(){return this + "#hello:" + counter;}@PreDestroypublic void preDestroy() {System.out.println(this + "#preDestroy:" + counter);}@Overridepublic void afterPropertiesSet() {counter++;System.out.println(this + "#afterPropertiesSet:" + counter);}@PostConstructpublic void postConstruct(){counter++;System.out.println(this + "#postConstruct:" + counter);}@Overridepublic void destroy() {System.out.println(this + "#destroy:" + counter);}
}

这里可以看到,我们的服务中有一个counter字段,默认是0。这个类我们实现了InitializingBean接口和DisposableBean接口,同时还创建了两个方法分别加上了@PostConstruct和@PreDestroy注解。这两套实现方式都可以在对象的额外初始化功能和释放功能,注解的实现不依赖Spring的接口,侵入性弱一点。
接下去,我们创建一个Main类来测试一下:

package me.josephzhu.spring101beans;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;import javax.annotation.Resource;@SpringBootApplication
public class Spring101BeansApplication implements CommandLineRunner {@Autowiredprivate ApplicationContext applicationContext;@Resourceprivate MyService helloService;@Autowiredprivate MyService service;public static void main(String[] args) {SpringApplication.run(Spring101BeansApplication.class, args);}@Overridepublic void run(String... args) throws Exception {System.out.println("====================");applicationContext.getBeansOfType(MyService.class).forEach((name, service)->{System.out.println(name + ":" + service);});System.out.println("====================");System.out.println(helloService.hello());System.out.println(service.hello());}
}

ApplicationContext直接注入即可,不一定需要用ApplicationContextAware方式来获取。执行程序后可以看到输出如下:

me.josephzhu.spring101beans.MyService@7fb4f2a9#constructor:1
me.josephzhu.spring101beans.MyService@7fb4f2a9#postConstruct:2
me.josephzhu.spring101beans.MyService@7fb4f2a9#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7fb4f2a9
====================
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#preDestroy:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#destroy:3

这里我们使用@Resource注解和@Autowired注解分别引用了两次对象,可以看到由于Bean默认配置为singleton单例,所以容器中MyService类型的对象只有一份,代码输出也可以证明这点。此外,我们也通过输出看到了构造方法以及两套Bean回调的次序是:

  1. 类自己的构造方法
  2. @PostConstruct注释的方法
  3. InitializingBean接口实现的方法
  4. @PreDestroy注释的方法
  5. DisposableBean接口实现的方法

Java 代码方式创建Bean

从刚才的输出中可以看到,在刚才的例子中,我们为Bean打上了@Component注解,容器为我们创建了名为myService的MyService类型的Bean。现在我们再来用Java代码方式来创建相同类型的Bean,创建如下的文件:

package me.josephzhu.spring101beans;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;@Configuration
public class ApplicationConfig {@Bean(initMethod = "init")public MyService helloService(){MyService myService = new MyService();myService.increaseCounter();return myService;}}

这里可以看到在定义Bean的时候我们关联了一个initMethod,因此我们需要修改Bean加上这个方法:

public void init() {counter++;System.out.println(this + "#init:" + counter);}

现在我们运行代码看看结果,得到了如下错误:

Field service in me.josephzhu.spring101beans.Spring101BeansApplication required a single bean, but 2 were found:- myService: defined in file [/Users/zyhome/IdeaProjects/spring101/spring101-beans/target/classes/me/josephzhu/spring101beans/MyService.class]- helloService: defined by method 'helloService' in class path resource [me/josephzhu/spring101beans/ApplicationConfig.class]

出现错误的原因是@Autowired了一个MyService,@Resource注解因为使用Bean的名称来查找Bean,所以并不会出错,而@Autowired因为根据Bean的类型来查抄Bean找到了两个匹配所有出错了,解决方式很简单,我们在多个Bean里选一个作为主Bean。我们修改一下MyService加上注解:

@Component
@Primary
public class MyService implements InitializingBean, DisposableBean

这样,我们的@Resource根据名字匹配到的是我们@Configuration出来的Bean,而@Autowired根据类型+Primary匹配到了@Component注解定义的Bean,重新运行代码来看看是不是这样:

me.josephzhu.spring101beans.MyService@6cd24612#constructor:1
me.josephzhu.spring101beans.MyService@6cd24612#postConstruct:3
me.josephzhu.spring101beans.MyService@6cd24612#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@6cd24612#init:5
me.josephzhu.spring101beans.MyService@7486b455#constructor:1
me.josephzhu.spring101beans.MyService@7486b455#postConstruct:2
me.josephzhu.spring101beans.MyService@7486b455#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7486b455
helloService:me.josephzhu.spring101beans.MyService@6cd24612
====================
me.josephzhu.spring101beans.MyService@6cd24612#hello:5
me.josephzhu.spring101beans.MyService@7486b455#hello:3
me.josephzhu.spring101beans.MyService@7486b455#preDestroy:3
me.josephzhu.spring101beans.MyService@7486b455#destroy:3
me.josephzhu.spring101beans.MyService@6cd24612#preDestroy:5
me.josephzhu.spring101beans.MyService@6cd24612#destroy:5

从输出中我们注意到几点:

  1. 先输出的的确是helloService,说明@Resource引入的是我们Java代码配置的MyService,helloService由于在我们配置的多调用了一次increaseCounter()以及关联的initMethod,所以counter的值是5
  2. initMethod执行的顺序在@PostConstruct注释的方法和InitializingBean接口实现的方法之后
  3. 虽然我们的MySerive的两种Bean的定义都是单例,但是这不代表我们的Bean就是一套,在这里我们通过代码配置和注解方式在容器内创建了两套MyService类型的Bean,它们都经历了自己的初始化过程。通过@Resource和@Autowired引入到了是不同的Bean,当然也就是不同的对象
    你还可以试试在使用@Autowired引入MyService的时候直接指定需要的Bean:
@Autowired
@Qualifier("helloService")
private MyService service;

两个重要的扩展点

我们来继续探索Spring容器提供给我们的两个有关Bean的重要扩展点。

  • 用于修改Bean定义的BeanFactoryPostProcessor。所谓修改定义就是修改Bean的元数据,元数据有哪些呢?如下图所示,类型、名字、实例化方式、构造参数、属性、Autowire模式、懒初始化模式、初始析构方法。实现了这个接口后,我们就可以修改这些已经定义的元数据,实现真正的动态配置。这里需要注意,我们不应该在这个接口的实现中去实例化Bean,否则这相当于提前进行了实例化会破坏Bean的生命周期。

  • 用于修改Bean实例的BeanPostProcessor。在这个阶段其实Bean已经实例化了,我们可以进行一些额外的操作对Bean进行修改。如下图,我们可以清晰的看到Bean的生命周期如下(BeanPostProcessor缩写为BPP):
  1. Bean定义加载
  2. BeanFactoryPostProcessor来修改Bean定义
  3. Bean逐一实例化
  4. BeanPostProcessor预处理
  5. Bean初始化
  6. BeanPostProcessor后处理


好,我们现在来实现这两种类型的处理器,首先是用于修改Bean定义的处理器:

package me.josephzhu.spring101beans;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition("helloService");if (beanDefinition != null) {beanDefinition.setScope("prototype");beanDefinition.getPropertyValues().add("counter", 10);}System.out.println("MyBeanFactoryPostProcessor");}
}

这里,我们首先找到了我们的helloService(Java代码配置的那个Bean),然后修改了它的属性和Scope(还记得吗,在之前的图中我们可以看到,这两项都是Bean的定义,定义相当于类描述,实例当然就是类实例了)。
然后,我们再来创建一个修改Bean实例的处理器:

package me.josephzhu.spring101beans;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof MyService) {System.out.println(bean + "#postProcessAfterInitialization:" + ((MyService)bean).increaseCounter());}return bean;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof MyService) {System.out.println(bean + "#postProcessBeforeInitialization:" + ((MyService)bean).increaseCounter());}return bean;}
}

实现比较简单,在这个处理器的两个接口我们都调用了一次增加计数器的操作。我们运行代码来看一下这两个处理器执行的顺序是否符合刚才那个图的预期:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@41330d4f#constructor:1
me.josephzhu.spring101beans.MyService@41330d4f#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@41330d4f#postConstruct:12
me.josephzhu.spring101beans.MyService@41330d4f#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@41330d4f#init:14
me.josephzhu.spring101beans.MyService@41330d4f#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@6f36c2f0#constructor:1
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6f36c2f0#postConstruct:12
me.josephzhu.spring101beans.MyService@6f36c2f0#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6f36c2f0#init:14
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@3b35a229#constructor:1
me.josephzhu.spring101beans.MyService@3b35a229#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@3b35a229#postConstruct:3
me.josephzhu.spring101beans.MyService@3b35a229#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@3b35a229#postProcessAfterInitialization:5
====================
me.josephzhu.spring101beans.MyService@6692b6c6#constructor:1
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6692b6c6#postConstruct:12
me.josephzhu.spring101beans.MyService@6692b6c6#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6692b6c6#init:14
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessAfterInitialization:15
myService:me.josephzhu.spring101beans.MyService@3b35a229
helloService:me.josephzhu.spring101beans.MyService@6692b6c6
====================
me.josephzhu.spring101beans.MyService@41330d4f#hello:15
me.josephzhu.spring101beans.MyService@6f36c2f0#hello:15
me.josephzhu.spring101beans.MyService@3b35a229#preDestroy:5
me.josephzhu.spring101beans.MyService@3b35a229#destroy:5

这个输出结果有点长,第一行就输出了MyBeanFactoryPostProcessor这是预料之中,Bean定义的修改肯定是最先发生的。我们看下输出的规律,1、11、12、13、14、15出现了三次,之所以从1跳到了11是因为我们的BeanFactoryPostProcessor修改了其中的counter属性的值为10。这说明了,我们的helloService的初始化进行了三次:

  • 第一套指针地址是5a7fe64f,对应输出第一个hello(),这是我们@Resource引入的

  • 第二套指针地址是69ee81fc,对应输出第二个hello(),这是我们@Autowird+@Qualifier引入的(刚才一节最后我们指定了helloService)

  • 第三套指针地址是29f7cefd,这是我们getBeansOfType的时候创建的,对应下面Key-Value的输出:


这里的输出说明了几点:

  • 我们的BeanFactoryPostProcessor生效了,不但修改了helloService的Scope为prototype而且修改了它的counter属性
  • 对于Scope=ptototype的Bean,显然在每次使用Bean的时候都会新建一个实例
  • BeanPostProcessor两个方法的顺序结合一开始说的Bean事件回调的顺序整体如下:
  1. 类自己的构造方法
  2. BeanFactoryPostProcessor接口实现的postProcessBeforeInitialization()方法
  3. @PostConstruct注释的方法
  4. InitializingBean接口实现的afterPropertiesSet()方法
  5. Init-method定义的方法
  6. BeanFactoryPostProcessor接口实现的postProcessAfterInitialization()方法
  7. @PreDestroy注释的方法
  8. DisposableBean接口实现的destroy()方法

最后,我们可以修改BeanFactoryPostProcessor中的代码把prototype修改为singleton看看是否我们的helloService这个Bean恢复为了单例:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@51891008#constructor:1
me.josephzhu.spring101beans.MyService@51891008#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@51891008#postConstruct:12
me.josephzhu.spring101beans.MyService@51891008#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@51891008#init:14
me.josephzhu.spring101beans.MyService@51891008#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@49c90a9c#constructor:1
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@49c90a9c#postConstruct:3
me.josephzhu.spring101beans.MyService@49c90a9c#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessAfterInitialization:5
====================
myService:me.josephzhu.spring101beans.MyService@49c90a9c
helloService:me.josephzhu.spring101beans.MyService@51891008
====================
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@49c90a9c#preDestroy:5
me.josephzhu.spring101beans.MyService@49c90a9c#destroy:5
me.josephzhu.spring101beans.MyService@51891008#preDestroy:15
me.josephzhu.spring101beans.MyService@51891008#destroy:15

本次输出结果的hello()方法明显是同一个bean,结果中也没出现三次1、11、12、13、14、15。

总结

本文以探索的形式讨论了下面的一些知识点:

  1. 容器管理对象的意义是什么
  2. Bean的生命周期回调事件
  3. Spring提供的Bean的两个重要扩展点
  4. @Resource和@Autowired的区别
  5. 注解方式和代码方式配置Bean
  6. @Primary和@Qualifier注解的作用
  7. Bean的不同类型的Scope

朱晔和你聊Spring系列S1E3:Spring咖啡罐里的豆子相关推荐

  1. Spring系列之Spring框架和SpringAOP集成过程分析(十)

    转载请注明出处:https://blog.csdn.net/zknxx/article/details/80724180 在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里 ...

  2. Spring系列之Spring常用注解总结

    参看博客:https://www.cnblogs.com/xiaoxi/p/5935009.html 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺 ...

  3. Spring系列 1.Spring概述及IOP

    Spring概述 简介 Spring : 春天 ->给软件行业带来了春天 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架. 2004年3月24日,Sp ...

  4. Spring系列之Spring Web MVC-20

    目录 Spring Web MVC DispatcherServlet 上下文层次结构 特殊Bean Web MVC 配置 程序配置 工作原理 异常 视图解析 配置 重定向 转发 内容协商 过滤器 F ...

  5. 【Spring 系列】Spring知识地图

    文章目录 Spring IOC 知道 会用 熟练 掌握 专家 Spring AOP 知道 会用 熟练 掌握 专家 Spring MVC 知道 会用 熟练 掌握 专家 Spring WebFlux 知道 ...

  6. Spring系列之Spring常用注解总结 原文:https://www.cnblogs.com/xiaoxi/p/5935009.html

    传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺点: 1.如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大:如果按需求分开.xml文 ...

  7. Spring系列之一 Spring MVC

    摘要: 最近在看Spring的书,之前一直跟着项目做,虽然项目用到了Spring的很多功能,但是我很少有机会在工作的项目中去配置Spring.感觉只有理论是不够的,虽然只是用Spring的配置让人感觉 ...

  8. Spring系列(六) Spring Web MVC 应用构建分析

    DispatcherServlet DispatcherServlet 是Spring MVC的前端控制器名称, 用户的请求到达这里进行集中处理, 在Spring MVC中, 它的作用是为不同请求匹配 ...

  9. 【Spring系列】- Spring循环依赖

    Spring循环依赖

最新文章

  1. 【蓝桥杯每日一练】 巴斯卡三角形(杨辉三角形)
  2. 另类架构师:在国企涂肥皂水、考研被调剂、在阿里跟十八罗汉当同事……
  3. 我就是互联网的老不死
  4. 【CCF】201609-1 最大波动
  5. Android 7.0 隐式广播-监听网络变化
  6. 开源势不可挡 IBM Power Systems Linux中心成立
  7. 利用ambassador实现container跨主机连接
  8. Eclipse里面导工程的时候报错faceted project problem
  9. 【转】DD_belatedPNG,解决IE6不支持PNG绝佳方案
  10. 笑死人不偿命,你敢进来看看吗
  11. 动态库动态调用WEbservice的问题!
  12. 无线网radius服务器ip,怎么设置无线路由器的radius服务器ip地址
  13. JDBC系列(二):JDBC代码的编写步骤
  14. SPSS聚类分析——一个案例演示聚类分析全过程
  15. 【预告】《天黑以后》(《The Darkest Hour》)
  16. android修改recovery菜单,安卓刷机Recovery菜单介绍刷入教程详解
  17. python Plot 画图用法
  18. SOCKS 代理协议漫谈
  19. 函数:fopen的使用方法
  20. javaFX裁剪视频exe

热门文章

  1. kotlin实现继承_Kotlin程序| 继承的例子
  2. Python字符串| join()方法与示例
  3. 汇编指令处理的数据长度
  4. linux操作系统信号捕捉函数之回调函数小结
  5. 链表题目---4 删除链表中重复的结点 和 判断链表是否为回文链表
  6. 【电路原理】学习笔记(1):电路模型的基本变量
  7. Python—“helloworld”
  8. Python内置数据类型之list
  9. 保驾护航金三银四,内容太过真实
  10. 丁香园 武汉 神童_杭州、武汉、成都哪个城市更适合程序员发展