文章目录
在 Spring注解驱动开发(一):使用@ComponentScan自定义扫描规则和组件注入规则 这篇文章中,我介绍了如何利用@ComponetScan 配合 @Componet 实现组件注入,这是向 Spring Ioc 容器中放入候选 Bean 的一种方式,在这篇文章中,我会向你介绍另一种向 Ioc 容器中注入组件的一种方式: @Configuration+@Bean
⭐️ 注意我这里使用的Spring版本是
5.2.15.RELEASE
,每个版本的源码可能有所变化
1. 预准备代码
- 这里我声明了两个实体类 Person 和 Student,其中的Student 的一个属性为
Person
,具体代码如下所示:
public class Person {
private Integer id;
private String name;
private String sex;
public Person() {
}
public Person(Integer id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
// 省略setter,getter方法
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public class Student {
private int stuNum;
// 持有一个 Person 字段
private Person person;
public Student() {
}
public Student(int stuNum, Person person) {
this.stuNum = stuNum;
this.person = person;
}
// 省略setter,getter方法
@Override
public String toString() {
return "Student{" +
"stuNum=" + stuNum +
", person=" + person +
'}';
}
}
为了体现@Configuration和@Bean的作用,这里我编写了一个配置类,其内部都有标注了@Bean的方法,用来生成 Person 和 Student 的实例,具体代码如下所示:
@Configuration
public class CustomerConfig {
/**
* 未指定Bean名称,利用方法名作为BeanName
*
* @return
*/
@Bean
public Person person1() {
// 构造器注入
return new Person(1, "jack", "sex");
}
@Bean
public Student student1(Person person1) {
return new Student(2019,person1);
}
}
2. 利用XML配置向容器中注入Bean
对于基于 Spring XML 配置文件注入Bean的方式,我相信你一定很熟悉了,这毕竟是学习 Spring 的第一步,这里我利用 bean
标签想容器中注入了一个Person 对象实例,配置文件如下所示:
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="org.zhang.blog.ioc.domain.Person">
<property name="id" value="1" />
<property name="name" value="jack"/>
</bean>
</beans>
接下来我们编写测试类查看Ioc容器中是否注入了对应的 Person 实例
public class IcoMain {
public static void main(String[] args) {
// 将编写的XML文件放入 Ioc 容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:META-INF/dependency-inject.xml");
// 获取容器中所有 Bean 的名字
String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
System.out.println("--------------------------------------");
Person person = context.getBean("person", Person.class);
System.out.println(person);
context.close();
}
}
运行代码后,我们发现容器中注入了一个叫做person的对象,它的属性值也是我们在XML文件中编写的值
好啦,这是利用Spring XML 文件向容器中注入Bean
3. 利用@Configuration+@Bean向容器中注入Bean
Spring 提供了 @Configuration 注解,从下面的源码中可以看见它也是 @Component
的派生注解,@Component注解的范围最广,所有自己写的类都可以注解,但是@Configuration注解一般注解在配置类上,起配置作用**,所以我们常用 @Configuration 标注配置类,至于什么是配置类我就不多说了,相信小伙伴们应该都理解(*^▽^*)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
// 是否对配置类中标注了@Bean的方法生成代理对象
boolean proxyBeanMethods() default true;
}
首先我先带你了解一下如何使用这两个注解向容器中注入Bean,再带你深入解析一下这两个注解的使用方式
下面是我所写的标注了@Configuration 注解的配置类,里面通过@Bean 标注了两个方法,分别生成两个实例person1,student1
@ComponentScan(value = "org.zhang.blog.ioc")
@Configuration
public class CustomerConfig {
/**
* 未指定Bean名称,利用方法名作为BeanName
*
* @return
*/
@Bean
public Person person1() {
// 构造器注入
return new Person(1, "jack", "sex");
}
@Bean
public Student student1(Person person1) {
return new Student(2019,person1);
}
}
接下来我编写配置类来向容器中注入这个配置类:
public class IcoMain {
public static void main(String[] args) {
// 换成注解配置上下文环境
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("-----------------Person---------------------");
Person person = context.getBean(Person.class);
System.out.println(person);
System.out.println("-----------------Student---------------------");
Student student = context.getBean(Student.class);
System.out.println(student);
context.close();
}
}
从结果可以看到,我们成功的向容器中注入了Person 和 Student 的对象实例
接下来我们尝试将 CustomerConfig 中的获取Person的方法的名字改成 person2
,我们在运行测试代码,结果如下所示:
@Configuration
public class CustomerConfig {
// 将原来的person1 ---> person2
@Bean
public Person person2() {
// 构造器注入
return new Person(1, "jack", "sex");
}
// ......
}
我们发现注入的Person实例的名字改为了 person2,这说明标注了@Bean 的方法注入的实例名称默认是方法名,那么我们有什么方法可以改变Bean的名字呢?
@Bean 源码中有一个 value
属性,可以指定生成Bean的名称
public @interface Bean {
@AliasFor("name")
String[] value() default {};
// ......
}
这里我就不做演示了,你可以自己尝试赋值,重新测试一下生成的Bean是否为你自己指定的value值
到这里你应该会使用@Configuration 配合 @Bean 向 Ioc 容器中注入组件了
4. @Configuration 属性详解
从上面的 @Configuration 注解源码可以看到,它有个核心的属性值 proxyBeanMethods
(默认值为true),你可以看源码注释了解这个属性值的作用,但是我还是在这里帮你解析一下它的作用:
- 对配置类进行CGLIB 动态代理,进行增强
- 对内部标注了@Bean 的方法生成共享对象
是不是听起来有点懵,这里我通过编码的方式带你深入理解一下这个属性的作用:
我们先改造以下我们的配置类
@Configuration
public class CustomerConfig {
@Bean
public Person person1() {
// 构造器注入
return new Person(1, "jack", "sex");
}
@Bean
public Student student1() {
// 此处调用person1() 获取 person 对象
return new Student(2019,this.person1());
}
}
重新编写测试类并执行
public class IcoMain {
public static void main(String[] args) {
// 换成注解配置上下文环境
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println("CustomerConfig: "+ context.getBean(CustomerConfig.class));
System.out.println("-----------------Person---------------------");
Person person = context.getBean(Person.class);
System.out.println("容器中的person对象: "+person);
System.out.println("-----------------Student---------------------");
Student student = context.getBean(Student.class);
System.out.println("student 中的person 对象"+student.getPerson());
System.out.println(student.getClass());
context.close();
}
}
接着,我们将配置类上面的 @Configuration 注解的proxyBeanMethods 设置为 false,在此执行测试代码:
@Configuration(proxyBeanMethods = false)
public class CustomerConfig {
@Bean
public Person person1() {
// 构造器注入
return new Person(1, "jack", "sex");
}
@Bean
public Student student1() {
return new Student(2019,this.person1());
}
}
两次执行结果对比,我们可以重新看一下我的总结的关于 proxyBeanMethods
作用了:
- 为注解标注类进行 CGLIB 动态代理
- 为@Bean注解设置共享对象
5. @Configuration 到底加不加
其实@Bean注解不一定非要配合 @Configuration 使用,你可以利用 @Componet + @Bean 实现注入,那种方式注入就会和利用 @Configuration并设置 proxyBeanMethods=false
产生相同的结果,你可以自己设置并验证一下
//@Configuration(proxyBeanMethods = false)
public class CustomerConfig {
/**
* 未指定Bean名称,利用方法名作为BeanName
*
* @return
*/
@Bean
public Person person1() {
// 构造器注入
return new Person(1, "jack", "sex");
}
@Bean
public Student student1() {
return new Student(2019,this.person1());
}
}
6. 总结
这篇文章,我向你传输了以下知识点
- @Configuration 注解的作用以及属性详解
- @Bean 注解基本使用
- @Bean 一定要和 @Configuration 使用吗
当然本篇文章并没有展开介绍@Bean的属性设置,我将会在下篇文章向你介绍
最后,我希望你看完本篇文章后,能够使用 @Configuration + @Bean 实现组件注入Ioc容器的功能,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!