Spring注解驱动开发(二):使用@Configuration和@Bean向容器中注册组件

发布于:2023-01-19 ⋅ 阅读:(200) ⋅ 点赞:(0)


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 作用了:

  1. 为注解标注类进行 CGLIB 动态代理
  2. 为@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. 总结

这篇文章,我向你传输了以下知识点

  1. @Configuration 注解的作用以及属性详解
  2. @Bean 注解基本使用
  3. @Bean 一定要和 @Configuration 使用吗

当然本篇文章并没有展开介绍@Bean的属性设置,我将会在下篇文章向你介绍

最后,我希望你看完本篇文章后,能够使用 @Configuration + @Bean 实现组件注入Ioc容器的功能,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!


网站公告

今日签到

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