每篇一句
对未来最大的慷慨,就是把一切献给现在
相关阅读
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二)
【小家Spring】AbstractBeanFactory#getBean()、doGetBean完成Bean的初始化、实例化,以及BeanPostProcessor后置处理器源码级详细分析
【小家Spring】AbstractAutowireCapableBeanFactory#populateBean实现Bean的依赖注入(属性赋值)和initializeBean对Bean的初始化
【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)
【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析
【小家Spring】BeanFactory体系和ApplicationContext体系,两大体系各接口分析、区别和联系
前言
前面已经讲述了很多Spring容器启动、解析、依赖注入等等源码层面的东西了,但是小伙伴有没有一种感觉就是:Spring的设计者把面向对象使用到了极致(使用得非常的好),并且它吧职责单一原则也是使用到了极致。
它各个功能区块,通过接口都进行隔离得很开,这是让Spring能组件化开发,可插拔,变得如此优秀、普适的重要原因。
它的IOC和AOP主要围绕两大阵营展开的,也就是咱们今天的主题:BeanFactory体系和ApplicationContext体系
,里面接口众多、抽象实现也众多,因此到了这个节点,我们是时候去宏观的看看这两大阵营的内容了。
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
BeanFactory和ApplicationContext 区别
基本区别
BeanFactory
:BeanFacotry是Spring中最原始的Factory,里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。它没有AOP功能、Web应用功能等等
ApplicationContext
:应用上下文,继承BeanFactory接口(因而提供BeanFactory所有的功能),ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承。它是Spring的一各更高级的容器,提供了更多的有用的功能:
- 国际化(MessageSource)(ApplicationContext.getMessage()拿到国际化消息)
- 访问资源,如URL和文件(ResourceLoader) (
ApplicationContext acxt =new ClassPathXmlApplicationContext("/applicationContext.xml");
能直接读取文件内容) - 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 消息发送、响应机制(ApplicationEventPublisher)
- AOP(拦截器)
相同点:BeanFactory和ApplicationContext
都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
两者装载bean的区别
BeanFactory
在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;(它只去加载Bean的定义信息,显示调用getBean()才会真正去实例化),这样懒加载的优点是:启动快,启动时占用资源少,但具有第一次惩罚
的特点
ApplicationContext
在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化(所有的单例、非懒加载的Bean都会容器启动时候立马实例化);
立马加载好的优点有(缺点你懂的,这里不提了):
1、启动时都初始化完成了,所以运行时会更快。
2、我们就能在系统启动的时候,尽早的发现系统中的配置问题 (因为启动时就得实例化处理)
3、可以(建议)把费时的操作放到系统启动中完成(比如初始化本地缓存、获取连接池的链接等等操作)
BeanFactory体系
先贴一张BeanFactory
的继承图表,直观展示该家族的成员们
现在把这些接口分层级,进行逐一说明。
这个里给结论:只要 context 上下文未关闭,
ContextRefreshedEvent
事件可以多次触发刷新动作, 某些ApplicationContext
支持”热”刷新。
比如,XmlWebApplicationContext
支持热刷新,GenericApplicationContext
就不支持。
简单的说AbstractRefreshableApplicationContext
都支持热刷新(所以它命名为Refreshable嘛),而GenericApplicationContext
以及它的子类都是不能支持热舒心的
一级接口:BeanFactory(IOC的始祖)
只有一个,那就是BeanFactory,它提供如下基本方法:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//@since 4.1
// 包含Bean定义或者包含单例Bean实例,都会返回true(不管这个Bean是具体的还是抽象的、lazy的还是eager的,in scope or not 都会返回true)
boolean containsBean(String name);
// 若是单例,getBean()每次都会返回同一个实例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
//@since 4.2
// 名称为name的Bean的类型,是否和给定的类型匹配(4.2新增的重载接口,传的是ResolvableType ,更加强大了)
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// name对应的Bean的类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// name对应的Bean的别名们(通过别名也可以依赖注入哦~~~) 通过alia别名,也可以getBean()
String[] getAliases(String name);
}
这10来个方法,很明显,这是一个典型的工厂模式的工厂接口。
需要注意的是,Spring5.1之后在此接口上又加入了如下两个接口:根据类型获取一个provider
// 返回指定bean的提供程序(Provider),允许延迟(这是重点)按需检索实例,包括可用性和唯一性选项
// @since 5.1
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
// @since 5.1
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
二级接口:HierarchicalBeanFactory、ListableBeanFactory、AutowireCapableBeanFactory
3个子接口,进行了一定功能上的增强
HierarchicalBeanFactory:分层的Bean工厂
提供父容器的访问功能.至于父容器的设置,需要找三级接口ConfigurableBeanFactory
的setParentBeanFactory
(接口把设置跟获取给拆开了,这是为何呢?Spring早期设计的Bug?).
public interface HierarchicalBeanFactory extends BeanFactory {
//返回本Bean工厂的父工厂(至于父工厂怎么设置进去的,却放在了三级接口(个人感觉是Spring的Bug哈哈))
//这个方法实现了工厂的分层
@Nullable
BeanFactory getParentBeanFactory();
//本地工厂是否包含这个Bean(忽略其他所有父工厂)。这也是分层思想的体现。
boolean containsLocalBean(String name);
}
ListableBeanFactory:可将Bean逐一列出的工厂
它提供了提供容器中bean迭代的功能,不再需要一个个bean地查找.比如可以一次获取全部的bean(太暴力了),根据类型获取bean等等。它的这个功能在Spirng内部有大量的应用。
选哟注意的是:如果同时实现了
HierarchicalBeanFactory
,返回值不会考虑父类BeanFactory,
只考虑当前factory定义的类.当然也可以使用BeanFactoryUtils
辅助类来查找祖先工厂中的类。除了getBeanNamesOfType和getBeansOfType
这两个方法,其余方法的逻辑都不会考虑父容器的Bean,只会考虑本容器自己的Bean
注意:getBeanDefinitionCount和containsBeanDefinition的实现方法因为效率比较低,还是频繁使用为好.
public interface ListableBeanFactory extends BeanFactory {
// 这三个都是和Bean定义信息有关的方法
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
//返回匹配给定类型(包括子类)的所有bean的名字,如果是普通bean,则是bean定义的名字,如果是 FactoryBean,则是其getObjectType方法返回的对象的名字
// 这个方法只考虑最顶层的bean(top-level beans),内部嵌套的bean(nested beans)即便可能匹配指定的类型也不考虑(不支持内部类)
// 在许多实现中,此方法返回的结果与调用getBeansOfType(type, true, true)一样
// 这个方法返回的bean名称应该尽可能与后台配置的bean定义顺序一样
// 此方法只会
// 若没有符合条件的,返回的空数组,而不是null
@since 4.2
String[] getBeanNamesForType(ResolvableType type);
// 同上
String[] getBeanNamesForType(@Nullable Class<?> type);
// includeNonSingletons:false表示只查单例Bean。true表示包含prototype或者其它Scope的Bean们
// allowEagerInit:主要是解决FactoryBean的情况。若为false,只会去检查FactoryBean本身,若为true,FactoryBean本身和它的产生的对象都会被检查匹配
// 上面的方法底层调用的为:return getBeanNamesForType(type, true, true); 所以上面方法是全拿(一般不建议调用)
String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
//返回匹配给定类型(包含子类)的实例,可能是通过bean定义创建,也可以是FactoryBean时其getObjectType返回(注意:此处返回的是实例了,不再是Bean定义了)
//此方法仅考虑最顶层bean,不含其内部嵌套的bean,即使内部嵌套的bean匹配给定类型
//此方法返回的结果与调用getBeansOfType(type, true, true)一样
// 实现者:Map的顺序要尽最大可能的与配置时一样
// 备注:此方法但凡一调用,即使有些Bean只是Bean定义的话,也会被立马初始化的~~~~~
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException;
// 这个接口会把所有标注有指定注解的Bean的定义信息的BeanName返回
//@since 4.0
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
// 返回的实例~~~~
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
//查找指定bean的指定类型的注解。
// 注意:如果本类没找到,还会去它的接口、它的父类里面找,这个厉害了我的哥
// getBeanNamesForAnnotation依赖于此接口
@Nullable
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) throws NoSuchBeanDefinitionException;
}
正如这个工厂接口的名字所示,这个工厂接口最大的特点就是可以列出工厂可以生产的所有实例。
这个工厂作为二级接口,有多个个独有的方法,扩展了跟BeanDefinition
的功能,提供了BeanDefinition
、BeanName
、注解有关
的各种操作
AutowireCapableBeanFactory:自动装配的Bean工厂
(该接口特别重要,扩展出来的方法也非常的多)对于想要拥有自动装配能力,并且想把这种能力暴露给外部应用的BeanFactory类需要实现此接口。
正常情况下,不要使用此接口,应该更倾向于使用BeanFactory或者ListableBeanFactory接口。此接口主要是针对框架之外,没有向Spring托管Bean的应用。通过暴露此功能,Spring框架之外的程序,具有自动装配等Spring的功能。
需要注意的是,ApplicationContext接口并没有实现此接口,因为应用代码很少用到此功能,如果确实需要的话,可以调用ApplicationContext的getAutowireCapableBeanFactory方法,来获取此接口的实例。
另外,如果一个类实现了此接口,那么很大程度上它还需要实现BeanFactoryAware
接口。它可以在应用上下文中返回BeanFactory
public interface AutowireCapableBeanFactory extends BeanFactory {
// 用于标识外部自动装配功能是否可用。但是此标识不影响正常的(基于注解的等)自动装配功能的使用
int AUTOWIRE_NO = 0; // 不注入
int AUTOWIRE_BY_NAME = 1; // 根据名称注入
int AUTOWIRE_BY_TYPE = 2; // 根据类型注入
int AUTOWIRE_CONSTRUCTOR = 3; // 根据构造器注入
@Deprecated
int AUTOWIRE_AUTODETECT = 4; // 标识自动识别一种装配策略来实现自动装配(Spring3.0后废弃)
//创建一个给定Class的实例。它会处理各种带有注解的域和方法,并且会调用所有Bean初始化时所需要调用的回调函数
//它会执行所有的关于Bean生命周期的接口方法如BeanPostProcessor
//此方法并不意味着by-name或者by-type方式的自动装配,如果需要使用这写功能,可以使用其下面的重载方法
<T> T createBean(Class<T> beanClass) throws BeansException;
// 和上面的区别为:创建这个Bean的时候可以指定autowireMode,然后可以把它需要注入的Bean都注入进来(这个Mode会放在Bean定义里,在依赖注入的时候会有用)
// Bean定义的默认值为:autowireMode = AUTOWIRE_NO;显然是不会开启自动装配的
// 在populateBean()给属性赋值(依赖注入的时候,会使用到此模式)
Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
//通过调用给定Bean的after-instantiation及post-processing接口,对bean进行装配值
// 此方法主要是用于处理Bean中带有注解的字段和方法。
// 此方法并不意味着by-name或者by-type方式的自动装配,如果需要使用这些功能,可以使用其重载方法autowireBeanProperties
// 只会调用populateBean
void autowireBean(Object existingBean) throws BeansException;
//通过指定的自动装配策略来初始化一个Bean 注意:他会创建一个新的Bean
//需要注意的是:此方法不会调用Bean上注册的诸如BeanPostProcessors的回调方法
// 只会调用populateBean
Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
//通过指定的自动装配方式来对给定的已经存在的Bean进行自动装配
//不过会调用指定Bean注册的BeanPostProcessors等回调函数来初始化Bean。
//如果指定装配方式为AUTOWIRE_NO的话,不会自动装配属性,但是,但是,但是依然会调用BeanPiostProcesser等回调方法。
//只会调用populateBean
void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException;
//配置参数中指定的bean,包括自动装配其域,对其应用如setBeanName功能的回调函数。并且会调用其所有注册的post processor.
// beanName表示在Bean定义中的名称。
// populateBean和initializeBean都会被调用
Object configureBean(Object existingBean, String beanName) throws BeansException;
//简单的说,就是把Bean定义信息里面的一些东西,赋值到已经存在的Bean里面
// 除了InstantiationAwareBeanPostProcessor的回调方法外,此方法不会在Bean上应用其它的例如BeanPostProcessors
void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException;
// 实例化这个Bean,会调用所有的postProcessors 方法
Object initializeBean(Object existingBean, String beanName) throws BeansException;
//调用参数中指定Bean的postProcessBeforeInitialization/postProcessorsAfterInitialization方法 初始化之前、之后
Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException;
Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException;
//销毁参数中指定的Bean,同时调用此Bean上的DisposableBean和DestructionAwareBeanPostProcessors方法
//在销毁途中,任何的异常情况都只应该被直接捕获和记录,而不应该向外抛出。
void destroyBean(Object existingBean);
//查找唯一符合指定类的实例,如果有,则返回实例的名字和实例本身
//底层依赖于:BeanFactory中的getBean(Class)方法
<T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws BeansException;
//解析出在Factory中与指定Bean有指定依赖关系的Bean(@Autowired依赖注入的核心方法)
@Nullable
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException;
//descriptor 依赖描述 (field/method/constructor)
//requestingBeanName 依赖描述所属的Bean
//autowiredBeanNames 与指定Bean有依赖关系的Bean
//typeConverter 用以转换数组和连表的转换器
//备注:结果可能为null,毕竟容器中可能不存在这个依赖嘛~~~~~~~~~~~~~~~~
@Nullable
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;
}
一共8个跟自动装配有关的方法,实在是繁杂,2个执行BeanPostProcessors的方法,2个分解指定依赖的方法(依赖注入)
从宏观上看,AutowireCapableBeanFactory提供了如下能力:
- 为已经实例化的对象装配属性,这些属性对象都是Spring管理的;
- 实例化一个类型,并自动装配,这些属性对象都是Spring管理的,实例化的类可以不被Spring管理(这点特别重要)。所以这个接口提供功能就是自动装配bean相关的,具体实现方式,其实我们在之前的getBean()解析时候已经有了,请参考:【小家Spring】AbstractBeanFactory#getBean()、doGetBean完成Bean的初始化、实例化,以及BeanPostProcessor后置处理器源码级详细分析
此接口主要是针对框架之外,没有向Spring托管Bean的应用。通过暴露此功能,Spring框架之外的程序,y也能具有自动装配的能力(此接口赋予它的)。
可以使用这个接口集成其它框架
。捆绑并填充(注入)并不由Spring管理生命周期并已存在的实例.像集成WebWork的Actions
和Tapestry Page
就很实用
一般应用开发者不会使用这个接口,所以像ApplicationContext这样的外观实现类不会实现这个接口
为了更深入的了解本接口的使用,请参照我专门写的一篇博文,脱离Spring容器来实现依赖注入(脱离容器):
【小家Spring】为脱离Spring IOC容器管理的Bean赋能【依赖注入】的能力,并分析原理(借助AutowireCapableBeanFactory赋能)
三级接口:ConfigurableBeanFactory 配置Bean工厂【巨大的工厂接口】
对二级接口HierarchicalBeanFactory
进行了再次增强,它还继承了另一个外来的接口SingletonBeanRegistry
(因此它也具有分层、注册单例Bean的能力了)。
SingletonBeanRegistry
:Spring容器对单例bean实例的登记管理主要通过接口SingletonBeanRegistry建模抽象,我们可以称之为"单例bean实例注册表"。
public interface SingletonBeanRegistry {
//以指定的名字将给定Object注册到BeanFactory中。
//此接口相当于直接把Bean注册,所以都是准备好了的Bean。(动态的向容器里直接放置一个Bean)
//什么BeanPostProcessor、InitializingBean、afterPropertiesSet等都不会被执行的,销毁的时候也不会收到destroy的信息
void registerSingleton(String beanName, Object singletonObject);
//以Object的形式返回指定名字的Bean,如果仅仅还是只有Bean定义信息,这里不会反悔
// 需要注意的是:此方法不能直接通过别名获取Bean。若是别名,请通过BeanFactory的方法先获取到id
@Nullable
Object getSingleton(String beanName);
//是否包含此单例Bean(不支持通过别名查找)
boolean containsSingleton(String beanName);
// 得到容器内所有的单例Bean的名字们
String[] getSingletonNames();
int getSingletonCount();
// @since 4.2
// 获取当前这个注册表的互斥量(mutex),使用者通过该互斥量协同访问当前注册表
// 实现类DefaultSingletonBeanRegistry就一句话:return this.singletonObjects; 返回当前所有的单例Bean
Object getSingletonMutex();
}
ConfigurableBeanFactory
中定义了太多太多的api,比如类加载器,类型转化,属性编辑器,BeanPostProcessor,作用域,bean定义,处理bean依赖关系,合并其他ConfigurableBeanFactory,bean如何销毁【共55+个方法,虽然方法繁多,还算井井有条】
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
//定义了两个作用域: 单例和原型.可以通过registerScope来添加.
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
//父容器设置.而且一旦设置了就不让修改(改就抛错)
// 配合父接口HierarchicalBeanFactory的getParentBeanFactory方法
void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException;
//类加载器设置与获取.默认使用当前线程中的类加载器
void setBeanClassLoader(@Nullable ClassLoader beanClassLoader);
@Nullable
ClassLoader getBeanClassLoader();
//为了类型匹配,搞个临时类加载器.好在一般情况为null,使用上面定义的标准加载器
void setTempClassLoader(@Nullable ClassLoader tempClassLoader);
@Nullable
ClassLoader getTempClassLoader();
//设置、是否缓存元数据,如果false,那么每次请求实例,都会从类加载器重新加载(热加载)
// 在字段mergedBeanDefinitions里存着,和getMergedBeanDefinition方法有关
void setCacheBeanMetadata(boolean cacheBeanMetadata);
boolean isCacheBeanMetadata();
//定义用于解析bean definition的表达式解析器
void setBeanExpressionResolver(@Nullable BeanExpressionResolver resolver);
@Nullable
BeanExpressionResolver getBeanExpressionResolver();
//类型转化器
void setConversionService(@Nullable ConversionService conversionService);
@Nullable
ConversionService getConversionService();
//添加一个属性编辑器的登记员:Registrar
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
//注册常用属性编辑器
void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass);
//用工厂中注册的通用的编辑器初始化指定的属性编辑注册器
void copyRegisteredEditorsTo(PropertyEditorRegistry registry);
//BeanFactory用来转换bean属性值或者参数值的自定义转换器
void setTypeConverter(TypeConverter typeConverter);
TypeConverter getTypeConverter();
//string值解析器(想起mvc中的ArgumentResolver了)
//增加一个嵌入式的StringValueResolver
void addEmbeddedValueResolver(StringValueResolver valueResolver);
// 上面增加过,这里就是true嘛
boolean hasEmbeddedValueResolver();
// 用注册的值解析器,依次解析这个value(注意是依次解析)。谁先解析出为null了,后面就不再解析了
@Nullable
String resolveEmbeddedValue(String value);
// 向工厂里添加Bean的后置处理器BeanPostProcessor
// 需要注意的是:内部实现是先执行remove,再add的设计技巧
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
int getBeanPostProcessorCount();
// 作用域定义:注册范围
void registerScope(String scopeName, Scope scope);
String[] getRegisteredScopeNames();
@Nullable
Scope getRegisteredScope(String scopeName);
//访问权限控制:返回本工厂的一个安全访问上下文 和java.security有关的
AccessControlContext getAccessControlContext();
//从其他的工厂复制相关的所有配置
void copyConfigurationFrom(ConfigurableBeanFactory otherFactory);
//给指定的Bean注册别名 内部还会checkForAliasCircle(name, alias);
void registerAlias(String beanName, String alias) throws BeanDefinitionStoreException;
void resolveAliases(StringValueResolver valueResolver);
//合并bean定义,包括父容器的 返回合并后的Bean定义信息
BeanDefinition getMergedBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
//是否是FactoryBean类型 判断指定Bean是否为一个工厂Bean
boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException;
//bean创建状态控制.在解决循环依赖时有使用 设置一个Bean是否正在创建
void setCurrentlyInCreation(String beanName, boolean inCreation);
boolean isCurrentlyInCreation(String beanName);
//处理bean依赖问题
//注册一个依赖于指定bean的Bean
void registerDependentBean(String beanName, String dependentBeanName);
//返回依赖于指定Bean的所欲Bean名
String[] getDependentBeans(String beanName);
//返回指定Bean依赖的所有Bean名(注意和上面的区别,是反过来的)
String[] getDependenciesForBean(String beanName);
// 销毁的相关方法
void destroyBean(String beanName, Object beanInstance);
void destroyScopedBean(String beanName);
//销毁所有的单例们
void destroySingletons();
}
这个工厂名为ConfigurableBeanFactory,真是名不虚传。功能也不可为不强大
四级接口:ConfigurableListableBeanFactory Bean工厂的集大成者
它是一个更强大的接口,继承了上述的所有接口,无所不包。
public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {
// 忽略的自动注入的类型。也就是说这些类型不能@Autowired了
void ignoreDependencyType(Class<?> type);
// 忽略自动装配的接口
void ignoreDependencyInterface(Class<?> ifc);
// 注册一个可以给解析的依赖。这样子注入进去的,在Autowired的时候就可以被自动注入进去了,即使不在容器里面
void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue);
//判断指定的Bean是否有资格作为自动装配的候选者
boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) throws NoSuchBeanDefinitionException;
//返回注册的Bean定义(这个比较常用) 它可以拿到单独的某个Bean定义信息
// ListableBeanFactory都是批量的返回
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
Iterator<String> getBeanNamesIterator();
// 清理元数据的缓存
void clearMetadataCache();
//暂时冻结所有的Bean配置
void freezeConfiguration();
boolean isConfigurationFrozen();
// 这个厉害了:实例化当前所有的剩下的单实例Bean们
void preInstantiateSingletons() throws BeansException;
}
加上自有的这8个方法,这个工厂接口总共有80+个方法,实在是巨大到不行了,所以它是集大成者。
上面介绍的全部是工厂接口,那么接下俩,就开始介绍接口的抽象类实现了。========
抽象类工厂(一级):AbstractBeanFactory
AbstractBeanFactory
作为一个抽象类,实现了三级接口ConfigurableBeanFactory
(方法50+的接口)大部分功能。
而且继承自FactoryBeanRegistrySupport
,所以它也具备了SingletonBeanRegistry
和SimpleAliasRegistry
的能力
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
... 实现了大部分的方法,其中最终的实现为getBean()/doGetBean()方法的实现,提供了模版。其实createBean抽象方法,还是子类去实现的
... isSingleton(String name) / isPrototype(String name) / containsBean(String name) 也能实现精准的判断了
// ===其中,它自己提供了三个抽象方法,子类必要去实现的===
// 效果同:ListableBeanFactory#containsBeanDefinition 实现类:DefaultListableBeanFactory
protected abstract boolean containsBeanDefinition(String beanName);
// 效果同:ConfigurableListableBeanFactory#getBeanDefinition 实现类:DefaultListableBeanFactory
protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;
// 创建Bean的复杂逻辑,子类去实现。(子类:AbstractAutowireCapableBeanFactory)
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException;
}
此处Spring其实用了一个小技巧:采用抽象方法和接口方法签名一模一样。比如这里的containsBeanDefinition
和getBeanDefinition
等。这样的好处是,子类若同时实现对应接口、抽象类等,能够达到类似:跨级实现的效果~~~(我明明没有实现它这个接口,但我却复写了这个接口的方法…)
抽象类工厂(二级):AbstractAutowireCapableBeanFactory
实例化bean和依赖注入是在AbstractBeanFactory的入口,但是实际还是在AbstractAutowireCapableBeanFactory这个类中实现。(入口是getBean()
方法)
// 继承自抽象实现:AbstractBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
// 它有很多属性,我觉得还是很有意义的
// Bean的实例化需要借助它,策略接口
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
// 解析方法参数的名字
@Nullable
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
// 这两个保存的类型,是ignore on dependency check and autowire(字段装配时,如果在这里面的类型讲被忽略)
// 使用:我们可以自己忽略类型:beanFactory.ignoreDependencyType(ArrayList.class); 比如在BeanFactoryPostProcessor 这里可以这么做
// 他们有些区别:因为平时使用较少,却别这里就不详细介绍了
// 总之,若你自己想忽略特定类型的自动注入的话,推荐使用ignoredDependencyTypes
private final Set<Class<?>> ignoredDependencyTypes = new HashSet<>();
private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>();
... 最重要的是实现了doCreateBean()的创建逻辑,然后执行队形的BeanPostProcessor
... applyPropertyValues():这里做的大部分工作就是依赖的准备工作
... 然后对bean实例化、初始化、并完成了它的依赖注入入口(具体逻辑由子类DefaultListableBeanFactory具体去实现)
... 它实现了对Bean的实例化、populateBean()、以及initializeBean(),会对Bean进行注入等操作
... 当然也管理了一些ignoredDependencyTypes,忽略的依赖等等
}
实例工厂类:DefaultListableBeanFactory Spring内部的唯一
使用的工厂实现(XmlBeanFactory已废弃)基于bean definition对象,是一个成熟的bean factroy
默认实现了ListableBeanFactory和BeanDefinitionRegistry接口,基于bean definition对象,**是一个成熟的bean factroy。**它是整个bean加载的核心部分,也是spring注册加载bean的默认实现
DefaultListableBeanFactory
既可以作为一个单独的beanFactory,也可以作为自定义beanFactory的父类。
至于特定格式的Bean定义信息(比如常见的有xml、注解等)的解析器可以自己实现,也可以使用原有的解析器,如:PropertiesBeanDefinitionReader
和XmLBeanDefinitionReader
、AnnotatedBeanDefinitionReader
// extends:相当于继承了抽象类所有的实现,并且是已经具有注入功能了(含有ListableBeanFactory、ConfigurableListableBeanFactory的所有接口)
// implements:直接实现ConfigurableListableBeanFactory,表示它具有了批量处理Bean、配置Bean等等功能
// BeanDefinitionRegistry:该接口目前还仅有这个类实现(它父接口为:AliasRegistry )
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
}
BeanDefinitionRegistry
接口简介: 定义了一些对 bean的常用操作
public interface BeanDefinitionRegistry extends AliasRegistry {
// 注册一个Bean定义信息(下面的方法都非常的简单,就不一一介绍了)
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
// 需要注意的是:这里面有写方法和ListableBeanFactory接口里定义的三个关于Bean定义的方法一样的:
//getBeanDefinitionNames/getBeanDefinitionCount等等
}
为什么要拆成这么多的类和接口呢。这里面可能基于几点考虑:
- 功能的不同维度,分不同的接口,方便以后的维护和其他人的阅读。(代码的可读性异常的重要,特别像Spring这种持久性的项目)
- 不同接口的实现,分布在不同的之类里,方便以后不同接口多种实现的扩展(单一职责,才好扩展。否则造成臃肿后,后面无法发展)
- 从整个类图的分布,可以看出spring在这块是面向接口编程,后面类的实现,他们认为只是接口功能实现的一种,随时可以拓展成多种实现 (面向接口编程,能极大的解耦各模块之间的互相影响)
ApplicationContext体系
如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现
ApplicationContext接口继承众多接口,集众多接口功能与一身,为Spring的运行提供基本的功能支撑。
根据程序设计的“单一职责原则”,其实每个较顶层接口都是“单一职责的”,只提供某一方面的功能,而ApplicationContext接口继承了众多接口,相当于拥有了众多接口的功能
下面我们看看ApplicationContext体系的家族图标(提供一个web环境的和一个非web环境的):
web环境的如下(XmlWebApplicationContext
、GroovyWebApplicationContext
、AnnotationConfigWebApplicationContext
):
非web环境下如:AnnotationConfigApplicationContext
、FileSystemXmlApplicationContext
、ClassPathXmlApplicationContext
一级接口:ApplicationContext
EnvironmentCapable
:可配置Environment
ListableBeanFactory
:前面有介绍:可将Bean逐一列出的工厂
HierarchicalBeanFactory
:前面有介绍:分层的工厂
MessageSource
:可管理message实现国际化等功能
ApplicationEventPublisher
:可publish事件,调用Listener
ResourcePatternResolver
:加载pattern指定的资源
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
//容器的unique id
@Nullable
String getId();
// 部署的应用的名称 web应用一般会把servletContext.getContextPath()赋值给他
// 非web应用就是""
String getApplicationName();
// 该应用context展示的名称
String getDisplayName();
// 容器首次被加载、启动的时间
long getStartupDate();
// 获取父容器 有可能为null
@Nullable
ApplicationContext getParent();
// 上面都没什么大作用,这个方法:虽然不继承AutowireCapableBeanFactory,但是我们可通过此方法得到它,从而用它的相关功能
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
二级接口:WebApplicationContext、ConfigurableApplicationContext
这是它两个直接子接口。
WebApplicationContext:web环境的Context
顾名思义嘛。但是它扩展的功能非常非常少~ 该容器是read-only 的
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
// 获取到Servlet的上下文====
@Nullable
ServletContext getServletContext();
}
ConfigurableApplicationContext:可配置的应用上下文
ApplicationContext
接口本身是 read-only 的,所以子接口 ConfigurableApplicationContext
就提供了如setID()
、setParent()
、setEnvironment()
等方法,用来配置ApplicationContext
。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";
String ENVIRONMENT_BEAN_NAME = "environment";
String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";
String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";
//设置此应用程序上下文的唯一ID。
void setId(String id);
//设置父容器,设置后不能再改了
void setParent(@Nullable ApplicationContext parent);
//设置environment 此处为ConfigurableEnvironment 也是可以配置的应用上下文
void setEnvironment(ConfigurableEnvironment environment);
// 此处修改父类返回值为ConfigurableEnvironment
@Override
ConfigurableEnvironment getEnvironment();
//添加一个新的BeanFactoryPostProcessor(refresh()的时候会调用的)
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
// 添加一个时间监听器
void addApplicationListener(ApplicationListener<?> listener);
// 注册协议处理器 允许处理额外的资源协议
void addProtocolResolver(ProtocolResolver resolver);
//加载或刷新配置的持久表示 最最最重要的一个方法
//表示可以是xml、可以是注解、可以是外部资源文件等等。。。。
// 这个方法执行完成后,所有的单例Bean都已经被实例化,Bean工厂肯定也就被创建好了
void refresh() throws BeansException, IllegalStateException;
//JVM运行时注册一个关闭挂钩,在关闭JVM时关闭此上下文,除非此时已经关闭
void registerShutdownHook();
//关闭此应用程序上下文,释放实现可能持有的所有资源和锁 包括一些销毁、释放资源操作
@Override
void close();
//标识上下文是否激活 refresh()后就会激活
boolean isActive();
// 返回此上下文内部的Bean工厂,可以用来访问底层工厂的特定功能。通过此工厂可以设置和验证所需的属性、自定义转换服务
// 备注:父类方法为获得AutowireCapableBeanFactory接口,而此处的ConfigurableListableBeanFactory可配置、可列出Bean的工厂是它的子类
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
AbstractApplicationContext
穿插在这里,不得不提一个抽象实现类,他就是:AbstractApplicationContext
。它实现了该接口的大部分功能(没管Web相关的东西,所以它只管了这个接口):
public abstract class AbstractApplicationContext extends DefaultResourceLoader
// 此处只实现了ConfigurableApplicationContext这个接口
implements ConfigurableApplicationContext {
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
static {
// 这里就把ContextClosedEvent 加载进来,为了兼容WebLogic 可能的一个bug
ContextClosedEvent.class.getName();
}
// 然后就是很多字段,保存了一些设置信息。比如:等等
@Nullable
private LifecycleProcessor lifecycleProcessor;
@Nullable
private ApplicationEventMulticaster applicationEventMulticaster;
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
// 这里最重要的一个实现,非refresh莫属:
@Override
public void refresh() throws BeansException, IllegalStateException {
...
}
//它提供下面抽象方法,由子类去实现
//Subclasses must implement this method to perform the actual configuration load
// 子类必须实现这个方法:子类可以创建一个新的BeanFactory,或者直接指向自己一个已经hold的引用(GenericApplicationContext就是指向自己已经持有的)
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// 销毁BeanFactory this.beanFactory=null
protected abstract void closeBeanFactory();
// refreshBeanFactory这里肯定已经有工厂实例了,这里直接返回呗
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
实现类又扩展了其他接口或者继承了其他父类,这些只是实现类为了扩展功能或者为了对实现上述接口提供便利而做的事情,对ApplicationContext接口抽象出来的功能没有影响或者没有太大帮助
特别注意(这是经常会被忽略的地方):
AbstractApplicationContext
它实现了大多数方法,包括关于Bean定义的相关方法如下:
// 这几个方法特比特殊:子类中有实现了接口AnnotationConfigRegistry的,方法签名和这个是一模一样的。所以子类虽然实现了AnnotationConfigRegistry接口,但并不需要再实现这个方法亦可
// 使用起来需要注意的是,这里借助的是getBeanFactory():属于ListableBeanFactory这个二级接口的方法
// 而这个二级接口说得很清楚:它去查找的时候,不会考虑层级关系。(也就是说不去父容器找)
// 所以使用这三个方法的时候要注意。若你确定Bean已经加载进来了,不妨也把父容器也考虑一把
@Override
public boolean containsBeanDefinition(String beanName) {
return getBeanFactory().containsBeanDefinition(beanName);
}
@Override
public int getBeanDefinitionCount() {
return getBeanFactory().getBeanDefinitionCount();
}
@Override
public String[] getBeanDefinitionNames() {
return getBeanFactory().getBeanDefinitionNames();
}
// 后面的getBeanNamesForType之类的,这里就不说了~
AbstractRefreshableApplicationContext
和GenericApplicationContext
它俩是AbstractApplicationContext
直接子类。
GenericApplicationContext
通用的应用上下文(请注意:它已经不是抽象类,可以直接使用了)
GenericApplicationContext
持有一个DefaultListableBeanFactory
实例,并且没有假设一个特定的bean definition 的format。实现了BeanDefinitionRegistry
接口以允许配置任何bean definition reader(也可以不是XmlBeanDefinitionReader)。 Generic:表现出它的通用
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
@Nullable
private ResourceLoader resourceLoader;
// 保证只会被刷新一次
private final AtomicBoolean refreshed = new AtomicBoolean();
...
//和其他在每次refresh时都创建一个新的内部BeanFactory实例的ApplicationContext实例不同
//本类中的BeanFactory从一开始就创建好并可在其中注册bean definitions,refresh方法可能在其中只调用一次(并不会每次刷新的时候都会去调用的)
}
我们使用它的时候,可以先调用registerBeanDefinition,注册一些Bean定义(至于这些Bean定义从哪儿来,子类自己思考,就不一定得从xml文件来了),然后再refresh()即可,使用案例:
GenericApplicationContext ctx = new GenericApplicationContext();
//使用XmlBeanDefinitionReader
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
//加载ClassPathResource
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
//调用Refresh方法
ctx.refresh();
//和其他ApplicationContext方法一样的使用方式
MyBean myBean = (MyBean) ctx.getBean("myBean");
当然它也有很大的缺点:它几乎是手动档的。什么解析Bean之类的,都得自己手动调用对应的Reader去处理,在当下约定大于配置的今天,直接使用它手动去控制,现在效率太低了。
一般情况下使用ClassPathXmlApplicationContext
或者FileSystemXmlApplicationContext
会比这个GenericApplicationContext
更方便,但是但是相应地缺少灵活性,因为只能使用特定Bean definition 格式和加载路径。
当然,也可以继承它做一些扩展,进行一些步骤的简单封装处理亦可。比如:
GenericXmlApplicationContext
:比较重要,内部使用XmlBeanDefinitionReader
去解析,根据传进来的xml路径解析然后把Bean定义信息注册好,然后自动调用refresh()方法StaticApplicationContext
:它能让你编程注册。而不是从配置文件中读取,一般用于测试环境(忽略)GenericWebApplicationContext
:对web环境的支持。重点是也实现了接口:ConfigurableWebApplicationContext
配置的contextAnnotationConfigApplicationContext
:这个非常的重要,内部使用AnnotatedBeanDefinitionReader
或者ClassPathBeanDefinitionScanner
去装载Bean的定义信息。然后其余的没啥了。这个我们经常使用,因为不用再需要xml文件了,使用@Configuration
配置类即可,更加的方便。
下面这个AbstractRefreshableApplicationContext
给出了另一种解决方案:如果你追求定制化的ApplicationContext实现,可从AbstractRefreshableApplicationContext
继承来实现从不同地方去加载Bean定义信息进来。
AbstractRefreshableApplicationContext
AbstractRefreshableApplicationContext实现了父类的方法refreshBeanFactory(),执行BeanFactory的“刷新”。
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
...
@Override
protected final void refreshBeanFactory() throws BeansException {
//1、如果当前上下文持有一个已经刷新过的bean工厂,需要先销毁此上下文所管理的所有的bean,再关闭bean工厂
//2、创建一个bean工厂:org.springframework.beans.factory.support.DefaultListableBeanFactory,并且把旧工厂的属性赋值给新的(若有的话)
//3、加载xml配置文件中的bean(或者是@Configuration的定义信息) 总之就是loadBeanDefinitions(beanFactory);
// 此处需要注意的是:loadBeanDefinitions()是个抽象方法,因为当前的并不知道去哪加载(可能是xml,可能是Config类,可能都有。因此交给子类共同去完成这个方案即可)
// 所以看下面的实现~~~~AbstractRefreshableConfigApplicationContext
}
}
AbstractRefreshableConfigApplicationContext
public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
implements BeanNameAware, InitializingBean {
@Nullable
private String[] configLocations; // 配置文件的位置(可能是xml,可能是config哦~ 目前还是不确定的 这里只是提供的抽象存储)
...
//它没有什么具体的操作。
}
这里它有两个比较典型的实现类:
FileSystemXmlApplicationContext
:taking the context definition files from the file system or from URLsClassPathXmlApplicationContext
:taking the context definition files from the class path
三级接口:ConfigurableWebApplicationContext 集大成者
web应用上下文使用的接口,并没有定义太多的操作,主要和Servlet上下文及配置文件。它一次性都继承了上面的两个二级接口,可以说是他俩的结合体
public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext {
String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() +
String SERVLET_CONFIG_BEAN_NAME = "servletConfig";
// 这里面定义的方法都比较简单,见名之意,没什么好说的
void setServletContext(@Nullable ServletContext servletContext);
void setServletConfig(@Nullable ServletConfig servletConfig);
@Nullable
ServletConfig getServletConfig();
void setNamespace(@Nullable String namespace);
@Nullable
String getNamespace();
// Spring配置文件的地方 比如默认位置为:/WEB-INF/applicationContext.xml
void setConfigLocation(String configLocation);
void setConfigLocations(String... configLocations);
@Nullable
String[] getConfigLocations();
}
此处有个非常重要的实现:AbstractRefreshableWebApplicationContext
,它相当于在父类基础在,再实现了ConfigurableWebApplicationContext
从而具有web的特性。它自己是抽象类,但是具体实现有如下:
XmlWebApplicationContext
:这个可以说是web环境下Spring的默认容器。之前讲解过了ContextLoaderListener
启动的时候,如果你没有指定web容器(比如若是Tomcat注解驱动的话,就会外面创建一个AnnotationConfigWebApplicationContext
传进来),那么this.context = createWebApplicationContext(servletContext);
(它会去找contextConfigLocation,找到xml配置文件)就会创建一个XmlWebApplicationContext
类型的容器。至于为何呢?其实默认类型在配置文件:org\springframework\web\context\ContextLoader.properties
这个文件里,里面写着:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
这里稍微说一下,你可以根据web.xml里配置的globalInitializerClasses
和 contextInitializerClasses
对应的class来定制成你的容器类型都ok,这个前面有说过,此处就不再鳌诉了~
它的loadBeanDefinitions()
方法也是从配置的xml文件里面去解析读取
GroovyWebApplicationContext
:略AnnotationConfigWebApplicationContext
:在注解驱动大行其道的今天,这个就特别的重要了。它的loadBeanDefinitions()
前面源码分析的时候重点分析过,因此这里就略过吧。理解好它,更有助于理解Spring Boot自己提供的容器类型:AnnotationConfigServletWebServerApplicationContext
总结
Spring是一个优秀的框架,具有良好的结构设计和接口抽象,它的每一个接口都是其功能具体到各个模块中的高度抽象,实际使用过程中相当于把接口的各个实现类按照接口所提供的组织架构装配起来以提供完整的服务,可以说掌握了Spring的接口就相当于掌握了Spring的大部分功能
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |