这是我Spring Frame 专栏的第四篇文章,在 Spring注解驱动开发(三):利用@Scope注解控制Bean的作用域这篇文章中,我向你介绍了如何使用@Scope 注解控制Bean的作用域,如果你未读过那篇文章,我建议你去读一下,你一定会有所收获
1. 背景介绍
在专栏的第二篇文章Spring注解驱动开发(二):使用@Configuration和@Bean向容器中注册组件中,我讲述了如何利用@Configuration+@Bean下个容器中注入Bean,那里我只是简单的讲述了如何使用@Bean,但是并未讲述它的一些’进阶用法’,这篇文章我就会通过讲解@Bean的属性来让你更加灵活的使用@Bean注解
2. 属性详解
我们先来看一下@Bean的源码,来熟悉其属性值
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
// 用来指定Bean在Ioc容器中的名称
@AliasFor("name")
String[] value() default {};
// 与 value 互为别名
@AliasFor("value")
String[] name() default {};
// 是否自动注入属性,已经被废弃
@Deprecated
Autowire autowire() default Autowire.NO;
// 是否作为 @Autowire 注入的候选Bean
boolean autowireCandidate() default true;
// 定义Bean的初始化方法
String initMethod() default "";
// 定义Bean的销毁方法
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
我在源码中对各个属性值做了最基本的解释,现在我再详细的介绍一下重要的属性值信息
2.1 value/name
value/name
是用来指定我们的Bean在容器中的名称,我们可以通过这个名称查询我们向Ioc容器中注入的Bean,其实我们仅使用@Bean向容器中注入的组件是单例的,这意味着它在整个Spring 上下文中是共享的,唯一的
❓ 你在使用Spring框架时,在获取我们向容器中注的自定义的组件时,是否很好奇这个组件被保存在哪里呢?
今天我先来带你入个门,介绍这个Spring Ioc用来存放单例Bean的容器,以便你能更好的理解这个属性的价值和作用
在我们是用Spring的时候吗如果你利用注解开发,你一定使用过AnnotationConfigApplicationContext
这个类,它是Spring 上下文环境,但是否这就意味着它是底层的Ioc容器呢,实则不然,我们先来看一下它的继承图
在整个继承树上,在最左侧的这条接口实现类图中,你会看到一个 BeanFactory
,它才是最纯粹的那个Ioc容器,而xxxApplication
这些类通过组合一个DefaultListableBeanFactory
(通过下面的代码你能看的很清楚)来拥有Ioc容器的功能,除此之外xxxApplication
还有国际化,事件机制,事务等功能
public class AnnotationConfigApplicationContext extends GenericApplicationContext {}
public class GenericApplicationContext extends AbstractApplicationContext{
// 这就是组合的那个BeanFacoty接口实现类
private final DefaultListableBeanFactory beanFactory;
}
好啦,现在我们找到了这个Ioc容器,那么它到底在哪里缓存了这些单例Bean呢?其实从DefaultListableBeanFactory
的源码中你就会发现一个Map
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** Map of singleton-only bean names, keyed by dependency type. */
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);
通过源码中的注释,你会发现Spring 底层通过key为beanName,value为BeanDefinition(你就当它是一个对Bean的全方位描述信息即可)的ConcurrentHashMap保存了所有的Bean定义信息。
但是我们还是并未看到那个保存,它到底在哪呢?
别急,我们先看一眼DefaultListableBeanFactory
的继承关系图:
这里面我圈出了一个DefaultSingletonBeanRegistry
,丛台的名字就可以发现,它就是那个注册单例Bean的对象,我们进入它就可以找到那个存放单例Bean的容器:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
⭐️ 好啦,到现在你应该知道value/name
这个属性在组件注入的重要性了吧!!!
2.2 autowire
从 @Bean 的源码中,你可以看到它被 @Deprecated
标注,这标识它不建议被使用了
但是我们这里还是要讲述一下它的作用:在向容器中利用 @Bean 注入组件时,可以从Ioc容器中按照指定规则对这个组件属性进行注入
它的取值有以下几种:
// 默认取值,不注入 ---> NO
NO(AutowireCapableBeanFactory.AUTOWIRE_NO),
// 通过名称注入 ---> byName
BY_NAME(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME),
// 通过类型注入 ---> byType
BY_TYPE(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
这里我通过代码向你展示以下它的用法:(⚠️官方已经不建议使用了,你不必太过关注这个属性了)
这里我们通过@Bean向容器中注入了 person1 和 student1 两个组件,并且指定了 student1的 @Bean注解的 autowire = Autowire.BY_TYPE
,也就是通过类型对组件中的属性赋值:
@Configuration
public class CustomerConfig {
@Bean
public Person person1() {
return new Person(1, "jack", "female");
}
@Bean(name="student1",autowire = Autowire.BY_TYPE)
public Student student1() {
return new Student();
}
}
接着我们编写测试类并执行:
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
Person person1 = context.getBean("person1", Person.class);
Student student1 = context.getBean("student1", Student.class);
System.out.println("person1 ===>"+person1);
System.out.println("sutdent1===>"+student1);
System.out.println("person1 == student1.getPerson() ===>"+(person1==student1.getPerson()));
context.close();
}
}
从结果我们可以发现,person1被注入到了student1里面
到这里,你应该能了解这个属性的作用了吧
2.3 initMethod,destroyMethod
这两个属性的具体含义就是用来指定Bean初始化和销毁时会调用的方法信息的,你只需要在要注入的Bean内部定义两个方法,并将方法名填写在 @Bean
注解对应的属性处就可以
- 在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;
}
// 省略 getter和setter方法
// 初始化方法
public void initMethod() {
System.out.println("这是initMethod");
}
// 销毁方法
public void destroyMethod() {
System.out.println("这是destroyMethod");
}
- 修改Bean注入的配置类
@Configuration
public class CustomerConfig {
@Bean(name="person1",initMethod = "initMethod",destroyMethod = "destroyMethod")
public Person person1() {
return new Person(1, "jack", "female");
}
@Bean(name="student1",autowire = Autowire.BY_TYPE)
public Student student1() {
return new Student();
}
}
- 修改测试类
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("Ioc 容器初始化完成");
Person person1 = context.getBean("person1", Person.class);
Student student1 = context.getBean("student1", Student.class);
System.out.println("person1 ===>"+person1);
System.out.println("sutdent1===>"+student1);
System.out.println("person1 == student1.getPerson() ===>"+(person1==student1.getPerson()));
context.close();
}
}
- 查看执行结果
好啦,到这里我想你已经能够使用这两个属性在Bean初始化前和销毁时做一些额外的工作
⭐️ 在这里我还想带你再看一点源码,看一下这两个方法是什么时候被调用的
这里我先跟你分享一个我看源码的经验: 在方法处标注断点,利用方法调用栈一点点往回阅读
- 我们先在在 intiMethod() 和 destroyMethod 处标注断点,然后开启Debug 模式
- 查看方法调用栈
可以看到从我标注了红色的位置到执行initMethod()之间的都是和反射相关的,真正调用 initMethod 的位置就是AbstractAutowireCapableBeanFactory#invokeInitMethods
,其内部通过反射执行了要注入的 Bean 的初始化方法
🐳 同样的你也可以自己看一下destroy方法是在什么时候执行的
3. 总结
这篇文章,我向你讲述了**@Bean中的重要属性的作用以及使用技巧**
最后,我希望你看完本篇文章后,能够使用@Bean的属性尤其是initMethod和destroyMethod对Bean记性一些高阶定制,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!