IOC:https://juejin.cn/post/7366459005881778202
AOP:https://juejin.cn/spost/7368355894708584488
扩展一下Spring的其他功能
紧接着前两章的IOC和AOP的原理篇,本篇介绍下Spring中的高级功能特性
1 解析占位符
Spring可以使用占位符给属性赋值,数据来自配置文件,也就是根据占位符内容从配置文件中得到真实数据,然后填充到属性
很容易想到,这本质就是修改BeanDefinition,修改BeanDefinition就需要使用BeanFactory后处理器
定义一个BeanFactory后处理器PropertyPlaceholderConfigurer
,用于解析BeanDefinition并将其持有的属性值字符串中的占位符替换成真实数据
//赋值解析Bean属性的占位符 public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor { //配置文件地址 private String location;
public void setLocation(String location) { this.location = location; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { Properties properties = loadProperties(); processProperties(beanFactory, properties); } //加载配置文件 private Properties loadProperties() { try { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource(location); Properties properties = new Properties(); properties.load(resource.getInputStream()); return properties; } catch (IOException e) { throw new BeansException("加载配置文件失败", e); } } private void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) throws BeansException { String[] definitionNames = beanFactory.getBeanDefinitionNames(); for (String definitionName : definitionNames) { BeanDefinition bd = beanFactory.getBeanDefinition(definitionName); PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues(); for (PropertyValue pv : pvs) { Object value = pv.getValue(); //只解析字符串 if (value instanceof String strVal) { //解析字符串中的占位符并返回解析后的结果 String newValue = resolvePlaceholder(strVal, properties); //用解析后的新增替换就值 bd.getPropertyValues().addPropertyValue( new PropertyValue(pv.getName(), newValue)); } } } } //解析字符串中占位符的工具类,自己写的,了解一下 public String resolvePlaceholder(String target, Map<object, object=""> datasource) { //设置快慢指针,low遇到'${'停下来,high遇到'}'时如果low处于停止状态则high也停下来 //如果low、high指针都停止了,则获取两个指针中间的字符串作为key,从数据源获取值追加到结果字符串 //然后重置low、high指针,解析运行 int low = 0, high = 0; //low、high指针的停止与否 boolean lowStatus = false, highStatus = false; //用来保存格式化之后的字符串 StringBuilder sb = new StringBuilder(); //缓存lowStatus = true时,遍历到的字符串 StringBuilder cache = new StringBuilder(); for (int i = 0; i < target.length(); i++) { char currentChar = target.charAt(i); char nextChar = i == target.length() - 1 ? target.charAt(i) : target.charAt(i + 1); if (currentChar == 36 && nextChar == 123) { lowStatus = true; //如果遇到新的"${"组合时,上一个"${"还没有闭合,就将缓存写入sb,并清空缓存 if (cache.length() > 0) { sb.append(cache); cache = new StringBuilder(); low = i; } } if (lowStatus && currentChar == 125) { highStatus = true; } //遇到"}"时,闭合"${",并根据"${"和"}"中间的字符串,从数据源获取数据追加到sb,并清空缓存 if (lowStatus && highStatus) { String key = target.substring(low + 2, high); String value = datasource.getOrDefault(key, "").toString(); sb.append(value); //reset lowStatus = false; highStatus = false; low = high = i + 1; //"${"成功闭合后 cache = new StringBuilder(); continue; } //low指针没有停下来 if (!lowStatus) { low++; sb.append(currentChar); } //low指针停下里了,就缓存当前遍历的字符串 else { cache.append(currentChar); } //为了防止缓存还没有写入sb循环就结束了的情况,在最后一次循环时将缓存数据写入sb if (i == target.length() - 1) { sb.append(cache); } high++; } return sb.toString(); }
}
</object,>
为了方便使用,占位符解析器应该作为一个组件注册给BeanFactory,不过这都是后话了
将该BeanFactory后处理器注册进容器,它就会开始工作,就像下面这样
测试一下,准备一个配置文件
#application.properties
name=狗剩
然后启动我们前面写的容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:placeholder.xml");
Person person = context.getBean(“person”, Person.class);
assert person != null;
person.sayHello();
2 包扫描
Spring中可以指定一个扫描路径,然后扫描到该路径下所有符合条件的类并注册成BeanDefinition
定义一个注解,只要类上标注了该注解,就会被扫描到
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
再定义一个注解用来指定Bean的作用域范围
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
String value() default "singleton";
}
引入一个组件,负责扫描指定路径下标准了Component的类,并封装为BeanDefinition
public class ClassPathScanningCandidateComponentProvider {
//basePackage是指定包路径
public Set findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
//Spring为了优化性能会直接扫描磁盘上的类文件看是否加了指定注解,如果加了才加载该类
//这里为了简单直接使用hutool工具类进行扫描
Set> classes = ClassUtil.scanPackageByAnnotation(
basePackage, Component.class);
for (Class clazz : classes) {
BeanDefinition bd = new BeanDefinition(clazz);
candidates.add(bd);
}
return candidates;
}
}
在引入一个组件,负责注册扫描到的的BeanDefinition,由此可知,该组件肯定组合了BeanDefinitionRegistry
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { //由于要将扫描到的类注册成BeanDefinition,所以必须持有一个BeanDefinition注册表组件 private BeanDefinitionRegistry registry;
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { this.registry = registry; } public void doScan(String... basePackages) { for (String basePackage : basePackages) { Set<beandefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { //设置作用域 String scope = resolveBeanScope(candidate); if(StrUtil.isNotEmpty(scope)) { candidate.setScope(scope); } //推断Bean名称 String beanName = determineBeanName(candidate); registry.registerBeanDefinition(beanName, candidate); } } } //获取Bean作用域 private String resolveBeanScope(BeanDefinition beanDefinition) { Class<!--?--> beanClass = beanDefinition.getBeanClass(); Scope scope = beanClass.getAnnotation(Scope.class); if (scope != null) { return scope.value(); } return StrUtil.EMPTY; } //获取Bean名称 private String determineBeanName(BeanDefinition bd) { Class<!--?--> beanClass = bd.getBeanClass(); Component component = beanClass.getAnnotation(Component.class); String value = component.value(); //如果Component指定了名称,就是要指定的,如果没有指定,就使用当前类名首字母小写的字符串作为名称 if (StrUtil.isEmpty(value)) { value = StrUtil.lowerFirst(beanClass.getSimpleName()); } return value; }
}
组件有了,下面就是使用该组件了
在XML配置文件里定义一个新标签,如果加载XML时发现有这个标签就会调用ClassPathBeanDefinitionScanner进行包扫描,这个新标签就是context:component-scan
XmlBeanDefinitionReader修改如下代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { //负责扫描指定路径下标注了Component的类,并注册成BeanDefinition private void scanPackage(String scanPath) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry()); //路径可能有多个 scanner.doScan(StrUtil.splitToArray(scanPath, ",")); }
protected void doLoadBeanDefinitions(InputStream inputStream) { Document document = XmlUtil.readXML(inputStream); Element root = document.getDocumentElement(); NodeList childNodes = root.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { if (childNodes.item(i) instanceof Element) { String beanNode = childNodes.item(i).getNodeName(); if (BEAN_ELEMENT.equals(beanNode)){ //....该if分支代码不变 } //如果存在context:component-scan标签 else if ("context:component-scan".equals(beanNode)) { Element bean = (Element) childNodes.item(i); String packages = bean.getAttribute("base-package"); if (StrUtil.isEmpty(packages)) { throw new BeansException("当context:component-scan存在时,其属性不能为空"); } scanPackage(packages); } } } }
}
XML文件如下
测试一下
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:package-scan.xml");
Target target = context.getBean(“target”, Target.class);
//XML文件并没有定义Target,但是这里却会得到Target,说明扫描成功了
assert target != null;
target.bar(2);
3 @Value注解
注解开发,在Bean的属性上添加@Value
直接,会根据占位符从环境中获取真实值并赋值给该属性
思路大致如下:
- 给BeanFactory注册一个组件用于解析占位符字符串
- 准备一个特殊的Bean后处理器中解析@Value注解,后处理器返回的Bean就是解析之后被赋值的Bean
- 在构造之后,属性填充前,回调这个特殊的Bean后处理器
思路有了,开始编码
首先添加一个新注解@Value
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Value {
//类似"hello,${name}"这种占位符字符串就放在该字段中
String value();
}
定义一个解析器组件用于解析Value注解的占位符字符串
public interface StringValueResolver {
//解析占位符字符串strVal,并返回解析后的真实字符串
String resolveStringValue(String strVal);
}
解析器实现类如下,由于解析占位符的代码在前面PropertyPlaceholderConfigurer中就实现了,肯定不会重新实现而是调用其方法,为了方便调用,下面的解析器实现类应该定义为PropertyPlaceholderConfigurer内部类
//调用PropertyPlaceholderConfigurer中的resolvePlaceholder方法,为了方便,该类是 class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final Properties properties; public PlaceholderResolvingStringValueResolver(Properties properties) { this.properties = properties; } public String resolveStringValue(String strVal) throws BeansException { //调用resolvePlaceholder解析字符串中的占位符 return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties); }
}
ConfigurableBeanFactory中定义了解析器的规范
,所以添加两个跟解析器有关的抽象方法
public interface ConfigurableBeanFactory
extends HierarchicalBeanFactory, SingletonBeanRegistry {
//上面两个抽象方法这里省略了
//.....
//注册解析器
void addEmbeddedValueResolver(StringValueResolver valueResolver);
//解析占位符字符串
String resolveEmbeddedValue(String value);
}
AbstractBeanFactory新增一个容器来保存所有的解析器,并实现了上面新增的两个抽象方法
//下面出现的都是添加或者修改的代码,其他代码原封不动 public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory { //保存解析器的集合 private final List embeddedValueResolvers = new ArrayList();
//注册解析器 @Override
public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
this.embeddedValueResolvers.add(valueResolver);
}
//解析占位符字符串
@Override
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
}
return result;
}
}
在哪注册解析器呢?肯定是在BeanFactory后处理器中,将来容器一启动就会回调所有BeanFactory后处理器的postProcessBeanFactory方法,PropertyPlaceholderConfigurer修改代码如下
//该类的其他代码不动 public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //加载属性配置文件 Properties properties = loadProperties(); //属性值替换占位符 processProperties(beanFactory, properties);
//下面两行就是新增的代码
//往容器中添加字符解析器,供解析@Value注解使用
StringValueResolver resolver =
new PlaceholderResolvingStringValueResolver(properties);
beanFactory.addEmbeddedValueResolver(resolver);
}//PlaceholderResolvingStringValueResolver设置为内部类 private class PlaceholderResolvingStringValueResolver implements StringValueResolver { //.... }
}
@Value注解的解析应该在Bean构造之后属性填充之前进行,也就是InstantiationAwareBeanPostProcessor的方法postProcessPropertyValues,AOP篇加了该的该抽象方法一直没有真正实现
现在给InstantiationAwareBeanPostProcessor新增一个实现类,实现的postProcessPropertyValues方法专用于解析@Value注解
//用于解析Value和Autowired注解的Bean后处理器 public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory; public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } //构造后 @Override public Object postProcessBeforeInstantiation(Class<!--?--> beanClass, String beanName) throws BeansException { return null; } //构造后 @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { return true; } //依赖注入 @Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException { //解析Value注解 Class<!--?--> clazz = bean.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Value valueAnno = field.getAnnotation(Value.class); if (valueAnno != null) { //开始解析 String value = beanFactory.resolveEmbeddedValue(valueAnno.value()); //使用hutool工具类将解析后的真实属性值赋值给Bean BeanUtil.setFieldValue(bean, field.getName(), value); } } return pvs; } //前初始化 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } //后初始化 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }
}
该Bean后处理器非常常用,这种Bean后处理器的注册不会写在XML文件中,而是硬编码
在代码中
ClassPathBeanDefinitionScanner中就硬编码了对AutowiredAnnotationBeanPostProcessor的注册
//该类修改的代码 public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { //AutowiredAnnotationBeanPostProcessor的名称是固定的 public static final String AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalAutowiredAnnotationProcessor";
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { this.registry = registry; } public void doScan(String... basePackages) { for (String basePackage : basePackages) { Set<beandefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { //设置作用域 String scope = resolveBeanScope(candidate); if(StrUtil.isNotEmpty(scope)) { candidate.setScope(scope); } //推断Bean名称 String beanName = determineBeanName(candidate); registry.registerBeanDefinition(beanName, candidate); } } //注册一个用于解析Value和Autowired注解的后处理器 registry.registerBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class)); } //其他代码不变.....
}
前面已经实现applyBeanPostprocessorsBeforeApplyingPropertyValues,AbstractAutowireCapableBeanFactory不用修改
至此,@Value的解析功能就实现了
测试前添加一个XML配置文件,application.properties用前面定义的文件
<!--添加解析占位符的后处理器--> <bean class="org.springframework.beans.factory.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:application.properties"> </property></bean>
</context:component-scan>
org.springframework.test.bean
这个包下的Person类修改一下
@Component
public class Person {
//给name属性添加Value注解
@Value("${name}")
private String name;
//其他代码不变
}
然后测试
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:anno.xml");
Person person = context.getBean("person", Person.class);
assert person != null;
//发现person对象的name属性值为:zhangsan,正是application.properties中定义的值
person.sayHello();
4 @Autowired注解
@Autowired
注解的解析也很简单,该注解和@Value
可以一块解析
添加一个@Autowired注解
//该注解没有任何属性,纯粹打标记
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface Autowired {
}
再添加一个注解@Qualifier
用于配合@Autowired
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Inherited
@Documented
public @interface Qualifier {
//根据名称寻找Bean
String value() default "";
}
@Autowired单独使用就是根据类型寻找Bean,配合@Qualifier就是根据名称和类型寻找Bean
所以BeanFacotry需要增加一个抽象方法,用于根据类型得到唯一Bean
public interface BeanFactory {
//根据类型获取Bean
T getBean(Class requiredType) throws BeansException;
}
DefaultListableBeanFactory负责实现这个方法
//该方法实现自BeanFactory
public T getBean(Class requiredType) throws BeansException {
List beanNames = new ArrayList<>();
for (Map.Entry entry : beanDefinitionMap.entrySet()) {
Class beanClass = entry.getValue().getBeanClass();
if (requiredType.isAssignableFrom(beanClass)) {
beanNames.add(entry.getKey());
}
}
//判断下是否唯一
if (beanNames.size() == 1) {
return getBean(beanNames.get(0), requiredType);
}
//不唯一则报错
throw new BeansException(requiredType + "expected single bean but found " +
beanNames.size() + ": " + beanNames);
}
AbstractApplicationContext也要实现该方法
@Override
public T getBean(Class requiredType) throws BeansException {
return getBeanFactory().getBean(requiredType);
}
然后在AutowiredAnnotationBeanPostProcessor类的postProcessPropertyValues方法增加中对@Autowired的支持
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
//处理@Value注解的代码不变
//…
//增加处理@Autowired注解的代码
for (Field field : fields) {
Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);
if (autowiredAnnotation != null) {
Class fieldType = field.getType();
String dependentBeanName = null;
Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);
Object dependentBean = null;
//如果加了@Qualifier,就根据名称和类型获取Bean
if (qualifierAnnotation != null) {
dependentBeanName = qualifierAnnotation.value();
dependentBean = beanFactory.getBean(dependentBeanName, fieldType);
}
//如果没有加@Qualifier,就根据类型获取Bean
else {
dependentBean = beanFactory.getBean(fieldType);
}
BeanUtil.setFieldValue(bean, field.getName(), dependentBean);
}
}
return pvs;
}
}
在Spring5以后,使用@Autowired进行属性注入时,如果该类型的Bean有多个,则会以属性名作为BeanName寻找Bean,而不需使用@Qualifier指定名称,代码实现其实也很简单,有兴趣可以自己实现一下
测试阶段,在上一节的基础上,修改application.properties
name=zhangsan
idNumber=114514
再修改下Person和IDCard类,确保这两个要被扫描到哦
@Component
public class IDCard {
@Value("${idNumber}")
private String idNumber;
//其他代码不变
}
@Component
public class Person {
@Value("${name}")
private String name;
@Autowired
private IDCard idCard;
}
XML配置文件anno.xml
不变,测试代码如下
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:anno.xml");
Person person = context.getBean("person", Person.class);
assert person != null;
//发现person的name、idCard都被正常填充了
person.sayHello();
5 循环依赖(没有代理对象)
IOC篇留了个坑,那就是循环依赖
class A{ @Autowired private B b;
public B getB() { return b; }
}
class B{
@Autowired
private A a;public A getA() { return a; }
}
产生循环依赖的本质就是如下
-
调用getBean(A.class),发现A在单例缓存中不存在,就走A的实例化流程
-
A的实例化过程中,发现依赖B,就会getBean(B.class),但是B在单例缓存中不存在,就走B的实例化流程
-
B的实例化过程中,发现依赖A,就会getBean(A.class),又会循环到第一步
就这样一直递归,直到爆栈
产生循环依赖的原因很简单,那就是在初始化Bean完成后才将Bean放入单例缓存,但是在依赖注入和属性填充阶段就开始产生依赖了
解决的方式也很简单,那就是将Bean放入单例缓存的时间提前到构造
之后,提前将Bean的引用暴露出来,依赖时不就能从缓存中获取到了吗
将原来的初始化Bean后放入的缓存singletonObjects称为一级缓存
,这里构造后放入的缓存称为二级缓存
修改单例Bean注册表DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
//一级缓存 public Map<string, object=""> singletonObjects = new ConcurrentHashMap<>(256); //二级缓存 public Map<string, object=""> earlySingletonObjects = new ConcurrentHashMap<>(256); //修改获取单例的逻辑 @Override public Object getSingleton(String beanName) { //先从一级缓存获取Bean Object bean = singletonObjects.get(beanName); //一级缓存没有,就从二级缓存获取Bean if (bean == null) { bean = earlySingletonObjects.get(beanName); } return bean; } //修改添加单例的逻辑 public void addSingleton(String beanName, Object singletonObject) { this.singletonObjects.put(beanName, singletonObject); //Bean放入一级缓存后,就从二级缓存移除 this.earlySingletonObjects.remove(beanName); }
}
</string,></string,>
然后修改一下doCreateBean的逻辑
protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
Object bean = null;
try {
//构造
bean = createBeanInstance(beanDefinition);
//构造后,放入二级缓存,将Bean的引用提前暴露出来,供依赖注入和属性填充时使用
if (beanDefinition.isSingleton()) {
earlySingletonObjects.put(beanName, bean);
}
//其他代码不变.....
} catch (Exception e) {
throw new BeansException("实例化bean失败", e);
}
//其他代码不变.....
}
测试一下
//使用二级缓存的方案 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:circular-reference.xml");
A a = context.getBean(A.class);
B b = context.getBean(B.class);
assert a == b.getA();
6 循环依赖(有代理对象)
此时有个问题,如果A被代理了,那么放入二级缓存的依然是原始A对象而非A的代理对象
实例化B时,发现B依赖A,就从二级缓存找到A,也就是原始A对象赋值给B的属性
B持有的A就是原始A对象,而不是A的代理对象
验证一下上面结论,给A添加切面
<!--Bean后处理器,这里应该使用CGLIB代理--> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <!--添加前置通知、前置通知的适配器,以及advisor切面--> <bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="expression" value="execution(* org.springframework.test.bean.A.getB())"> <property name="advice" ref="beforeAdviceMethodInterceptor"> </property></property></bean> <bean id="beforeAdviceMethodInterceptor" class="org.springframework.aop.framework.advice.MethodBeforeAdviceInterceptor"> <property name="advice" ref="beforeAdvice"> </property></bean> <bean id="beforeAdvice" class="org.springframework.test.service.MyBeforeAdvice">
</context:component-scan>
判断B持有的A,是否和容器中的A是同一个对象,发现并不是同一个对象
//使用二级缓存的方案 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:circular-reference.xml");
A a = context.getBean(A.class);
B b = context.getBean(B.class);
//看A有没有被增强
a.getB();
//看A和B持有的A是否是同一个对象
assert a == b.getA();
在Spring中,如果A被代理了,那么B依赖的A依然是代理对象,这是怎么做到的呢?
其实也很简单,再像上面一样提前创建代理对象存入三级缓存,并将其引用暴露出来就行了
三级缓存需要延迟获取,故需要用到Lambda的特性,引入了一个函数式接口
@FunctionalInterface
public interface ObjectFactory {
//该方法返回值就是被放入三级缓存的Bean
T getObject() throws BeansException;
}
DefaultSingletonBeanRegistry修改成如下代码
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { //一级缓存 public Map singletonObjects = new ConcurrentHashMap<>(256); //二级缓存 public Map earlySingletonObjects = new ConcurrentHashMap<>(256); //三级缓存 private Map> singletonFactories = new ConcurrentHashMap<>(256);
@Override public Object getSingleton(String beanName) { //先从一级缓存获取Bean Object bean = singletonObjects.get(beanName); //一级缓存没有,就从二级缓存获取Bean if (bean == null) { bean = earlySingletonObjects.get(beanName); //如果二级缓存也是空,就从三级缓存获取 if (bean == null) { ObjectFactory<!--?--> singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { bean = singletonFactory.getObject(); //将单例Bean从三级缓存放入二级缓存 earlySingletonObjects.put(beanName, bean); singletonFactories.remove(beanName); } } } return bean; } public void addSingleton(String beanName, Object singletonObject) { this.singletonObjects.put(beanName, singletonObject); //Bean放入一级缓存后,从二级缓存移除 this.earlySingletonObjects.remove(beanName); //再从三级缓存移除 this.singletonFactories.remove(beanName); } //向三级缓存添加数据 protected void addSingletonFactory(String beanName, ObjectFactory<!--?--> singletonFactory) { singletonFactories.put(beanName, singletonFactory); }
}
</string,></string,></string,>
InstantiationAwareBeanPostProcessor增加一个方法,用于提前创建并暴露代理类型
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
//getEarlyBeanReference意为提前暴露bean
//该方法并不需要所有实现类实现,所以设计成默认方法
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
}
只有DefaultAdvisorAutoProxyCreator才需要实现该方法
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
//增加一个缓存用来记录哪些Bean在getEarlyBeanReference中提前被处理了
private Set<object> earlyProxyReferences = new HashSet<>();
//相当于将代理类的创建从后初始化阶段提前到了该方法中
//如果该方法执行过了,那么该类的后初始化方法也就没有必要执行了
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
//该方法已经处理过beanName了,在后初始化阶段就不用重复处理了
earlyProxyReferences.add(beanName);
return wrapIfNecessary(bean, beanName);
}
//后初始化
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//如果getEarlyBeanReference中已经处理过该Bean,那么这里就不用重复处理了
if (!earlyProxyReferences.contains(beanName)) {
return wrapIfNecessary(bean, beanName);
}
return bean;
}
private Object wrapIfNecessary(Object bean, String beanName) {
//将避免死循环的代码移到wrapIfNecessary中
if (isInfrastructureClass(bean.getClass())) {
return bean;
}
//其他代码不变.....
}
}
最后修改AbstractAutowireCapableBeanFactory类的代码
public abstract class AbstractAutowireCapableBeanFactory
extends AbstractBeanFactory implements AutowireCapableBeanFactory {
protected Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
Object bean = null;
try {
bean = createBeanInstance(beanDefinition);
//构造后,放入三级缓存,可以同时解决有代理对象的循环依赖
if (beanDefinition.isSingleton()) {
Object finalBean = bean;
//这里设计成Lambda表达式的好处是,getEarlyBeanReference不会立即执行
//只有实例化B时,从缓存获取A,才会执行getEarlyBeanReference并创建A的代理对象
addSingletonFactory(
beanName,
() -> getEarlyBeanReference(beanName, beanDefinition, finalBean)
);
}
//调用bean后处理器执行构造后的逻辑,如果返回false,就不走后面实例化流程
boolean continueWithPropertyPopulation
= applyBeanPostProcessorsAfterInstantiation(beanName, bean);
if (!continueWithPropertyPopulation) {
return bean;
}
//依赖注入
applyBeanPostprocessorsBeforeApplyingPropertyValues(
beanName, bean, beanDefinition);
//属性填充
applyPropertyValues(beanName, bean, beanDefinition);
//前初始化、初始化、后初始化
bean = initializeBean(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("实例化bean失败", e);
}
//将成品Bean注册到单例Bean注册表之前,先注册拥有销毁方法的Bean
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
//实例化完成后,将Bean注册进单例Bean注册表
Object exposedObject = bean;
if (beanDefinition.isSingleton()) {
//如果有代理对象,此处获取代理对象
exposedObject = getSingleton(beanName);
//注册单例Bean的同时,移除二级、三级缓存的Bean
addSingleton(beanName, exposedObject);
}
return bean;
}
//回调DefaultAdvisorAutoProxyCreator后处理器的getEarlyBeanReference方法
protected Object getEarlyBeanReference(String beanName,
BeanDefinition beanDefinition, Object bean) {
Object exposedObject = bean;
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
exposedObject = ((InstantiationAwareBeanPostProcessor) bp)
.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return exposedObject;
}
}
}
return exposedObject;
}
}
测试一下
//使用三级缓存的方案解决有代理时的循环依赖
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:circular-reference.xml");
A a = context.getBean(A.class);
B b = context.getBean(B.class);
assert b.getA() == a;
7 类型转换
8 懒加载
当容器上下文对象启动时,并不希望一次性实例化全部单例Bean,可以引入懒加载机制
给BeanDefinition增加一个属性,表示Bean是否需要懒加载,默认false
public class BeanDefinition {
//是否需要懒加载
private boolean lazyInit = false;
public void setLazyInit(boolean b){
lazyInit=b;
}
public boolean isLazyInit(){
return lazyInit;
}
//其他代码不变
}
然后XmlBeanDefinitionReader解析lazyInit标签并赋值给BeanDefinition的lazyInit属性,很简单,这就省略了
然后DefaultListableBeanFactory#preInstantiateSingletons中,判断一下,如果是lazyInit = true,就不立刻实例化
@Override
public void preInstantiateSingletons() throws BeansException {
beanDefinitionMap.forEach((beanName, beanDefinition) -> {
if (beanDefinition.isSingleton() && !beanDefinition.isLazyInit()) {
getBean(beanName);
}
});
}
只有将来主动getBean时,才实例化对应的Bean
这是一个从 https://juejin.cn/post/7369052013151780916 下的原始话题分离的讨论话题