
在 Spring IoC 和 DI 部分学习了 Spring 是如何帮助我们管理对象的。
@Controller、@Service、@Repository、@Component、@Configuration 和方法注解 @Bean 来声 Bean 对象。ApplicationContext 或者 BeanFactory 来获取对象@Autowired、Setter 方法或者构造方法等来为应用程序注入所依赖的Bean对象默认情况下,Spring 容器中的 bean 都是单例的,这种行为模式就称之为 Bean 的作用域。
名称 | 生命周期 | 作用粒度 | 底层对象 | 所属环境 |
|---|---|---|---|---|
singleton | Spring 容器存活期间 | 容器级全局共享 | Spring 容器 | 所有环境通用 |
prototype | 每次获取新建实例 | 每次请求 Bean 时 | Spring 容器 | 所有环境通用 |
request | 一次 HTTP 请求 | 单次请求内共享 | HttpServletRequest | 仅 Web 环境 |
session | 一次用户会话 | 单个用户会话级别 | HttpSession | 仅 Web 环境 |
application | 整个 Web 应用 | 全局共享 | ServletContext | 仅 Web 环境 |
websocket | 一次 WebSocket 连接 | 单连接 | WebSocket 会话 | 仅 WebSocket |
定义几个不同作用域的 Bean,需要搭配不同的注解,如下所示:
@Component
public class DogBeanConfig {
@Bean // 默认是单例singleton
public Dog dog(){
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@RequestScope
public Dog requestDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@SessionScope
public Dog sessionDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@ApplicationScope
public Dog applicationDog() {
Dog dog = new Dog();
return dog;
}
}测试不同作用域的Bean取到的对象是否一样:
@RestController
public class DogController {
@Autowired
private Dog singleDog;
@Autowired
private Dog prototypeDog;
@Autowired
private Dog requestDog;
@Autowired
private Dog sessionDog;
@Autowired
private ApplicationContext applicationContext;
@RequestMapping("/single")
public String single(){
Dog contextDog = (Dog)applicationContext.getBean("singleDog");
return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/prototype")
public String prototype(){
Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");
return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/request")
public String request(){
Dog contextDog = (Dog)applicationContext.getBean("requestDog");
return "dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/session")
public String session(){
Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
return "dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/application")
public String application(){
Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
return "dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();
}
}单例作用域:http://127.0.0.1:8080/single
多次访问,得到的都是同一个对象,并且 @Autowired 和 applicationContext.getBean() 也是同一个对象。

多例作用域:http://127.0.0.1:8080/prototype
观察 ContextDog,每次获取的对象都不一样(而注入的对象 dog 在 Spring 容器启动时,就已经注入了,所以多次请求也不会发生变化)

请求作用域:http://127.0.0.1:8080/request
在一次请求中,@Autowired 和 applicationContext.getBean() 也是同一个对象。
但是每次请求,都会重新创建对象

会话作用域:http://127.0.0.1:8080/session
在一个session中,多次请求,获取到的对象都是同一个。
换一个浏览器访问,发现会重新创建对象。(另一个Session)

Application作用域:http://127.0.0.1:8080/application
在一个应用中,多次访问都是同一个对象

singleton | application | |
|---|---|---|
生命周期 | Spring 容器级(随容器启动) | ServletContext 级(Web 应用级别) |
管理者 | Spring IoC 容器 | Web 容器(Tomcat/Jetty) |
存储位置 | Spring 的单例缓存池(Map) | ServletContext#setAttribute() |
可访问范围 | Spring 管理的类之间 | 整个 Web 应用中共享 |
是否依赖 Web 环境 | ❌ 否(通用) | ✅ 是(仅限 Web 项目) |
被 Spring 管理? | ✅ 是 | ✅ 是,但底层基于 Web 容器 |
ServletContext 等)。web.xml 中的 ContextLoaderListener 或 Spring Boot 的启动类)来启动 Spring 容器。ApplicationContext / WebApplicationContext )ServletContext 获得一些信息)。🧩 类比一下:
💥通常情况下,一个 Web 应用 应该只拥有一个 Spring 容器 实例(ApplicationContext),并且 Spring 的底层设计就是这么保障的,以避免 Bean 冲突、上下文混乱等问题。
生命周期指的是一个对象从诞生到销毁的整个生命过程,这个过程就叫做一个对象的生命周期。
Bean的生命周期分为以下5个部分:
@AutoWired)BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法。init-method@PostConstructBeanPostProcessor@PreDestroy、DisposableBean 接口方法、destroy-method。比如我们现在需要买一栋房子,那么我们的流程是这样的:
执行流程如下图所示:

创建Bean的代码入口在 AbstractAutowireCapableBeanFactory#createBean
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//...代码省略
try {
// 在实例化之前, 是否有快捷创建的Bean, 也就是通过PostProcessorsBeforeInstantiation返回的Bean
// 如果存在, 则会替代原来正常通过target bean生成的bean的流程
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 创建Bean
// 方法中包含了实例化、属性赋值、初始化过程
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
//...代码省略
}
}点进去继续看源码:AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 创建bean实例
//...代码省略
if (instanceWrapper == null) {
// 实例化Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//...代码省略
//初始化bean实例
Object exposedObject = bean;
try {
// 依据bean definition 完成bean属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 执行bean初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException &&
beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
//...代码省略
return exposedObject;
}这三个方法与三个生命周期阶段一一对应
createBeanInstance():实例化populateBean():属性赋值initializeBean():初始化继续点进去 initializeBean():
// 初始化Bean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
// 调用的三个Bean开头的Aware方法
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 调用初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
// 调用的三个Bean开头的Aware方法
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}SpringBoot 的自动配置就是当 Spring 容器启动后,一些配置类、bean 对象等就自动存入到了 IoC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
需求:使用 Spring 管理第三方的 jar 包的配置
引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入

第三方文件代码:
@Component
public class MyConfig {
public void func() {
System.out.println("start func() ...");
}
}获取 MyConfig 这个 Bean:
@SpringBootTest
class BeanTheoryApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
MyConfig myConfig = applicationContext.getBean(MyConfig.class);
System.out.println(myConfig);
}
}运行后报错:

Spring 通过五大注解和 @Bean 注解可以帮助我们把 Bean 加载到 Spring IoC 容器中,以上有个前提就是这些注解类需要和 SpringBoot 启动类在同一个目录下(@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类)
当我们引入第三方的 Jar 包时,第三方的 Jar 代码目录肯定不在启动类的目录下,如何告诉 Spring 帮我们管理这些 Bean 呢?
常见的解决方案有两种:
@ComponentScan 组件扫描@Import 导入(使用 @Import 导入的类会被 Spring 加载到 IoC 容器中)@ComponentScan在启动类中添加扫描路径:
@ComponentScan("com.liren.third")
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}也可以指定扫描多个包:
@ComponentScan({"com.liren.autoconfig", "com.example.demo"})
Spring是否使用了这种方式呢? 非常明显,没有。(因为我们引入第三方框架时,没有加扫描路径。比如mybatis) 如果SpringBoot采用这种方式,当我们引入大量的第三方依赖,比如Mybatis、jackson等时,就需要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。
@Import@Import 导入主要有以下几种形式:
ImportSelector 接口实现类@Import(MyConfig.class)
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}也可以采用导入多个类:
@Import({MyConfig1.class, MyConfig2.class})
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}很明显,这种方式也很繁琐,所以 SpringBoot 依然没有采用这种方式!
ImportSelector 接口实现类首先写一个 ImportSelector 接口实现类,重写里面的 selectImports() 方法:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.liren.third.MyConfig"};
}
}启动类中导入我们自定义的 MyImportSelector:
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}这两种方式都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些 Bean 对象或配置类。如果漏掉其中一些 Bean,很可能导致我们的项目出现大的事故。
依赖中有哪些 Bean,使用时需要配置哪些 Bean,第三方依赖最清楚,那能否由第三方依赖来做这件事呢?
@EnableXxxx 开头的注解,注解中封装的就是 @Import 注解@Import 注解,导入 MyImportSelector.class
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyConfig.class) public @interface EnableMyConfig { }@EnableMyConfig
@SpringBootApplication
public class BeanTheoryApplication {
public static void main(String[] args) {
SpringApplication.run(BeanTheoryApplication.class, args);
}
}这种方式也可以导入第三方依赖提供的 Bean,并且这种方式更优雅一点,SpringBoot 采用的也是这种方式。
一切的来自起源 SpringBoot 的启动类开始:(@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类)
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// 获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
// 从Spring上下文中获取对象
BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
beanLifeComponent.use();
}
}这个类和普通类唯一的区别就是 @SpringBootApplication 注解,这个注解也是 SpringBoot 实现自动配置的核心。
这个注解的内容如下所示:

@Target:描述注解的使用范围(即被修饰的注解可以用在什么地方)@Retention:描述注解保留的时间范围@Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息@Inherited:使被它修饰的注解具有继承性(如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解)@EnableAutoConfiguration 详解@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}@Import({AutoConfigurationImportSelector.class})@Import 注解,导入了实现 ImportSelector 接口的实现类:public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取自动配置的配置类信息
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}selectImports() 方法底层调用 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取在配置文件中配置的所有自动配置类的集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations(...) 方法获取在配置文件中配置的所有自动配置类的集合:
// 获取所有基于
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
// META-INF/spring.factories文件中配置类的集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found in " + "META-INF/spring/"
+ this.autoConfigurationAnnotation.getName() + ".imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}获取所有基于 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(在 IDEA 中双击 shift 之后打开查看)、META-INF/spring.factories 文件中配置类的集合。
在引入的起步依赖中,通常都有包含以上两个文件:

@Conditional 等注解的判断进行动态加载。@Conditional 是 spring 底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。META-INF/spring.factories 文件是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring.factories 文件中配置即可,Spring就会把相应的实现类注入到容器中。比如 Redis 的配置:RedisAutoConfiguration

可以看到,配置文件中使用 @Bean 声明了一些对象,spring 就会自动调用配置类中使用 @Bean 标识的方法,并把对象注册到 Spring IoC 容器中。
@AutoConfigurationPackage@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
Registrar实现了ImportBeanDefinitionRegistrar类,就可以被注解@Import导入到spring容器里。
其中 (new PackageImports(metadata)).getPackageNames().toArray(new String[0]) 表示当前启动类所在的包名。
结论: @AutoConfigurationPackage 就是将启动类所在的包下面所有的组件都扫描注册到 spring 容器中 。
SpringBoot 自动配置原理的大概流程如下:

当 SpringBoot 程序启动时,会加载配置文件当中所定义的配置类,通过 @Import 注解将这些配置类全部加载到 Spring 的 IOC 容器中,交给 IOC 容器管理。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。