美文网首页
Spring 依赖注入(DI) 源码解析

Spring 依赖注入(DI) 源码解析

作者: 扛麻袋的少年 | 来源:发表于2020-07-03 20:27 被阅读0次

    写在前面

      在分析Spring依赖注入的过程之前,建议您先了解:Spring IOC 源码解析,这样或许会让你的思路更加清晰。

    1.依赖注入介绍

      依赖注入,即 Spring 中的 DI(Dependency Injection)

      在我们学习 Spring 的使用时,我们最熟悉它的特点是:IOC 控制反转DI 依赖注入。Spring 通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

      书面介绍:依赖注入,就是指对象是被动的接受依赖类而不是自己主动去寻找。也就是说对象不是从IOC容器中查找它的依赖类,而是在容器实例化对象的时候,已经将该对象所依赖的类注入给了该对象。

    2.何时进行依赖注入

      当 Spring IOC 容器启动时完成定位加载注册操作,此时 IOC容器已经获取到 applicationContext.xml 配置文件中的全部配置,并以 BeanDefinition类的形式保存在一个名为:beanDefinitionMap 的 ConcurrentHashMap 中。如下所示:

    //存储注册信息的BeanDefinition
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    

      此时 IOC容器只是对这些 BeanDefinition 类进行存储,而并没有真正的创建实例对象,也就是说并没有进行依赖注入操作。 那么何时完成依赖注入操作??

      何时依赖注入,此处就涉及到了 lazy-init 属性的使用。lazy-init 属性是 Spring 中用来延迟加载 bean 的。使用在用户定义的<bean>标签中,默认为false。使用实例如下:

    //lazy-init默认为 false,可省略
    <bean id="testBean" class="com.eacxzm.TestBean">
     
    //lazy-init为 true时
    <bean id="testBean" class="com.eacxzm.TestBean" lazy-init="true">
    

    依赖注入,分如下两种情况:lazy-init 属性为 true / false 时

      ① lazy-init 属性默认为 false。这种情况会在启动 Spring 容器时,完成实例对象的创建。即 Spring 容器启动时触发依赖注入。

      ②当用户为<bean>标签手工配置 lazy-init=true属性后,这样容器在启动时并不会完成实例对象的创建,不会触发依赖注入。只有当用户第一次通过调用 Spring 的 getBean()方法时,才会向 IOC 容器索要 Bean 对象,此时 IOC 容器才会触发依赖注入。

    3.源码分析从何入手

      我们在学习 Spring 的使用时,都是通过如下一段代码,开启 Spring 的学习之路。通过本文以上介绍,依赖注入源码分析,就是从 getBean() 方法入手。

    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    User user = (User)applicationContext.getBean("user");
    

      Spring IOC 源码解析,我们知道 BeanFactory 接口定义了 Spring IOC 容器的基本功能规范,是Spring最底层的接口。BeanFactory 接口中重载了几个 getBean( )方法。getBean() 方法的具体实现,是由 AbstractBeanFactory 类来实现的,所以我们从 AbstractBeanFactory 类中的 getBean() 方法入手。此处附 BeanFactory接口一览

    public interface BeanFactory {
     
        //对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象,如果需要得到工厂本身,需要转义
        String FACTORY_BEAN_PREFIX = "&";
     
        //根据 bean 的名字,获取在 IOC 容器中得到 bean 实例
        Object getBean(String name) throws BeansException;
     
        //根据 bean 的名字和 Class 类型来得到 bean 实例,增加了类型安全验证机制。
        <T> T getBean(String name, Class<T> requiredType) throws BeansException;
     
        //根据 Class 类型来获取bean实例
        <T> T getBean(Class<T> requiredType) throws BeansException;
     
        //根据 bean 的名字和指定参数,来获取bean实例
        Object getBean(String name, Object... args) throws BeansException;
    
        //省略部分代码    
    }
    

    4.Spring DI源码分析时序图

    \color{red}{单击放大查看(高清图下载请转至文末链接)}】你也可以直接访问链接获取:https://www.processon.com/view/link/5e683e17e4b0a388cc3fae82

    Spring依赖注入(DI)源码分析时序图.jpg

    5.实例化出来的bean对象用什么类型来存储?

    答案是:使用 FactoryBean类型来存储。

    此处需要了解一下 BeanFactory 和 FactoryBean 的区别

    • BeanFactory:bean工厂,用来生产 Bean 对象的一个工厂类;
    • FactoryBean:由 Spring 生产出来的 Bean,就是 FactoryBean。是由 Spring 帮你生产的,不是由开发者自己生产(new)出来的

    6.JDK反射和Cglib代理,Spring默认使用哪个创建 Bean 实例?

      Spring 默认使用 Cglib 代理来创建 Bean 实例。

      因为:使用 Cglib 代理来创建 Bean,Spring 可以拥有该代理类的控制权;如果使用反射,获取到的控制权是有限的。

      比如接下来我们要介绍的 Spring AOP 功能(面向切面编程)Spring Listener(监听机制),如果使用反射,显然无法满足这些功能,所以 Spring 默认使用的是 Cglib 代理。

    7.附部分源码

    7.1 autowireByName()

    //根据名称对属性进行自动依赖注入
    protected void autowireByName(
            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
    
        //对Bean对象中非简单属性(不是简单继承的对象,如8中原始类型,字符串,URL等都是简单属性)进行处理
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            //如果Spring IOC容器中包含指定名称的Bean
            if (containsBean(propertyName)) {
                //调用getBean方法向IOC容器索取指定名称的Bean实例,迭代触发属性的初始化和依赖注入
                Object bean = getBean(propertyName);
                //为指定名称的属性赋予属性值
                pvs.add(propertyName, bean);
                //指定名称属性注册依赖Bean名称,进行属性依赖注入
                registerDependentBean(propertyName, beanName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added autowiring by name from bean name '" + beanName +
                            "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
                            "' by name: no matching bean found");
                }
            }
        }
    }
    

    7.2 autowireByType()

    //根据类型对属性进行自动依赖注入
    protected void autowireByType(
            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
    
        //获取用户定义的类型转换器
        TypeConverter converter = getCustomTypeConverter();
        if (converter == null) {
            converter = bw;
        }
    
        //存放解析的要注入的属性
        Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
        //对Bean对象中非简单属性(不是简单继承的对象,如8中原始类型,字符
        //URL等都是简单属性)进行处理
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            try {
                //获取指定属性名称的属性描述器
                PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
                // Don't try autowiring by type for type Object: never makes sense,
                // even if it technically is a unsatisfied, non-simple property.
                //不对Object类型的属性进行autowiring自动依赖注入
                if (Object.class != pd.getPropertyType()) {
                    //获取属性的setter方法
                    MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
                    // Do not allow eager init for type matching in case of a prioritized post-processor.
                    //检查指定类型是否可以被转换为目标对象的类型
                    boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance());
                    //创建一个要被注入的依赖描述
                    DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
                    //根据容器的Bean定义解析依赖关系,返回所有要被注入的Bean对象
                    Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
                    if (autowiredArgument != null) {
                        //为属性赋值所引用的对象
                        pvs.add(propertyName, autowiredArgument);
                    }
                    for (String autowiredBeanName : autowiredBeanNames) {
                        //指定名称属性注册依赖Bean名称,进行属性依赖注入
                        registerDependentBean(autowiredBeanName, beanName);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
                                    propertyName + "' to bean named '" + autowiredBeanName + "'");
                        }
                    }
                    //释放已自动注入的属性
                    autowiredBeanNames.clear();
                }
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
            }
        }
    }
    

    7.3 applyPropertyValues()

    //对未配置autowiring的属性进行依赖注入处理的过程
    protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
        if (pvs.isEmpty()) {
            return;
        }
    
        //封装属性值
        MutablePropertyValues mpvs = null;
        List<PropertyValue> original;
    
        if (System.getSecurityManager() != null) {
            if (bw instanceof BeanWrapperImpl) {
                //设置安全上下文,JDK安全机制
                ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
            }
        }
    
        if (pvs instanceof MutablePropertyValues) {
            mpvs = (MutablePropertyValues) pvs;
            //属性值已经转换
            if (mpvs.isConverted()) {
                // Shortcut: use the pre-converted values as-is.
                try {
                    //为实例化对象设置属性值
                    bw.setPropertyValues(mpvs);
                    return;
                }
                catch (BeansException ex) {
                    throw new BeanCreationException(
                            mbd.getResourceDescription(), beanName, "Error setting property values", ex);
                }
            }
            //获取属性值对象的原始类型值
            original = mpvs.getPropertyValueList();
        }
        else {
            original = Arrays.asList(pvs.getPropertyValues());
        }
    
        //获取用户自定义的类型转换
        TypeConverter converter = getCustomTypeConverter();
        if (converter == null) {
            converter = bw;
        }
        //创建一个Bean定义属性值解析器,将Bean定义中的属性值解析为Bean实例对象的实际值
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
    
        // Create a deep copy, resolving any references for values.
    
        //为属性的解析值创建一个拷贝,将拷贝的数据注入到实例对象中
        List<PropertyValue> deepCopy = new ArrayList<>(original.size());
        boolean resolveNecessary = false;
        for (PropertyValue pv : original) {
            //属性值不需要转换
            if (pv.isConverted()) {
                deepCopy.add(pv);
            }
            //属性值需要转换
            else {
                String propertyName = pv.getName();
                //原始的属性值,即转换之前的属性值
                Object originalValue = pv.getValue();
                //7.3.1.-->重点方法:转换属性值,例如将引用转换为IOC容器中实例化对象引用
                Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
                //转换之后的属性值
                Object convertedValue = resolvedValue;
                //属性值是否可以转换
                boolean convertible = bw.isWritableProperty(propertyName) &&
                        !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
                if (convertible) {
                    //使用用户自定义的类型转换器转换属性值
                    convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
                }
                // Possibly store converted value in merged bean definition,
                // in order to avoid re-conversion for every created bean instance.
                //存储转换后的属性值,避免每次属性注入时的转换工作
                if (resolvedValue == originalValue) {
                    if (convertible) {
                        //设置属性转换之后的值
                        pv.setConvertedValue(convertedValue);
                    }
                    deepCopy.add(pv);
                }
                //属性是可转换的,且属性原始值是字符串类型,且属性的原始类型值不是
                //动态生成的字符串,且属性的原始值不是集合或者数组类型
                else if (convertible && originalValue instanceof TypedStringValue &&
                        !((TypedStringValue) originalValue).isDynamic() &&
                        !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
                    pv.setConvertedValue(convertedValue);
                    //重新封装属性的值
                    deepCopy.add(pv);
                }
                else {
                    resolveNecessary = true;
                    deepCopy.add(new PropertyValue(pv, convertedValue));
                }
            }
        }
        if (mpvs != null && !resolveNecessary) {
            //标记属性值已经转换过
            mpvs.setConverted();
        }
    
        // Set our (possibly massaged) deep copy.
        //7.3.2.-->重点方法:进行属性依赖注入
        try {
            bw.setPropertyValues(new MutablePropertyValues(deepCopy));
        }
        catch (BeansException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Error setting property values", ex);
        }
    }
    

    7.3.1 resolveValueIfNecessary()方法,完成对属性值的解析操作

    //解析属性值,对注入类型进行转换
    @Nullable
    public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
        // We must check each value to see whether it requires a runtime reference
        // to another bean to be resolved.
        //对引用类型的属性进行解析
        if (value instanceof RuntimeBeanReference) {
            RuntimeBeanReference ref = (RuntimeBeanReference) value;
            //调用引用类型属性的解析方法
            return resolveReference(argName, ref);
        }
        //对属性值是引用容器中另一个Bean名称的解析
        else if (value instanceof RuntimeBeanNameReference) {
            String refName = ((RuntimeBeanNameReference) value).getBeanName();
            refName = String.valueOf(doEvaluate(refName));
            //从容器中获取指定名称的Bean
            if (!this.beanFactory.containsBean(refName)) {
                throw new BeanDefinitionStoreException(
                        "Invalid bean name '" + refName + "' in bean reference for " + argName);
            }
            return refName;
        }
        //对Bean类型属性的解析,主要是Bean中的内部类
        else if (value instanceof BeanDefinitionHolder) {
            // Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.
            BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
            return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
        }
        else if (value instanceof BeanDefinition) {
            // Resolve plain BeanDefinition, without contained name: use dummy name.
            BeanDefinition bd = (BeanDefinition) value;
            String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
                    ObjectUtils.getIdentityHexString(bd);
            return resolveInnerBean(argName, innerBeanName, bd);
        }
        //对集合数组类型的属性解析
        else if (value instanceof ManagedArray) {
            // May need to resolve contained runtime references.
            ManagedArray array = (ManagedArray) value;
            //获取数组的类型
            Class<?> elementType = array.resolvedElementType;
            if (elementType == null) {
                //获取数组元素的类型
                String elementTypeName = array.getElementTypeName();
                if (StringUtils.hasText(elementTypeName)) {
                    try {
                        //使用反射机制创建指定类型的对象
                        elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader());
                        array.resolvedElementType = elementType;
                    }
                    catch (Throwable ex) {
                        // Improve the message by showing the context.
                        throw new BeanCreationException(
                                this.beanDefinition.getResourceDescription(), this.beanName,
                                "Error resolving array type for " + argName, ex);
                    }
                }
                //没有获取到数组的类型,也没有获取到数组元素的类型
                //则直接设置数组的类型为Object
                else {
                    elementType = Object.class;
                }
            }
            //创建指定类型的数组
            return resolveManagedArray(argName, (List<?>) value, elementType);
        }
        //解析list类型的属性值
        else if (value instanceof ManagedList) {
            // May need to resolve contained runtime references.
            return resolveManagedList(argName, (List<?>) value);
        }
        //解析set类型的属性值
        else if (value instanceof ManagedSet) {
            // May need to resolve contained runtime references.
            return resolveManagedSet(argName, (Set<?>) value);
        }
        //解析map类型的属性值
        else if (value instanceof ManagedMap) {
            // May need to resolve contained runtime references.
            return resolveManagedMap(argName, (Map<?, ?>) value);
        }
        //解析props类型的属性值,props其实就是key和value均为字符串的map
        else if (value instanceof ManagedProperties) {
            Properties original = (Properties) value;
            //创建一个拷贝,用于作为解析后的返回值
            Properties copy = new Properties();
            original.forEach((propKey, propValue) -> {
                if (propKey instanceof TypedStringValue) {
                    propKey = evaluate((TypedStringValue) propKey);
                }
                if (propValue instanceof TypedStringValue) {
                    propValue = evaluate((TypedStringValue) propValue);
                }
                if (propKey == null || propValue == null) {
                    throw new BeanCreationException(
                            this.beanDefinition.getResourceDescription(), this.beanName,
                            "Error converting Properties key/value pair for " + argName + ": resolved to null");
                }
                copy.put(propKey, propValue);
            });
            return copy;
        }
        //解析字符串类型的属性值
        else if (value instanceof TypedStringValue) {
            // Convert value to target type here.
            TypedStringValue typedStringValue = (TypedStringValue) value;
            Object valueObject = evaluate(typedStringValue);
            try {
                //获取属性的目标类型
                Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
                if (resolvedTargetType != null) {
                    //对目标类型的属性进行解析,递归调用
                    return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
                }
                //没有获取到属性的目标对象,则按Object类型返回
                else {
                    return valueObject;
                }
            }
            catch (Throwable ex) {
                // Improve the message by showing the context.
                throw new BeanCreationException(
                        this.beanDefinition.getResourceDescription(), this.beanName,
                        "Error converting typed String value for " + argName, ex);
            }
        }
        else if (value instanceof NullBean) {
            return null;
        }
        else {
            return evaluate(value);
        }
    }
    

    7.3.2 bw.setPropertyValues() 方法,完成对属性的注入操作

      bw.setPropertyValues() 方法的真正实现,是 AbstractNestablePropertyAccessor 类下的processKeyedProperty() 方法,此处附上该方法。

    //实现属性依赖注入功能
    @SuppressWarnings("unchecked")
    private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        //调用属性的getter(readerMethod)方法,获取属性的值
        Object propValue = getPropertyHoldingValue(tokens);
        PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
        if (ph == null) {
            throw new InvalidPropertyException(
                    getRootClass(), this.nestedPath + tokens.actualName, "No property handler found");
        }
        Assert.state(tokens.keys != null, "No token keys");
        String lastKey = tokens.keys[tokens.keys.length - 1];
    
        //注入array类型的属性值
        if (propValue.getClass().isArray()) {
            Class<?> requiredType = propValue.getClass().getComponentType();
            int arrayIndex = Integer.parseInt(lastKey);
            Object oldValue = null;
            try {
                if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
                    oldValue = Array.get(propValue, arrayIndex);
                }
                Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
                        requiredType, ph.nested(tokens.keys.length));
                //获取集合类型属性的长度
                int length = Array.getLength(propValue);
                if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
                    Class<?> componentType = propValue.getClass().getComponentType();
                    Object newArray = Array.newInstance(componentType, arrayIndex + 1);
                    System.arraycopy(propValue, 0, newArray, 0, length);
                    setPropertyValue(tokens.actualName, newArray);
                    //调用属性的getter(readerMethod)方法,获取属性的值
                    propValue = getPropertyValue(tokens.actualName);
                }
                //将属性的值赋值给数组中的元素
                Array.set(propValue, arrayIndex, convertedValue);
            }
            catch (IndexOutOfBoundsException ex) {
                throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
                        "Invalid array index in property path '" + tokens.canonicalName + "'", ex);
            }
        }
    
        //注入list类型的属性值
        else if (propValue instanceof List) {
            //获取list集合的类型
            Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
            List<Object> list = (List<Object>) propValue;
            //获取list集合的size
            int index = Integer.parseInt(lastKey);
            Object oldValue = null;
            if (isExtractOldValueForEditor() && index < list.size()) {
                oldValue = list.get(index);
            }
            //获取list解析后的属性值
            Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
                    requiredType, ph.nested(tokens.keys.length));
            int size = list.size();
            //如果list的长度大于属性值的长度,则多余的元素赋值为null
            if (index >= size && index < this.autoGrowCollectionLimit) {
                for (int i = size; i < index; i++) {
                    try {
                        list.add(null);
                    }
                    catch (NullPointerException ex) {
                        throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
                                "Cannot set element with index " + index + " in List of size " +
                                size + ", accessed using property path '" + tokens.canonicalName +
                                "': List does not support filling up gaps with null elements");
                    }
                }
                list.add(convertedValue);
            }
            else {
                try {
                    //将值添加到list中
                    list.set(index, convertedValue);
                }
                catch (IndexOutOfBoundsException ex) {
                    throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
                            "Invalid list index in property path '" + tokens.canonicalName + "'", ex);
                }
            }
        }
    
        //注入map类型的属性值
        else if (propValue instanceof Map) {
            //获取map集合key的类型
            Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
            //获取map集合value的类型
            Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
            Map<Object, Object> map = (Map<Object, Object>) propValue;
            // IMPORTANT: Do not pass full property name in here - property editors
            // must not kick in for map keys but rather only for map values.
            TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
            //解析map类型属性key值
            Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
            Object oldValue = null;
            if (isExtractOldValueForEditor()) {
                oldValue = map.get(convertedMapKey);
            }
            // Pass full property name and old value in here, since we want full
            // conversion ability for map values.
            //解析map类型属性value值
            Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
                    mapValueType, ph.nested(tokens.keys.length));
            //将解析后的key和value值赋值给map集合属性
            map.put(convertedMapKey, convertedMapValue);
        }
    
        else {
            throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
                    "Property referenced in indexed property path '" + tokens.canonicalName +
                    "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
        }
    }
    
    

      其他源码就不再过多一步步介绍,你可以按照 4.Spring DI 源码时序图 ,打开源码来进一步分析,此处粘贴过多代码无多大意义。附 spring-framework-5.0.2.RELEASE (中文注释)版本,直接解压 IDEA 打开即可

    地址: 1.spring-framework-5.0.2.RELEASE (中文注释)版本

      2.网盘地址:spring-framework-5.0.2.RELEASE (中文注释)版本(提取码:uck4 )


    恭喜您,枯燥源码看到这里。Spring 依赖注入(DI) 就介绍到此


    博主写作不易,来个关注呗

    求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙

    博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ

    相关文章

      网友评论

          本文标题:Spring 依赖注入(DI) 源码解析

          本文链接:https://www.haomeiwen.com/subject/blseqktx.html