spring源码学习 四:Bean后处理器与@Autowire

前言

这一篇文章我们来了解一下常见的Bean后处理器,还记得它是干嘛的吗?前面的文章很高频地提到它,它是给Bean地各个生命周期前后提供扩展功能的组件,一起来学习它是怎么工作的吧。

一、常见的Bean后处理器

我们先准备好一个容器,这个容器我们之前没有见过,叫GenericApplicationContext它是一个干净的ApplicationContext容器,什么叫干净?

它里面没什么东西,也不会帮我们添加Bean后处理器,这样的话,我们就可以手动去添加我们想要的Bean后处理器,可以直观的看到添加的Bean后处理器有什么效果。像我们之前学的AnnotationConfigApplicationContext它是会自动帮我们装配Bean后处理器的,我们用它的话就看不到效果啦。

那可不可以用BeanFactory呢,它也是干净的,不会帮我们添加Bean后处理器。这是可以的,但是GenericApplicationContext有refresh方法,它会帮我们调用Bean工厂后处理器,且会自动执行我们手动加的Bean后处理器,显然BeanFactory做不到,不仅要手动添加,还要手动调用,没那么方便。

首先我们来看看main方法:

public class BeanProcessorLearn {
    public static void main(String[] args) {
        //新建的“干净的容器”
        GenericApplicationContext context = new GenericApplicationContext();

        //注册三个Bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);

        //初始化容器
        context.refresh();

        //销毁
        context.close();

    }
}

主要做的是往干净的容器里添加三个Bean,我们来看看这三个Bean做了什么事:

public class Bean2 {
}
public class Bean3 {
}

Bean2和Bean3什么事也没做,只是一个刚定义好的类。

public class Bean1 {

    private Bean2 bean2;

    @Autowired
    public void setBean2(Bean2 bean2) {
        System.out.println("@Autowire 生效:"+bean2);
        this.bean2 = bean2;
    }

    private Bean3 bean3;

    @Resource
    public void setBean3(Bean3 bean3) {
        System.out.println("@Resource 生效:"+bean3);
        this.bean3 = bean3;
    }

    private String home;
    @Autowired
    public void setHome(@Value("${JAVA_HOME}") String home){
        System.out.println("@Value 生效:"+home);
        this.home=home;
    }

    @PostConstruct
    public void init(){
        System.out.println("@PostConstruct 生效");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("@PreDestroy 生效");
    }

    @Override
    public String toString() {
        return "Bean1{" +
                "bean2=" + bean2 +
                ", bean3=" + bean3 +
                ", home='" + home + '\'' +
                '}';
    }
}

Bean1做的事就多了,但是不要害怕,我们稍微看一下就可以理解,首先是@Autowired和@Resource注解来注入Bean2和Bean3,待会我们要测试哪个Bean后处理器解析这两个注解的。

然后是通过@Autowired注入@Value(“${JAVA_HOME}”) 的String 环境变量,这样做是为了可以打印一些信息,看看@Value是谁来处理的,如果直接属性注入就做不到打印这个动作,接下来是@PostConstruct初始化方法和@PreDestroy销毁方法,同样要看看是谁处理这两个注解的,最后重写了toString方法。

现在我们执行一下main方法,会发现什么也没有。

请添加图片描述

这说明那些注解都没有被解析。

现在我们在调用refresh方法前,往容器里添加一个Bean后处理器:

context.registerBean(AutowiredAnnotationBeanPostProcessor.class);//解析@Autowire @Value

AutowiredAnnotationBeanPostProcessor我们看看它是不是解析@Autowire和@Value,但是在这之前我们要再多一个动作:

context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());//@Value注解属性的解析

这一步可以先不管,它是为了可以解析@Value里属性"${JAVA_HOME}"的值,才能注入。我们运行一下:

请添加图片描述

可以看到,这两个注解确实生效了。

现在我们再加点料:

context.registerBean(CommonAnnotationBeanPostProcessor.class);//解析@Resource @PostConstruct @PreDestroy

它是来解析解析@Resource @PostConstruct @PreDestroy这三的,运行一下:

请添加图片描述

没有问题。

完整代码:

public static void main(String[] args) {
    //新建的“干净的容器”
    GenericApplicationContext context = new GenericApplicationContext();

    //注册三个Bean
    context.registerBean("bean1", Bean1.class);
    context.registerBean("bean2", Bean2.class);
    context.registerBean("bean3", Bean3.class);

    context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());//@Value注解属性的解析
    context.registerBean(AutowiredAnnotationBeanPostProcessor.class);//解析@Autowire @Value
    context.registerBean(CommonAnnotationBeanPostProcessor.class);//解析@Resource @PostConstruct @PreDestroy
    //初始化容器
    context.refresh();

    //销毁
    context.close();

}

感兴趣的同学可以去搜一搜@ConfigurationProperties这个注解是哪个Bean处理器解析的,它是SpringBoot的注解,用来注入配置文件中值到类属性。

二、@Autowire解析过程

现在我们来看看@Autowire具体是怎么被解析的。

我们只能拿BeanFactory来学习了,因为这样才能看到Bean后处理器工作的流程,刚刚也说到ApplicationContext就算不帮我们加入Bean后处理器,也会在我们加入后自动帮我们执行。

这回,我们直接用Bean后处理的方法,来模拟它的工作过程。

public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    beanFactory.registerSingleton("bean2",new Bean2());//通过这种方法放入到容器的已经是成品Bean,不会再走创建、依赖注入、初始化
    beanFactory.registerSingleton("bean3",new Bean3());
    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());// @Value

    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();//bean后处理器
    processor.setBeanFactory(beanFactory);//设置BeanFactory
    Bean1 bean1 = new Bean1();
    System.out.println("调用前Bean:"+bean1);
    System.out.println("开始调用");
    processor.postProcessProperties(null,bean1,"bean1");//手动进行依赖注入
    System.out.println("调用后Bean:"+bean1);


}

我们看看main里写了什么,首先创建了一个beanFactory,然后把成品Bean2和Bean3直接放入容器里,因为Bean2和Bean3都是一个最原始的类,没有任何属性和方法,忘记了的同学可以往上翻阅。

然后我们new了一个AutowiredAnnotationBeanPostProcessor,它就是用来解析@Autowire的Bean后处理器,这里我们需要给它设置一个beanFactory,目的是让它在依赖出入时,能找到需要的Bean,然后我new了一个Bean1,这样手动new出来的Bean是没有走依赖注入和初始化步骤的,我们让处理器去postProcessProperties,也就是依赖注入,它的第一个参数有兴趣的同学可以去了解一下,第二个参数是需要依赖注入的Bean,第三个参数是Bean的名字。我们来看看执行结果:

请添加图片描述

可以看到调用前Bean1的属性都是null,注入后Bean2和home有了值,但是bean3没有,这是因为Bean3是标注了@Resource注解,它的Bean后处理器还没出场呢。

@Resource
public void setBean3(Bean3 bean3) {
    System.out.println("@Resource 生效:"+bean3);
    this.bean3 = bean3;
}

现在我们进入postProcessProperties的源码看看它是怎么做的:

请添加图片描述

代码很少,总共有两个步骤:

  • 第一步:找到标记了@Autowire注解的方法和属性

  • 第二步:执行inject,通过反射来赋值

我们看看这个InjectionMetadata里有什么内容。

因为findAutowiringMetadata方法是私有的,所以我们通过反射来调用它看看。

    public static void main(String[] args) throws Throwable {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("bean2",new Bean2());//通过这种方法放入到容器的已经是成品Bean,不会再走创建、依赖注入、初始化
        beanFactory.registerSingleton("bean3",new Bean3());
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());// @Value

        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();//bean后处理器
        processor.setBeanFactory(beanFactory);//设置BeanFactory
        Bean1 bean1 = new Bean1();
//        System.out.println("调用前Bean:"+bean1);
//        System.out.println("开始调用");
//        processor.postProcessProperties(null,bean1,"bean1");//手动进行依赖注入
//        System.out.println("调用后Bean:"+bean1);
        Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        findAutowiringMetadata.setAccessible(true);
        InjectionMetadata metadata = (InjectionMetadata)findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);
        System.out.println(metadata);

    }

因为InjectionMetadata是没有重写toString的,所以我们在打印语句加个断点来看看里面有什么东西。

请添加图片描述

可以看到里面记录了两个AutowiredMethodElement一个是setBean2方法,另一个是setHome方法。

我们往Bean1里加个@Autowire标注的test1属性,看看里边会不会多出什么东西。

请添加图片描述

可以看到多出来一个AutowiredFieldElement

现在我们调用metadata的inject方法,给bean1注入值,注意要删掉test1属性。

metadata.inject(bean1,"bean1",null);

执行方法后就可以看到bean1成功注入了@Autowire属性。

那inject里做了什么?这里我们做一下模拟,就不读源码了,跨度很大。

首先我们通过反射,可以表示一个类中的属性,我们就拿Bean1里的属性Bean3来举例。

Field bean3 = Bean1.class.getDeclaredField("bean3");

上文我们可以得知,被@Autowire标记的成员变量会被找到,此时会被封装成DependencyDescriptor,从名字上看,他叫依赖描述器,他可以传入一个Field参数和一个boolean值,Field表示某个属性,boolean表示是否必须。

DependencyDescriptor descriptor=new DependencyDescriptor(bean3,false);

接下来我们通过beanFactory的doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) 方法,把DependencyDescriptor传进去,就可以在Bean容器里找到类型相同的Bean。

Field bean3 = Bean1.class.getDeclaredField("bean3");
DependencyDescriptor descriptor=new DependencyDescriptor(bean3,false);
Object o = beanFactory.doResolveDependency(descriptor, null, null, null);
System.out.println(o);

看看输出结果:

请添加图片描述

果然找到了Bean3。

找到需要的Bean就很简单了,通过反射就可完成值的注入。

而set注入呢?做法是很类似的:

Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor descriptor2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
Object o1 = beanFactory.doResolveDependency(descriptor2, null, null, null);
System.out.println(o1);

和属性值注入不同的点是,DependencyDescriptor里传的参数不一样,要传一个

MethodParameter,它是为了定位方法参数的,第一个参数是Method,第二个参数是Method参数的下标,所以有多少个参数就要创建多少个MethodParameter,有多少个MethodParameter就有多少个DependencyDescriptor。

上述两种情况中,如果boolean值传true,就表示必须注入值,如果在Bean容器里找不到对应的类型值,就会抛出异常。

后续章节我们会聊聊doResolveDependency做的事。

三、总结

  • Bean后处理器处理对应的注解
  1. AutowiredAnnotationBeanPostProcessor:解析@Autowire @Value

  2. CommonAnnotationBeanPostProcessor:解析@Resource @PostConstruct @PreDestroy

  • @Autowire解析流程
  1. 查找标记了@Autowire的属性和方法,将其反射信息封装成InjectionMetadata

  2. 在Bean容器里通过类型找到所需的注入值,再通过反射将值注入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值