曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?
寫在前面的話
相關背景及資源:
曹工說Spring Boot原始碼系列開講了(1)-- Bean Definition到底是什麼,附spring思維導圖分享
工程程式碼地址 思維導圖地址
工程結構圖:
大體思路
總體來說,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
// 這裡獲取到的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 name="testService" class="org.springframework.simple.TestService" />
<bean id="testController" class="org.springframework.simple.TestController">
<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);
}
}
可以看到,已經注入進去了。沒有什麼問題。
總結
今天比較晚,寫得也比較急,有問題的話,請大家務必指出,謝謝大家
原始碼地址:
https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-beans-json-ext