如何在Spring Boot中注入自己的组件

@Configuration、@Bean

  • 在之前使用Spring 的时候,我们想要注入一个组件,需要先在resource目录下创建一个xml文件,例如我们想注入两个Bean对象,按照之前的方式:
public class User {

    private String name;
    private Integer age;
    
    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }

    // 省略get、set方法
}

public class Pet {

    private String name;
    public Pet(String name) {
        this.name = name;
    }
    // 省略get、set方法
}

<bean id="haha" class="com.atguigu.boot.bean.User">
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
</bean>

<bean id="hehe" class="com.atguigu.boot.bean.Pet">
    <property name="name" value="tomcat"></property>
</bean>
  • 而在Spring Boot中,只需要两个注解就可以完成。
@Configuration // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    @Bean // 给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        return zhangsan;
    }

    @Bean("tom") // 如果指定了value,则组件在容器中的实例为指定的值
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}
  • 测试:
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);

        System.out.println("组件:"+(tom01 == tom02));


        //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);
    }
}

image.pngimage.png

可以看到Myconfig、user01、tom都已经注入到容器中了。这就是Spring Boot中注入组件的方式。

  • 值得注意的是:在Spring 5.2.0的版本中,@Configuration 注解添加了一个proxyBeanMethods 的属性,它有两个值,true、false分别对应Full模式和Lite模式
    • Full模式:保证每个@Bean方法被调用多少次返回的组件都是单实例的
    • Lifte模式:每个@Bean方法被调用多少次返回的组件都是新创建的
  • 最佳实践:
    • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

@Component、@Controller、@Service、@Repository

  • 上述几个注解就不行过多解释了
    • @Component:普通组件
    • @Service:业务逻辑层
    • @Controller:web层
    • @Repository:持久层

上面的四个注解功能是一样的,都可以用来创建bean实例。

@ComponentScan @Import

  • @ComponentScan 上面讲过,用来配置包扫描路径,@SpringBootApplication注解中就包含了它,默认是启动类所在的包路径,如本示例项目中"com.atguigu.boot"
  • @Import 引入其它组件,是一个类的集合,可以引入多个组件
@Import({User.class, DBHelper.class}) // 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
@Configuration // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
    // 省略之前的代码
}
  • 验证代码
@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        // 省略上面的代码
	//5、获取组件
	String[] beanNamesForType = run.getBeanNamesForType(User.class);
        System.out.println("======5、获取组件======");
        for (String s : beanNamesForType) {
            System.out.println(s);
        }

        DBHelper bean1 = run.getBean(DBHelper.class);
        System.out.println(bean1);
    }
}

image.png

如图,@Import引入了声明的组件。

@Conditional

  • 条件装配:满足Conditional指定的条件,则进行组件注入
  • 使用ctrl+N 查找到这个注解,ctrl+H查看继承树
  • image.png
  • 可以看到它下面衍生出了许多的注解,如
    • @ConditionalOnBean:在含有某个组件时才进行
    • @ConditionalOnClass:含有某个类时才进行
    • @ConditionalOnJava:当是某个Java版本时才进行
    • @ConditionalOnResource:当含有某个资源文件时才进行
    • ......
  • 我们选择其中的@ConditionalOnBean看看效果
  • 修改上面的MyConfig类
@Configuration // 告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    @ConditionalOnBean(name = "tom")
    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        return zhangsan;
    }

    // @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}
  • 测试
@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        // 删除或注释上面的代码
	// 6.@Conditional注解验证
        System.out.println("======6、@Conditional注解验证======");
        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:" + tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:" + user01);
    }
}

image.png

可以看到,当配置了@ConditionalOnBean(name = "tom")时,user组件也没有被注入到容器中,因为加了这个注解,意味这只有容器中存在tom 组件时user01 的@Bean才会生效。将@ConditionalOnBean(name = "tom") 修改为@ConditionalOnMissingBean(name = "tom") user01即会被注入到容器中。
image.png

  • 当@@Conditional 类注解作用于方法上,只会影响到该方法的注册的组件是否成功,作用于类上面时,将会影响这个类下面所有的组件注入,这个注解及其子注解会在Spring Boot 源码中频繁使用。

@ImportResource

当改造一个老项目或者整合第三方项目时,之前已经存在了许多xml 的配置文件,不想一个个整合成对应的类,那么就可以使用@ImportResource 注解

  • 导入资源注解:可以导入配置文件

  • 作用:

    • 在自行配置类之前,先会解析指定的配置文件资源
    • 配置文件xml可以 以xml的形式配置bean对象,放到IOC中
  • 测试

// bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
// 启动类
@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        // 删除或注释上面的代码
	// 6.@Conditional注解验证
        boolean haha = run.containsBean("haha");
    	boolean hehe = run.containsBean("hehe");
    	System.out.println("haha:"+haha);
    	System.out.println("hehe:"+hehe);
    }
}

输出:
image.png

可以看到容器中没有这两个组件

  • 在MyConfig类上添加@ImportResource 注解
@Configuration // 告诉SpringBoot这是一个配置类 == 配置文件
@ImportResource("classpath:beans.xml") // 引入配置文件
public class MyConfig {

    @ConditionalOnBean(name = "tom")
    @Bean
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        return zhangsan;
    }

    // @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

再次执行:
image.png

可以看到容器中这两个组件成功注册到IOC容器中了。

Spring Boot配置绑定

使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

Java原生方法

配置文件内容,想要把mycar的信息加载进去

server.port=8888

mycar.brand=YD
mycar.price=100000
//使用java原生代码:(比较麻烦)
public class getProperties {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        Properties pps = new Properties();
	// 注意此处路径,因为该项目时聚合项目,所以需要加上子模块的名称
        pps.load(new FileInputStream("boot-01-helloworld/src/main/resources/application.properties"));
        Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
        while(enum1.hasMoreElements()) {
            String strKey = (String) enum1.nextElement();
            String strValue = pps.getProperty(strKey);
            System.out.println(strKey + "=" + strValue);
            //封装到JavaBean。
        }
    }
}
输出:
mycar.price=100000
server.port=8888
debug=true
spring.banner.image.location=classpath:222.jpg
mycar.brand=YD
spring.servlet.multipart.max-file-size=10MB

@ConfigurationProperties

  • 作用:绑定properties配置文件(主配置文件application.properties)中的内容,到javabean对象中
  • 位置:配置类上的属性绑定
  • 注意:一般会把该类放到容器中,同时使用该注解bean配置参数,该配置文件也必须注册到容器中,否则无法生效
  • 使用
    • 在类上加上@ConfigurationProperties(prefix = "mycar")进行参数绑定
    • @Component注解把该类放到容器中
@Data
@Component
@ConfigurationProperties("mycar") // 效果与@ConfigurationProperties(prefix = "mycar")相同
public class Car {

    private String brand;
    private Integer price;
}

// 启动类
@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        // 删除或注释上面的代码
	Car bean = run.getBean("car",Car.class);
        System.out.println(bean);
    }
}
输出:
Car(brand=YD, price=100000)
  • 该注解也可用于配置文件的方法上,进行组件配置的绑定。
//配置类
@Configuration
public class MyDataSourceConfig {
   @ConfigurationProperties("spring.datasource")//把配置文件的信息,绑定到返回的组件上
   @Bean
   public DataSource dataSource() {
      DruidDataSource druidDataSource = new DruidDataSource();
      //数据源的设置(这种方式写死了,最好使用配置文件的自动注入进来)
//    druidDataSource.setUrl("jdbc:mysql://localhost:3306/zhangyang?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
//    druidDataSource.setUsername("root");
//    druidDataSource.setPassword("root");
//    druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
      return druidDataSource;
   }
}

@EnableConfigurationProperties

  • 配置类上使用@EnableConfigurationProperties 开启某个类属性配置功能,并且把该类自动注册到容器中
  • 这时候如果该类上有@ConfigurationProperties注解,会进行自动的配置
    效果同上面的相同,不再进行演示。

@Value

如果配置文件中属性比较单一,而且只在一个地方使用,那么可以使用Spring中的@Value注解,绑定Bean类的属性值

@RestController
public class HelloController {

    @Value("${mycar.brand}")
    private String carBrand;

    @GetMapping("/car")
    public String car(){
        System.out.println("carBrand:" + carBrand);
        return carBrand;
    }
}

调用接口,输出:
image.png

总结

这篇博客真是写了好久,写得太冗余了,下次得精简一下。
文笔和叙述能力还有待提高。

声明

这篇博客基于B站尚硅谷的视频:雷丰阳2021版SpringBoot2零基础入门springboot全套完整版(spring boot2)编写。
在此先表示感谢。
如有侵权,请联系我删除。

Q.E.D.


空有烟霞之志,叹无水云之身。