spring容器之bean加載策略之構(gòu)造函數(shù)注入模式

在上節(jié)spring容器之創(chuàng)建bean實(shí)例中我們看了# createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args)完成了bean的初始化,雖然那個(gè)方法有點(diǎn)長(zhǎng),但我們最后總結(jié)發(fā)現(xiàn),spring是通過(guò)不同的策略模式來(lái)完成bean的初始化如:

  • 通過(guò)回調(diào)#obtainFromSupplier(final String beanName, final RootBeanDefinition mbd) 方法來(lái)初始化
  • 通過(guò)工廠方法的方式:#instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs)
  • 還有一種就是通過(guò)構(gòu)造函數(shù)自動(dòng)注入的方式:#autowireConstructor(final String beanName, final RootBeanDefinition mbd, Constructor<?>[] chosenCtors, final Object[] explicitArgs)來(lái)實(shí)現(xiàn)bean的初始化
  • 最后一種是默認(rèn)構(gòu)造函數(shù)的方式:#instantiateBean(final String beanName, final RootBeanDefinition mbd)的方法來(lái)實(shí)現(xiàn)bean的初始化工作

關(guān)于前面的兩種我們已經(jīng)說(shuō)了,這里就不在重復(fù)了,我們來(lái)看后兩種:

通過(guò)構(gòu)造函數(shù)自動(dòng)注入的方式
AbstractAutowireCapableBeanFactory.java
protected BeanWrapper autowireConstructor(
        String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

    return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}



public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
        @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
    //準(zhǔn)備一個(gè)BeanWrapperImpl用于bean實(shí)例的封裝
    BeanWrapperImpl bw = new BeanWrapperImpl();
    //初始化bw
    this.beanFactory.initBeanWrapper(bw);
    //1.獲取constructorToUse argsHolderToUse和argsToUse參數(shù)
    Constructor<?> constructorToUse = null;//所使用的的構(gòu)造函數(shù)
    ArgumentsHolder argsHolderToUse = null;//構(gòu)造參數(shù)
    Object[] argsToUse = null;
    //2.通過(guò)explicitArgs參數(shù)來(lái)決定實(shí)例化bean所使用的構(gòu)造函數(shù)及構(gòu)造參數(shù)
    //explicitArgs為通過(guò)getBean傳入的,如果在調(diào)用的過(guò)程中指定構(gòu)造函數(shù)和參數(shù)那么直接使用
    if (explicitArgs != null) {
        argsToUse = explicitArgs;
    }
    //2.1.這里表示沒(méi)有指定那么會(huì)從配置文件中去解析獲取
    else {
        Object[] argsToResolve = null;
        synchronized (mbd.constructorArgumentLock) {
            //嘗試著從緩存中獲取工廠方法或者構(gòu)造函數(shù)
            constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
            if (constructorToUse != null && mbd.constructorArgumentsResolved) {
                // Found a cached constructor...
                //從緩存中獲取構(gòu)造參數(shù)
                argsToUse = mbd.resolvedConstructorArguments;
                if (argsToUse == null) {
                    //從緩存中沒(méi)獲取到,則從原先準(zhǔn)備的構(gòu)造參數(shù)緩存中獲取
                    argsToResolve = mbd.preparedConstructorArguments;
                }
            }
        }
        //2.2.如果緩存中
        //解析保存在beanDefinition中的參數(shù)
        //如給定方法的構(gòu)造函數(shù) A(int ,int ),則通過(guò)此方法后就會(huì)把配置文件中的("1","1")轉(zhuǎn)換為 (1,1)
        //緩存中的值可能是原始值也有可能是最終值
        if (argsToResolve != null) {
            argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
        }
    }
    //2.3.沒(méi)有被緩存
    if (constructorToUse == null || argsToUse == null) {
        // Take specified constructors, if any.

        //如果chosenCtors沒(méi)有被傳入,那么獲取構(gòu)造函數(shù)
        Constructor<?>[] candidates = chosenCtors;
        if (candidates == null) {
            Class<?> beanClass = mbd.getBeanClass();
            try {
                candidates = (mbd.isNonPublicAccessAllowed() ?
                        beanClass.getDeclaredConstructors() : beanClass.getConstructors());
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Resolution of declared constructors on bean Class [" + beanClass.getName() +
                        "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
            }
        }
        //2.4.使用默認(rèn)的構(gòu)造函數(shù)初始化bean實(shí)例
        //因?yàn)檫@里構(gòu)造參數(shù)不存在
        if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
            Constructor<?> uniqueCandidate = candidates[0];
            if (uniqueCandidate.getParameterCount() == 0) {
                synchronized (mbd.constructorArgumentLock) {
                    mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
                    mbd.constructorArgumentsResolved = true;
                    mbd.resolvedConstructorArguments = EMPTY_ARGS;
                }
                //封裝初始化之后的bean
                bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
                return bw;
            }
        }

        // Need to resolve the constructor.
        //2.5.是否需要解析器
        boolean autowiring = (chosenCtors != null ||
                mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
        //用于承載解析后的構(gòu)造函數(shù)參數(shù)的值
        ConstructorArgumentValues resolvedValues = null;
        int minNrOfArgs;
        if (explicitArgs != null) {
            minNrOfArgs = explicitArgs.length;
        }
        else {
            //獲取配置文件構(gòu)造參數(shù)
            ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
            resolvedValues = new ConstructorArgumentValues();
            //解析構(gòu)造參數(shù)
            minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
        }
        //3.對(duì)構(gòu)造函數(shù)進(jìn)行排序
        //public構(gòu)造函數(shù)優(yōu)先,非public次之
        AutowireUtils.sortConstructors(candidates);
        //最小參數(shù)的權(quán)重
        int minTypeDiffWeight = Integer.MAX_VALUE;
        Set<Constructor<?>> ambiguousConstructors = null;
        LinkedList<UnsatisfiedDependencyException> causes = null;
        //遍歷candidates 獲取構(gòu)造函數(shù)的參數(shù)類型
        for (Constructor<?> candidate : candidates) {
            Class<?>[] paramTypes = candidate.getParameterTypes();
            // 如果已經(jīng)找到選用的構(gòu)造函數(shù)或者需要的參數(shù)個(gè)數(shù)小于當(dāng)前的構(gòu)造函數(shù)參數(shù)個(gè)數(shù),則終止。
            //因?yàn)?,已?jīng)按照參數(shù)個(gè)數(shù)降序排列了
            if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
                // Already found greedy constructor that can be satisfied ->
                // do not look any further, there are only less greedy constructors left.
                break;
            }
            //這里表示參數(shù)的個(gè)數(shù)不相等,那么繼續(xù)
            if (paramTypes.length < minNrOfArgs) {
                continue;
            }
            //參數(shù)的持有者對(duì)象
            ArgumentsHolder argsHolder;
            if (resolvedValues != null) {
                try {
                    //獲取注解上的參數(shù)名稱
                    String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
                    if (paramNames == null) {
                        //獲取構(gòu)造參數(shù)的探索器
                        ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
                        if (pnd != null) {
                            //通過(guò)探測(cè)器獲取參數(shù)名稱
                            paramNames = pnd.getParameterNames(candidate);
                        }
                    }
                    //根據(jù)bean的名稱和構(gòu)造參數(shù)以及構(gòu)造函數(shù)來(lái)創(chuàng)建參數(shù)持有者ArgumentsHolder對(duì)象
                    argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
                            getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
                }//如果是UnsatisfiedDependencyException異常則添加到causes中
                catch (UnsatisfiedDependencyException ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
                    }
                    // Swallow and try next constructor.
                    if (causes == null) {
                        causes = new LinkedList<>();
                    }
                    causes.add(ex);
                    continue;
                }
            }
            //resolvedValues為null的情況下
            else {
                // Explicit arguments given -> arguments length must match exactly.
                //構(gòu)造函數(shù)沒(méi)有參數(shù)的情況下
                if (paramTypes.length != explicitArgs.length) {
                    continue;
                }
                //通過(guò)explicitArgs來(lái)創(chuàng)建ArgumentsHolder
                argsHolder = new ArgumentsHolder(explicitArgs);
            }
            //isLenientConstructorResolution主要是判斷解析構(gòu)造函數(shù)的時(shí)候是否以寬松模式還是嚴(yán)格模式
            //嚴(yán)格模式:解析構(gòu)造函數(shù)時(shí),必須所有的都需要匹配,否則拋出異常
            //寬松模式:使用具有"最接近的模式"進(jìn)行匹配
            //typeDiffWeight:類型差異權(quán)重
            int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                    argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
            // Choose this constructor if it represents the closest match.
            //如果它代表著當(dāng)前最接近的匹配則選擇作為構(gòu)造函數(shù)
            if (typeDiffWeight < minTypeDiffWeight) {
                constructorToUse = candidate;
                argsHolderToUse = argsHolder;
                argsToUse = argsHolder.arguments;
                minTypeDiffWeight = typeDiffWeight;
                ambiguousConstructors = null;
            }
            //類型差異權(quán)重等于參數(shù)最小權(quán)重,將constructorToUse進(jìn)行保存
            else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
                if (ambiguousConstructors == null) {
                    ambiguousConstructors = new LinkedHashSet<>();
                    ambiguousConstructors.add(constructorToUse);
                }
                ambiguousConstructors.add(candidate);
            }
        }
        //沒(méi)有可以執(zhí)行的構(gòu)造器函數(shù)或工廠方法,直接拋UnsatisfiedDependencyException異常
        if (constructorToUse == null) {
            if (causes != null) {
                UnsatisfiedDependencyException ex = causes.removeLast();
                for (Exception cause : causes) {
                    this.beanFactory.onSuppressedException(cause);
                }
                throw ex;
            }
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Could not resolve matching constructor " +
                    "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
        }
        else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Ambiguous constructor matches found in bean '" + beanName + "' " +
                    "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
                    ambiguousConstructors);
        }
        //4.將解析的參數(shù)進(jìn)行保存
        if (explicitArgs == null && argsHolderToUse != null) {
            argsHolderToUse.storeCache(mbd, constructorToUse);
        }
    }

    Assert.state(argsToUse != null, "Unresolved constructor arguments");
    //5.實(shí)例化bean并封裝在bw中
    bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
    return bw;
}

關(guān)于構(gòu)造函數(shù)創(chuàng)建bean實(shí)例的過(guò)程中,我們發(fā)現(xiàn)跟#instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) 方法一樣,關(guān)于詳細(xì)部分這里不再深究可以去看我的上一篇文章,接下來(lái)我們看看一個(gè)重要的部分就是創(chuàng)建完的bean是如何完成初始化的過(guò)程.

初始化過(guò)程

在#autowireConstructor()方法的末尾我們看到的是最后將創(chuàng)建完的bean通過(guò)方法# instantiate(String beanName, RootBeanDefinition mb Constructor<?> constructorToUse, Object[] argsToUse)來(lái)實(shí)現(xiàn)的,跟蹤代碼發(fā)現(xiàn):

private Object instantiate(
        String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) {

    try {
        InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
        if (System.getSecurityManager() != null) {
            return AccessController.doPrivileged((PrivilegedAction<Object>) () ->
                    strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse),
                    this.beanFactory.getAccessControlContext());
        }
        else {
            return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
        }
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                "Bean instantiation via constructor failed", ex);
    }
}

該方法我們?cè)谏掀恼轮兄v過(guò)了這里不再啰嗦,發(fā)現(xiàn)這里并不是真正的核心處理方法,接著看:

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
        final Constructor<?> ctor, Object... args) {
    //判斷是否有方法需要重載
    if (!bd.hasMethodOverrides()) {
        if (System.getSecurityManager() != null) {
            // use own privileged to change accessibility (when security is on)
            //設(shè)置訪問(wèn)權(quán)限
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                ReflectionUtils.makeAccessible(ctor);
                return null;
            });
        }
        //1.通過(guò) BeanUtils直接使用構(gòu)造器對(duì)象實(shí)例化Bean對(duì)象
        return BeanUtils.instantiateClass(ctor, args);
    }
    else {
        //2初始化CGLB對(duì)象
        return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
    }
}

在該方法中,首先設(shè)置對(duì)訪問(wèn)權(quán)限的設(shè)置,首先是通過(guò)構(gòu)造器來(lái)實(shí)例化bean對(duì)象

  BeanUtils.java
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    Assert.notNull(ctor, "Constructor must not be null");
    try {
        //設(shè)置構(gòu)造器可訪問(wèn)的權(quán)限
        ReflectionUtils.makeAccessible(ctor);
        //通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象
        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
            return KotlinDelegate.instantiateClass(ctor, args);
        }
        else {
            //獲取構(gòu)造函數(shù)的參數(shù)類型
            Class<?>[] parameterTypes = ctor.getParameterTypes();
            Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
            //初始化新的參數(shù)長(zhǎng)度
            Object[] argsWithDefaultValues = new Object[args.length];
            //循環(huán)處理構(gòu)造參數(shù)的類型
            for (int i = 0 ; i < args.length; i++) {
                if (args[i] == null) {
                    //如果參數(shù)是isPrimitive類型的,保存其對(duì)應(yīng)的值在argsWithDefaultValues數(shù)組中
                    Class<?> parameterType = parameterTypes[i];
                    argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
                }
                //用來(lái)構(gòu)建實(shí)例的參數(shù)不為null時(shí)
                else {
                    argsWithDefaultValues[i] = args[i];
                }
            }
            //通過(guò)夠構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象
            return ctor.newInstance(argsWithDefaultValues);
        }
    }
    //對(duì)各種異常進(jìn)行封裝,統(tǒng)一拋出BeanInstantiationException異常
    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());
    }
}

上述方法主要是通過(guò)構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象,主要的區(qū)別在于:

  • 如果我們傳入的構(gòu)造參數(shù)存在,則直接創(chuàng)建
  • 反之是對(duì)構(gòu)造參數(shù)的解析獲取,最后利用反射和內(nèi)省機(jī)制來(lái)構(gòu)建bean實(shí)例

在2我們可以看到是初始化cglb實(shí)例,接著看:

protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName,
        BeanFactory owner, @Nullable Constructor<?> ctor, Object... args) {

    throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
}

該方法為空實(shí)現(xiàn),主要是由子類org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy 來(lái)實(shí)現(xiàn),具體來(lái)看代碼:

CglibSubclassingInstantiationStrategy.java
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    return instantiateWithMethodInjection(bd, beanName, owner, null);
}

@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
        @Nullable Constructor<?> ctor, Object... args) {

    // Must generate CGLIB subclass...
    //<>生成一個(gè)CGLB子類對(duì)象
    return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}


/**
 * An inner class created for historical reasons to avoid external CGLIB dependency
 * in Spring versions earlier than 3.2.
 */
private static class CglibSubclassCreator {

    private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
            {NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};

    private final RootBeanDefinition beanDefinition;

    private final BeanFactory owner;

    CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
        this.beanDefinition = beanDefinition;
        this.owner = owner;
    }

上述為創(chuàng)建CGLB實(shí)例的過(guò)程,在<>處我們可以看到還是通過(guò)#instantiate(@Nullable Constructor<?> ctor, Object... args)方法來(lái)完成實(shí)例的初始化過(guò)程,代碼如下:

/**
     * Create a new instance of a dynamically generated subclass implementing the
     * required lookups.
     * @param ctor constructor to use. If this is {@code null}, use the
     * no-arg constructor (no parameterization, or Setter Injection)
     * @param args arguments to use for the constructor.
     * Ignored if the {@code ctor} parameter is {@code null}.
     * @return new instance of the dynamically generated subclass
     */
    public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
        //創(chuàng)建一個(gè)cglb的代理類
        Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
        Object instance;
        //如果當(dāng)前構(gòu)造器不存在,通過(guò)BeanUtils調(diào)用默認(rèn)構(gòu)造器來(lái)創(chuàng)建
        if (ctor == null) {
            instance = BeanUtils.instantiateClass(subclass);
        }
        else {
            try {
                //獲取代理對(duì)象的構(gòu)造器
                Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
                //創(chuàng)建實(shí)例
                instance = enhancedSubclassConstructor.newInstance(args);
            }
            catch (Exception ex) {
                throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
                        "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
            }
        }
        // SPR-10785: set callbacks directly on the instance instead of in the
        // enhanced class (via the Enhancer) in order to avoid memory leaks.
        Factory factory = (Factory) instance;
        factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
                new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
                new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
        return instance;
    }

在獲代碼中我們可以看到,創(chuàng)建CGLB對(duì)象的過(guò)程,首先是獲取代理對(duì)象,如果當(dāng)前構(gòu)造器不存在則則使用默認(rèn)無(wú)參的,最后完成bean的創(chuàng)建,這就是關(guān)于CGLB實(shí)例創(chuàng)建的過(guò)程

小結(jié)

關(guān)于#createBeanInstance()的過(guò)程,spring對(duì)于不同創(chuàng)建都會(huì)選擇其對(duì)應(yīng)的策略模式來(lái)完成,關(guān)于策略模式有以下幾種:

  • 通過(guò)Supplier回調(diào)方式的方式來(lái)實(shí)現(xiàn)bean的創(chuàng)建
  • 通過(guò)工廠方法來(lái)完成bean的創(chuàng)建
  • 通過(guò)構(gòu)造器自動(dòng)注入的方式來(lái)完成bean的創(chuàng)建
  • 最后一種是通過(guò)默認(rèn)構(gòu)造器的方式

在以上4中的策略模式中,其中工廠方法和構(gòu)造器自動(dòng)注入的方式最為復(fù)雜,簡(jiǎn)友們可以仔細(xì)的看看,哈哈哈,代碼有點(diǎn)長(zhǎng)哦!其兩者的方式很接近,當(dāng)然我們?cè)谧詈笱苌隽薈GLiB實(shí)例的創(chuàng)建的過(guò)程,發(fā)現(xiàn)spring是通過(guò)#bd.hasMethodOverrides()來(lái)判斷是否有覆蓋的方法,如果有則只能通過(guò)cglib的方式來(lái)實(shí)例化,反之利用反射的方式來(lái)創(chuàng)建.

關(guān)于createBeanInstance()的詳細(xì)拆分就到這里了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文連接:http://www.itdecent.cn/p/be016c5aae6a本文作者:gks09@qq....
    管愷森閱讀 2,139評(píng)論 0 10
  • 終于拿到offer了,但是還是想試試下周幾家大公司,努力準(zhǔn)備吧,不可懈怠!那什么是依賴注入呢? 所謂依賴注入,就是...
    Ernest_Chou閱讀 986評(píng)論 0 0
  • 1. 詳解Spring 中如何控制2個(gè)bean中的初始化順序 ??開(kāi)發(fā)過(guò)程中有這樣一個(gè)場(chǎng)景,2個(gè) bean 初始化...
    未名枯草閱讀 1,379評(píng)論 0 1
  • 體重減輕是件讓人高興的事,那么體重增加就是件讓人苦惱的事了。 前段時(shí)間發(fā)燒體重降了好幾斤,之后略有上浮,但始終保持...
    a01dc8d731f5閱讀 174評(píng)論 0 0
  • 首先,我想對(duì)自己說(shuō)一聲對(duì)不起!昨天斷更了,哎!不知道是因?yàn)樯?,腦袋里面沒(méi)有貨,難道是讀書少了? 昨天晚上開(kāi)始,...
    逝水年華丶勿忘閱讀 234評(píng)論 0 0

友情鏈接更多精彩內(nèi)容