Spring注解驱动开发(四):@Bean属性详解(指定Bean初始化和销毁方法)


这是我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向容器中注入了 person1student1 两个组件,并且指定了 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注解对应的属性处就可以

  1. 在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");
    }
  1. 修改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();
    }
}

  1. 修改测试类
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();
    }
}
  1. 查看执行结果
    在这里插入图片描述
    好啦,到这里我想你已经能够使用这两个属性在Bean初始化前和销毁时做一些额外的工作

⭐️ 在这里我还想带你再看一点源码,看一下这两个方法是什么时候被调用的
这里我先跟你分享一个我看源码的经验: 在方法处标注断点,利用方法调用栈一点点往回阅读

  1. 我们先在在 intiMethod() 和 destroyMethod 处标注断点,然后开启Debug 模式
    在这里插入图片描述
  2. 查看方法调用栈
    在这里插入图片描述
    可以看到从我标注了红色的位置到执行initMethod()之间的都是和反射相关的,真正调用 initMethod 的位置就是AbstractAutowireCapableBeanFactory#invokeInitMethods,其内部通过反射执行了要注入的 Bean 的初始化方法
    🐳 同样的你也可以自己看一下destroy方法是在什么时候执行的
3. 总结

这篇文章,我向你讲述了**@Bean中的重要属性的作用以及使用技巧**
最后,我希望你看完本篇文章后,能够使用@Bean的属性尤其是initMethod和destroyMethod对Bean记性一些高阶定制,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值