一、前言

Aware.java是个没有定义任何方法的接口,但是它却拥有众多子接口(考点:Spring策略模式的使用),在spring源码中有多处都在使用这些子接口完成各种场景下的回调操作,当业务有需要时,我们只需创建自定义类来实现相关接口,再声明为bean,就可以被spring容器主动回调

二、spring源码分析Aware子类的使用场景

接下来通过分析spring源码,我们来看看典型的Aware子类有哪些,使用场景是什么?

  1. 在spring容器初始化过程中,会执行AbstractApplicationContext类的prepareBeanFactory方法(金手指:这个是refresh()中的第三个方法),这里面会创建一个bean后置处理器ApplicationContextAwareProcessor,如下图红框所示:
    鬼吹灯,掘金Spring容器中的Aware接口_# (2)Spring框架

  2. 在bean被初始化之前,所有的bean后置处理器的postProcessBeforeInitialization方法都会被执行,如下图红框所示:
    鬼吹灯,掘金Spring容器中的Aware接口_其他_02

  3. 由以上两步可以确定:对于每个bean后置处理器来说,它的postProcessBeforeInitialization方法会在每个bean的初始化之前被调用一次;

金手指:
对于每个bean后置处理器来说,它的postProcessBeforeInitialization方法会在每个bean的初始化之前被调用一次,就是在refresh()第三个方法prepareBeanFactory()方法中。

  1. 来看看ApplicationContextAwareProcessor类的postProcessBeforeInitialization方法,按照前面的分析,该方法在每个bean被初始化之前都会被执行,如下图红框所示,invokeAwareInterfaces方法会被调用,这是我们要关注的重点:
    鬼吹灯,掘金Spring容器中的Aware接口_# (2)Spring框架_03

  2. 展开invokeAwareInterfaces方法看看:

private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof EnvironmentAware) {   // 环境
                ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
            }
            if (bean instanceof EmbeddedValueResolverAware) {  // EmbededValueResolver
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                        new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
            }
            if (bean instanceof ResourceLoaderAware) {   // 统一资源加载
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {  // 事件广播器
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof MessageSourceAware) {  // 国际化
                ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {   // ApplicationContext本身
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
        }
    }

Aware分为两种情况
1、ApplicationContext六个:对于环境、统一资源加载、容器事件机制、国际化、ApplicationContext本身 embededValueResolver六个Aware,在第三个方法prepareBeanFactory()里面调用。
2、bean三个:对于BeanNameAware BeanClassLoaderAware BeanFactoryAwarer,在倒数第二个方法initializeBean()方法里面调用

从上述代码可以看出,如果当前的bean实现了某个接口,那么它的某个对应的方法就会被调用,例如我们创建了一个bean实现了ApplicationContextAware接口,那么这个bean的setApplicationContext方法就会被调用,入参是applicationContext成员变量,这样我们的bean就能得到applicationContext对象了;

以上就是Aware的接口使用原理:业务按需要实现特定的Aware接口,spring容器会主动找到该bean,然后调用特定的方法,将特定的参数传递给bean;

三、BeanNameAware接口的调用场景

BeanNameAware也是Aware的子接口,不过它的调用场景和前面分析的几个Aware子接口不同,并未出现在ApplicationContextAwareProcessor类的invokeAwareInterfaces方法中,我们来看看它是如何被调用的;

  1. 如下图所示,红框中就是BeanNameAware接口被调用的地方,而绿框中的applyBeanPostProcessorsBeforeInitialization方法就是前面我们分析的那些Aware子接口被调用的位置(因为会调动到postProcessBeforeInitialization()方法):
    鬼吹灯,掘金Spring容器中的Aware接口_# (2)Spring框架_04

  2. BeanNameAware接口被调用的地方:方法invokeAwareMethods如下所示,和前面的讨论一样,特定类型的bean,其特定的方法被调用,传入特定的入参:

private void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);  // 被调用的只有set,没有get
            }
            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }
四、实战,通过Aware接口得到想要的对象

了解Aware接口在spring环境中的用法之后,我们可以通过实战来验证前面所学了,本次实战我们创建两个类,分别实现ApplicationContextAware和BeanNameAware这两个接口,看声明的方法是否能被调用,并验证传入的对象是否有效(文章结尾处提供本次实战的工程源码下载);

  1. 基于maven新建一个SpringBoot的web工程customizeaware;
  2. 新建工具类Utils,提供静态方法printTrack用于打印当前线程堆栈信息:
package com.bolingcavalry.customizeaware.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Utils {  // 工具类
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);  // 打印日志

     // 打印当前线程堆栈信息
    public static void printTrack(String prefix){  
        StackTraceElement[] st = Thread.currentThread().getStackTrace();

        if(null==st){
            logger.info("invalid stack");
            return;
        }

        StringBuffer sbf =new StringBuffer();

        for(StackTraceElement e:st){
            if(sbf.length()>0){
                sbf.append(" <- ");
                sbf.append(System.getProperty("line.separator"));
            }

            sbf.append(java.text.MessageFormat.format("{0}.{1}() {2}"
                    ,e.getClassName()
                    ,e.getMethodName()
                    ,e.getLineNumber()));
        }

        logger.info(prefix
                + "\n************************************************************\n"
                + sbf.toString()
                + "\n************************************************************");
    }
}
  1. 新建类CustomizeBeanNameAware,实现BeanNameAware接口,方法setBeanName被调用的时候会打印当前堆栈信息:
package com.bolingcavalry.customizeaware.aware;

import com.bolingcavalry.customizeaware.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Service;

@Service
public class CustomizeBeanNameAware implements BeanNameAware {  // 自定义一个BeanNameAware实现类
    private String beanName;  // 类变量

    @Override
    public void setBeanName(String beanName) {   // 被调用的只有set,没有get
        Utils.printTrack("beanName is set to " + beanName);  // 被调用的时候打印
        this.beanName = beanName;
    }

    public String getBeanName() {  // 被调用的只有set,没有get
        return this.beanName;  
    }
}
  1. 新建类CustomizeApplicationContextAware,实现ApplicationContextAware接口,方法setApplicationContext被调用的时候会打印当前堆栈信息::
package com.bolingcavalry.customizeaware.aware;

import com.bolingcavalry.customizeaware.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;


@Service
public class CustomizeApplicationContextAware implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Utils.printTrack("applicationContext is set to " + applicationContext);
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext(){
        return this.applicationContext;
    }
}
  1. 新建一个Controller,提供http服务用于验证CustomizeBeanNameAware和CustomizeApplicationContextAware这两个bean,看看它们被spring容器设置的beanName和applicationContext是否可用:
package com.bolingcavalry.customizeaware.controller;

import com.bolingcavalry.customizeaware.aware.CustomizeApplicationContextAware;
import com.bolingcavalry.customizeaware.aware.CustomizeBeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;


@RestController
public class HelloWorldController {

    @Autowired
    private CustomizeBeanNameAware customizeBeanNameAware;

    @Autowired
    private CustomizeApplicationContextAware customizeApplicationContextAware;

    @RequestMapping("/hello")  // 浏览器访问http://localhost:8080/hello调用
    public String hello(){

        String[] beanDefinitionNames = customizeApplicationContextAware.getApplicationContext().getBeanDefinitionNames();

        StringBuilder stringBuilder = new StringBuilder();

        int arrayLength = 0;

        if(null!=beanDefinitionNames){
            arrayLength = beanDefinitionNames.length;
            //将所有bean的名称拼接成字符串(带html的换行符号<br>)
            for(String name : beanDefinitionNames){
                stringBuilder.append(name).append("<br>");
            }
        }

        return "hello, "
                + new Date()
                + "<br><br>CustomizeBeanNameAware instance bean name : "
                + customizeBeanNameAware.getBeanName()
                + "<br><br>bean definition names, size "
                + arrayLength
                + ", detail :<br><br>"
                + stringBuilder;
    }
}
  1. 启动应用,看到启动日志中带有CustomizeBeanNameAware和CustomizeApplicationContextAware的接口方法被调用时输出的日志,并且线程堆栈和我们之前看的spring源码位置一致,分别是ApplicationContextAwareProcessor.invokeAwareInterfaces()和AbstractAutowireCapableBeanFactory.invokeAwareMethods();

  2. 浏览器访问web服务,地址:http://localhost:8080/hello,两个实现了Aware接口的bean的被设置的信息都打印出来了,都是来自spring环境的内容;

  3. 验证结束,自定义的Aware接口实现类如果声明为bean,在初始化的时候就会被spring容器按照接口类型找出来,通过调用接口方法的方式将特定的对象实例传递给bean;

五、面试金手指

总述:Aware分为两种情况
1、ApplicationContext六个:对于环境、统一资源加载、容器事件机制、国际化、ApplicationContext本身 embededValueResolver六个Aware,在第三个方法prepareBeanFactory()里面调用。
对于每个bean后置处理器来说,它的postProcessBeforeInitialization方法会在每个bean的初始化之前被调用一次,就是在refresh()第三个方法prepareBeanFactory()方法中
2、bean三个:对于BeanNameAware BeanClassLoaderAware BeanFactoryAware,在倒数第二个方法initializeBean()方法里面调用

实战需要注意的点:
1、XxxAware接口实现类是要注入到spirngioc容器的,一定要使用@Servece或@Component注解;
2、调用的时候只调用setXxx()方法,不调用getXxx()方法。

六、小结

Aware接口揭秘,完成了。

天天打码,天天进步!