文章目录
前言
在之前使用 spring 的时候,想要添加对象到 IOC 容器有两种方式:
- 需要写一个 xml 文件,文件里面配置好 bean 标签。
- 使用注解的方式。
而 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 属性之后,我们该如何使用呢?
- Full mode(proxyBeanMethods = true) 配置类组件之间如果有依赖关系,方法会调用容器查询之前的单实例组件,用 Full 模式。
- 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 的区别是
- @Bean 一次只能添加一个 bean,@Import 可以批量添加组件更加方便一些
- @Import 添加的组件 bean 的属性为 null,@Bean 初始化时可以往属性依赖注入默认值
- @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 组件的方式:
- @Configuration 注解可以将配置类以 bean 的形式添加到 IOC 中
- @Bean 注解添加需要普通的对象,@Bean 只能用在 @Configuration 的方法上
- @Controller、@Service、@Component、@Repository 等等的以下常用注解
- @Import 注解可以快速的添加一些对象到容器中
- @ImportResource 可以将 xml 中配置的 bean 添加到容器中