Aware.java是个没有定义任何方法的接口,但是它却拥有众多子接口(考点:Spring策略模式的使用),在spring源码中有多处都在使用这些子接口完成各种场景下的回调操作,当业务有需要时,我们只需创建自定义类来实现相关接口,再声明为bean,就可以被spring容器主动回调;
二、spring源码分析Aware子类的使用场景接下来通过分析spring源码,我们来看看典型的Aware子类有哪些,使用场景是什么?
-
在spring容器初始化过程中,会执行AbstractApplicationContext类的prepareBeanFactory方法(金手指:这个是refresh()中的第三个方法),这里面会创建一个bean后置处理器ApplicationContextAwareProcessor,如下图红框所示:
-
在bean被初始化之前,所有的bean后置处理器的postProcessBeforeInitialization方法都会被执行,如下图红框所示:
-
由以上两步可以确定:对于每个bean后置处理器来说,它的postProcessBeforeInitialization方法会在每个bean的初始化之前被调用一次;
金手指:
对于每个bean后置处理器来说,它的postProcessBeforeInitialization方法会在每个bean的初始化之前被调用一次,就是在refresh()第三个方法prepareBeanFactory()方法中。
-
来看看ApplicationContextAwareProcessor类的postProcessBeforeInitialization方法,按照前面的分析,该方法在每个bean被初始化之前都会被执行,如下图红框所示,invokeAwareInterfaces方法会被调用,这是我们要关注的重点:
-
展开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方法中,我们来看看它是如何被调用的;
-
如下图所示,红框中就是BeanNameAware接口被调用的地方,而绿框中的applyBeanPostProcessorsBeforeInitialization方法就是前面我们分析的那些Aware子接口被调用的位置(因为会调动到postProcessBeforeInitialization()方法):
-
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这两个接口,看声明的方法是否能被调用,并验证传入的对象是否有效(文章结尾处提供本次实战的工程源码下载);
- 基于maven新建一个SpringBoot的web工程customizeaware;
- 新建工具类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************************************************************");
}
}
- 新建类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;
}
}
- 新建类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;
}
}
- 新建一个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;
}
}
-
启动应用,看到启动日志中带有CustomizeBeanNameAware和CustomizeApplicationContextAware的接口方法被调用时输出的日志,并且线程堆栈和我们之前看的spring源码位置一致,分别是ApplicationContextAwareProcessor.invokeAwareInterfaces()和AbstractAutowireCapableBeanFactory.invokeAwareMethods();
-
浏览器访问web服务,地址:http://localhost:8080/hello,两个实现了Aware接口的bean的被设置的信息都打印出来了,都是来自spring环境的内容;
-
验证结束,自定义的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接口揭秘,完成了。
天天打码,天天进步!