【重写SpringFramework】第一章beans模块:依赖注入(chapter 1-7)

1. 前言

上一节我们讲了环境变量的解析,执行的是字段注入的流程,但这并不是通常意义上的依赖注入,关键在于缺少了筛选的过程。换句话说,环境变量的解析只是字段注入的特殊情况,注入的值来自环境变量而不是 Spring 容器所管理的单例。

为了便于理解,我们可以把依赖解析的过程看作是一场招聘会。Spring 容器作为组织者,发布了大量工作岗位,容器中的所有单例都尝试投递简历。每个工作岗位的基本要求是符合指定的类型,只有一部分单例可以通过初轮筛选,进入面试环节。面试的要求会更加具体,最终只有一个优胜者的依赖项。为了更好地描述不同阶段的 Bean,我们引入以下两个概念:

  • 候选项(candidate):满足最基本的筛选条件,可能有多个对象入围
  • 合格项(qualifier):经过综合考量,选出最符合条件的一个对象

在这里插入图片描述

2. 依赖分类

2.1 概述

Spring 将依赖项分为两类,首先是预先设置的依赖项。比如 BeanFactoryApplicationContextResourceLoaderApplicationEventPublisher 等,它们实际上是 Spring 容器本身,只是以不同的形式呈现罢了。在 BeanFactory 的实例创建之后,手动注册这些依赖项,当执行依赖解析的操作时,优先查找预设的依赖项,如果找不到,则进一步寻找容器托管的单例。

其次是由 Spring 容器管理的单例,又可以分为两种。一是由外界创建的对象,比较典型的是表示环境变量的 Environment 对象。二是容器创建的对象,通过 BeanDefinition 的方式实现,这是最常见的依赖项的来源。

在这里插入图片描述

2.2 预设依赖

ConfigurableBeanFactory 接口定义了 registerResolvableDependency 方法,作用是将某个对象作为指定类型的依赖项,也就是预设依赖项。

public interface ConfigurableBeanFactory {
    void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue);
}

DefaultListableBeanFactory 持有 resolvableDependencies 字段来存储指定的依赖项,这些依赖项都是唯一的,不存在多个候选项,因此使用 Map 存储,一个萝卜一个坑。预设依赖项的数量不会太多,初始容量是 16。

public class DefaultListableBeanFactory {
    private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

    @Override
    public void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue) {
        if (!dependencyType.isInstance(autowiredValue)) {
            throw new IllegalArgumentException(autowiredValue + "不是依赖类型[" + dependencyType.getName() + "]的实例");
        }
        this.resolvableDependencies.put(dependencyType, autowiredValue);
    }
}

3. 单一类型依赖

3.1 主流程

在这里插入图片描述

回到 DefaultListableBeanFactorydoResolveDependency 方法,第二步是集合类型依赖,这个回头再讲,先跳过。第三步是依赖解析流程的关键所在,单一类型依赖的解析过程可以分为三步,如下所示:

  1. 获取符合条件的候选项列表(筛选简历环节)
  2. 如果存在多个候选项,需要找出最适合的那个(面试环节)
  3. 得到最终的依赖项后,需要确保对应的实例存在,可能会触发新的获取 Bean 流程
//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//依赖解析
private Object doResolveDependency(DependencyDescriptor descriptor, String beanName) {
    //1. 环境变量解析(略)
    //2. 集合类型依赖(TODO)

    //3. 单一对象依赖
    //3.1 寻找依赖的候选项列表
    Map<String, Object> matchingBeans = findAutowireCandidates(descriptor.getDependencyType(), descriptor);

    //3.2 获得唯一的候选项
    String autowiredBeanName;
    Object instanceCandidate;
    if (matchingBeans.size() > 1) {
        //存在多个候选项,进一步判断找出唯一的候选项
        autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
        if (autowiredBeanName == null && this.autowireCandidateResolver.isRequired(descriptor)) {
            throw new RuntimeException("依赖类型为" + descriptor.getDependencyType().getName() + "的候选项不是唯一的");
        }
        instanceCandidate = matchingBeans.get(autowiredBeanName);
    }else{
        Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
        autowiredBeanName = entry.getKey();
        instanceCandidate = entry.getValue();
    }

    //3.3 获取依赖项实例
    Object result;
    if(instanceCandidate instanceof Class){
        //如果依赖项是Class类型,则创建Bean实例
        result = getBean(autowiredBeanName);
    }else {
        result = instanceCandidate;
    }
    return result;
}

3.2 寻找候选项列表

findAutowireCandidates 方法的作用是返回一组符合条件的候选项,可以视为筛选简历的环节。findAutowireCandidates 方法可以分为两步,首先从预设的依赖项中查找类型匹配的实例,其次获取指定类型的所有 Bean 的名称,并进行筛选。一个 Bean 是否有资格成为候选者,至少要符合以下几个条件:

  • 比如声明了 @Qualifier 注解的情况。isAutowireCandidate 方法的逻辑较为繁琐,此处不做过多讨论,可简单认为返回 true 即可。
  • Spring 还规定了其他的限制,比如不能是自引用,候选项不能是集合类型等(仅了解)
//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//寻找符合条件的候选项集合
private Map<String,Object> findAutowireCandidates(Class<?> requiredType, DependencyDescriptor descriptor) {
    Map<String, Object> result = new LinkedHashMap<>();

    //1. 查找预设的依赖项
    for(Class<?> autowiringType: this.resolvableDependencies.keySet()){
        if(autowiringType.isAssignableFrom(requiredType)){
            Object autowiringValue = this.resolvableDependencies.get(autowiringType);
            if(requiredType.isInstance(autowiringValue)){
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }

    //2. 查找指定类型的所有Bean的名称(包括父容器)
    List<String> candidateNames = getBeanNamesForType(requiredType);
    if(getParentBeanFactory() != null){
        candidateNames.addAll(getParentBeanFactory().getBeanNamesForType(requiredType));
    }

    for (String candidate : candidateNames) {
        //进一步检查是否满足相关要求,比如声明了@Qualifier的情况
        if(isAutowireCandidate(candidate, descriptor)){
            //候选项是已注册的单例,直接将Bean加入列表
            if(descriptor instanceof MultiElementDescriptor || containsSingleton(candidate)){
                result.put(candidate, getBean(candidate));
            }
            //否则添加Class对象
            else{
                result.put(candidate, getType(candidate));
            }
        }
    }
    return result;
}

这里有一个很重要的问题,为什么候选项不是已注册的单例,就只获取 Class 对象,难道不能用 getBean 方法创建实例吗?直接创建对象当然是可行的,但 Spring 的考虑是避免提前实例化对象而造成副作用,这是原则问题,所有的流程都必须无条件做出让步。那么什么是提前实例化,造成的副作用(side effect)又是什么?

在一个软件系统中,对象之间的依赖关系可能非常复杂,如果不加以控制,就会造成连锁反应,我们称之为依赖裂变。举个例子,本来只想实例化 A 对象和依赖的 B 对象,结果实例化了十几个对象。假如 F 对象的依赖项 G 此时尚不存在,导致解析失败,抛出异常。换句话说,F 对象不应该在创建 A 的时候实例化,这就是提现实例化。所谓的副作用就是不可预知的后果,不可预知是说造成的影响可大可小,最严重的情况会导致 Spring 容器创建失败。考虑到代码的健壮性,必须做最坏的打算。

在这里插入图片描述

我们现在寻找的是候选项,还不是板上钉钉的最终合格项,在面试的过程中有被淘汰的可能。因此应当尽量避免提现实例化,如果候选项对应的单例还不存在,那就把它的 Class 对象添加到列表中。等到最终确定了唯一的合格项,再创建实例不迟。

3.3 获取唯一的候选项

findAutowireCandidates 方法返回的 matchingBeans 变量是 Map 类型,说明可能存在多个候选项。如果只有一个元素,那么直接视为合格项即可。如果大于一个元素,还需要进行筛选,接下来进入面试环节determineAutowireCandidate 方法按照三个条件进行考察,它们之间有优先级,只要满足任何一个条件则立即返回。

  • 候选项是否被显式标记为[首要的],也就是检查 BeanDefinitionprimary 属性
  • 优先级检查,候选项可能实现了 Ordered 接口或者声明了 @Order 注解,需要找出优先级最高的(仅了解)
  • 作为兜底的措施,优先从预设的依赖项中查找,其次检查候选项的 beanName 与字段名或方法参数名是否相同
//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//从众多候选项找到一个最符合条件的合格项
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
    //查找primary标记为true的候选项
    String primaryCandidate = determinePrimaryCandidate(candidates);
    if(primaryCandidate != null){
        return primaryCandidate;
    }

    //查找优先级最高的候选项(略)

    //查找预设的依赖项,或者beanName与字段/参数名相同的候选项
    for (Map.Entry<String, Object> entry : candidates.entrySet()) {
        String candidateName = entry.getKey();
        Object beanInstance = entry.getValue();
        if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
                (candidateName != null && candidateName.equals(descriptor.getDependencyName()))) {
            return candidateName;
        }
    }
    return null;
}

3.4 获取依赖项实例

经过了简历筛选和面试两轮淘汰之后,拿到了唯一的合格项,也就是最终依赖项。此时变量 instanceCandidate 可能是一个 Bean,也可能是 Class 对象。接下来判断变量 instanceCandidate 是不是 Class 类型,如果是则需要调用 getBean 方法创建依赖项的实例。第三步的逻辑比较简单,但告诉我们一个非常重要的信息,依赖解析的过程可能会导致连锁反应。因此,Spring 通过避免对候选项进行提前实例化,将连锁反应的影响降至最低。

4. 集合类型依赖

4.1 概述

依赖注入的目标不仅可以是单一类型,还可以是 ListMap、数组等集合类型。相对于单一类型的依赖来说,集合类型依赖的处理反而更简单,只需查找指定类型的所有候选项,它们都被看作是符合条件的。也就是说只要符合简历要求的统统都要,省去了面试的环节

4.2 代码实现

doResolveDependency 方法的第二步就是解析集合类型的依赖,具体工作由 resolveMultipleBeans 方法完成。

//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//依赖解析
private Object doResolveDependency(DependencyDescriptor descriptor, String beanName){
    //1. 处理@Value注解(略)

    //2. 依赖是集合类型
    Object multipleBeans = resolveMultipleBeans(descriptor);
    if (multipleBeans != null) {
        return multipleBeans;
    }

    //3. 单一对象依赖(略)
}

resolveMultipleBeans 方法实现了 CollectionMap 两种集合类型的依赖解析,由于两种类型的处理方式差不多,这里仅以 Collection 为例进行分析。为了简化代码,我们不考虑多层嵌套,仅处理类似 List<Foo>Map<String, Foo> 这种只嵌套了一层的情况。

  1. 获取集合内部元素的类型,如果是 List<Foo> 类型,则拿到 Foo 的类型信息。
  2. 调用 findAutowireCandidates 方法查找所有符合条件的候选项。(只有筛选简历的环节)
  3. 候选项列表的类型是一个 Map,我们需要的是 Collection 类型,因此必须进行类型转换操作。同样地,如果是 List 接口类型,会生成对应的 ArrayList 对象。
//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//处理集合类型的依赖项
private Object resolveMultipleBeans(DependencyDescriptor descriptor) {
    Class<?> type = descriptor.getDependencyType();

    //处理Collection类型
    if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        //1) 获取集合元素的类型(不考虑多层嵌套的情况)
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        //2) 寻找符合条件的候选项列表
        Map<String, Object> matchingBeans = findAutowireCandidates(elementType, new MultiElementDescriptor(descriptor));

        //3) 类型转换,比如List类型会转成ArrayList
        return getTypeConverter().convertIfNecessary(matchingBeans.values(), type);
    }
    //处理Map类型(略)
    return null;
}

需要注意的是,在 findAutowireCandidates 方法中,由于集合类型使用的属性描述符实现类是 MultiElementDescriptor,直接调用 getBean 方法创建实例。也就是说,这里并不担心提前实例化的副作用,这是因为集合依赖的所有候选项都不会被淘汰,可以放心地进行实例化。

5. 延迟依赖解析

5.1 概述

在实际应用中,有时会出现依赖项尚未注册到 Spring 容器中,此时会导致依赖解析失败。Spring 的解决方案是在解析依赖项时返回一个包装对象,然后手动地触发真正的依赖解析操作。Spring 为延迟依赖解析提供了一组 API,如下所示:

  • ObjectProvider:标记接口,表明实现类提供了延迟初始化的功能
  • DependencyObjectProvider:重写了 getObject 方法,实际调用依赖解析的方法
  • Jsr330DependencyProvider:作用是兼容 JDK 的 Provider 接口

在这里插入图片描述

5.2 相关 API

ObjectProvider 是一个标记接口,实现类重写的是父接口 ObjectFactorygetObject 方法。看起来有点乏善可陈,但是为什么一定要定义这个标记接口?这是因为 ObjectFactory 接口的实例由 Spring 容器在创建实例时自动调用,而 ObjectProvider 接口的实例只能由用户手动调用

public interface ObjectProvider <T> extends ObjectFactory<T> {}

DependencyObjectProvider 作为 ObjectProvider 接口的实现类,getObject 方法调用了 doResolveDependency 方法。显而易见,只有当调用 getObject 方法的时候才会执行依赖解析的操作。

private class DependencyObjectProvider implements ObjectProvider<Object> {
    private final DependencyDescriptor descriptor;
    private final String beanName;

    @Override
    public Object getObject() throws BeansException {
        return doResolveDependency(this.descriptor);
    }
}

Jsr330DependencyProvider 扩展了 DependencyObjectProvider,主要目的是为了兼容 JDK 的 Provider 接口。

private class Jsr330DependencyProvider extends DependencyObjectProvider implements Provider<Object> {

    @Override
    public Object get() throws BeansException {
        return getObject();
    }
}

5.3 代码实现

上节提到,resolveDependency 方法起到辅助作用,doResolveDependency 方法才是真正进行依赖解析的操作。实际上,延迟依赖解析正是在 resolveDependency 方法中执行的。代码处理了两种情况,如下所示:

  • 如果字段或方法参数的类型是 OjbectFactoryObjectProvider,则返回一个 DependencyObjectProvider 包装对象。
  • 如果字段或方法参数的类型是 Provider,返回一个 Jsr330DependencyProvider 包装对象。
//所属类[cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory]
//依赖解析的入口方法,负责外围工作
@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName) throws BeansException {
    //此时返回的是包装对象,将实际的依赖解析延迟到真正使用的时候
    if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    }

    if(descriptor.getDependencyType() == javaxInjectProviderClass){
        return new Jsr330DependencyProvider(descriptor, requestingBeanName);
    }

    return doResolveDependency(descriptor, requestingBeanName);
}

6. 测试

6.1 单一类型依赖

先来看测试类,UserControlleruserService 字段声明了 @Autowired,表示需要将一个 UserService 的实例注入给该字段。此外还定义了 getUser 方法,实际上委托给 UserService 来创建一个 User 对象。这种结构是 web 应用中的典型用例,控制器类负责映射请求,业务类完成具体功能。

//测试类
public class UserController {
    @Autowired
    private UserService userService;

    public User getUser(){
        return this.userService.getUser();
    }
}

//测试类
public class UserService {

    public User getUser(){
        return new User("Tom", 20);
    }
}

测试方法分为三步,第一步是准备工作,第二步把两个测试类对应的 BeanDefinition 注册到容器中,第三步通过容器获取 UserController 的实例。Spring 容器在创建实例之后,执行自动装配的操作。检测到 userService 字段声明了 @Autowired 注解,寻找对应的依赖项也就是 UserService 对象,然后通过反射的方式赋给字段。

//测试方法
@Test
public void testAutowiredSingle(){
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
    processor.setBeanFactory(factory);
    factory.addBeanPostProcessor(processor);

    factory.registerBeanDefinition("userController", new RootBeanDefinition(UserController.class));
    factory.registerBeanDefinition("userService", new RootBeanDefinition(UserService.class));

    UserController controller = factory.getBean("userController", UserController.class);
    User user = controller.getUser();
    System.out.println("@Autowired单一依赖测试: " + user);
}

从测试结果可以看到,调用 UserControllergetUser 方法,实际上会调用 UserServicegetUser 方法返回一个 User 对象。

单一依赖测试: User{name='Tom', age=20}

6.2 集合类型依赖

在测试类 AutowireConfig 中,定义了一个集合类型的字段 userList,setter 方法声明了 @Autowired 注解,本次测试的目标是集合依赖以及 setter 方法注入。

//测试类
public class AutowireConfig {
    private Set<User> userList;

    @Autowired
    public void setUserList(Set<User> userList) {
        this.userList = userList;
    }

    public Set<User> getUserList() {
        return userList;
    }
}

在测试方法中,首先是准备工作,然后向容器中注册了两个 User 对象,最后调用 getBean 方法获取 AutowireConfig 的实例。当实例创建之后,在执行自动装配的流程中,检测到 setUserList 方法上声明了 @Autowired 注解,且方法参数是一个集合类型。这时容器会寻找所有的 User 实例,全部添加到集合中,然后赋给 userList 字段。

注:集合类型也可以是 Map 类型,测试流程与 List 类似,不赘述,详见 testAutowiredMap 测试方法。

//测试方法
@Test
public void testAutowiredList(){
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
    processor.setBeanFactory(factory);
    factory.addBeanPostProcessor(processor);

    factory.registerBeanDefinition("autowireConfig", new RootBeanDefinition(AutowireConfig.class));
    factory.registerSingleton("tom", new User("Tom", 18));
    factory.registerSingleton("jerry", new User("Jerry", 22));

    AutowireConfig autowireConfig = factory.getBean("autowireConfig", AutowireConfig.class);
    System.out.println("List集合依赖测试: " + autowireConfig.getUserList());
}

从测试结果可以看到,Spring 容器中所有 User 类型的单例都打印了出来。

List集合依赖测试: [User{name='Tom', age=18}, User{name='Jerry', age=22}]

6.3 延迟依赖解析

先定义一个测试类 LazyBean,在构造方法中打印日志,作用是观察对象是何时创建的。然后是 AutowireConfig,声明一个 ObjectProvider 包装类型的字段。

//测试类:延迟加载
public class LazyBean {

    public LazyBean() {
        System.out.println("创建LazyBean");
    }
}

//测试类:持有一个延迟加载的对象
public class AutowireConfig {
    private ObjectProvider<LazyBean> lazyBean;

    @Autowired
    public void setLazyBean(ObjectProvider<LazyBean> lazyBean) {
        this.lazyBean = lazyBean;
    }
}

测试方法分为三步,首先是准备工作,然后注册 AutowireConfig,并调用 getBean 方法触发实例化操作。需要注意的是,此时还没有注册 LazyBean 实例,依赖解析并没有报错。第三步,注册 LazyBean,然后调用 getLazyBean 方法,此时获得的是包装类型 ObjectProvider,因此需要进一步调用 getObject 方法获取目标对象。

//测试方法
@Test
public void testLazyDependency() {
    //1. 准备工作
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
    processor.setBeanFactory(factory);
    factory.addBeanPostProcessor(processor);

    //2. 触发AutowireConfig的创建工作,依赖解析没有报错
    factory.registerBeanDefinition("autowireConfig", new RootBeanDefinition(AutowireConfig.class));
    AutowireConfig autowireConfig = factory.getBean("autowireConfig", AutowireConfig.class);
    System.out.println("获取AutowireConfig实例");

    //3. 第一次获取LazyBean,触发依赖解析的操作
    factory.registerBeanDefinition("lazyBean", new RootBeanDefinition(LazyBean.class));
    ObjectProvider<LazyBean> lazyBean = autowireConfig.getLazyBean();
    lazyBean.getObject();
}

从测试结果可以看到,获取 AutowireConfig 实例的日志先打印,直到第一次获取 LazyBean 实例时,才触发真正的依赖解析的操作。

获取AutowireConfig实例
创建LazyBean

6.4 其他依赖

在测试类中定义 BeanFactoryEnvironment 类型的字段,并声明 @Autowired 注解,并将 required 属性指定为 false,此举是为了避免在其他测试中报错。

//测试类
public class AutowireConfig {
    @Autowired(required = false)
    private BeanFactory beanFactory;
    @Autowired(required = false)
    private Environment environment;
}

测试方法注意两点,一是将 BeanFactory 注册为预设依赖项。二是创建 StandardEnvironment 实例,指定运行环境为 dev,然后手动注册环境变量的实例。

//测试方法
@Test
public void testOtherDependency() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
    processor.setBeanFactory(factory);
    factory.addBeanPostProcessor(processor);

    factory.registerBeanDefinition("autowireConfig", new RootBeanDefinition(AutowireConfig.class));
    //将BeanFactory注册为预设依赖
    factory.registerResolvableDependency(BeanFactory.class, factory);
    //手动注册Environment
    StandardEnvironment environment = new StandardEnvironment();
    environment.addActiveProfile("dev");
    factory.registerSingleton("environment", environment);

    AutowireConfig autowireConfig = factory.getBean("autowireConfig", AutowireConfig.class);
    System.out.println("手动注册依赖测试:运行环境为" + Arrays.toString(autowireConfig.getEnvironment().getActiveProfiles()));
    System.out.println("预设依赖测试:" + autowireConfig.getBeanFactory());
}

从测试结果可以看到,BeanFactoryEnvironment 的实例都被注入到了测试类中。

手动注册依赖测试:运行环境为[dev]
预设依赖测试:cn.stimd.spring.beans.factory.support.DefaultListableBeanFactory@4590c9c3

7. 总结

本节介绍了依赖注入的另一种情况,即候选项的类型是一个对象。对象解析分为两种情况,一是单一类型,二是集合类型。由于集合类型的解析是以单一类型的解析为基础,因此重点在于单一类型的解析。本节内容由以下四个部分组成:

  • 依赖分类:Spring 提供了两种依赖项,一是预设的依赖项,二是容器托管的单例。

  • 单一类型依赖:首先是筛选简历,需要对 Spring 容器中的所有单例进行筛选,找出一组初步符合条件(指定类型)的候选项。然后是面试环节,从几个方面对若干候选项进行考察,符合任一条件的优先被挑选,最后得到唯一的合格项。在这一过程中,需要注意依赖裂变的问题,尽量避免提前实例化所引发的副作用。

  • 集合类型依赖:只有筛选简历环节,指定类型的单例均认为是符合条件的。

  • 延迟依赖解析:在依赖解析时返回一个包装对象,直到第一次访问该依赖项时才执行真正的依赖解析操作。

在这里插入图片描述

此外,Spring 还提供了对 JDK 的 @Resource 注解的支持,CommonAnnotationBeanPostProcessor 组件实现了依赖注入的功能。实际上 @Resource 注解的功能更为强大,出于精简代码的考虑,我们不对该组件进行介绍,感兴趣的读者可以尝试实现。

8. 项目信息

新增修改一览,新增(4),修改(6)。

beans
├─ src
│  ├─ main
│  │  └─ java
│  │     └─ cn.stimd.spring.beans
│  │        └─ factory
│  │           ├─ config
│  │           │  └─ ConfigurableBeanFactory.java (*)
│  │           ├─ support
│  │           │  ├─ AbstractBeanFactory.java (*)
│  │           │  └─ DefaultListableBeanFactory.java (*)
│  │           └─ ObjectProvider.java (+)
│  └─ test
│     └─ java
│        └─ beans
│           └─ autowire
│              ├─ AutowireConfig.java (*)
│              ├─ AutowireTest.java (*)
│              ├─ LazyBean.java (+)
│              ├─ UserController.java (+)
│              └─ UserService.java (+)
└─ pom.xml (*)

注:+号表示新增、*表示修改
  • 项目地址:https://gitee.com/stimd/spring-wheel

  • 本节分支:https://gitee.com/stimd/spring-wheel/tree/chapter1-7

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值