前言
感知,是一个很玄乎的词。其实我们可以这样理解。正常情况下,我们的bean,是不应该意识到Spring框架的存在的,它虽然被Spring所管理,但是它本身意识不到管理者的存在。从初始化,到销毁,在它自己看来,其实都是一个正常的java对象应该有的流程。只不过调用者是Spring而已。那么总有些时候,我们需要让bean【感知到】Spring框架的存在,体现在代码中,就是获取到属于Spring框架的一部分,比如bean工厂:是谁生产出自己的。ApplicationContext:自己属于哪个上下文。或者更加单纯的beanName:自己在Spring中叫什么名字。一旦我们的需求需要这些信息的时候,我们的bean就需要通过一些手段,来获取到它们。这就是Aware接口的作用。
但其实,Aware接口本身设计出来是用于Spring框架本身的,其实它不太提倡由用户去实现这些接口,因为这样的程序会难以避免的和Spring框架耦合在一起,我们知道高耦合的程序是不好的。但我们有些时候难免会遇到一些情况,需要我们这样处理。又或者像现在这样,当我们去研究Spring本身的时候,也绕不开这个点。
工程结构
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─akitsuki
│ │ │ └─springframework
│ │ │ ├─beans
│ │ │ │ ├─exception
│ │ │ │ │ BeanException.java
│ │ │ │ │
│ │ │ │ └─factory
│ │ │ │ │ Aware.java
│ │ │ │ │ BeanClassLoaderAware.java
│ │ │ │ │ BeanFactory.java
│ │ │ │ │ BeanFactoryAware.java
│ │ │ │ │ BeanNameAware.java
│ │ │ │ │ ConfigurableListableBeanFactory.java
│ │ │ │ │ DisposableBean.java
│ │ │ │ │ HierarchicalBeanFactory.java
│ │ │ │ │ InitializingBean.java
│ │ │ │ │ ListableBeanFactory.java
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ AutowireCapableBeanFactory.java
│ │ │ │ │ BeanDefinition.java
│ │ │ │ │ BeanFactoryPostProcessor.java
│ │ │ │ │ BeanPostProcessor.java
│ │ │ │ │ BeanReference.java
│ │ │ │ │ ConfigurableBeanFactory.java
│ │ │ │ │ DefaultSingletonBeanRegistry.java
│ │ │ │ │ PropertyValue.java
│ │ │ │ │ PropertyValues.java
│ │ │ │ │ SingletonBeanRegistry.java
│ │ │ │ │
│ │ │ │ ├─support
│ │ │ │ │ AbstractAutowireCapableBeanFactory.java
│ │ │ │ │ AbstractBeanDefinitionReader.java
│ │ │ │ │ AbstractBeanFactory.java
│ │ │ │ │ BeanDefinitionReader.java
│ │ │ │ │ BeanDefinitionRegistry.java
│ │ │ │ │ CglibSubclassingInstantiationStrategy.java
│ │ │ │ │ DefaultListableBeanFactory.java
│ │ │ │ │ DisposableBeanAdapter.java
│ │ │ │ │ InstantiationStrategy.java
│ │ │ │ │ SimpleInstantiationStrategy.java
│ │ │ │ │
│ │ │ │ └─xml
│ │ │ │ XmlBeanDefinitionReader.java
│ │ │ │
│ │ │ ├─context
│ │ │ │ │ ApplicationContext.java
│ │ │ │ │ ApplicationContextAware.java
│ │ │ │ │ ConfigurableApplicationContext.java
│ │ │ │ │
│ │ │ │ └─support
│ │ │ │ AbstractApplicationContext.java
│ │ │ │ AbstractRefreshableApplicationContext.java
│ │ │ │ AbstractXmlApplicationContext.java
│ │ │ │ ApplicationContextAwareProcessor.java
│ │ │ │ ClasspathXmlApplicationContext.java
│ │ │ │
│ │ │ ├─core
│ │ │ │ └─io
│ │ │ │ ClasspathResource.java
│ │ │ │ DefaultResourceLoader.java
│ │ │ │ FileSystemResource.java
│ │ │ │ Resource.java
│ │ │ │ ResourceLoader.java
│ │ │ │ UrlResource.java
│ │ │ │
│ │ │ └─util
│ │ │ ClassUtils.java
│ │ │
│ │ └─resources
│ └─test
│ ├─java
│ │ └─com
│ │ └─akitsuki
│ │ └─springframework
│ │ └─test
│ │ │ ApiTest.java
│ │ │
│ │ └─bean
│ │ UserDao.java
│ │ UserService.java
│ │
│ └─resources
│ spring.xml
虽然看起来很多,但实际上这一章的内容相当轻松。大部分内容都是前面积累下来的,新增的内容并没有多少。抱着轻松的心情来开始这一章吧。
我什么也不做,就想给你盖个戳
既然我们要让bean感知到Spring的存在,那么我们需要一个标记,来告诉Spring:这个bean,需要感知到你。那么对于Spring来说,这个标记,就是Aware接口。
package com.akitsuki.springframework.beans.factory;
/**
* 标记接口,本身没有内容,仅用于提供标记功能
* 实现这个接口及其子接口,可以被Spring容器【感知】
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:11
*/
public interface Aware {
}
空空如也的接口,没有继承,也没有自己的方法。但有了它,Spring就可以通过 instanceof
操作,来知道,这个bean是否需要进行感知,从而进行统一的处理。这就像是被盖上了一个印章一样。
来,坐在这儿,你想盖哪种戳?
我们虽然说,感知Spring。但我们不可能把庞大的Spring,一股脑的全部给Bean。来,你要的Spring给你了,拿去玩吧。我们肯定是要按需分配。你告诉我,你想要哪块儿,我再把你需要的部分给你。于是,就有了Aware的各种子接口。在这里,我们介绍实现其中的四种:BeanFactory感知接口、BeanClassLoader感知接口、BeanName感知接口、ApplicationContext感知接口。
package com.akitsuki.springframework.beans.factory;
import com.akitsuki.springframework.beans.exception.BeanException;
/**
* 实现此接口,即可感知到所属的BeanFactory
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:12
*/
public interface BeanFactoryAware extends Aware {
/**
* 设置所属的BeanFactory
*
* @param beanFactory beanFactory
* @throws BeanException 防止出现初始化错误
*/
void setBeanFactory(BeanFactory beanFactory) throws BeanException;
}
首先是我们的BeanFactory感知接口,提供了一个setBeanFactory方法,很好理解,bean所需要的BeanFactory,就会通过这个方法来传递给它。
package com.akitsuki.springframework.beans.factory;
/**
* 实现此接口,即可感知到所使用的BeanClassLoader
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:14
*/
public interface BeanClassLoaderAware extends Aware {
/**
* 设置所使用的classLoader
*
* @param classLoader classLoader
*/
void setBeanClassLoader(ClassLoader classLoader);
}
与上面类似,这里传递的是ClassLoader。
package com.akitsuki.springframework.beans.factory;
/**
* 实现此接口,即可感知到当前bean的name
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:17
*/
public interface BeanNameAware extends Aware {
/**
* 设置beanName
*
* @param beanName beanName
*/
void setBeanName(String beanName);
}
嗯,beanName,我们的老朋友
package com.akitsuki.springframework.context;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.Aware;
/**
* 实现此接口,即可感知到所属的ApplicationContext
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:19
*/
public interface ApplicationContextAware extends Aware {
/**
* 设置ApplicationContext
*
* @param applicationContext applicationContext
* @throws BeanException applicationContext中的方法可能会抛出此异常
*/
void setApplicationContext(ApplicationContext applicationContext) throws BeanException;
}
最后出场的是我们的重量级选手,ApplicationContext。
可以看到,这些接口的方法都差不多,都是将bean需要的内容,传递给它。那么我们来思考一下,这些东西,要在什么时候传递给bean呢?很自然的想法就是,在创建它的时候。但是这个时候我们就犯了难:在创建bean的时候,我们是拿不到ApplicationContext的>_<!这个时候,beanFactory、classLoader、beanName,我们都可以获取到,唯独ApplicationContext拿不到。这要怎么办?往前想想,前面我们实现的一个功能,可以帮助我们实现这一个需求。嗯…想到了吗?现在来揭晓答案:bean后置处理器!我们可以在ApplicationContext的刷新方法中,将自身作为参数传入处理器中,再将处理器注册到beanFactory中。最后,在beanFactory创建bean的时候,就可以在执行bean处理器的时候操作ApplicationContext了,真的是很好玩的一个设计。
后置处理,解决大问题
来,我们看看这个后置处理器要怎么设计
package com.akitsuki.springframework.context.support;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanPostProcessor;
import com.akitsuki.springframework.context.ApplicationContext;
import com.akitsuki.springframework.context.ApplicationContextAware;
import lombok.AllArgsConstructor;
/**
* ApplicationContext感知包装处理器,由于ApplicationContext的创建在Bean创建之后
* 所以不能在创建bean的时候就拿到,就需要后置处理器,在bean初始化之前来将ApplicationContext进行注入
*
* @author ziling.wang@hand-china.com
* @date 2022/11/24 14:28
*/
@AllArgsConstructor
public class ApplicationContextAwareProcessor implements BeanPostProcessor {
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeanException {
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(applicationContext);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeanException {
return bean;
}
}
嗯嗯,非常简单易懂的设计,判断bean实现了 ApplicationContextAware
接口,就对其进行强转,再调用 setApplicationContext
方法,将applicationContext设置进去即可。顺带一提,这里用lombok的 @AllArgsConstructor
注解,省略了有参构造方法的编写,实际上这里应该是要传入进来 ApplicationContext
来对属性进行初始化的,需要注意。
接下来是 refresh
方法的改造,方法位于 AbstractApplicationContext
中。
@Override
public void refresh() throws BeanException {
//创建beanFactory,加载beanDefinition
refreshBeanFactory();
//获取beanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
//添加ApplicationContextAwareProcessor
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
//在bean实例化之前,执行BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
//注册BeanPostProcessor
registerBeanPostProcessors(beanFactory);
//提前实例化单例bean对象
beanFactory.preInstantiateSingletons();
}
可以看到,方法中我们增加了一步:向beanFactory中添加了后置处理器。这样就可以保证在bean初始化的时候,执行到这个处理器了。由此也可以看出,Spring设计的这些功能,并不仅仅是用来给用户使用的,Spring内部本身也大量的用到了这些设计。
你要这个是吗?好,给你
那么接口我们也定义好了,难题也解决了。接下来就该正式的去实现这些方法了。我们刚才也说到,应该在创建bean的时候,将这些感知对象传递进去,那么我们就来改造一下创建bean的过程。让我看看!AbstractAutowireCapableBeanFactory
。
/**
* 初始化bean
*
* @param beanName bean名称
* @param bean 待初始化bean
* @param beanDefinition bean定义
* @return bean
*/
private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
//执行Aware感知操作
invokeAwareMethods(beanName, bean);
//执行beanPostProcessor before处理
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
//执行bean初始化内容
try {
invokeInitMethod(beanName, wrappedBean, beanDefinition);
} catch (Exception e) {
throw new BeanException("执行bean初始化方法失败,bean名称:" + beanName);
}
//执行beanPostProcessor after处理
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
/**
* 执行bean注册的感知接口相应操作
*
* @param beanName
* @param bean
*/
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(this);
}
if (bean instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) bean).setBeanClassLoader(getClassLoader());
}
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
}
}
可以看到,我们把操作放在了初始化中,在执行初始化前置处理之前,我们先执行了Aware感知的相关操作。操作的内容和前面对ApplicationContext的类似,我们就不多说了。invokeAwareMethods
方法执行完毕后,紧接着就会到bean初始化前后置处理器的执行。在那里又会对ApplicationContext进行处理。
这里有一点点地方需要注意的是,可以看到对BeanClassLoader的处理,这里调用了getClassLoader方法。但实际上前面我们并没有这个方法。这个方法是这次新加的,加在了 AbstractBeanFactory
中,内容也很简单。
private ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
public ClassLoader getClassLoader() {
return this.classLoader;
}
我全都要!快给我!
好了,这次我们的测试环节来的格外的快,因为这次内容还是相对来说比较简单的(虽然不像第一章那样简单)。来看看我们bean的变化吧
package com.akitsuki.springframework.test.bean;
import com.akitsuki.springframework.beans.factory.*;
import com.akitsuki.springframework.context.ApplicationContext;
import com.akitsuki.springframework.context.ApplicationContextAware;
import lombok.Getter;
import lombok.Setter;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:42
*/
@Getter
@Setter
public class UserService implements InitializingBean, DisposableBean,
BeanFactoryAware, BeanClassLoaderAware, BeanNameAware, ApplicationContextAware {
private BeanFactory beanFactory;
private ClassLoader beanClassLoader;
private String beanName;
private ApplicationContext applicationContext;
private String dummyString;
private int dummyInt;
private UserDao userDao;
public void queryUserInfo(Long id) {
System.out.println("dummyString:" + dummyString);
System.out.println("dummyInt:" + dummyInt);
String userName = userDao.queryUserName(id);
if (null == userName) {
System.out.println("用户未找到>_<");
} else {
System.out.println("用户名:" + userDao.queryUserName(id));
}
}
@Override
public void destroy() throws Exception {
System.out.println("userService的destroy执行了");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("userService的afterPropertiesSet执行了");
}
}
嗯,真的是我全都要。实现了我们上面完成的那四个接口,这也导致了我们的UserService实现的接口数量达到了可怕的6个,承受着这个年纪不该承受的重量。至于这里看起来没有实现接口方法的原因,是因为这些接口都是set方法,而这里通过lombok的@Setter注解,刚好为这些属性提供了setter方法,所以就没有显式的去写了。
接下来是我们的正式测试类
package com.akitsuki.springframework.test;
import com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/15 13:58
*/
public class ApiTest {
@Test
public void test() {
//初始化BeanFactory
ClasspathXmlApplicationContext context = new ClasspathXmlApplicationContext("classpath:spring.xml");
context.registerShutdownHook();
//获取bean,测试
UserService userService = context.getBean("userService", UserService.class);
userService.queryUserInfo(1L);
userService.queryUserInfo(4L);
userService.queryUserInfo(114L);
System.out.println("beanFactory:" + userService.getBeanFactory());
System.out.println("beanClassLoader:" + userService.getBeanClassLoader());
System.out.println("beanName:" + userService.getBeanName());
System.out.println("applicationContext:" + userService.getApplicationContext());
}
}
测试结果
执行UserDao的initMethod
userService的afterPropertiesSet执行了
dummyString:dummy
dummyInt:114514
用户名:akitsuki
dummyString:dummy
dummyInt:114514
用户名:hanazawa
dummyString:dummy
dummyInt:114514
用户未找到>_<
beanFactory:com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory@3551a94
beanClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
beanName:userService
applicationContext:com.akitsuki.springframework.context.support.ClasspathXmlApplicationContext@531be3c5
执行UserDao的destroyMethod
userService的destroy执行了
Process finished with exit code 0
可以看到,我们新加的那些Spring的部分,都已经被注入进来了。这也就意味着,我们的bean成功的感知到了Spring的部分。终于,这次练习也圆满结束了,值得鼓励。
相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring
,这里对应的代码是mini-spring-08