深入理解-Spring-之源码剖析IOC(二)

简介:

5. 如何创建Bean实例并构建Bean的依赖关系网

我们刚刚创建了Bean工厂,并创建 BeanDefinitions 放进Map里,以beanName为key。那么我们现在有了Bean定义,但还没有实例,也没有构建Bean与Bean之间的依赖关系。

我们知道,构建依赖关系是 IOC 的一个重要的任务,我们怎么能放过。那么是在哪里做的呢?在 finishBeanFactoryInitialization(beanFactory) 方法中。该方法中重要的一步是 : beanFactory.preInstantiateSingletons(),我们有必要看看该方法实现:

 
@Override
public void preInstantiateSingletons() throws BeansException {
if ( this .logger.isDebugEnabled()) {
this .logger.debug( "Pre-instantiating singletons in " + this );
}

// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>( this .beanDefinitionNames);

// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName); // 注意:FactoryBean
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
((SmartFactoryBean<?>) factory).isEagerInit(),
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName); // 创建bean
}
}
}

// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null ) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null ;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}

该方法首先循环所有的BeanNames,并且调用getBean方法,该方法实际上就是创建bean并递归构建依赖关系。该方法会调用 doGetBean(name, null, null, false),我们进入该方法查看,该方法很长,楼主挑选重要代码:

 
String [] dependsOn = mbd.getDependsOn(); // if (dependsOn != null) {
for ( String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'" );
}
registerDependentBean(dep, beanName);
getBean(dep); // 递归
}} // Create bean instance.if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

可以看到,该方法首先会获取依赖关系,拿着依赖的BeanName 递归调用 getBean方法,直到调用 getSingleton 方法返回依赖bean,而 getSingleton 方法的参数是 createBean 返回的实例,该方法内部调用 AbstractAutowireCapableBeanFactory.doCreateBean 方法:

 
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object [] args)
throws BeanCreationException {

// Instantiate the bean.
BeanWrapper instanceWrapper = null ;
if (mbd.isSingleton()) {
instanceWrapper = this .factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null ) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed" , ex);
}
mbd.postProcessed = true ;
}
}

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug( "Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references" );
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed" , ex);
}
}

if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false );
if (earlySingletonReference != null ) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (! this .allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String [] dependentBeans = getDependentBeans(beanName);
Set< String > actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for ( String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example." );
}
}
}
}

// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature" , ex);
}

return exposedObject;
}

该方法很长,我们重点关注以下2行代码:

instanceWrapper = createBeanInstance(beanName, mbd, args) 创建实例。

populateBean(beanName, mbd, instanceWrapper) , 该方法用于填充Bean,该

  1. 方法可以就是说就是发生依赖注入的地方。

我们看看 createBeanInstance 方法:

 
protected BeanWrapper createBeanInstance( String beanName, RootBeanDefinition mbd, @Nullable Object [] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);

if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}

Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null ) {
return obtainFromSupplier(instanceSupplier, beanName);
}

if (mbd.getFactoryMethodName() != null ) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}

// Shortcut when re-creating the same bean...
boolean resolved = false ;
boolean autowireNecessary = false ;
if (args == null ) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null ) {
resolved = true ;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null , null );
}
else {
return instantiateBean(beanName, mbd);
}
}

// Need to determine the constructor...
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}

// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}

该方法的doc注释是这样介绍的:为指定的bean创建一个新的实例,使用适当的实例化策略:工厂方法、构造函数自动装配或简单实例化。

我们看,该方法首先创建Class 对象,然后获取构造器对象,最后调用 instantiateBean(beanName, mbd) 方法,我们看看该方法实现:

 
protected BeanWrapper instantiateBean( final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
final BeanFactory parent = this;
if (System.getSecurityManager() != null ) {
beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
getInstantiationStrategy().instantiate(mbd, beanName, parent ),
getAccessControlContext());
}
else {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent );
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Instantiation of bean failed" , ex);
}
}

该方法核心逻辑是 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent),携带BeanName ,RootBeanDefinition ,发挥的策略对象是 SimpleInstantiationStrategy,该方法内部调用静态方法 BeanUtils.instantiateClass(constructorToUse), 组后调用 Constructor 的 newInstance 方法, 也就是最终使用反射创建了该实例:

 
public static <T> T instantiateClass (Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null" );
try {
ReflectionUtils.makeAccessible(ctor);
return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?" , ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?" , ex);
}
catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor" , ex);
}
catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception" , ex.getTargetException());
}
}

该方法会判断是否是 Kotlin 类型。如果不是,则调用构造器的实例方法。

到这里,我们的实例已经创建。但是我们的实例的依赖还没有设置,刚刚我们在 doCreateBean方法说关心以下2行代码:

instanceWrapper = createBeanInstance(beanName, mbd, args) 创建实例。

populateBean(beanName, mbd, instanceWrapper) , 该方法用于填充Bean,该方法可以就是说就是发生依赖注入的地方。

我们已经解析了第一个,现在看第二个方法:

 
protected void populateBean( String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
if (bw == null ) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance" );
}
else {
// Skip property population phase for null instance.
return ;
}
}

// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
boolean continueWithPropertyPopulation = true ;

if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false ;
break ;
}
}
}
}

if (!continueWithPropertyPopulation) {
return ;
}

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null );

if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

// Add property values based on autowire by name if applicable.
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}

// Add property values based on autowire by type if applicable.
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}

pvs = newPvs;
}

boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);

if (hasInstAwareBpps || needsDepCheck) {
if (pvs == null ) {
pvs = mbd.getPropertyValues();
}
PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
if (hasInstAwareBpps) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvs == null ) {
return ;
}
}
}
}
if (needsDepCheck) {
checkDependencies(beanName, mbd, filteredPds, pvs);
}
}

if (pvs != null ) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}

该方法核心逻辑是

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null),

即获取该bean的所有属性,也就是我们配置property元素。最后执行 applyPropertyValues(beanName, mbd, bw, pvs) 方法。

注意,现在的PropertyValues 都是字符串,没有值的,这个方法的作用就是获取值,关键代码:

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue)

该方法会获取 pvName 所对应的容器value,该方法内部会调用 BeanWrapperImpl.resolveReference(argName, ref) 方法,我们看看该方法:

 
@Nullable
private Object resolveReference( Object argName, RuntimeBeanReference ref) {
try {
Object bean;
String refName = ref.getBeanName();
refName = String .valueOf(doEvaluate(refName));
if (ref.isToParent()) {
if ( this .beanFactory.getParentBeanFactory() == null ) {
throw new BeanCreationException(
this .beanDefinition.getResourceDescription(), this .beanName,
"Can't resolve reference to bean '" + refName +
"' in parent factory: no parent factory available" );
}
bean = this .beanFactory.getParentBeanFactory().getBean(refName);
}
else {
bean = this .beanFactory.getBean(refName);
this .beanFactory.registerDependentBean(refName, this .beanName);
}
if (bean instanceof NullBean) {
bean = null ;
}
return bean;
}
catch (BeansException ex) {
throw new BeanCreationException(
this .beanDefinition.getResourceDescription(), this .beanName,
"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
}
}

其中有一行熟悉的代码:bean = this.beanFactory.getBean(refName),对,这里就是发生递归的地方。该方法会拿着属性名称从容器中获取实例。

我们回到 applyPropertyValues 方法。此时deepCopy 集合已经有值了,不再仅仅是字符串了。

然后调用 setPropertyValues(new MutablePropertyValues(deepCopy)) 方法, 该方法会调用 AbstractPropertyAccessor.setPropertyValues 方法完成注入,而该方法会循环元素列表, 循环中调用 setPropertyValue(PropertyValue pv) 方法, 该方法最后会调用 nestedPa.setPropertyValue(tokens, pv) 方法, 该方法又会调用 processLocalProperty(tokens, pv) 方法,该方法最后又会调用 ph.setValue(valueToApply) 方法,也就是BeanWrapperImpl.setValue() 方法,终于,我们要看到反射了,看到反射说明到了尽头。

 
@Override
public void setValue(final @Nullable Object value) throws Exception {
final Method writeMethod = ( this .pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) this .pd).getWriteMethodForActualAccess() :
this .pd.getWriteMethod());
if (System.getSecurityManager() != null ) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(writeMethod);
return null ;
});
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
writeMethod.invoke(getWrappedInstance(), value), acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(getWrappedInstance(), value);
}
}

该方法是最后一步,我们看到该方法会找的set方法,然后调用 Method 的 invoke 方法,完成属性注入。

6. 总结

我们从源码层面剖析 IOC 的初始化过程,也了解了 IOC 的底层原理实现, 我们总结一下:

Spring 的 Bean 其实就是 BeanDefinition, 在 Bean 的创建和依赖注入的过程中, 需要根据 BeanDefinition 的信息来递归的完成依赖注入。

从我们分析的代码可以看到,这些递归都是以 getBean() 为入口的, 一个递归是在上下文体系中查找需要的 Bean 和创建 Bean 的递归调用, 另一个 Bean 实在依赖注入时,通过递归调用容器的 getBean 方法, 得到当前的依赖 Bean, 同时也触发对依赖 Bean 的创建和注入。

在对 Bean 的属性尽心依赖注入时, 解析的过程也是一个递归的过程, 这样, 根据依赖关系, 一层一层的完成 Bean 的创建和注入, 知道最后完成当前 Bean 的创建, 有了这个顶层 Bean 的创建和对他的属性依赖注入的完成, 意味着当前 Bean 相关的整个依赖链的注入也完成了.

总结一下 IOC 的初始化过程吧:

  1. 资源(Resource)定位;

  2. BeanDefinition 的载入和 BeanFactory 的构造.

  3. 想 IOC 容器(BeanFactory)注册 BeanDefinition.

  4. 根据 lazy-init 属性初始化 Bean 实例和依赖注入.

现在回过头看看, 我们已经了解了 Spring IOC 的设计, 那么我们自己可以实现一个简单的 IOC 吗? 楼主想试试,并且楼主已经写好了,下篇和大家一起实现一个简单的 IOC。


原文发布时间为:2018-09-7

本文作者:莫那鲁道

本文来自云栖社区合作伙伴“Java架构沉思录”,了解相关信息可以关注“Java架构沉思录”。

相关文章
|
2月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
211 26
|
4天前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
60 14
|
5月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
160 2
|
29天前
|
Java 容器 Spring
什么是Spring IOC 和DI ?
IOC : 控制翻转 , 它把传统上由程序代码直接操控的对象的调用权交给容 器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转 移,从程序代码本身转移到了外部容器。 DI : 依赖注入,在我们创建对象的过程中,把对象依赖的属性注入到我们的类中。
|
4月前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
136 69
|
1月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
4月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
80 21
|
4月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
152 7
|
4月前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
101 12