曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享

工程代码地址 思维导图地址

工程结构图:

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

大体思路

总体来说,bean definition是什么,我们前面几讲,说了个大概了;目前,我们将聚焦于怎么获取bean definition。

我们这次做个实验,就是将bean definition(一共两个bean,有依赖关系,依赖是手动指定的)定义在json文件内,然后自定义一个applicationcontext,从该文件内读取bean definiton,最后我们测试下是否能work。

注意哈,这里的依赖,依然和前面讲的一样,都是手动指定依赖,类似@Autowired这种,还会放到后面才会讲,开车也要先学手动档嘛,是伐?

建议大家直接拖源码下来看:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-beans-json-extend

定义json文件

json文件内,要表达bean definition,按照我们前面说的,基本就包括几个必要的就行了,比如beanClassName。但我这里还是展示一个完整的,但我也是用fastjson先在之前的工程里生成了一个json,之后再拷贝到了json文件里:

// 这里获取到的bean definition的实际类型是 GenericBeanDefiniton,所以序列化出来的的json,就是一个 // GenericBeanDefiniton集合的json List<BeanDefinition> beanDefinitionList = factory.getBeanDefinitionList() JSON.toJSONString(beanDefinitionList)

json文件内容如下:

[ { "abstract": false, "autowireCandidate": true, "autowireMode": 0, "beanClass": "org.springframework.simple.TestService", "beanClassName": "org.springframework.simple.TestService", "constructorArgumentValues": { "argumentCount": 0, "empty": true, "genericArgumentValues": [], "indexedArgumentValues": {} }, "dependencyCheck": 0, "enforceDestroyMethod": true, "enforceInitMethod": true, "lazyInit": false, "lenientConstructorResolution": true, "methodOverrides": { "empty": true, "overrides": [] }, "nonPublicAccessAllowed": true, "primary": false, "propertyValues": { "converted": false, "empty": true, "propertyValueList": [], "propertyValues": [] }, "prototype": false, "qualifiers": [], "resolvedAutowireMode": 0, "role": 0, "scope": "", "singleton": true, "synthetic": false }, { "abstract": false, "autowireCandidate": true, "autowireMode": 0, "beanClass": "org.springframework.simple.byconstructor.TestControllerByConstructor", "beanClassName": "org.springframework.simple.byconstructor.TestControllerByConstructor", "constructorArgumentValues": { "argumentCount": 2, "empty": false, "genericArgumentValues": [], "indexedArgumentValues": { 0: { "converted": false, "value": { "beanName": "testService", "toParent": false } }, 1: { "converted": false, "value": "wire by constructor" } } }, "dependencyCheck": 0, "enforceDestroyMethod": true, "enforceInitMethod": true, "lazyInit": false, "lenientConstructorResolution": true, "methodOverrides": { "empty": true, "overrides": [] }, "nonPublicAccessAllowed": true, "primary": false, "propertyValues": { "converted": false, "empty": true, "propertyValueList": [], "propertyValues": [] }, "prototype": false, "qualifiers": [], "resolvedAutowireMode": 0, "role": 0, "scope": "", "singleton": true, "synthetic": false } ]

大家可能看得有点懵,其实换成xml,就是类似下面这样的:

<bean /> <bean> <constructor-arg ref="testService"/> </bean> 扩展 applicationContext package org.springframework.beans.extend.json.applicationcontext; import org.springframework.beans.BeansException; import org.springframework.beans.extend.json.JsonBeanDefinitionReader; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.ResourceEntityResolver; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractRefreshableConfigApplicationContext; import java.io.IOException; public class ClassPathJsonApplicationContext extends AbstractRefreshableConfigApplicationContext { @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //其实主要内容和xmlapplicationcontext是一样的,主要就是下面这行不一样,new了一个json reader JsonBeanDefinitionReader beanDefinitionReader = new JsonBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); // 这里通过json bean definiton reader去读取bean definition loadBeanDefinitions(beanDefinitionReader); } /** *通过json bean definiton reader去读取bean definition **/ protected void loadBeanDefinitions(JsonBeanDefinitionReader reader) throws BeansException, IOException { // 这里获取json文件的path,这个location是在new ClassPathJsonApplicationContext时传进来的 String[] configResources = getConfigLocations(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } } public ClassPathJsonApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } /** * 这里一模一样,不需要任何变化 **/ public ClassPathJsonApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } } 扩展jsonBeanDefinitionReader package org.springframework.beans.extend.json; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.core.NamedThreadLocal; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; import org.xml.sax.InputSource; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.*; /** * 类似 * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader} * 只是本类是去json文件里读取bean definition * */ @Slf4j public class JsonBeanDefinitionReader extends AbstractBeanDefinitionReader { private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<Set<EncodedResource>>("json bean definition resources currently being loaded"); public JsonBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); } @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // 以下照抄xmlbeanDefintionReader开始 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } EncodedResource encodedResource = new EncodedResource(resource); if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } //照抄xmlbeanDefintionReader结束 //这里的encodedResource.getResource()就是我们的json文件,这里通过spring core里面的一个工具类读取为InputStream String json = null; try (InputStream inputStream = encodedResource.getResource().getInputStream()) { json = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8")); } catch (IOException e) { log.error("{}",e); return 0; } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } //熟悉的fastjson,熟悉的味道 List<GenericBeanDefinition> list = JSON.parseArray(json, GenericBeanDefinition.class); if (CollectionUtils.isEmpty(list)) { return 0; } /** * 1:因为GenericBeanDefinition,只有setBeanClassName,所以bean反序列化时,只序列化了这个字 * 段;实际我们知道,beanClass很重要,所以我们只能自己处理一下了 * 2:第二个问题,我们在下面解释 **/ for (GenericBeanDefinition genericBeanDefinition : list) { /** * 1、处理beanClass */ Class<?> clazz = null; try { clazz = Thread.currentThread().getContextClassLoader().loadClass(genericBeanDefinition.getBeanClassName()); } catch (ClassNotFoundException e) { log.error("bean class cant be load for beandefinition: {}",genericBeanDefinition); throw new RuntimeException(); } genericBeanDefinition.setBeanClass(clazz); /** * 2、处理constructor问题,因为Object value = valueHolder.getValue(); * 是Object类型,但这个实际是一个可变类型,当构造器参数为String类型时,这个Object就是 * String类型的,当构造器参数类型为其他bean的引用时,这个object就是RuntimeBeanReference * 的, * 因为fastjson把我的object转成jsonobject类型了,所以这里要手动搞成RuntimeBeanReference */ ConstructorArgumentValues constructorArgumentValues = genericBeanDefinition.getConstructorArgumentValues(); if (constructorArgumentValues.isEmpty()) { continue; } Map<Integer, ConstructorArgumentValues.ValueHolder> map = constructorArgumentValues.getIndexedArgumentValues(); if (CollectionUtils.isEmpty(map)) { continue; } for (ConstructorArgumentValues.ValueHolder valueHolder : map.values()) { Object value = valueHolder.getValue(); if (value instanceof JSONObject) { JSONObject jsonObject = (JSONObject) value; RuntimeBeanReference runtimeBeanReference = jsonObject.toJavaObject(RuntimeBeanReference.class); valueHolder.setValue(runtimeBeanReference); } } } //这里new一个BeanNameGenerator,这是自带的 setBeanNameGenerator(new AnnotationBeanNameGenerator()); BeanNameGenerator beanNameGenerator = getBeanNameGenerator(); // 获取BeanDefinitionRegistry,bean factory默认实现了BeanDefinitionRegistry BeanDefinitionRegistry registry = getRegistry(); //注册bean definition到BeanDefinitionRegistry里面去 for (GenericBeanDefinition genericBeanDefinition : list) { String beanName = beanNameGenerator.generateBeanName(genericBeanDefinition, registry); registry.registerBeanDefinition(beanName,genericBeanDefinition); } return list.size(); } } 收工了,测试一下 public class BootStrap { public static void main(String[] args) { // new一个我们的自定义json上下文 ClassPathJsonApplicationContext context = new ClassPathJsonApplicationContext("beanDefinition.json"); // getBean试一下 TestControllerByConstructor bean = context.getBean(TestControllerByConstructor.class); System.out.println(bean); } }

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

可以看到,已经注入进去了。没有什么问题。

总结

今天比较晚,写得也比较急,有问题的话,请大家务必指出,谢谢大家

源码地址:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-beans-json-extend

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfxxs.html