Bean的管理与SpringBoot自动装配原理解读

 更新时间:2024年11月04日 16:11:24   作者:薛定谔的盐.  
在SpringBoot项目中,启动时自动创建IOC容器并初始化bean对象,支持通过依赖注入获取,Bean可以通过name或类型获取,支持单例和非单例等多种作用域,对于第三方Bean,推荐在配置类中用@Bean标识方法进行定义

1、Bean管理

默认情况下,SpringBoot项目在启动的时候会自动的创建IOC容器,并且在启动的过程当中会自动的将bean对象都创建好,存放在IOC容器当中。

应用程序在运行时需要依赖什么bean对象,就直接进行依赖注入就可以了。

1.1 获取Bean

要想主动从IOC容器中获取到bean对象,需要先拿到IOC容器

想获取到IOC容器,直接将IOC容器对象注入进来就可以了

@Autowired
private ApplicationContext applicationContext; //IOC容器对象

在IOC容器中提供了一些方法,可以主动从IOC容器中获取到bean对象,

介绍3种常用方式:

1.根据name获取bean

Object getBean(String name)

2.根据类型获取bean

<T> T getBean(Class<T> requiredType)

3.根据name获取bean(带类型转换)

&lt;T&gt; T getBean(String name, Class&lt;T&gt; requiredType)
@SpringBootTest
class SpringbootTheoryApplicationTests {

    @Autowired
    private ApplicationContext applicationContext; //IOC容器对象

    //获取bean对象
    @Test
    public void testGetBean(){
        //根据bean的名称获取
        MyController bean1 = (MyController) applicationContext.getBean("myController");
        System.out.println(bean1);

        //根据bean的类型获取
        MyController bean2 = applicationContext.getBean(MyController.class);
        System.out.println(bean2);

        //根据bean的名称 及 类型获取
        MyController bean3 = applicationContext.getBean("myController", MyController.class);
        System.out.println(bean3);
    }

}

运行结果:

com.wbr.controller.MyController@2fa47368

com.wbr.controller.MyController@2fa47368

com.wbr.controller.MyController@2fa47368

可以看到三个bean的打印结果都是一样的,说明IOC容器当中的bean对象只有一个。

默认情况下,IOC中的bean对象是单例

1.2 Bean的作用域

IOC容器中,默认bean对象是单例模式(只有一个实例对象)。

想要设置bean对象为非单例就需要设置bean的作用域。

在Spring中支持五种作用域,后三种在web环境才生效:

作用域说明
singleton容器内同名称的bean只有一个实例(单例)(默认)
prototype每次使用该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例
session每个会话范围内会创建新的实例
application每个应用范围内会创建新的实例

可以借助Spring中的@Scope注解来进行配置作用域

测试一:

Controller类

//当我们未加@Scope注解时,默认bean的作用域为:@Scope("singleton") (单例模式)
@Lazy //延迟加载(第一次使用bean对象时,才会创建bean对象并交给ioc容器管理)
@RestController
public class MyController {

    @Autowired
    private MyService myService;

    public MyController(){
        System.out.println("MyController 构造方法执行了...");
    }
}

测试类

@SpringBootTest
class SpringbootTheoryApplicationTests {

    @Autowired
    private ApplicationContext applicationContext; //IOC容器对象

    //获取bean对象
    @Test
    public void testGetBean(){
        //根据bean的名称获取
        MyController bean1 = (MyController) applicationContext.getBean("myController");
        System.out.println(bean1);

        //根据bean的类型获取
        MyController bean2 = applicationContext.getBean(MyController.class);
        System.out.println(bean2);

        //根据bean的名称 及 类型获取
        MyController bean3 = applicationContext.getBean("myController", MyController.class);
        System.out.println(bean3);
    }
}

运行结果:

MyController 构造方法执行了...
com.wbr.controller.MyController@3835d3fd
com.wbr.controller.MyController@3835d3fd
com.wbr.controller.MyController@3835d3fd

注意事项:

  • IOC容器中的bean默认使用的作用域:singleton (单例)
  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)

测试二:

给MyController加上@Scope("prototype")注解:

@Lazy //延迟加载(第一次使用bean对象时,才会创建bean对象并交给ioc容器管理)
@Scope("prototype")
@RestController
public class MyController {

    @Autowired
    private MyService myService;

    public MyController(){
        System.out.println("MyController 构造方法执行了...");
    }
}

再次运行测试方法得到以下结果:

MyController 构造方法执行了...
com.wbr.controller.MyController@73aae7a
MyController 构造方法执行了...
com.wbr.controller.MyController@3856d0cb
MyController 构造方法执行了...
com.wbr.controller.MyController@2125535d

注意事项:

  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例
  • 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性

1.3 第三方Bean

情况1:自定义类上加上@Component以及它的这三个衍生注解@Controller、@Service、@Repository,用来声明这个bean对象。

情况2:这个类它不是我们自己编写的,而是我们引入的第三方依赖当中提供的。

我们可以使用 ObjectMapper 作为第三方依赖。ObjectMapper 是 Jackson 库中的核心类,用于在 Java 对象和 JSON 之间进行转换。

当我们需要使用到ObjectMapper对象时,应该如何进行依赖注入?

由于第三方提供的类是只读的。无法在第三方类上添加@Component注解或衍生注解。

解决方案1:在启动类上添加用@Bean标识的方法

启动类:

@SpringBootApplication
public class SpringbootTheoryApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootTheoryApplication.class, args);
    }

    //声明第三方bean
    @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
    public ObjectMapper objectMapper(){
        return new ObjectMapper();
    }
}

测试类:

@SpringBootTest
class SpringbootWebConfig2ApplicationTests {

    @Autowired
    private ObjectMapper objectMapper;

    // 第三方bean的管理
    @Test
    public void testThirdBean() throws Exception {
        // 将 JSON 字符串转换为 Java 对象
        String json = "{\"name\":\"zhangsan\",\"age\":21}";
        Person person = objectMapper.readValue(json, Person.class);

        System.out.println(person.getName() + " : " + person.getAge());
    }

    // 用于测试的简单 POJO 类
    static class Person {
        private String name;
        private int age;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}

运行结果:

zhangsan : 21

注意事项:

  • 以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性)

解决方案2:在配置类中定义@Bean标识的方法

  • 如果需要定义第三方Bean时, 通常会单独定义一个配置类
@Configuration //声明一个配置类
public class ObjectMapperConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

注释掉SpringBoot启动类中创建第三方bean对象的代码,重新执行测试方法:

zhangsan : 21

在方法上加上一个@Bean注解,Spring 容器在启动的时候,它会自动的调用这个方法,并将方法的返回值声明为Spring容器当中的Bean对象。

注意事项 :

  • 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
  • 如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。

2、SpringBoot原理

SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置。

2.1 起步依赖

假如没有使用SpringBoot,用的是Spring框架进行web程序的开发,此时我们就需要引入web程序开发所需要的一些依赖。

  • spring-webmvc依赖:这是Spring框架进行web程序开发所需要的依赖
  • servlet-api依赖:Servlet基础依赖
  • jackson-databind依赖:JSON处理工具包

如果要使用AOP,还需要引入aop依赖、aspect依赖

项目中所引入的这些依赖,还需要保证版本匹配,否则就可能会出现版本冲突问题。

使用了SpringBoot,就不需要像上面这么繁琐的引入依赖了。由于Maven的依赖传递。我们只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web。

在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。

2.2 SpringBoot自动装配

话不多说,上源码跟踪图!!

2.3 @Conditional

我们在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。

以Conditional开头的这些注解都是条件装配的注解。下面我们就来介绍下条件装配注解。

@Conditional注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
  • 位置:方法、类

@Conditional本身是一个父注解,派生出大量的子注解:

  • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

下面我们通过代码来演示下Conditional注解的使用:

  • @ConditionalOnClass注解
@Configuration
public class HeaderConfig {
​
    @Bean
    @ConditionalOnClass(name="io.jsonwebtoken.Jwts")//环境中存在指定的这个类,才会将该bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
    
}
  • 测试类
@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    private ApplicationContext applicationContext;
​
    @Test
    public void testHeaderParser(){
        System.out.println(applicationContext.getBean(HeaderParser.class));
    }
}

执行testHeaderParser()测试方法:

因为io.jsonwebtoken.Jwts字节码文件在启动SpringBoot程序时已存在,所以创建HeaderParser对象并注册到IOC容器中。

  • @ConditionalOnMissingBean注解
@Configuration
public class HeaderConfig {
​
    @Bean
    @ConditionalOnMissingBean //不存在该类型的bean,才会将该bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
    
}

执行testHeaderParser()测试方法:

SpringBoot在调用@Bean标识的headerParser()前,IOC容器中是没有HeaderParser类型的bean,所以HeaderParser对象正常创建,并注册到IOC容器中。

再次修改@ConditionalOnMissingBean注解:

@Configuration
public class HeaderConfig {
​
    @Bean
    @ConditionalOnMissingBean(name="deptController2")//不存在指定名称的bean,才会将该bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
}

执行testHeaderParser()测试方法:

因为在SpringBoot环境中不存在名字叫deptController2的bean对象,所以创建HeaderParser对象并注册到IOC容器中。

再次修改@ConditionalOnMissingBean注解:

@Configuration
public class HeaderConfig {
​
    @Bean
    @ConditionalOnMissingBean(HeaderConfig.class)//不存在指定类型的bean,才会将bean加入IOC容器
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
}
@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    private ApplicationContext applicationContext;
​
    @Test
    public void testHeaderParser(){
        System.out.println(applicationContext.getBean(HeaderParser.class));
    }
}

执行testHeaderParser()测试方法:

因为HeaderConfig类中添加@Configuration注解,而@Configuration注解中包含了@Component,所以SpringBoot启动时会创建HeaderConfig类对象,并注册到IOC容器中。

当IOC容器中有HeaderConfig类型的bean存在时,不会把创建HeaderParser对象注册到IOC容器中。而IOC容器中没有HeaderParser类型的对象时,通过getBean(HeaderParser.class)方法获取bean对象时,引发异常:NoSuchBeanDefinitionException

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 解决Sentinel链路模式规则无效问题

    解决Sentinel链路模式规则无效问题

    本文介绍了如何在Spring Cloud Alibaba项目中使用Sentinel链路流控规则,并解决规则不生效的问题,通过关闭Sentinel过滤器,可以避免重复统计请求
    2025-01-01
  • SpringBoot实现优雅停机的流程步骤

    SpringBoot实现优雅停机的流程步骤

    优雅停机(Graceful Shutdown) 是指在服务器需要关闭或重启时,能够先处理完当前正在进行的请求,然后再停止服务的操作,本文给大家介绍了SpringBoot实现优雅停机的流程步骤,需要的朋友可以参考下
    2024-03-03
  • SpringBoot3安全管理操作方法

    SpringBoot3安全管理操作方法

    这篇文章主要介绍了SpringBoot3安全管理,在实际开发中,最常用的是登录验证和权限体系两大功能,在登录时完成身份的验证,加载相关信息和角色权限,在访问其他系统资源时,进行权限的验证,保护系统的安全,文中有详细的操作步骤,需要的朋友可以参考下
    2023-08-08
  • Java中的Random()函数及两种构造方法

    Java中的Random()函数及两种构造方法

    Java中存在着两种Random函数分别是java.lang.Math.Random和java.util.Random,文中给大家介绍了random()的两种构造方法,感兴趣的朋友跟随小编一起看看吧
    2018-11-11
  • SpringBoot项目启动后立马自动关闭的解决方案

    SpringBoot项目启动后立马自动关闭的解决方案

    这篇文章主要介绍了SpringBoot项目启动后立马自动关闭的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • java使用FileVisitor遍历文件和目录

    java使用FileVisitor遍历文件和目录

    这篇文章主要为大家详细介绍了java使用FileVisitor遍历文件和目录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • 基于Redis分布式锁Redisson及SpringBoot集成Redisson

    基于Redis分布式锁Redisson及SpringBoot集成Redisson

    这篇文章主要介绍了基于Redis分布式锁Redisson及SpringBoot集成Redisson,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小小伙伴可以参考一下
    2022-09-09
  • SrpingDruid数据源加密数据库密码的示例代码

    SrpingDruid数据源加密数据库密码的示例代码

    本篇文章主要介绍了SrpingDruid数据源加密数据库密码的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • springboot-mybatis/JPA流式查询的多种实现方式

    springboot-mybatis/JPA流式查询的多种实现方式

    这篇文章主要介绍了springboot-mybatis/JPA流式查询,本文给大家分享三种方式,每种方式结合示例代码给大家讲解的非常详细,需要的朋友可以参考下
    2022-12-12
  • java动态代理(jdk与cglib)详细解析

    java动态代理(jdk与cglib)详细解析

    静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
    2013-09-09

最新评论