spring组件之BeanFactory

1. 什么是beanFactory

beanFactory 从名称上来看,它的意思是bean 工厂,它是生产 bean保存 bean (针对单例 bean)的地方。

我们先来看看beanFactory提供了哪些方法:

public interface BeanFactory {

    /**
     * factoryBean使用
     */
    String FACTORY_BEAN_PREFIX = "&";

    /**
     * 根据名称获取bean
     */
    Object getBean(String name) throws BeansException;

    /**
     * 根据名称获取bean
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    /**
     * 根据名称获取bean
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * 根据类型获取bean
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;

    /**
     * 根据类型获取bean
     */
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    /**
     * 获取BeanProvider
     */
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

    /**
     * 获取BeanProvider
     */
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    /**
     * 是否包含bean
     */
    boolean containsBean(String name);

    /**
     * 是否为单例bean
     */
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    /**
     * 是否为原型bean
     */
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /**
     * 判断类型是否匹配
     */
    boolean isTypeMatch(String name, ResolvableType typeToMatch) 
            throws NoSuchBeanDefinitionException;

    /**
     * 判断类型是否匹配
     */
    boolean isTypeMatch(String name, Class<?> typeToMatch) 
            throws NoSuchBeanDefinitionException;

    /**
     * 根据名称获取bean的类型
     */
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /**
     * 根据名称获取bean的类型
     */
    @Nullable
    Class<?> getType(String name, boolean allowFactoryBeanInit) 
            throws NoSuchBeanDefinitionException;

    /**
     * 根据bean名称获取bean的别名
     */
    String[] getAliases(String name);

}
复制代码

BeanFactory 接口定义了beanFactory的基本操作,可以看到,其提供了非常多的getBean(...)方法。

我们再看看看beanFactory 的继承关系:

可以看到,beanFactory 经过了重重继承,功能也由其子接口一步步扩展,接口提供功能,类来进行实现,最终得到的是DefaultListableBeanFactory,这也是 spring 默认使用的beanFactory

我们来看看这些接口的功能:

  • BeanFactory:最基本的BeanFactory,定义了基本的操作
  • HierarchicalBeanFactory:提供了继承功能,用来获取ParentBeanFactory(设置ParentBeanFactory的操作在ConfigurableBeanFactory中)
  • ListableBeanFactory:提供list操作,如列出符合条件的BeanNameBean
  • ConfigurableBeanFactory:提供配置操作,如设置ParentBeanFactory、设置BeanClassLoader、设置BeanClassLoader
  • AutowireCapableBeanFactory:提供bean的手动注入功能,如createBean(Class)可以将Class初始化spring bean,autowireBean(Object)可以将一个java实例初始化成spring bean
  • ConfigurableListableBeanFactory:组合了ListableBeanFactoryConfigurableBeanFactory接口的功能

最后是DefaultListableBeanFactory,这个类实现了以上的所有接口,包含了上面的所有功能,接下来我分析我们基本都是基于这个类。

2. DefaultListableBeanFactory

spring 的 applicationContext 中默认的 beanFactory 就是 DefaultListableBeanFactory,因此这个类非常非常重要,这里我们简单列举下平时经常用到的属性:

  • beanDefinitionMap:存放beanDefinitionMap
  • beanDefinitionNames:存放beanDefinition名称的List
  • singletonObjects:继承自DefaultSingletonBeanRegistry类,存放单例beanmap

beanFactory 中,最重要的内容要算beanDefinitionsingletonBean了,DefaultListableBeanFactory中有各自对应的结构来存储这些。接下来我们再看看一些重要的方法:

方法说明
DefaultSingletonBeanRegistry#addSingleton(String, Object)手动添加一个单例 beanbeanFactory
AbstractAutowireCapableBeanFactory#createBean(Class<?>, int, boolean)手动创建一个spring bean(有完整的spring bean生命周期,即实例化、属性注入、初始化等)
AbstractAutowireCapableBeanFactory#autowire(Class<?>, int, boolean)创建对应的Class实例后,再对其属性注入(只有两个操作:实例化、属性注入)
AbstractAutowireCapableBeanFactory#autowireBean(Object)对传入的bean进行属性注入(只有属性注入操作)
BeanFactory#getBean(String, ...)根据bean的名称获取对应的bean,如果该bean不存在但对应的beanDefinition存在,则创建bean,如果对应的beanDefinition也不存在,则抛出异常,该方法拥有众多的重载方法
BeanFactory#getBean(Class<T>, ...)根据bean的类型获取对应的 bean,如果bean不存在则抛出异常,如果找到多个bean且未指定primary也会抛出异常,该方法也有众多的重载方法
DefaultListableBeanFactory#getBeanDefinition(String)根据bean的名称获取一个beanDefinition
DefaultListableBeanFactory#getBeanNamesForType(Class<?>)根据类型获取bean的名称,返回值为数组,可以返回多个名称
DefaultListableBeanFactory#getBeansOfType(Class<T>)根据类型获取bean,返回值为Map,可以返回多个beanMapkeybean的名称,valuebean

3. 代码示例:spring 下的策略模式

示例:在支付项目中,我们可能同时要接入微信、支付宝、银联支付、苹果支付等,如果我们像这样来处理支付:

if(微信支付) {
    // 处理微信支付操作
} else if(支付宝支付) {
    // 处理支付宝支付操作
} else if(...) {
    ...
}
复制代码

后续接入了新的支付方式后,这个if-else也要不断往下加,对于这么多的if-else,聪明如你一定想到使用策略模式来进行优化,这里我们来看看spring 容器结合策略模式的妙用。

3.1 List 注入

准备一个Constant类,里面就两个常量:

public abstract class Constant {

    /**
     * 微信支付方式
     */
    public static final String WX_PAY = "wx";

    /**
     * 支付宝支付方式
     */
    public static final String ALI_PAY = "ali";

}
复制代码

定义一个接口,处理主要操作:

public interface IPayService {

    /**
     * 判断当前service是否支持处理
     * @param context 将所有的参数封装在 context 中
     * @return
     */
    boolean support(BizContext context);

    /**
     * 具体的处理操作
     * @return
     */
    void handler();
}

复制代码

BizContext 内容如下:

public class BizContext {

    /**
     * 支持方式
     */
    private String payWay;

    // 省略 get 与 set 方法
}
复制代码

这个类用来封装参数,虽然目前只有一个参数,但实际业务中会有多个,可以统一放在这个类里。

接着是接口的两个实现类:

/**
 * 处理微信支付
 */
@Service
public class WxPayService implements IPayService {
    @Override
    public boolean support(BizContext context) {
        return Constant.WX_PAY.equals(context.getPayWay());
    }

    @Override
    public void handler() {
        System.out.println("处理微信支付");
    }
}

/**
 * 处理支付宝支付
 */
@Service
public class AliPayService implements IPayService {

    @Override
    public boolean support(BizContext context) {
        return Constant.ALI_PAY.equals(context.getPayWay());
    }

    @Override
    public void handler() {
        System.out.println("处理支付宝支付");
    }
}

复制代码

准备一个业务类:

@Service
public class BizService {

    /**
     * 注入 List
     */
    @Autowired
    private List<IPayService> payServiceList;

    /**
     * 处理类
     */
    public void handler(String payWay) {
        // 准备参数
        BizContext context = new BizContext();
        context.setPayWay(payWay);

        // 遍历,找到支持当前参数的类
        for(IPayService payService : payServiceList) {
            if(payService.support(context)) {
                payService.handler();
                return;
            }
        }
        throw new IllegalArgumentException("支付方式不支持");
    }

}

复制代码

主类:

@ComponentScan
public class Demo03Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context 
                = new AnnotationConfigApplicationContext();
        context.register(Demo03Main.class);
        context.refresh();

        BizService bizService = (BizService) context.getBean("bizService");
        // 通过改变传入参数来确定最终使用哪个类来处理
        bizService.handler(Constant.ALI_PAY);
        // bizService.handler(Constant.WX_PAY);

    }
}
复制代码

运行,结果如下:

处理支付宝支付
复制代码

这个示例比较简单,主要是通过@Autowired注入一个List<IPayService>,然后遍历这个List,找到能处理当前参数的IPayService,再执行其handler(...)方法。

可以看到,如果使用spring,我们根本不必关心策略类的注册,注册操作spring已经为我们处理好了,后续我们不管有多少的支付方式,只需实现IPayService,然后重写相应的方法就行了,原来的代码不必改动,扩展性大大地提高了。

值班一提的,这种**support(...)方法判断能否处理、handler(...)方法进行具体操作**的方式在 spring mvc 中进行了大量地运用,如运行 controller 方法前的参数解析、处理 @ResponseBody 的返回值。

3.2 使用 getBeansOfType(Class<T>) 方法

我们再来分析上面的业务,我们发现判断条件并不复杂,不必单独使用一个方法来判断,我们也可以直接指定一个业务类型,用指定的IPayService处理指定的类型就可以了。

这次我们的IPayService改成这样:

public interface IPayService {

    /**
     * 返回当前service支持的类型
     * @return
     */
    String getPayWay();

    /**
     * 具体的处理操作
     * @return
     */
    void handler();
}
复制代码

然后两个业务类这样写:

/**
 * 处理微信支付
 */
@Service
public class WxPayService implements IPayService {

    @Override
    public String getPayWay() {
        return Constant.WX_PAY;
    }

    @Override
    public void handler() {
        System.out.println("处理微信支付");
    }
}

/**
 * 处理支付宝支付
 */
@Service
public class AliPayService implements IPayService {

    @Override
    public String getPayWay() {
        return Constant.ALI_PAY;
    }

    @Override
    public void handler() {
        System.out.println("处理支付宝支付");
    }
}

复制代码

业务类里需要手动加载IPayServicebean

@Service
public class BizService implements ApplicationContextAware {

    /**
     * 保存IPayService的map
     * key 是 能处理的类型,value 是具体的 service
     */
    private volatile Map<String, IPayService> payServiceMap;

    // 这个是为了调用 getBeansOfType 方法
    private ApplicationContext applicationContext;

    /**
     * 具体的处理操作
     */
    public void handler(String payWay) {
        IPayService payService = getPayService(payWay);
        payService.handler();
    }

    /**
     * 获取 PayService
     */
    private IPayService getPayService(String payWay) {
        // 加载所有 IPayService 到 payServiceMap,只需要加载一次
        if(null == payServiceMap) {
            synchronized (this) {
                if(null == payServiceMap) {
                    // 获取所有 IPayService 类型的 bean
                    // 最终调用的是 DefaultListableBeanFactory#getBeansOfType(Class<T>)
                    Map<String, IPayService> beansOfType
                            = applicationContext.getBeansOfType(IPayService.class);
                    // 构建 payServiceMap
                    Map<String, IPayService> map = new HashMap<>(beansOfType.size());
                    for(Map.Entry<String, IPayService> entry : beansOfType.entrySet()) {
                        IPayService payService = entry.getValue();
                        map.put(payService.getPayWay(), payService);
                    }
                    payServiceMap = map;
                }
            }
        }
        // 之后只需直接从 payServiceMap 中获取即可
        IPayService payService = payServiceMap.get(payWay);
        if(null == payService) {
            throw new IllegalArgumentException("支付方式不支持");
        }
        return payService;
    }

    /**
     * 获取 applicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
            throws BeansException {
        this.applicationContext = applicationContext;
    }
}
复制代码

主类也基本不变:

@ComponentScan
public class Demo04Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Demo04Main.class);
        context.refresh();

        BizService bizService = (BizService) context.getBean("bizService");
        bizService.handler(Constant.ALI_PAY);
        // bizService.handler(Constant.WX_PAY);

    }
}
复制代码

运行,结果如下:

处理支付宝支付
复制代码

可以看到,这种方式也能根据传入的参数进行对应的处理操作。

这种方式先是对每种业务指定一个PayWay,每个service都只处理这一种类型的业务,获取service的操作使用的是getBeansOfType(...),获取后将这些service与其对应的PayWay统一放进一个Map中(只需加载一次),后面有业务过来了,只要根据PayWay直接从Map中获取其处理类进行处理就行了。

使用这种方式的最大好处就是只需根据PayWay直接获取service即可,不用遍历所有service再判断。注意:这种方式适合在判断条件不复杂的情况下使用,如果判断条件较复杂,还是使用上面的方式。

4. 源码分析

接下来我们来分析下spring一些关键方法的源码。

4.1 AbstractBeanFactory#getBean(String)

这个方法是beanFactory最重要的方法之一,里面包含了bean的整个创建过程(实例化、属性注入、初始化等),关于这个方法,在spring启动流程中已经作了深入分析,想了解的小伙伴可以参考以下文章:

在接下来要分析的两个方法中,我们也会看到它们也会调用该方法来获取bean

4.2 DefaultListableBeanFactory#getBeansOfType(Class<T>)

这个方法的作用是获取所有Class类型的bean,代码如下:

@Override
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException {
    return getBeansOfType(type, true, true);
}

public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, 
         boolean allowEagerInit) throws BeansException {
    // 获取名称
    String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
    // 获取名称对应的 bean
    Map<String, T> result = new LinkedHashMap<>(beanNames.length);
    for (String beanName : beanNames) {
        try {
            // 调用 getBean(String) 方法
            Object beanInstance = getBean(beanName);
            if (!(beanInstance instanceof NullBean)) {
                result.put(beanName, (T) beanInstance);
            }
        }
        catch (BeanCreationException ex) {
            ...
        }
    }
    return result;
}
复制代码

看到这里,我们就明白了,这个方法的处理操作如下:

  1. 根据Class类型获取所有的beanNames
  2. 遍历beanNames,调用getBean(String)方法获取对应的bean
  3. 返回得到的所有bean

看来,处理关键是beanNames的获取,也就是DefaultListableBeanFactory#getBeanNamesForType(Class<?>, boolean, boolean)方法了,一路跟进去,最终到了DefaultListableBeanFactory#doGetBeanNamesForType,代码如下:

private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, 
        booleanallowEagerInit) {

    List<String> result = new ArrayList<>();
    // 1. 遍历所有的 beanDefinition 名称
    for (String beanName : this.beanDefinitionNames) {
        if (!isAlias(beanName)) {
            try {
                // 2. 得到 beanName 对应的 beanDefinition
                RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                if (!mbd.isAbstract() && (allowEagerInit ||
                        (mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
                                !requiresEagerInitForType(mbd.getFactoryBeanName()))) {
                    boolean isFactoryBean = isFactoryBean(beanName, mbd);
                    BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
                    boolean matchFound = false;
                    boolean allowFactoryBeanInit = allowEagerInit || containsSingleton(beanName);
                    boolean isNonLazyDecorated = dbd != null && !mbd.isLazyInit();
                    // 3. 判断类型是否匹配
                    // 不是 FactoryBean
                    if (!isFactoryBean) {
                        if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) {
                            // 判断类型是否匹配
                            matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
                        }
                    }
                    else  {
                        if (includeNonSingletons || isNonLazyDecorated ||
                                (allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {
                            matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
                        }
                        if (!matchFound) {
                            // 在 beanName 前加 &
                            beanName = FACTORY_BEAN_PREFIX + beanName;
                            // 判断类型是否匹配
                            matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
                        }
                    }
                    if (matchFound) {
                        result.add(beanName);
                    }
                }
            }
            catch (CannotLoadBeanClassException | BeanDefinitionStoreException ex) {
                ...
            }
        }
    }
    // 手动注册到 beanFactory 中的单例 bean
    for (String beanName : this.manualSingletonNames) {
        try {
            if (isFactoryBean(beanName)) {
                // 省略了一些内容
                ...
                // 在 beanName 前加 &
                beanName = FACTORY_BEAN_PREFIX + beanName;
            }
            // 判断匹配
            if (isTypeMatch(beanName, type)) {
                result.add(beanName);
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            ...
        }
    }
    return StringUtils.toStringArray(result);
}


复制代码

这个方法的内容比较多,不过逻辑并不复杂,关键部分都给出了注释,这里对流程总结如下:

  1. 遍历所有的beanDefinitionNames
  2. 对每一个 beanName,得到其对应的beanDefinition
  3. 判断传入的类型与当前的beanDefinition是否匹配,如果匹配,则将beanName添加到匹配结果中
  4. 返回匹配的结果

处理匹配的方法为AbstractBeanFactory#isTypeMatch(String, ResolvableType, boolean),这个类的逻辑比较复杂,spring 考虑的情况比较多,如懒加载factoryBean代理对象等,这里简单提供下这个方法的匹配思路:

  1. 根据 beanNamesingletonObjects 获取bean的实例,如果实例存在,则进入第2步,否则进行第3步
  2. 判断得到的bean的实例是否匹配传入的类(调用ResolvableType#isAssignableFrom(java.lang.Class<?>)方法判断),如果匹配,则返回true,否则返回false
  3. 根据 beanName 获取对应的BeanDefinition,判断BeanDefinitionbeanClass与传入的类型是否匹配(也是调用ResolvableType#isAssignableFrom(java.lang.Class<?>)方法判断),如果匹配,则返回true,否则返回false

以上就是AbstractBeanFactory#isTypeMatch(String, ResolvableType, boolean)方法的大致逻辑了,就不深入分析了。

4.3 BeanFactory#getBean(Class<T>)

接下来我们来看看BeanFactory#getBean(Class<T>)方法:

@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
    return getBean(requiredType, (Object[]) null);
}

/**
 * 进一步调用
 */
@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
    Assert.notNull(requiredType, "Required type must not be null");
    // 得到结果
    Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
    if (resolved == null) {
        throw new NoSuchBeanDefinitionException(requiredType);
    }
    return (T) resolved;
}
复制代码

我们继续进入DefaultListableBeanFactory#resolveBean

@Nullable
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, 
        boolean nonUniqueAsNull) {
    // 这里是关键
    NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
    if (namedBean != null) {
        return namedBean.getBeanInstance();
    }
    // 去 父 BeanFactory 查找,不作分析
    BeanFactory parent = getParentBeanFactory();
    ...
    return null;
}
复制代码

这一步主要是调用了resolveNamedBean(...)处理,后面去ParentBeanFactory的操作,我们不作过多分析。继续进入DefaultListableBeanFactory#resolveNamedBean(ResolvableType, Object[], boolean)方法:

private <T> NamedBeanHolder<T> resolveNamedBean(ResolvableType requiredType, 
        @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException {
    Assert.notNull(requiredType, "Required type must not be null");
    // 获取对应类型的bean的所有名称,与 getBeansOfType(Class) 一样的操作
    String[] candidateNames = getBeanNamesForType(requiredType);
    // 得到的 beanName 数量大于1,需要确实哪个优先级高
    if (candidateNames.length > 1) {
        List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
        for (String beanName : candidateNames) {
            if (!containsBeanDefinition(beanName) 
                    || getBeanDefinition(beanName).isAutowireCandidate()) {
                autowireCandidates.add(beanName);
            }
        }
        if (!autowireCandidates.isEmpty()) {
            candidateNames = StringUtils.toStringArray(autowireCandidates);
        }
    }
    // 返回结果只有一个,调用 getBean(String, ...) 获取 bean
    if (candidateNames.length == 1) {
        String beanName = candidateNames[0];
        return new NamedBeanHolder<>(beanName, 
                (T) getBean(beanName, requiredType.toClass(), args));
    }
    // 返回结果有多个
    else if (candidateNames.length > 1) {
        // 收集起来,全部放到 candidates 中,value 为 bean或对应的Class
        Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
        for (String beanName : candidateNames) {
            if (containsSingleton(beanName) && args == null) {
                Object beanInstance = getBean(beanName);
                candidates.put(beanName, 
                        (beanInstance instanceof NullBean ? null : beanInstance));
            }
            else {
                candidates.put(beanName, getType(beanName));
            }
        }
        // 找到 primary 的bean,可以使用 @Primary 标记
        String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass());
        if (candidateName == null) {
            // 确定bean的优先级,可以使用 @Order 或实现 Orderd 接口
            candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
        }
        if (candidateName != null) {
            Object beanInstance = candidates.get(candidateName);
            if (beanInstance == null || beanInstance instanceof Class) {
                // 调用 getBean(String, ...) 获取 bean
                beanInstance = getBean(candidateName, requiredType.toClass(), args);
            }
            return new NamedBeanHolder<>(candidateName, (T) beanInstance);
        }
        // 无法确定唯一,抛出异常
        if (!nonUniqueAsNull) {
            throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
        }
    }
    return null;
}
复制代码

这个方法里主要调用了getBeanNamesForType(...)(与getBeansOfType(Class)方法一样的操作,关于这个方法就不再分析了),如果得到的结果大于1个,则会进行一系列的判断。

对这个方法,流程总结如下:

  1. 根据类型获取所有满足条件的beanNames,获取方式与getBeansOfType(Class)一样
  2. 如果得到的beanNames只有1个,则调用 getBean(String, ...) 获取 bean,然后返回
  3. 如果得到的beanNames有多个,需要进一步确定返回哪个bean,确定方式包含两个方面:@Primary与优先级
  4. 处理@Primary注解流程如下:
    • 判断这些beanNames对应的Class标记了@Primary
    • 如果有且仅有一个标记了@Primary,那么这个就是最终返回的类,
    • 如果有两个或以上的Class标记了@Primary,则抛出异常
    • 如果没有Class标记@Primary,则不处理
  5. 优先级的处理方式如下:
    • 优先级可以通过@Order注解或实现Orderd接口来指定
    • 如果没有指定优先级,则默认使用最低优先级
    • 如果存在优级最高的bean,则返回该bean
  6. 如果最终得到的bean还是超过1个,则抛出异常

BeanFactory#getBean(Class<T>)方法的分析就先到这里了,从源码来看,这个方法会遍历所有的beanDefinitionMap,且获取时可能会抛异常,如果能确定bean的名称,建议优先考虑调用AbstractBeanFactory#getBean(String)方法来获取bean

5. 总结

本文主要介绍了beanFactory的相关内容

  1. beanFactory定义的基本方法
  2. beanFactory的继承结构
  3. 通过两个实例展示下spring下的策略模式使用
  4. 最后分析了beanFactory两个方法的实现

本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值