谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?(3)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?(3)

》通过构造函数实例化对象


如果上面你对使用factoryMethd进行实例化对象已经足够了解的话,那么下面的源码分析基本没有什么很大区别,我们接着看看代码。

首先,我们回到createBeanInstance方法中,

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // 上面的代码已经分析过了
    // 1.使用supplier来得到一个对象
    // 2.通过factotryMethod方法实例化一个对象
    // 看起来是不是有点熟悉,在使用factotryMethod创建对象时也有差不多这样的一段代码,看起来就是使用缓存好的方法直接创建一个对象
    boolean resolved = false;
    boolean autowireNecessary = false;
    // 不对这个参数进行讨论,就认为一直为null
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            // bd中的resolvedConstructorOrFactoryMethod不为空,说明已经解析过构造方法了
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                // resolved标志是否解析过构造方法
                resolved = true;
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }
    if (resolved) {
        // 构造函数已经解析过了,并且这个构造函数在调用时需要自动注入参数
        if (autowireNecessary) {
            // 此时部分解析好的参数已经存在了beanDefinition中,并且构造函数也在bd中
            // 那么在这里只会从缓存中去取构造函数以及参数然后反射调用
            return autowireConstructor(beanName, mbd, null, null);
        }
        else {
            // 这里就是直接反射调用空参构造
            return instantiateBean(beanName, mbd);
        }
    }
  // 推断出能够使用的需要参数的构造函数
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    // 在推断出来的构造函数中选取一个合适的方法来进行Bean的实例化
    // ctors不为null:说明存在1个或多个@Autowired标注的方法
    // mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR:说明是自动注入
    // mbd.hasConstructorArgumentValues():配置文件中配置了构造函数要使用的参数
    // !ObjectUtils.isEmpty(args):外部传入的参数,必定为null,不多考虑
    // 上面的条件只要满足一个就会进入到autowireConstructor方法
    // 第一个条件满足,那么通过autowireConstructor在推断出来的构造函数中再进一步选择一个差异值最小的,参数最长的构造函数
    // 第二个条件满足,说明没有@Autowired标注的方法,但是需要进行自动注入,那么通过autowireConstructor会去遍历类中申明的所有构造函数,并查找一个差异值最小的,参数最长的构造函数
    // 第三个条件满足,说明不是自动注入,那么要通过配置中的参数去类中申明的所有构造函数中匹配
    // 第四个必定为null,不考虑
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }
    // 反射调用空参构造
    return instantiateBean(beanName, mbd);
}

因为autowireConstructor方法的执行逻辑跟instantiateUsingFactoryMethod方法的执行逻辑基本一致,只是将Method对象换成了Constructor对象,所以对这个方法我不再做详细的分析。


我们主要就看看determineConstructorsFromBeanPostProcessors这个方法吧,这个方法的主要目的就是推断出候选的构造方法。


determineConstructorsFromBeanPostProcessors方法做了什么?

// 实际调用的就是AutowiredAnnotationBeanPostProcessor中的determineCandidateConstructors方法
// 这个方法看起来很长,但实际确很简单,就是通过@Autowired注解确定哪些构造方法可以作为候选方法,其实在使用factoryMethod来实例化对象的时候也有这种逻辑在其中,后续在总结的时候我们对比一下
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
      throws BeanCreationException {
    // 这里做的事情很简单,就是将@Lookup注解标注的方法封装成LookupOverride添加到BeanDefinition中的methodOverrides属性中,如果这个属性不为空,在实例化对象的时候不能选用SimpleInstantiationStrateg,而要使用CglibSubclassingInstantiationStrategy,通过cglib代理给方法加一层拦截了逻辑
      // 避免重复检查
    if (!this.lookupMethodsChecked.contains(beanName)) {
      try {
        ReflectionUtils.doWithMethods(beanClass, method -> {
          Lookup lookup = method.getAnnotation(Lookup.class);
          if (lookup != null) {
            Assert.state(this.beanFactory != null, "No BeanFactory available");       // 将@Lookup注解标注的方法封装成LookupOverride
            LookupOverride override = new LookupOverride(method, lookup.value());
            try {
                            // 添加到BeanDefinition中的methodOverrides属性中
              RootBeanDefinition mbd = (RootBeanDefinition)
                  this.beanFactory.getMergedBeanDefinition(beanName);
              mbd.getMethodOverrides().addOverride(override);
            }
            catch (NoSuchBeanDefinitionException ex) {
              throw new BeanCreationException(beanName,
                  "Cannot apply @Lookup to beans without corresponding bean definition");
            }
          }
        });
      }
      catch (IllegalStateException ex) {
        throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
      }
      this.lookupMethodsChecked.add(beanName);
    }
    // 接下来要开始确定到底哪些构造函数能被作为候选者
    // 先尝试从缓存中获取
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    if (candidateConstructors == null) {
      // Fully synchronized resolution now...
      synchronized (this.candidateConstructorsCache) {
        candidateConstructors = this.candidateConstructorsCache.get(beanClass);、
                    // 缓存中无法获取到,进入正式的推断过程
        if (candidateConstructors == null) {
          Constructor<?>[] rawCandidates;
          try {
                        // 第一步:先查询这个类所有的构造函数,包括私有的
            rawCandidates = beanClass.getDeclaredConstructors();
          }
          catch (Throwable ex) {
            // 省略异常信息
          }
          List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
                    // 保存添加了Autowired注解并且required属性为true的构造方法
          Constructor<?> requiredConstructor = null;
                    // 空参构造
          Constructor<?> defaultConstructor = null;
                    // 看方法注释上说明的,这里除非是kotlin的类,否则必定为null,不做过多考虑,我们就将其当作null
          Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
          int nonSyntheticConstructors = 0;
                    // 对类中的所有构造方法进行遍历
          for (Constructor<?> candidate : rawCandidates) {
                        // 非合成方法
            if (!candidate.isSynthetic()) {
              nonSyntheticConstructors++;
            }
            else if (primaryConstructor != null) {
              continue;
            }
                        // 查询方法上是否有Autowired注解
            AnnotationAttributes ann = findAutowiredAnnotation(candidate);
            if (ann == null) {
                            // userClass != beanClass说明这个类进行了cglib代理
              Class<?> userClass = ClassUtils.getUserClass(beanClass);
              if (userClass != beanClass) {
                try {
                                    // 如果进行了cglib代理,那么在父类上再次查找Autowired注解
                  Constructor<?> superCtor =
                      userClass.getDeclaredConstructor(candidate.getParameterTypes());
                  ann = findAutowiredAnnotation(superCtor);
                }
                catch (NoSuchMethodException ex) {
                  // Simply proceed, no equivalent superclass constructor found...
                }
              }
            }
                        // 说明当前的这个构造函数上有Autowired注解
            if (ann != null) {
              if (requiredConstructor != null) {
                // 省略异常抛出
              }
                            // 获取Autowired注解中的required属性
              boolean required = determineRequiredStatus(ann);
              if (required) {
                                // 类中存在多个@Autowired标注的方法,并且某个方法的@Autowired注解上被申明了required属性要为true,那么直接报错
                if (!candidates.isEmpty()) {
                // 省略异常抛出
                }
                requiredConstructor = candidate;
              }
                            // 添加到集合中,这个集合存储的都是被@Autowired注解标注的方法
              candidates.add(candidate);
            }
                        // 空参构造函数
            else if (candidate.getParameterCount() == 0) {
              defaultConstructor = candidate;
            }
          }
          if (!candidates.isEmpty()) {
            // 存在多个被@Autowired标注的方法
                        // 并且所有的required属性被设置成了false (默认为true)
            if (requiredConstructor == null) {
                            // 存在空参构造函数,注意,空参构造函数可以不被@Autowired注解标注
              if (defaultConstructor != null) {
                                // 将空参构造函数也加入到候选的方法中去
                candidates.add(defaultConstructor);
              }
              // 省略日志打印
            }
            candidateConstructors = candidates.toArray(new Constructor<?>[0]);
          }
                    // 也就是说,类中只提供了一个构造函数,并且这个构造函数不是空参构造函数
          else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
            candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
          }
                    // 省略中间两个判断,primaryConstructor必定为null,不考虑
          // .....
          }
          else {
                        // 说明无法推断出来
            candidateConstructors = new Constructor<?>[0];
          }
          this.candidateConstructorsCache.put(beanClass, candidateConstructors);
        }
      }
    }
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
  }

这里我简单总结下这个方法的作用


1.获取到类中的所有构造函数


2.查找到被@Autowired注解标注的构造函数


如果存在多个被@Autowired标注的构造函数,并且其required属性没有被设置为true,那么返回这些被标注的函数的集合(空参构造即使没有添加@Autowired也会被添加到集合中)

如果存在多个被@Autowired标注的构造函数,并且其中一个的required属性被设置成了true,那么直接报错

如果只有一个构造函数被@Autowired标注,并且其required属性被设置成了true,那么直接返回这个构造函数


3.如果没有被@Autowired标注标注的构造函数,但是类中有且只有一个构造函数,并且这个构造函数不是空参构造函数,那么返回这个构造函数


4.上面的条件都不满足,那么determineCandidateConstructors这个方法就无法推断出合适的构造函数了


可以看到,通过AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors方法可以处理构造函数上的@Autowired注解。


但是,请注意,这个方法并不能决定到底使用哪个构造函数来创建对象(即使它只推断出来一个,也不一定能够使用),它只是通过@Autowired注解来确定构造函数的候选者,在构造函数都没有添加@Autowired注解的情况下,这个方法推断不出来任何方法。真正确定到底使用哪个构造函数是交由autowireConstructor方法来决定的。前文已经分析过了instantiateUsingFactoryMethod方法,autowireConstructor的逻辑基本跟它一致,所以这里不再做详细的分析。


factoryMethod跟构造函数的比较


整体逻辑比较


微信图片_20221113174815.png

从上图中可以看到,整体逻辑上它们并没有什么区别,只是查找的对象从factoryMethod换成了构造函数


执行细节比较


细节的差异主要体现在推断方法上

推断factoryMethod

微信图片_20221113174850.png

推断构造函数

微信图片_20221113174916.png


它们之间的差异我已经在图中标识出来了,主要就是两点


  1. 通过构造函数实例化对象,多了一层处理,就是要处理构造函数上的@Autowired注解以及方法上的@LookUp注解(要决定选取哪一种实例化策略,SimpleInstantiationStrategy/CglibSubclassingInstantiationStrategy)
  2. 在最终的选取也存在差异,对于facotyMehod而言,在宽松模式下(除ConfigurationClassBeanDefinition外,也就是扫描@Bean得到的BeanDefinition,都是宽松模式),会选取一个最精准的方法,在严格模式下,会选取一个参数最长的方法
  3. 对于构造函数而言,会必定会选取一个参数最长的方法


关于计算类型差异的补充内容


思考了很久,我还是决定再补充一些内容,就是关于上面两幅图的最后一步,对应的核心代码如下:

int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
if (typeDiffWeight < minTypeDiffWeight) {
    factoryMethodToUse = candidate;
    argsHolderToUse = argsHolder;
    argsToUse = argsHolder.arguments;
    minTypeDiffWeight = typeDiffWeight;
    ambiguousFactoryMethods = null;
}

1.判断bd是严格模式还是宽松模式,上面说过很多次了,bd默认就是宽松模式,只要在ConfigurationClassBeanDefinition中使用严格模式,也就是扫描@Bean标注的方法注册的bd(对应的代码可以参考:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法)


我们再看看严格模式跟宽松模式在计算差异值时的区别


宽松模式

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
    // 计算实际使用的参数跟方法申明的参数的差异值
      int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
    // 计算没有经过类型转换的参数跟方法申明的参数的差异值
      int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
      return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);
    }
public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
    int result = 0;
    for (int i = 0; i < paramTypes.length; i++) {
        // 在出现类型转换时,下面这个判断才会成立,也就是在比较rawArguments跟paramTypes的差异时才可能满足这个条件
        if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
            return Integer.MAX_VALUE;
        }
        if (args[i] != null) {
            Class<?> paramType = paramTypes[i];
            Class<?> superClass = args[i].getClass().getSuperclass();
            while (superClass != null) {
                // 如果我们传入的值是方法上申明的参数的子类,那么每多一层继承关系,差异值加2
                if (paramType.equals(superClass)) {
                    result = result + 2;
                    superClass = null;
                }
                else if (ClassUtils.isAssignable(paramType, superClass)) {
                    result = result + 2;
                    superClass = superClass.getSuperclass();
                }
                else {
                    superClass = null;
                }
            }
            // 判断方法的参数是不是一个接口,如果是,那么差异值加1
            if (paramType.isInterface()) {
                result = result + 1;
            }
        }
    }
    return result;
}

严格模式(主要应用于@Bean标注的方法对应的BeanDefinition)

public int getAssignabilityWeight(Class<?>[] paramTypes) {
    // 严格模式下,只有三种返回值
    // 1.Integer.MAX_VALUE,经过类型转换后还是不符合要求,返回最大的类型差异
    // 因为解析后的参数可能返回一个NullBean(创建对象的方法返回了null,Spring会将其包装成一个NullBean),不过一般不会出现这种情况,所以我们可以当这种情况不存在
      for (int i = 0; i < paramTypes.length; i++) {
        if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
          return Integer.MAX_VALUE;
        }
      }
    // 2.Integer.MAX_VALUE - 512,进行过了类型转换才符合要求
      for (int i = 0; i < paramTypes.length; i++) {
        if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {
          return Integer.MAX_VALUE - 512;
        }
      }
    // 3.Integer.MAX_VALUE - 1024,没有经过类型转换就已经符合要求了,返回最小的类型差异
      return Integer.MAX_VALUE - 1024;
    }

首先,不管是factoryMethod还是constructor,都是采用上面的两个方法来计算类型差异,但是正常来说,只有factoryMethod会采用到严格模式(除非程序员手动干预,比如通过Bean工厂后置处理器修改了bd中的属性,这样通常来说没有很大意义)


所以我们分为三种情况讨论


1、factoryMethod+宽松模式


这种情况下,会选取一个最精确的方法,同时方法的参数要尽量长


测试代码:

public class FactoryObject {
  public DmzService getDmz() {
    System.out.println(0);
    return new DmzService();
  }
  public DmzService getDmz(OrderService indexService) {
    System.out.println(1);
    return new DmzService();
  }
  public DmzService getDmz(OrderService orderService, IndexService indexService) {
    System.out.println(2);
    return new DmzService();
  }
    public DmzService getDmz(OrderService orderService, IndexService indexService,IA ia) {
        System.out.println(3);
        return new DmzService();
    }
}
public class ServiceImpl implements IService {
}
public class IAImpl implements IA {
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="constructor"><!--必须要开启自动注入,并且是通过构造函数进行自动注入,否则选用无参构造-->
  <!--factoryObject 提供了创建对象的方法-->
  <bean id="factoryObject" class="com.dmz.spring.instantiation.service.FactoryObject"/>
  <bean class="com.dmz.spring.instantiation.service.OrderService" id="orderService"/>
  <bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" />
  <bean class="com.dmz.spring.instantiation.service.ServiceImpl" id="iService"/>
  <bean class="com.dmz.spring.instantiation.service.IndexService" id="indexService"/>
</beans>
/**
 * @author 程序员DMZ
 * @Date Create in 23:59 2020/6/1
 * @Blog https://daimingzhi.blog.csdn.net/
 */
public class XMLMain {
  public static void main(String[] args) {
    ClassPathXmlApplicationContext cc =
        new ClassPathXmlApplicationContext("application.xml");
  }
}

运行程序发现,选用了第三个(getDmz(OrderService orderService, IndexService indexService))构造方法。虽然最后一个方法的参数更长,但是因为其方法申明的参数上存在接口,所以它的差异值会大于第三个方法,因为不会被选用


2、factoryMethod+严格模式


这种情况下,会选取一个参数尽量长的方法


测试代码:

/**
 * @author 程序员DMZ
 * @Date Create in 6:28 2020/6/1
 * @Blog https://daimingzhi.blog.csdn.net/
 */
@ComponentScan("com.dmz.spring.instantiation")
@Configuration
public class Config {
  @Bean
  public DmzService dmzService() {
    System.out.println(0);
    return new DmzService();
  }
  @Bean
  public DmzService dmzService(OrderService indexService) {
    System.out.println(1);
    return new DmzService();
  }
  @Bean
  public DmzService dmzService(OrderService orderService, IndexService indexService) {
    System.out.println(2);
    return new DmzService();
  }
  @Bean
  public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia) {
    System.out.println("config " +3);
    return new DmzService();
  }
  @Bean
  public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia, IService iService) {
    System.out.println("config " +4);
    return new DmzService();
  }
}
/**
 * @author 程序员DMZ
 * @Date Create in 6:29 2020/6/1
 * @Blog https://daimingzhi.blog.csdn.net/
 */
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.register(Config.class);
    ac.refresh();
  }
}

运行程序,发现选用了最后一个构造函数,这是因为在遍历候选方法时,会先遍历参数最长的,而在计算类型差异时,因为严格模式下,上面所有方法的差异值都是一样的,都会返回Integer.MAX_VALUE - 1024。实际上,在不进行手动干预的情况下,都会返沪这个值。


3、构造函数+宽松模式


这种情况下,也会选取一个参数尽量长的方法

之所以会这样,主要是因为在autowireConstructor方法中进行了一次短路判断,如下所示微信图片_20221113175420.png

在上图中,如果已经找到了合适的方法,那么直接就不会再找了,而在遍历的时候是从参数最长的方法开始遍历的,测试代码如下:

@Component
public class DmzService {
    // 没有添加@Autowired注解,也会被当作候选方法
  public DmzService(){
    System.out.println(0);
  }
  @Autowired(required = false)
  public DmzService(OrderService orderService) {
    System.out.println(1);
  }
  @Autowired(required = false)
  public DmzService(OrderService orderService, IService iService) {
    System.out.println(2);
  }
  @Autowired(required = false)
  public DmzService(OrderService orderService, IndexService indexService, IService iService,IA ia) {
    System.out.println("DmzService "+3);
  }
}
/**
 * @author 程序员DMZ
 * @Date Create in 6:29 2020/6/1
 * @Blog https://daimingzhi.blog.csdn.net/
 */
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.register(Config.class);
    ac.refresh();
  }
}


相关文章
|
19天前
|
XML 安全 Java
|
2天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
18 6
|
4天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
31 3
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
17天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
27 1
|
10天前
|
XML 安全 Java
Spring Boot中使用MapStruct进行对象映射
本文介绍如何在Spring Boot项目中使用MapStruct进行对象映射,探讨其性能高效、类型安全及易于集成等优势,并详细说明添加MapStruct依赖的步骤。
|
28天前
|
Java Maven Spring
Spring 小案例体验创建对象的快感
本文介绍了如何在IDEA中创建一个Spring项目,包括项目创建、配置pom.xml文件以引入必要的依赖、编写实体类HelloSpring及其配置文件applicationContext.xml,最后通过测试类TestHelloSpring展示如何使用Spring的bean创建对象并调用方法。
29 0
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
7月前
|
存储 Java 数据库
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期
Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期
|
设计模式 前端开发 Java
Spring Bean对象生命周期
Spring Bean对象生命周期
416 0
下一篇
DataWorks