一、前言

  • Springboot源码解析是一件大工程,逐行逐句的去钻研代码,会很干燥,也不容易坚持下去。
  • 咱们不谋求大而全,而是试着每次去钻研一个小知识点,最终聚沙成塔,这就是咱们的springboot源码管中窥豹系列。

二、bean如何生成?

  • 咱们之前介绍了beanDefinition的构造: springboot源码解析-管中窥豹系列之BeanDefinition(八)
  • 咱们之前也介绍了BeanPostProcessor和BeanFactoryPostProcessor
  • 咱们也介绍了主动拆卸,各种应用getBean的调用
  • 有两个外围问题:beanDefinition是什么时候加载进springcontext容器的?又是如何生成bean的?
  • 咱们上一节解决了第一个问题:beanDefinition是什么时候加载?
  • 咱们这一节解决第二个问题:如何生成bean?

三、源码剖析

进入到main办法:

public static void main(String[] args) throws Exception {    SpringApplication.run(MyServerApplication.class, args);}

从main办法进入到run办法:

public ConfigurableApplicationContext run(String... args) {    StopWatch stopWatch = new StopWatch();    stopWatch.start();    ConfigurableApplicationContext context = null;    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();    configureHeadlessProperty();    SpringApplicationRunListeners listeners = getRunListeners(args);    listeners.starting();    try {        ApplicationArguments applicationArguments = new DefaultApplicationArguments(                args);        ConfigurableEnvironment environment = prepareEnvironment(listeners,                applicationArguments);        configureIgnoreBeanInfo(environment);        Banner printedBanner = printBanner(environment);        context = createApplicationContext();        exceptionReporters = getSpringFactoriesInstances(                SpringBootExceptionReporter.class,                new Class[] { ConfigurableApplicationContext.class }, context);        prepareContext(context, environment, listeners, applicationArguments,                printedBanner);        refreshContext(context);        afterRefresh(context, applicationArguments);        stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)                    .logStarted(getApplicationLog(), stopWatch);        }        listeners.started(context);        callRunners(context, applicationArguments);    }    catch (Throwable ex) {        handleRunFailure(context, ex, exceptionReporters, listeners);        throw new IllegalStateException(ex);    }    try {        listeners.running(context);    }    catch (Throwable ex) {        handleRunFailure(context, ex, exceptionReporters, null);        throw new IllegalStateException(ex);    }    return context;}

再从run办法进到refreshContext(context)外面的refresh:

public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {        // Prepare this context for refreshing.        prepareRefresh();        // Tell the subclass to refresh the internal bean factory.        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        // Prepare the bean factory for use in this context.        prepareBeanFactory(beanFactory);        try {            // Allows post-processing of the bean factory in context subclasses.            postProcessBeanFactory(beanFactory);            // Invoke factory processors registered as beans in the context.            invokeBeanFactoryPostProcessors(beanFactory);            // Register bean processors that intercept bean creation.            registerBeanPostProcessors(beanFactory);            // Initialize message source for this context.            initMessageSource();            // Initialize event multicaster for this context.            initApplicationEventMulticaster();            // Initialize other special beans in specific context subclasses.            onRefresh();            // Check for listener beans and register them.            registerListeners();            // Instantiate all remaining (non-lazy-init) singletons.            finishBeanFactoryInitialization(beanFactory);            // Last step: publish corresponding event.            finishRefresh();        }        catch (BeansException ex) {            if (logger.isWarnEnabled()) {                logger.warn("Exception encountered during context initialization - " +                        "cancelling refresh attempt: " + ex);            }            // Destroy already created singletons to avoid dangling resources.            destroyBeans();            // Reset 'active' flag.            cancelRefresh(ex);            // Propagate exception to caller.            throw ex;        }        finally {            // Reset common introspection caches in Spring's core, since we            // might not ever need metadata for singleton beans anymore...            resetCommonCaches();        }    }}

这次咱们看这个办法:finishBeanFactoryInitialization(beanFactory)

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {    // Initialize conversion service for this context.    if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&            beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {        beanFactory.setConversionService(                beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));    }    // Register a default embedded value resolver if no bean post-processor    // (such as a PropertyPlaceholderConfigurer bean) registered any before:    // at this point, primarily for resolution in annotation attribute values.    if (!beanFactory.hasEmbeddedValueResolver()) {        beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));    }    // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.    String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);    for (String weaverAwareName : weaverAwareNames) {        getBean(weaverAwareName);    }    // Stop using the temporary ClassLoader for type matching.    beanFactory.setTempClassLoader(null);    // Allow for caching all bean definition metadata, not expecting further changes.    beanFactory.freezeConfiguration();    // Instantiate all remaining (non-lazy-init) singletons.    beanFactory.preInstantiateSingletons();}

外围办法在最初一句:beanFactory.preInstantiateSingletons()

public void preInstantiateSingletons() throws BeansException {    if (logger.isTraceEnabled()) {        logger.trace("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)) {                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);                if (bean instanceof FactoryBean) {                    final FactoryBean<?> factory = (FactoryBean<?>) bean;                    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);            }        }    }    // 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();            }        }    }}
  • 重点看第一个for循环
  • 对所有的BeanDefinition,非动态,非懒加载,并且是单例,就执行getBean(beanName)
  • 先疏忽FactoryBean,咱们前面再说
  • 咱们看看getBean(beanName)
@Overridepublic Object getBean(String name) throws BeansException {    return doGetBean(name, null, null, false);}protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {    final String beanName = transformedBeanName(name);    Object bean;    // Eagerly check singleton cache for manually registered singletons.    Object sharedInstance = getSingleton(beanName);    if (sharedInstance != null && args == null) {        if (logger.isTraceEnabled()) {            if (isSingletonCurrentlyInCreation(beanName)) {                logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +                        "' that is not fully initialized yet - a consequence of a circular reference");            }            else {                logger.trace("Returning cached instance of singleton bean '" + beanName + "'");            }        }        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);    }    else {        // Fail if we're already creating this bean instance:        // We're assumably within a circular reference.        if (isPrototypeCurrentlyInCreation(beanName)) {            throw new BeanCurrentlyInCreationException(beanName);        }        // Check if bean definition exists in this factory.        BeanFactory parentBeanFactory = getParentBeanFactory();        if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {            // Not found -> check parent.            String nameToLookup = originalBeanName(name);            if (parentBeanFactory instanceof AbstractBeanFactory) {                return ((AbstractBeanFactory) parentBeanFactory).doGetBean(                        nameToLookup, requiredType, args, typeCheckOnly);            }            else if (args != null) {                // Delegation to parent with explicit args.                return (T) parentBeanFactory.getBean(nameToLookup, args);            }            else if (requiredType != null) {                // No args -> delegate to standard getBean method.                return parentBeanFactory.getBean(nameToLookup, requiredType);            }            else {                return (T) parentBeanFactory.getBean(nameToLookup);            }        }        if (!typeCheckOnly) {            markBeanAsCreated(beanName);        }        try {            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);            checkMergedBeanDefinition(mbd, beanName, args);            // Guarantee initialization of beans that the current bean depends on.            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);                    try {                        getBean(dep);                    }                    catch (NoSuchBeanDefinitionException ex) {                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,                                "'" + beanName + "' depends on missing bean '" + dep + "'", ex);                    }                }            }            // 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);            }            else if (mbd.isPrototype()) {                // It's a prototype -> create a new instance.                Object prototypeInstance = null;                try {                    beforePrototypeCreation(beanName);                    prototypeInstance = createBean(beanName, mbd, args);                }                finally {                    afterPrototypeCreation(beanName);                }                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);            }            else {                String scopeName = mbd.getScope();                final Scope scope = this.scopes.get(scopeName);                if (scope == null) {                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");                }                try {                    Object scopedInstance = scope.get(beanName, () -> {                        beforePrototypeCreation(beanName);                        try {                            return createBean(beanName, mbd, args);                        }                        finally {                            afterPrototypeCreation(beanName);                        }                    });                    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);                }                catch (IllegalStateException ex) {                    throw new BeanCreationException(beanName,                            "Scope '" + scopeName + "' is not active for the current thread; consider " +                            "defining a scoped proxy for this bean if you intend to refer to it from a singleton",                            ex);                }            }        }        catch (BeansException ex) {            cleanupAfterBeanCreationFailure(beanName);            throw ex;        }    }    // Check if required type matches the type of the actual bean instance.    if (requiredType != null && !requiredType.isInstance(bean)) {        try {            T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);            if (convertedBean == null) {                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());            }            return convertedBean;        }        catch (TypeMismatchException ex) {            if (logger.isTraceEnabled()) {                logger.trace("Failed to convert bean '" + name + "' to required type '" +                        ClassUtils.getQualifiedName(requiredType) + "'", ex);            }            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());        }    }    return (T) bean;}
  • 办法比拟长,咱们一点点看
  • Object sharedInstance = getSingleton(beanName);
  • 先看看能不能拿到sharedInstance,这部分的bean是手动插入的bean
  • 没拿到,则执行else局部
  • 最重要的是这一句: createBean(beanName, mbd, args)
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)        throws BeanCreationException {    if (logger.isTraceEnabled()) {        logger.trace("Creating instance of bean '" + beanName + "'");    }    RootBeanDefinition mbdToUse = mbd;    // Make sure bean class is actually resolved at this point, and    // clone the bean definition in case of a dynamically resolved Class    // which cannot be stored in the shared merged bean definition.    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {        mbdToUse = new RootBeanDefinition(mbd);        mbdToUse.setBeanClass(resolvedClass);    }    // Prepare method overrides.    try {        mbdToUse.prepareMethodOverrides();    }    catch (BeanDefinitionValidationException ex) {        throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),                beanName, "Validation of method overrides failed", ex);    }    try {        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);        if (bean != null) {            return bean;        }    }    catch (Throwable ex) {        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,                "BeanPostProcessor before instantiation of bean failed", ex);    }    try {        Object beanInstance = doCreateBean(beanName, mbdToUse, args);        if (logger.isTraceEnabled()) {            logger.trace("Finished creating instance of bean '" + beanName + "'");        }        return beanInstance;    }    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {        // A previously detected exception with proper bean creation context already,        // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.        throw ex;    }    catch (Throwable ex) {        throw new BeanCreationException(                mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);    }}

细枝末节先不看,进入到doCreateBean(beanName, mbdToUse, args)

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.isTraceEnabled()) {            logger.trace("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 " +                            "'getBeanNamesForType' 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;}

还是看外围办法:instanceWrapper = createBeanInstance(beanName, mbd, args)

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);        }    }    // Candidate constructors for autowiring?    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {        return autowireConstructor(beanName, mbd, ctors, args);    }    // Preferred constructors for default construction?    ctors = mbd.getPreferredConstructors();    if (ctors != null) {        return autowireConstructor(beanName, mbd, ctors, null);    }    // No special handling: simply use no-arg constructor.    return instantiateBean(beanName, mbd);}

看最初一行: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);    }}

进入这个办法:getInstantiationStrategy().instantiate(mbd, beanName, parent)

// SimpleInstantiationStrategy.classpublic Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {    // Don't override the class with CGLIB if no overrides.    if (!bd.hasMethodOverrides()) {        Constructor<?> constructorToUse;        synchronized (bd.constructorArgumentLock) {            constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;            if (constructorToUse == null) {                final Class<?> clazz = bd.getBeanClass();                if (clazz.isInterface()) {                    throw new BeanInstantiationException(clazz, "Specified class is an interface");                }                try {                    if (System.getSecurityManager() != null) {                        constructorToUse = AccessController.doPrivileged(                                (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);                    }                    else {                        constructorToUse = clazz.getDeclaredConstructor();                    }                    bd.resolvedConstructorOrFactoryMethod = constructorToUse;                }                catch (Throwable ex) {                    throw new BeanInstantiationException(clazz, "No default constructor found", ex);                }            }        }        return BeanUtils.instantiateClass(constructorToUse);    }    else {        // Must generate CGLIB subclass.        return instantiateWithMethodInjection(bd, beanName, owner);    }}

看看BeanUtils.instantiateClass(constructorToUse)

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {    Assert.notNull(ctor, "Constructor must not be null");    try {        ReflectionUtils.makeAccessible(ctor);        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {            return KotlinDelegate.instantiateClass(ctor, args);        }        else {            Class<?>[] parameterTypes = ctor.getParameterTypes();            Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");            Object[] argsWithDefaultValues = new Object[args.length];            for (int i = 0 ; i < args.length; i++) {                if (args[i] == null) {                    Class<?> parameterType = parameterTypes[i];                    argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);                }                else {                    argsWithDefaultValues[i] = args[i];                }            }            return ctor.newInstance(argsWithDefaultValues);        }    }    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());    }}
  • 到此,找到起点了,利用构造函数通过反射生产bean

欢送关注微信公众号:丰极,更多技术学习分享。