标签:展示 lda ted 实体 nullable throwable call factory app
我们平时使用 Spring 时,想要 依赖注入 时使用最多的是 @Autowired
注解了,本文主要讲解 Spring 是如何处理该注解并实现 依赖注入 的功能的。
首先我们看一个测试用例:
User
实体类:
public class User {
private Long id;
private String name;
// 省略 get 和 set 方法
}
测试类:
public class AnnotationDependencyInjectTest {
/**
* @Autowired 字段注入
*/
@Autowired
private User user;
private City city;
/**
* @Autowired 方法注入
*/
@Autowired
public void initCity(City city) {
this.city = city;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AnnotationDependencyInjectTest.class);
context.refresh();
AnnotationDependencyInjectTest bean = context.getBean(AnnotationDependencyInjectTest.class);
// @Autowired 字段注入
System.out.println(bean.user);
// @Autowired 方法注入
System.out.println(bean.city);
UserHolder userHolder = context.getBean(UserHolder.class);
// @Autowired 构造器注入
System.out.println(userHolder.getUser());
context.close();
}
@Bean
public User user() {
User user = new User();
user.setId(1L);
user.setName("leisurexi");
return user;
}
@Bean
public City city() {
City city = new City();
city.setId(1L);
city.setName("北京");
return city;
}
/**
* @Autowired 构造函数注入
*/
static class UserHolder {
private User user;
@Autowired
public UserHolder(User user) {
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
}
上面分别展示了 @Autowired
注解的字段注入和方法注入,下面我们开始分析 Spring 是如何实现的。
首先使 @Autowired
注解生效的一个关键类是 AutowiredAnnotationBeanPostProcessor
,该类实现了 InstantiationAwareBeanPostProcessorAdapter
抽象类;该抽象类就是一个适配器的作用提供了接口方法的默认实现,InstantiationAwareBeanPostProcessorAdapter
又实现了 SmartInstantiationAwareBeanPostProcessor
接口,同时实现该接口的 determineCandidateConstructors()
方法可以指定 bean
的候选构造函数;然后 SmartInstantiationAwareBeanPostProcessor
接口又继承了 InstantiationAwareBeanPostProcessor
接口,该接口提供了 bean
实例化前后的生命周期回调以及属性赋值前的后置处理方法,@Autowired
注解的属性注入就是通过重写该接口的 postProcessProperties()
实现的。这两个接口都在 在 Spring IoC createBean 方法详解 一文中有介绍过。下面我们看一下 AutowiredAnnotationBeanProcessor
的继承关系图:
关于 AutowiredAnnotationBeanPostProcessor
这个后置处理器是怎么加入到 beanFactory
中的,我们在 Spring IoC component-scan 节点详解 一文中介绍过主要是通过 AnnotationConfigUtils#registerAnnotationConfigProcessors()
实现的。
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 忽略其它代码
// 注册用于处理@Autowired、@Value、@Inject注解的后置处理器
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new
RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def,
AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// 忽略其它代码
}
AutowiredAnnotationBeanPostProcessor
中跟属性注入有关的方法有两个:postProcessMergedBeanDefinition
和 postProcessPropertyValues
。
前者是 MergedBeanDefinitionPostProcessor
接口中的方法,定义如下:
public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {
/**
* 对指定bean的BeanDefinition合并后的处理方法回调
*/
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
/**
* @since 5.1
* 通知已重新设置指定beanName的BeanDefinition,如果实现该方法应该清除受影响的bean的所有元数据
*/
default void resetBeanDefinition(String beanName) {
}
}
后者是 InstantiationAwareBeanPostProcessor
接口中的方法,定义如下:
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
/**
* Bean 实例化前调用,返回非 {@code null} IoC 容器不会对 Bean 进行实例化 并且后续的生命周期回调方 * 法不会调用,返回 {@code null} 则进行 IoC 容器对 Bean 的实例化
*/
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
/**
* Bean 实例化之后,属性填充之前调用,返回 {@code true} 则进行默认的属性填充步骤,返回 {@code * false} 会跳过属性填充阶段。
*/
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
/**
* Bean 实例化后属性赋值前调用,PropertyValues 是已经封装好的设置的属性值,返回 {@code null} 继续
* 使用现有属性,否则会替换 PropertyValues。
* @since 5.1版本新加的和底下的方法一样
*/
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
throws BeansException {
return null;
}
/**
* 跟上面方法一样的功能,只不过是5.1以前版本所使用的
* 返回 {@code null} 会跳过属性填充阶段
*/
@Deprecated
@Nullable
default PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}
}
关于这两个方法的调用时机,可以查看 Spring IoC createBean 方法详解。
首先执行的是 postProcessMergedBeanDefinition()
。
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 寻找需要注入的字段或方法,并封装成 InjectionMetadata,见下文详解
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
// 检查元数据中的注解信息
metadata.checkConfigMembers(beanDefinition);
}
InjectionMetadata
就是注入的元信息描述,主要字段如下:
public class InjectionMetadata {
// 需要依赖注入的目标类
private final Class<?> targetClass;
// 注入元素的集合
private final Collection<InjectedElement> injectedElements;
// 忽略其它代码
}
InjectedElement
就是注入的元素,主要字段如下:
public abstract static class InjectedElement {
// 注入的属性或方法
protected final Member member;
// 需要注入的是否是字段
protected final boolean isField;
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
// 首先从缓存中获取
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
// 判断是否需要刷新,即metadata为null或者metadata中存储的targetClass和当前clazz不等
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// 这里相当于是一个double check,防止多线程出现的并发问题
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
// 构建注入元信息,见下文详解
metadata = buildAutowiringMetadata(clazz);
// 放入缓存中
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
// 返回注入元信息
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
// 判断当前类或其字段或其方法是否标注了autowiredAnnotationTypes中的注解,没有的话直接返回空的
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 遍历targetClass中的字段
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// 获取field上的@Autowired注解信息
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
// 如果字段是静态类型是不会进行注入的
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
// 获取@Autowired注解中的required属性
boolean required = determineRequiredStatus(ann);
// 将装成AutowiredFieldElement添加进currElements
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 遍历targetClass中的方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
// 找到桥接方法
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
// 判断方法的可见性,如果不可见则直接返回
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
// 获取method上的@Autowired注解信息
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
// 如果是静态方法是不会进行注入的
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
// 方法注入没有参数就违背了初衷,就是在脱裤子放屁
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " + method);
}
}
// 获取@Autowired注解中的required属性
boolean required = determineRequiredStatus(ann);
// 将方法和目标类型封装成属性描述符
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
// 封装成AutowiredMethodElement添加进currElements
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
// 将currElements整个添加进elements
elements.addAll(0, currElements);
// 获取targetClass的父类,进行下一次循环
targetClass = targetClass.getSuperclass();
}
// 当targetClass为空或者targetClass等于Object.class时会退出循环
while (targetClass != null && targetClass != Object.class);
// 将elements和clazz封装成InjectionMetadata返回
return InjectionMetadata.forElements(elements, clazz);
}
上面代码中的 findAutowiredAnnotation()
就是在遍历 autowiredAnnotationTypes
属性,看字段或者方法上的注解是否存在于 autowiredAnnotationTypes
中,或者其派生注解,找到第一个就返回,不会再继续遍历了。
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 ‘javax.inject.Inject‘ annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
}
从 AutowiredAnnotationBeanPostProcessor
类的构造函数中,我们可以发现 autowiredAnnotationTypes
默认添加了 @Autowired
、@Value
以及 @Inject
(在 JSR-330 的jar包存在于当前环境时)。
至此,使用 @Autowired
修饰的字段和方法已经封装成 InjectionMetadata
并放在 injectionMetadataCache
缓存中,便于后续使用。
postProcessMergedBeanDefinition()
调用后 bean
就会进行实例化接着调用 postProcessProperties()
。
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 获取缓存中的 InjectionMetadata
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 进行属性的注入
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
// 返回注入的属性
return pvs;
}
// InjectMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 获取检查后的元素
Collection<InjectedElement> checkedElements = this.checkedElements;
// 如果checkedElements不为空就使用checkedElements,否则使用injectedElements
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 遍历elementsToIterate
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean ‘" + beanName + "‘: " + element);
}
// AutowiredFieldElement、AutowiredMethodElement这两个类继承InjectionMetadata.InjectedElement,各自重写了inject方法
element.inject(target, beanName, pvs);
}
}
}
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 强转成Field类型
Field field = (Field) this.member;
Object value;
if (this.cached) {
// 如果缓存过,直接使用缓存的值,一般第一次注入都是false
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
// 构建依赖描述符
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
// 获取类型转换器
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
// 进行依赖解决,获取符合条件的bean
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
// 加锁
synchronized (this) {
// 如果没有被缓存
if (!this.cached) {
// 找到了需要的bean || 该字段是必要的
if (value != null || this.required) {
// 将依赖描述符赋值给cachedFieldValue
this.cachedFieldValue = desc;
// 注册bean的依赖关系,用于检测是否循环依赖
registerDependentBeans(beanName, autowiredBeanNames);
// 如果符合条件的bean只有一个
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
// beanFactory含有名为autowiredBeanName的bean && 类型是匹配的
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
// 将该属性解析到的bean的信息封装成ShortcutDependencyDescriptor
// 之后可以通过调用resolveShortcut()来间接调beanFactory.getBean()快速获取bean
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
// 缓存标识设置为true
this.cached = true;
}
}
}
// 如果找到了符合的bean,设置字段可访问,利用反射设置值
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
上面代码中的 beanFactory.resolveDependency()
在 Spring IoC createBean 方法详解 一文中有介绍过,这里不再赘述;同样 registerDependentBeans()
最终会调用 DefaultSingletonBeanRegistry.registerDependentBean()
,该方法在 Spring IoC getBean 方法详解 一文中有介绍过,这里也不再赘述。
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 检查是否需要跳过
if (checkPropertySkipping(pvs)) {
return;
}
// 强转成Method类型
Method method = (Method) this.member;
Object[] arguments;
if (this.cached) {
// Shortcut for avoiding synchronization...
// 如果缓存过,直接调用beanFactory.resolveDependency()返回符合的bean
arguments = resolveCachedArguments(beanName);
}
else {
// 获取参数数量
int argumentCount = method.getParameterCount();
arguments = new Object[argumentCount];
// 创建依赖描述符数组
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
// 记录用于自动注入bean的名称集合
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
// 获取类型转换器
TypeConverter typeConverter = beanFactory.getTypeConverter();
// 遍历参数
for (int i = 0; i < arguments.length; i++) {
// 将方法和参数的下标构建成MethodParameter,这里面主要记录了参数的下标和类型
MethodParameter methodParam = new MethodParameter(method, i);
// 将MethodParameter构建成DependencyDescriptor
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
// 进行依赖解决,找到符合条件的bean
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
// 这里跟字段注入差不多,就是注册bean的依赖关系,并且缓存每个参数的ShortcutDependencyDescriptor
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
}
// 找到了符合条件的bean
if (arguments != null) {
try {
// 设置方法可访问,利用反射进行方法调用,传入参数
ReflectionUtils.makeAccessible(method);
method.invoke(bean, arguments);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
构造器注入就是通过调用 determineCandidateConstructors()
来返回合适的构造器。
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException {
// Quick check on the concurrent map first, with minimal locking.
// 首先从缓存中获取
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
// 缓存为空
if (candidateConstructors == null) {
// Fully synchronized resolution now...
// 这里相当于double check
synchronized (this.candidateConstructorsCache) {
candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
Constructor<?>[] rawCandidates;
try {
// 获取beanClass的所有构造函数
rawCandidates = beanClass.getDeclaredConstructors();
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
// 存放标注了@Autowired注解的构造器
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
// 存放标注了@Autowired注解,并且required为true的构造器
Constructor<?> requiredConstructor = null;
Constructor<?> defaultConstructor = null;
for (Constructor<?> candidate : rawCandidates) {
// 获取构造器上的@Autowired注解信息
MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
if (ann == null) {
// 如果没有从候选者找到注解,则尝试解析beanClass的原始类(针对CGLIB代理)
Class<?> userClass = ClassUtils.getUserClass(beanClass);
if (userClass != beanClass) {
try {
Constructor<?> superCtor =
userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann = findAutowiredAnnotation(superCtor);
}
catch (NoSuchMethodException ex) {
// Simply proceed, no equivalent superclass constructor found...
}
}
}
if (ann != null) {
// 如果requiredConstructor不为空,代表有多个标注了@Autowired且required为true的构造器,此时Spring不知道选择哪个抛出异常
if (requiredConstructor != null) {
throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate +". Found constructor with ‘required‘ Autowired annotation already: " + requiredConstructor);
}
// 获取@Autowired注解的reuired属性的值
boolean required = determineRequiredStatus(ann);
if (required) {
// 如果当前候选者是@Autowired(required = true),则之前不能存在其他使用@Autowire注解的构造函数,否则抛异常
if (!candidates.isEmpty()) {
throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with ‘required‘ Autowired annotation: " + candidate);
}
// required为true将当前构造器赋值给requiredConstructor
requiredConstructor = candidate;
}
// 将当前构造器加入进候选构造器中
candidates.add(candidate);
}
// 没有标注了@Autowired注解且参数长度为0,赋值为默认构造器
else if (candidate.getParameterCount() == 0) {
defaultConstructor = candidate;
}
}
// 有标注了@Autowired注解的构造器
if (!candidates.isEmpty()) {
// Add default constructor to list of optional constructors, as fallback.
// 没有标注了@Autowired且required为true的构造器
if (requiredConstructor == null) {
// 默认构造器不为空
if (defaultConstructor != null) {
// 将默认构造器加入进候选构造器中
candidates.add(defaultConstructor);
}
}
// 将候选者赋值给candidateConstructors
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
// 只有1个构造器 && 参数长度大于0(非默认构造器),只能用它当做候选者了
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
// 只有1个构造器 && 参数长度大于0,只能用它当做候选者了
else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&
defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};
}
else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
candidateConstructors = new Constructor<?>[] {primaryConstructor};
}
// 返回一个空的Constructor
else {
candidateConstructors = new Constructor<?>[0];
}
// 缓存候选的构造器
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
// 如果候选构造器长度大于0,直接返回,否则返回null
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
关于 SmartInstantiationAwareBeanPostProcessor
接口的调用时机,在 Spring IoC createBean 方法详解 一文中有介绍过,这里就不再赘述了。
本文主要介绍了 Spring 对 @Autowired
注解的主要处理过程,结合前面的 Spring IoC getBean 方法详解 和 Spring IoC createBean 方法详解 文章一起看才能更好的理解。
最后,我模仿 Spring 写了一个精简版,代码会持续更新。地址:https://github.com/leisurexi/tiny-spring。访问新博客地址,观看效果更佳 https://leisurexi.github.io/
标签:展示 lda ted 实体 nullable throwable call factory app
原文地址:https://www.cnblogs.com/yijinqincai/p/13088395.html