SpringBoot--Bean管理详解

发布于:2025-05-16 ⋅ 阅读:(22) ⋅ 点赞:(0)

Bean管理

Bean扫描

回顾spring:

在XML配置文件中,可以借助

 <context:component-scan base-package = "com.lyc">

或者注解

 @ComponentScan(basePackages="com.lyc")

再springboot项目中,既没有标签,也没有写注解,但他仍然可以扫描到我们写的业务代码。这是为什么呢?

原因如下:

在springboot启动类中的@springBootApplication是一个组合注解。

 @SpringBootConfiguration
 @EnableAutoConfiguration
 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 public @interface SpringBootApplication {

其中就组合了@ComponentScan注解,因此可以扫描Bean对象。

注意事项:这相当于在启动类上添加了@ComponentScan注解,所以默认只能扫描启动类所在的包及其子包,而其他包扫描不到。如果想扫描其他包,需要手动添加@ComponentScan注解。

Bean注册

回顾spring:

在spring项目中,我们可以添加以下注解来进行Bean的注册

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时,用此注解
@Service @Component的衍生注解 标注在业务类上
@Controller @Component的衍生注解 标注在控制类上
@Repository @Component的衍生注解 标注在数据访问类上(由于与MyBatis整合,用得少)

问题:在spring项目中,如果将三方jar包中的Bean对象注入到IOC容器中,这些注解还能使用吗?

解答:不能,在spring项目中这些jar包被设为只读,不能修改,因此:

如果要注册的Bean对象来自第三方(不是自定义),是无法使用@Component及衍生注解的声明Bean的

如何解决?

spring为我们提供了两个注解解决问题:

  • @Bean

  • @Import

前置知识:

如何将第三方jar包通过Maven坐标引入?

先使用maven命令将第三方jar包安装在本地仓库

mvn install:install-file 	-Dfile=jar包所在本地磁盘的路径
							-DgroupId=组织名称
                            -DartifactId=项目名称
                            -Dversion=版本号
                            -Dpackaging=打包方式

再在pom依赖中导入坐标即可。

<dependency>
    <groupId>组织名称</groupId>
    <artifactId>项目名称</artifactId>
    <version>版本号</version>
</dependency>
@Bean的使用

(@Bean只写在方法上,返回的是一个对象,但一般不获取已经在容器中的对象)

使用@Bean注解将第三方的Bean对象注入到IOC容器中,可以在启动类里面去声明一个方法,这个方法里去创建一个对象

再在这个方法上声明一个@Bean注解(将方法返回值交给IOC容器管理,成为IOC容器的Bean对象)。

public class Myconfig {
    @Bean("User")//相当于XML中的<bean id="user" class="com.lyc.pojo.User"/>
    //这个方法的名字,就相当于bean标签中的id值
    //这个方法的返回值,就相当于bean标签中的class属性的值
    public User getUser(){
        return new User();//返回要注入到bean的对象
    }
}

如何证明注入IOC容器成功呢? 将对应类型的对象拿出来就好了。

在springboot项目中的启动方法(run()方法),run()方法会将spring初始化好的容器返回。通过容器来调用getBean方法拿到对应类想的Bean对象。

ConfigurableApplicationContext context = SpringApplication.run(SpringbootMybatisApplication.class, args);
User user = context.getBean(User.class);
System.out.plintln(user)

但是这种方式并不推荐使用。因为在Java代码中类的设计遵循一个单一职责原则,一个类只负责一个功能,启动类就应该只负责启动,不应该再加一个注册的功能。

如果说要配置一个第三方的Bean,建议在配置类中集中注册。就比如:在新增一个config包,里面专门来写配置类。

@Configuration//这个也被spring容器托管,注册到容器中,因为它本身也是一个@Component,表明这是一个配置类,相当于XML当中的 <beans>
@ComponentScan("com.lyc.pojo")//@ComponmentScan是扫描主程序同级的包,所以分层包放在其他地方,就会发生错误
@Import(MyConfig2.class)
public class Myconfig {
    @Bean("User")//相当于XML中的<bean id="user" class="com.lyc.pojo.User"/>
    //这个方法的名字,就相当于bean标签中的id值
    //这个方法的返回值,就相当于bean标签中的class属性的值
    public User getUser(){
        return new User();//返回要注入到bean的对象
    }
}

@Configuration

  • 作用@Configuration 注解用于定义一个配置类,表示该类中有一个或多个 @Bean 方法。@Configuration 是一个特化版的 @Component,它会告诉 Spring 该类是一个配置类,能够包含 Bean 定义方法,并且支持代理机制。

  • 使用场景:用于配置类的定义,通常与 @Bean 一起使用,用来显式声明 Bean。在 Spring Boot 项目中,它常用于设置应用的配置,或者在传统的 Spring 项目中配置一些自定义 Bean。

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

@Component 不同,@Configuration 表明这是一个配置类,其中可以定义多个 @Bean 方法,而这些 Bean 会以一种更为严格和优化的方式加载到 Spring 容器中(通过代理机制)。

注意事项:

  • @Bean只能声明方法。

  • @Bean("名称")//相当于XML中的< bean id="名称" class="对象实体类路径"/ >

  • 这个方法的名字,就相当于bean标签中的id值

  • 这个方法的返回对象,就相当于bean标签中的class属性的值。

  • 如果方法的内部需要使用IOC容器中的Bean对象,只需要在方法上声明即可,spring会自动注入

@Import的使用

常用方式

  • 导入配置类(手动扫描)

  • 导入ImportSelector 接口实现类

  • @EnableXxxx注解,用于封装@Import注解

举例说明:

当我们的配置类不在启动类的包名下时,启动类是无法扫描到我们的配置类的,我们可以在启动类上添加@Import(配置类的字节码文件),这样就spring就会把配置类文件中对应的Bean对象注入到IOC容器中。

但我们需要导入的配置类过多时,我们可以使用数组的形式,来注入配置类文件,@Import为我们提供了方法。

package org.springframework.context.annotation;
 ​
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 ​
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Import {
     Class<?>[] value();
 } //以数组的方式进行注入

但当我们的配置类文件过多时,以数组形式注入,也会显得代码冗余,如此便用到了我们的第二个方式:导入ImportSelector 接口实现类

  • 首先定义一个类去实现ImportSelector接口。

  • 重写selectImports方法,在方法中只需要返回一个字符串的数组。而数组中里面的每一个字符串就是要注入到IOC容器中的Bean对象的全类名。

注意事项:springboot会自动调用selectImports方法得到里面数组的内容,然后将这些类的Bean对象自动注入到IOC容器中

代码展示:

public class CommonImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata){
    List<String> imports = new ArrayList<>();
	return new String[]{"com.lyc.config.CommonConfig"};
}
}

这时只需要在启动类上声明@Import(CommonImportSelector.class)即可。

注意事项:在实际开发中,数组的内容一般不会写死,,基本上都是从配置文件中读取出来的。这样就降低了代码的耦合性,是代码之间更加灵活,程序员只需要将全类名,写入到配置文件中即可。

代码展示:

common.imports

# 一行写一个全类名即可
com.lyc.config.CommonConfig

CommonImportSelector.java

 public class CommonImportSelector implements ImportSelector{
 @Override
 public String[] selectImports(AnnotationMetadata importingClassMetadata){
     //读取配置文件内容
     List<String> imports = new ArrayList<>(); //初始化数组
    Inputstream is =  CommonImportSelector.class.getClassLoader().getResourceAsStream("common.imports");
    //通过反射拿到class对象在调用方法将配置文件转换为字节流,字节流不适合文本文件的读取,需要将其转换为字符流
     BufferedReader br = new BufferedReader(new InputStreamReader(is));//将字节流通过转换流装换成字符流,在使用字符缓冲流提高读写性能,
     String line = null; // 定义String类型的变量
     try{
         while((line = br.readLine())!=null){ //逐行读取数据,只要数据不为空,则放在imports数组中
             imports.add(line);
         }
     }catch(Exception e){
         throw new RuntimeException(e);
     }finally{
         if(br != null){
             try{
                br.close();//关闭流
             }catch (Exception e){
         throw new RuntimeException(e);
             }
         }
     }
     return imports.toArray(new String[0]);//new String[0] 只起到一个提供类型的作用
 }
 }

如何在进一步,让代码显得更简洁呢?

在启动类中定义一个组合注解,将@Import注解组合一下,在启动类中添加组合注解即可

 package com.lyc.springbootmybatis.annotation;
 ​
 import org.springframework.context.annotation.Import;
 ​
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 ​
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Import(CommonImportSelector.class)
 public @interface EnableCommonConfig {
     
 }

然后再在启动类上声明@EnableCommonConfig即可,这样的优点是提高了代码的安全性,缺点就是别人不好维护。

在springboot中的源码就有很多这样的EnableXxxx注解,说明在实际开发中这种方法被大众所接受

注册条件

注入到IOC容器中的Bean对象如何插入数据?

  • 可以在新建对象时为对象赋值,之后再进行注入。

这种方法是可以插入数据,但是又是和Java代码高度耦合,修改代码是极其繁琐,因此不建议这样做。

  • 也可以将对象属性值写入配置文件,再通过@Value导入配置文件,实现与Java代码的解耦。

代码展示:

application.yml

 User: 
     name: nihao
     age: 19

 public class Myconfig {
     @Bean("User")
     public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){
        User user = new User();
         user.setName(name);
         user.setAge(age);
         return new User();//返回要注入到bean的对象
     }
 }

当我们将配置文件注释之后,再次运行,结果发现,程序报错,和我们的设想不一致,我们设想,有配置文件则选择注入,没有配置文件,就不去注入即可,那么如何去实现?

解析:

springboot提供了设置注册生效条件的注解@Conditional

可以借助这个注解来设置并注册条件,该注解使用较为繁琐,因此springboot官方提供了不少衍生注解来使用,

常用衍生注解:

注解 说明
@ConditionalOnProperty() 配置而文件中存在对应的属性时,该注解声明下的Bean才会被注册
@ConditionalOnMissingBean() 当不存在当前类型的Bean时,该注解声明下的Bean才会被注册
@ConditionalOnClass() 当前环境存在指定的类时,该注解声明下的Bean才会被注册

这个注解如何解决我们刚才的问题呢?

我们的要求:如果配置文件中配置了指定的信息,则注入,否则不注入。

代码展示:

 public class Myconfig {
     @ConditionalOnProperty(prefix = "User",name = {"name,age"})
     @Bean("User")
     public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){
        User user = new User();
         user.setName(name);
         user.setAge(age);
         return new User();//返回要注入到bean的对象
     }
 }

测试结果:当将配置文件注释之后,User对象并没有被注入到IOC容器中

当配置文件没有被注释时,User Bean对象被注入到IOC容器中

需求:如果IOC容器不存在User,则注入Employee对象,否则不注入

 public class Myconfig {
     @ConditionalOnProperty(prefix = "User",name = {"name,age"})
     @Bean("User")
     public User getUser(@Value("${User.name}") String name,@Value("${User.age}") int age){
        User user = new User();
         user.setName(name);
         user.setAge(age);
         return new User();//返回要注入到bean的对象
     }
     @Bean("employee")
     @ConditionalOnMissingBean(User.class)
     public Employee getEmployee(){
         return new Employee();
     }
 }

测试结果:当将配置文件注释之后, @ConditionalOnProperty(prefix = "User",name = {"name,age"})注解检测不到属性,则不将User对象注入到IOC容器中,又 @ConditionalOnMissingBean(User.class) 注解检测到User对象在IOC容器中不存在,则将employee对象注入IOC容器。

当配置文件没有被注释时,@ConditionalOnProperty(prefix = "User",name = {"name,age"})注解检测到属性,则将User对象注入到IOC容器中,又 @ConditionalOnMissingBean(User.class) 注解检测到User对象存在与IOC容器,则不将employee对象注入IOC容器。

案例需求:

如果当前环境中存在DispatcherServlet类,则注入Employee,否则不注入。(如果项目中引入了web依赖,则环境中存在DispatcherServlet,否则没有)

image-20250514204153729

 @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
      public Employee getEmployee(){
         return new Employee();
     }

当存在DispatcherServlet 类时,则将Employee对象注入到IOC容器中。如果不存在,则不注入。

以上就是Bean管理的详细内容,希望对大家有所帮助!


网站公告

今日签到

点亮在社区的每一天
去签到