写在前面的话

相关背景及资源:

曹工说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先在之前的工程里生成了一个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 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-extend

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

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

    写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...

  2. 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 正 ...

  3. 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 大 ...

  4. 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  6. 曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  7. 曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  9. # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

随机推荐

  1. goroutiine同步/channel、互斥锁、读写锁、死锁/条件变量

    1. Goroutine同步[数据同步] 为什么需要goroutine同步 gorotine同步概念.以及同步的几种方式 1.1 为什么需要goroutine同步 package main impor ...

  2. 20191121-6 Scrum立会报告+燃尽图 02

    此作业的要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/10066一.小组情况 队名:扛把子 组长:孙晓宇 组员:宋晓丽 梁梦瑶 韩 ...

  3. Redis面试题详解:哨兵+复制+事务+集群+持久化等

    Redis主要有哪些功能? 1.哨兵(Sentinel)和复制(Replication) Redis服务器毫无征兆的罢工是个麻烦事,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制. ...

  4. 数据降维-PCA主成分分析

    1.什么是PCA? PCA(Principal Component Analysis),即主成分分析方法,是一种使用最广泛的数据降维算法.PCA的主要思想是将n维特征映射到k维上,这k维是全新的正交特 ...

  5. Linux 基本命令操作 (文件共享) 一

    前言:在学习Linux过程中,遇到一些经典而又基本的命令操作,想记录下来去帮助刚学Linux的同学.下面是有关相关的操作,我会进行详细的分解步骤:希望能够帮助到你们.由于时间仓促,再加上笔者的能力有限 ...

  6. getElementById()

    getElementById():方法的用途是寻找一个有着给定id属性值得元素: element = document.getElementById(ID); 这个方法将返回一个有着给定id属性值得元 ...

  7. kubectl: Error from server: error dialing backend: remote error: tls: internal error

    使用kubectl logs,发现报了tls的错误,然后查看kubelet的日志,发现报了上面的错误,然后通过命令kubectl get csr查看发现有很多处于pending状态 最后通过命令 ku ...

  8. Xtrabackup 全备和还原以及增量备份和还原

    目录 MySQL环境介绍 全备和还原 准备备份目录 创建测试数据 全量备份 模拟删除数据 还原数据操作 第一步 备份备份文件 第二步 关闭数据库 第三步 移除数据库的data目录 第四步 恢复前准备 ...

  9. centos7更新php5.4到php5.6/php7

    centos7系统yum安装的php版本为5.4. 因业务需求,开发可能需要php5.6环境. 本文应需而生,介绍从php5.4升级到php5.6. 如需更新到php7环境,步骤一样. 如果是线上应用 ...

  10. 如何将本地项目推送到码云仓库或者GitHub仓库

    将本地项目推送到码云仓库. 前提: git 和码云   1.在码云上先创建一个仓库. 2.在要被上传的项目的目录右键, 选择 Git Bash Here. 3.在窗口输入命令: git init 这时 ...