【springboot】springboot 注解之配置类添加组件


前言

在之前使用 spring 的时候,想要添加对象到 IOC 容器有两种方式:

  1. 需要写一个 xml 文件,文件里面配置好 bean 标签。
  2. 使用注解的方式。

而 springboot 沿用了 spring 注解的方式添加 bean。接下来我们了解下springboot如何在 IOC 中添加一个对象。


一、springboot 的配置类

在使用 spring 的时候,有需要配置的对象通常是使用 xml 方式进行配置。而到了 springboot 中默认使用配置类的方式进行对象的配置或与其他框架的整合。
配置类使用 @Configuration 注解进行标注。反过来说 使用 @Configuration 标注的类,在 springboot 中被认为配置类。
配置类也会以组件 bean 的方式保存到 IOC 容器中。

// @Configuration 注解表示该类是一个配置类,相当于 spring 中的配置文件。
// 配置类本身也是一个 bean 组件。
@Configuration
public class BeanConfig {

}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 看配置类是否是容器中的 bean
        BeanConfig config = run.getBean(BeanConfig.class);
        System.out.println("BeanConfig:" + config);
    }
}

结果如下:

BeanConfig:com.study.springbootfunction.config.BeanConfig$$EnhancerBySpringCGLIB$$235caeaa@4a7fd0c9

二、@Bean 注解

1.使用 @Bean 注解

配置类的方法上标注 @Bean 注解表示给 IOC 容器中添加一个 bean。
bean id 为方法名;返回类型为 bean 的类型;返回的值就是容器中的对象实例。

// 添加一个测试用的实体对象
@Data
@Accessors(chain = true)
@ToString
public class User implements Serializable {

    private String name;

    private String password;

    private Integer age;

    private String gender;

    private String phone;

    private LocalDate birthday;
}

// 配置类
@Configuration
public class BeanConfig {
	// 给 IOC 容器中添加一个 id 为 user01 的 bean 对象
    @Bean
    public User user01() {
        return new User()
                .setName("张三")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 从容器中获取组件
        User user01 = run.getBean("user01", User.class);
        System.out.println("user01:" + user01);
    }
}

结果如下:

user01:User(name=张三, password=123456, age=18, gender=男, phone=18812341234, birthday=2005-03-22)

2.@Bean 的属性

@Bean 注解中有一个 name 属性,如果有值的话表示该 bean 的 id。

@Configuration
public class BeanConfig {
	// 给 IOC 容器中添加一个 id 为 user02 的 bean 对象
    @Bean(name = "user02")
    public User user() {
        return new User()
                .setName("李四")
                .setPassword("123456")
                .setAge(18)
                .setGender("女")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 从容器中获取组件
        User user02 = run.getBean("user02", User.class);
        System.out.println("user02:" + user02);
    }
}

结果如下:

user02:User(name=李四, password=123456, age=18, gender=女, phone=18812341234, birthday=2005-03-22, car=null)

3.@Bean 的单例

被 @Bean 注解添加的 bean 都是单实例的。

@Configuration
public class BeanConfig {
    @Bean
    public User user01() {
        return new User()
                .setName("张三")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 从容器中获取组件
        User user01 = run.getBean("user01", User.class);
        User user01New = run.getBean("user01", User.class);
        System.out.println(MessageFormat.format("从容器获取的 bean 是否是单实例:{0}", user01 == user01New));
    }
}

结果如下:

从容器获取的 bean 是否是单实例:true

三、@Configuration 属性

@Configuration 注解中的 proxyBeanMethods 属性默认为 true。如果为 true,使用 @Bean 标注的方法无论调用多少次都返回同一个对象。
我们先看 proxyBeanMethods 为 false 的情况

// proxyBeanMethods 属性为 false
@Configuration(proxyBeanMethods = false)
public class BeanConfig {
    @Bean
    public User user01() {
        return new User()
                .setName("张三")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 5、看配置类被 @Bean 标注的方法多次调用返回是否是同一个对象
        BeanConfig config = run.getBean(BeanConfig.class);
        System.out.println("BeanConfig:" + config);
        System.out.println(MessageFormat.format("配置类被 @bean 标注的方法多次调用返回是否是同一个对象:{0}", config.user() == config.user()));
    }
}

结果如下:

BeanConfig:com.study.springbootfunction.config.BeanConfig@6d5c2745
配置类被 @bean 标注的方法多次调用返回是否是同一个对象:false

从这里可以看出 proxyBeanMethods 属性为 false 的时候,每次调用被 @Bean 标注的方法都会生成一个不同的对象。
接下来我们看下 proxyBeanMethods 属性为 true 的时候。

// proxyBeanMethods 属性默认为 true,不写也可以
@Configuration(proxyBeanMethods = true)
public class BeanConfig {
    @Bean
    public User user01() {
        return new User()
                .setName("张三")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 5、看配置类被 @Bean 标注的方法多次调用返回是否是同一个对象
        BeanConfig config = run.getBean(BeanConfig.class);
        System.out.println("BeanConfig:" + config);
        System.out.println(MessageFormat.format("配置类被 @bean 标注的方法多次调用返回是否是同一个对象:{0}", config.user() == config.user()));
    }
}

结果如下:

BeanConfig:com.study.springbootfunction.config.BeanConfig$$EnhancerBySpringCGLIB$$858dbcbb@7a360554
配置类被 @bean 标注的方法多次调用返回是否是同一个对象:true

通过结果我们可以看到当 proxyBeanMethods 值为 true 时,我们配置类对象调用多少次被 @Bean 标注的方法返回的对象都是一个对象。至于原因我们也可以通过返回的结果第一行看到从 IOC 容器获取的 配置类对象是一个被 CGLIB 代理的对象。
当 proxyBeanMethods 值为 true 时,配置类对象调用被 @Bean 标注的方法返回的对象是同一个对象。那么配置类内部的方法,调用也是返回同一个对象吗?我们看一下。

// proxyBeanMethods 属性默认为 true,可以不写
@Configuration
public class BeanConfig {
    @Bean
    public User user01() {
        return new User()
                .setName("张三")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18));
    }
	// user03 方法调用了 user01 方法,看 bean user01 与 user03 是否是一个对象
    @Bean
    public User user03() {
        return user01();
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象
        System.out.println(MessageFormat.format("配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象:{0}",
                run.getBean("user03", User.class) == run.getBean("user01", User.class)));
    }
}

结果如下:

配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象:true

通过结果可以看到哪怕是配置类内部方法调用,依然会返回用一个对象。那么这个功能有什么用呢?主要是为了 bean 的依赖注入的时候,可以保证属性对象与 IOC 容器中的对象是同一个。

// 添加一个新的对象实例
@Data
@ToString
@Accessors(chain = true)
public class Car {
    private String name;
}
// 将 Car 作为属性加入类中
@Data
@Accessors(chain = true)
@ToString
public class User implements Serializable {

    private String name;

    private String password;

    private Integer age;

    private String gender;

    private String phone;

    private LocalDate birthday;

    private Car car;
}

@Configuration
public class BeanConfig {
	// 容器中添加一个 car 的 bean
    @Bean
    public Car car() {
        return new Car().setName("奔奔");
    }
    // 将 car 注入到 user04 中
    @Bean
    public User user04() {
        return new User()
                .setName("王五")
                .setPassword("123456")
                .setAge(18)
                .setGender("男")
                .setPhone("18812341234")
                .setBirthday(LocalDate.now().minusYears(18))
                .setCar(car());
    }
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象
        System.out.println(MessageFormat.format("配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象:{0}",
                run.getBean("user04", User.class).getCar() == run.getBean(Car.class)));
    }

}

结果如下:

配置类中内部方法调用 @Bean 标注的方法返回是否是同一个对象:true

了解了 @Configuration 的 proxyBeanMethods 属性之后,我们该如何使用呢?

  1. Full mode(proxyBeanMethods = true) 配置类组件之间如果有依赖关系,方法会调用容器查询之前的单实例组件,用 Full 模式。
  2. Lite mode(proxyBeanMethods = false) 配置类组件之间没有依赖关系,使用 Lite 模式可以减少判断,加速容器启动过程。

三、使用 spring 常用的注解

spring IOC 容器添加 bean 使用注解的方式的时候,我们常用到 @Controller、@Service、@Component、@Repository 等等之类的注解。

1.启动类上的 @SpringBootApplication 注解

我们在使用 spring 与 springMVC 框架的时候,使用注解方式添加组件的时候需要在 xml 中配置 <context:component-scan></context:component-scan> 标签。
在 springboot 中该标签被 @ComponentScan 注解替代。该注解中的 basePackages 的属性可以配置包扫描的路径。
但是我们在搭建一个最简单的 springboot 项目的时候并没有看到需要哪里需要添加配置 @ComponentScan 注解。
这时候我们就要了解 springboot 启动类上 @SpringBootApplication 这个最基本的注解。我们看下该注解的源码。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}

可以看到该注解上面除了 @Target、@Retention、@Target、@Documented 这四个元注解还有 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解。
@SpringBootConfiguration 标注了该类是一个配置类;@EnableAutoConfiguration 引入了 springboot 特有的 ImportSelector;这里不做过多的解释,主要是 @ComponentScan 注解,说明启动类上标注的 @SpringBootApplication 注解已经具有了包扫描 @Controller 等常用注解添加到 IOC 容器的功能。
那么扫描路径是什么呢?springboot 中的默认路径是启动类的同级别及其子包下的所有类。如:
在这里插入图片描述

2.@SpringBootApplication 注解属性

如果我们的项目是如下情况该怎么解决。
在这里插入图片描述

@Component
public class Entity {
}

项目中我们希望将 exclude 包中的 Entity 对象也添加到 IOC 容器中,该如何操作呢?
这时候我们就需要用到 @SpringBootConfiguration 注解中的 scanBasePackages 属性。

@SpringBootApplication(scanBasePackages = {"com.study"})
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 查看 Entity 是否添加到 IOC 容器中
        System.out.println(MessageFormat.format("Entity 是否添加到 IOC 容器中:{0}", run.containsBean("entity")));
    }
}

返回结果如下:

Entity 是否添加到 IOC 容器中:true

从这里我们看到 scanBasePackages 属性可以配置包扫描的基本路径,我们也可以通过该属性配置自己想要的扫描路径。scanBasePackages 属性的值类型是数组,我们可以添加多个配置路径所以我们也可以写成这样。

@SpringBootApplication(scanBasePackages = {"com.study.springbootfunction", "com.study.exclude"})
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 查看 Entity 是否添加到 IOC 容器中
        System.out.println(MessageFormat.format("Entity 是否添加到 IOC 容器中:{0}", run.containsBean("entity")));
    }
}

四、使用 @Import 添加组件

springboot 中的 @Import 注解使用方法与 spring 中一样。只要添加到添加到容器的类上就可以。

// 添加一个测试 @Import 对象
@Data
@ToString
@Accessors(chain = true)
public class ImportTest {
    private String property;
}
// @Import 注解 添加 ImportTest、User 两个类
@Import({ImportTest.class, User.class})
@Configuration(proxyBeanMethods = true)
public class BeanConfig {
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 查看 @Import 注解添加的 bean
        System.out.println(MessageFormat.format("查看 @Import 注解添加的 bean:{0}", run.getBean(ImportTest.class)));
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        System.out.println(MessageFormat.format("查看 user 类型的 bean 的名称:{0}", Arrays.toString(beanNamesForType)));
    }
}

结果如下:

查看 @Import 注解添加的 bean:ImportTest(property=null)
查看 user 类型的 bean 的名称:[com.study.springbootfunction.entity.User]

从结果也可以看出 @Import 也可以往 IOC 容器中添加组件。
@Import 与 @Bean 的区别是

  1. @Bean 一次只能添加一个 bean,@Import 可以批量添加组件更加方便一些
  2. @Import 添加的组件 bean 的属性为 null,@Bean 初始化时可以往属性依赖注入默认值
  3. @Import 的 bean id 是 对象的全量类名且不能修改,@Bean 可以自己定义 bean id

五、使用 @ImportResource 导入 spring 的配置文件

我们也可以导入 spring 的一些 xml 文件添加 bean 组件。

// 添加一个测试对象
public class ImportResourceTest {
}

在这里插入图片描述
添加 ApplicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 在 ApplicationContext.xml 文件中添加 bean importResourceTest -->
    <bean id="importResourceTest" class="com.study.springbootfunction.entity.ImportResourceTest" scope="singleton"/>

</beans>
// 在随便一个 bean 组件对象类上添加 @ImportResource 注解并添加 xml 文件的路径
@ImportResource("classpath:xml/**/*.xml")
@Configuration
public class BeanConfig {
}

测试:

@SpringBootApplication
public class SpringbootFunctionApplication {

    public static void main(String[] args) {
        // 返回 IOC 容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootFunctionApplication.class, args);
        // 查看 ImportResourceTest 是否添加到 IOC 容器中
        System.out.println(MessageFormat.format("ImportResourceTest 是否添加到 IOC 容器中:{0}", run.containsBean("importResourceTest")));
    }
}

结果如下:

ImportResourceTest 是否添加到 IOC 容器中:true

总结

本篇文章中一共说了五种 springboot 添加 bean 组件的方式:

  1. @Configuration 注解可以将配置类以 bean 的形式添加到 IOC 中
  2. @Bean 注解添加需要普通的对象,@Bean 只能用在 @Configuration 的方法上
  3. @Controller、@Service、@Component、@Repository 等等的以下常用注解
  4. @Import 注解可以快速的添加一些对象到容器中
  5. @ImportResource 可以将 xml 中配置的 bean 添加到容器中
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值