公眾號: 西魏陶淵明
CSDN: https://springlearn.blog.csdn.net
天下代碼一大抄, 抄來抄去有提高, 看你會抄不會抄!
一、前言
Spring
是 java
開發(fā)者,永遠繞不開的結(jié)。Spring
是非常值得開發(fā)者來學(xué)習(xí)的, 以目前 Spring
在 java
領(lǐng)域的統(tǒng)治性地位, 可以說學(xué) java
就是在學(xué) Spring
。但是作為新入門的開發(fā)人員,甚至說是有一定工作經(jīng)驗的同學(xué),面對如此龐大的框架,都不一定是充分掌握了所有的知識點。因為大多數(shù)人的學(xué)習(xí),都不是系統(tǒng)的學(xué)習(xí),都是片面的。以經(jīng)驗為主。本系列專題的主要目的就是,一起系統(tǒng)的來學(xué)習(xí)一下Spring這個框架, 以一個六年經(jīng)驗的老鳥的視角里,來重學(xué)Spring。通過直接閱讀 Spring的官方文檔來獲取一手知識。
因為內(nèi)容較多,建議收藏學(xué)習(xí)。
二、BeanFactory 工廠
2.1 什么是Bean ?
平時我們來創(chuàng)建對象, 一般都是 new。如果這個對象里有一個屬性, 那么就需要我來進行set,賦值。但是如果要有10個屬性呢? 你也要自己來賦值嗎? 那不累死個人嘛。Spring的解決方案就是, 這么重的活, 開發(fā)者不用關(guān)心了,都交給我來處理吧。那么Spring是如何來處理的呢? 對,就是BeanFactory
,Spring通過 BeanFactory的方式幫實現(xiàn)對象的實例化。那么所有被Spring管理的對象,我們就可以理解成Bean對象。
凡是有屬性和方法的對象都是Bean對象,凡是被Spring管理的Bean對象就是Spring Bean對象。
2.2 如何使用Bean工廠
- 方式一直接使用代碼自動注入
@Component
public class SpringIocTest{
@Autowired
private BeanFactory beanFactory;
}
- 方式二使用BeanFactoryAware注入
@Component
public class SpringIocTest implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
2.3 BeanFactory的體系
在 Spring
中 BeanFactory
是一個非常重要的組件, 要想搞清楚 Spring
, 一定要先搞清楚 BeanFactory
的體系,這里我們詳細來解釋下 BeanFactory的體系。
看這張圖,密密麻麻的都是,但是我們不要擔(dān)心,實際我們不用關(guān)心這么多。大部分人都是因為看到了這里,給勸退了, 下面給大家精簡一下。希望對你有所幫助。
我們只關(guān)心上面這張圖就好了,但是看類還是比較多,為什么呢? 因為Spring定義BeanFactory接口比較細,每個接口的維度都很細維度。但是我們能看到最底層的實現(xiàn),是實現(xiàn)了所有接口的功能。下面我們以此來解釋每個接口的功能。來窺探一下Spring中BeanFactory的體系。非常的全,建議大家可以收藏一下,沒必要死記硬背。如果不理解的話,背下來也沒有什么的用。
下面分享,希望對大家有點用。
2.3.1 BeanFactory
最頂層的接口,提供了根據(jù)Bean名稱獲取Bean的最基礎(chǔ)的能力。詳細可以看下面的注釋說明。接口沒有任何實現(xiàn),只是做定義。
public interface BeanFactory {
// 如果要獲取FactoryBean,那么要的Bean的名稱前加 &
String FACTORY_BEAN_PREFIX = "&";
// 根據(jù)名稱獲取實例,如果沒有就拋異常,結(jié)果是Object類型
Object getBean(String name) throws BeansException;
// 跟前者一樣,不同是結(jié)果是泛型類型,會自動幫我們轉(zhuǎn)換類型
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 允許指定顯式構(gòu)造函數(shù)參數(shù),很少會用
Object getBean(String name, Object... args) throws BeansException;
// 根據(jù)類型獲取Bean實例,如果找到了多個類型,則會報錯
<T> T getBean(Class<T> requiredType) throws BeansException;
// 根據(jù)類型獲取實例,并顯式構(gòu)造函數(shù)參數(shù)
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 根據(jù)類型獲取Bean的生成對象,這里并不是直接獲取了Bean的實例
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
// 跟前者大同小異
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 判斷是否保存這個名字的實例
boolean containsBean(String name);
// 判斷是否單例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 判斷是否是原型模式
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// bean名稱和類型是否匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
// bean名稱和類型是否匹配
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 獲取名稱的類型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 根據(jù)名稱獲取類型,FactoryBean比較特殊,allowFactoryBeanIn // it是說,是否也要算FactoryBean,一般情況用true
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
// bean聲明的別名,如果沒有則為空數(shù)組
String[] getAliases(String name);
}
2.3.2 HierarchicalBeanFactory
Hierarchical翻譯: 分層
HierarchicalBeanFactory的意思是具有層次關(guān)系,這個BeanFactory可以創(chuàng)建一個BeanFactory,那么是否可以根據(jù)這個BeanFactory知道是誰創(chuàng)建他的呢? 這個接口就是干這個事情的。
public interface HierarchicalBeanFactory extends BeanFactory {
// 返回當(dāng)前工廠的父工廠
@Nullable
BeanFactory getParentBeanFactory();
// 返回當(dāng)工廠是否包含這個bean,不從父工廠中去獲取
boolean containsLocalBean(String name);
}
2.3.3 ListableBeanFactory
- 一個接口可能會有多個實現(xiàn),每個實現(xiàn)都是一個Bean。所以根據(jù)一個類型可能會獲取多個Bean的實例。
- 一個工廠會有很多的Bean,能不能一下獲取工廠所有的Bean呢?
這個工廠名字定義的很有意思,Listable, List 所以大多接口是返回集合。你不信,你看下面展示。
public interface ListableBeanFactory extends BeanFactory {
// 是否包含BeanDefinition,BeanDefinition是bean實例化的基 // 本信息。
boolean containsBeanDefinition(String beanName);
// 獲取BeanDefinition的數(shù)量
int getBeanDefinitionCount();
// 獲取BeanDefinition的名稱
String[] getBeanDefinitionNames();
// 根據(jù)類型,獲取這個類型的所有Bean的名稱
String[] getBeanNamesForType(ResolvableType type);
// 根據(jù)類型獲取bean的名稱,包含非單例的,允許初始化
String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit);
// 根據(jù)類型,獲取這個類型的所有Bean的名稱
String[] getBeanNamesForType(@Nullable Class<?> type);
// 根據(jù)類型獲取bean的名稱,包含非單例的,允許初始化
String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
// 根據(jù)類型獲取Bean的字典,key是名稱 value是實例
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
// 根據(jù)類型獲取Bean的字典(包含非單例),key是名稱 value是實例
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
throws BeansException;
// 獲取被當(dāng)前注解修飾的Bean的名稱,只獲取名稱不實例化,支持注解派 // 生的方式
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
// 獲取被該注解修飾的bean,key是名稱,value是實例。
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
// 獲取當(dāng)前名稱Bean的,當(dāng)前注解的信息
@Nullable
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException;
}
2.3.4 ConfigurableBeanFactory
這個工廠,是最容易看出他的用途的,名字一個看就是跟配置相關(guān)的。
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
// 單例:一個容器只都存在實例
String SCOPE_SINGLETON = "singleton";
// 原型:每次getBean一次生成一個實例
String SCOPE_PROTOTYPE = "prototype";
// 設(shè)置他的父工廠
void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException;
// 設(shè)置類加載器以用于加載 bean 類。默認是線程上下文類加載器。
void setBeanClassLoader(@Nullable ClassLoader beanClassLoader);
// 返回此工廠的類加載器以加載 bean 類
@Nullable
ClassLoader getBeanClassLoader();
// 指定用于類型匹配目的的臨時 ClassLoader。默認為無
void setTempClassLoader(@Nullable ClassLoader tempClassLoader);
// 獲取臨時的類加載器
@Nullable
ClassLoader getTempClassLoader();
// 設(shè)置是否緩存 bean 元數(shù)據(jù),例如給定的 bean 定義(以合并方式)和解析的 bean 類。默認開啟。
void setCacheBeanMetadata(boolean cacheBeanMetadata);
// 返回是否緩存 bean 元數(shù)據(jù)
boolean isCacheBeanMetadata();
// bean 定義值中的表達式指定解析策略。
// 默認是 StandardBeanExpressionResolver。
void setBeanExpressionResolver(@Nullable BeanExpressionResolver resolver);
// 獲取解析類型 StandardBeanExpressionResolver
@Nullable
BeanExpressionResolver getBeanExpressionResolver();
// 設(shè)置轉(zhuǎn)換層統(tǒng)一的API,后面有專門章節(jié)說這個體系。
void setConversionService(@Nullable ConversionService conversionService);
// 獲取轉(zhuǎn)換API
@Nullable
ConversionService getConversionService();
// 給工廠添加一個屬性設(shè)置的注冊器,實際用的不多,但是有必要去了解,后面也會介紹
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
// 為給定類型的所有屬性注冊給定的自定義屬性編輯器。在工廠配置期間調(diào)用。
void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass);
// BeanFactory 中注冊的自定義編輯器初始化給定的 PropertyEditorRegistry
void copyRegisteredEditorsTo(PropertyEditorRegistry registry);
// 設(shè)置類型轉(zhuǎn)換器
void setTypeConverter(TypeConverter typeConverter);
// 獲取類型轉(zhuǎn)換器
TypeConverter getTypeConverter();
// 添加字符串解析器。
void addEmbeddedValueResolver(StringValueResolver valueResolver);
// 是否有字符串解析器
boolean hasEmbeddedValueResolver();
// 解析數(shù)據(jù)
@Nullable
String resolveEmbeddedValue(String value);
// 添加一個新的 BeanPostProcessor,它將應(yīng)用于此工廠創(chuàng)建的 bean。在工廠配置期間調(diào)用。
// 非系統(tǒng)定義的處理器,都可以使用Order進行排序
// 這是一個非常重要的Bean處理器
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
// 處理器的個人
int getBeanPostProcessorCount();
// 注冊由給定 Scope 實現(xiàn)支持的給定范圍
// 這里稍微解釋下什么是Scope,就比如Session內(nèi)有效或者是Request內(nèi)有效
void registerScope(String scopeName, Scope scope);
// 返回所有當(dāng)前注冊范圍的名稱,不會公開諸如“singleton”和“prototype”之類的內(nèi)置作用域
String[] getRegisteredScopeNames();
// 獲取域的域?qū)ο?/span>
@Nullable
Scope getRegisteredScope(String scopeName);
// 提供與該工廠相關(guān)的安全訪問控制上下文。
AccessControlContext getAccessControlContext();
// 拷貝當(dāng)Bean工廠的配置
void copyConfigurationFrom(ConfigurableBeanFactory otherFactory);
// 給bean注冊一個別名
void registerAlias(String beanName, String alias) throws BeanDefinitionStoreException;
// 解析在此工廠中注冊的所有別名目標(biāo)名稱和別名,并將給定的 StringValueResolver 應(yīng)用于它們。
void resolveAliases(StringValueResolver valueResolver);
// 返回給定 bean 名稱的合并 BeanDefinition,如有必要,將子 bean 定義與其父合并。
BeanDefinition getMergedBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 是否是FactoryBean
boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException;
// 設(shè)置當(dāng)前Bean正在創(chuàng)建中。僅供容器內(nèi)部會使用。
void setCurrentlyInCreation(String beanName, boolean inCreation);
// 當(dāng)前Bean是否創(chuàng)建中
boolean isCurrentlyInCreation(String beanName);
// 為給定的 bean 注冊一個依賴 bean
void registerDependentBean(String beanName, String dependentBeanName);
// 返回依賴于指定 bean 的所有 bean 的名稱
String[] getDependentBeans(String beanName);
// 獲取當(dāng)前Bean依賴的Bean
String[] getDependenciesForBean(String beanName);
// 銷毀bean
void destroyBean(String beanName, Object beanInstance);
// 銷毀當(dāng)前目標(biāo)作用域中的指定作用域bean(如果有)
void destroyScopedBean(String beanName);
// 銷毀單例
void destroySingletons();
}
2.3.5 AutowireCapableBeanFactory
Autowire是不是看著很熟,提供自動注入的方法。
public interface AutowireCapableBeanFactory extends BeanFactory {
// 不需要自動裝配
int AUTOWIRE_NO = 0;
// 表示按名稱自動裝配 bean 屬性的常量
int AUTOWIRE_BY_NAME = 1;
// 按照類型來自動裝配
int AUTOWIRE_BY_TYPE = 2;
// 指示自動裝配可以滿足的最貪婪構(gòu)造函數(shù)的常量
int AUTOWIRE_CONSTRUCTOR = 3;
//
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
// 5.1 才有的。初始化現(xiàn)有 bean 實例時的“原始實例”約定的后綴:附加到完全限定的 bean 類名,例如“com.mypackage.MyClass.ORIGINAL”,以強制返回給定的實例,即沒有代理等。
String ORIGINAL_INSTANCE_SUFFIX = ".ORIGINAL";
//-------------------------------------------------------------------------
// 創(chuàng)建和填充bean 實例的方法
//-------------------------------------------------------------------------
// 創(chuàng)建bean
<T> T createBean(Class<T> beanClass) throws BeansException;
// 自動裝配bean
void autowireBean(Object existingBean) throws BeansException;
// 給一個空實例,也能進行填充。
Object configureBean(Object existingBean, String beanName) throws BeansException;
//-------------------------------------------------------------------------
// 對 bean 生命周期進行細粒度控制的專用方法
//-------------------------------------------------------------------------
Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException;
void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck)
throws BeansException;
void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException;
Object initializeBean(Object existingBean, String beanName) throws BeansException;
Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException;
Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException;
void destroyBean(Object existingBean);
<T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws BeansException;
Object resolveBeanByName(String name, DependencyDescriptor descriptor) throws BeansException;
@Nullable
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName) throws BeansException;
@Nullable
Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;
}
2.3.6 ConfigurableListableBeanFactory
看名字大概就能猜出些什么了,具體接口定義看下面。
public interface ConfigurableListableBeanFactory
extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {
// 自動裝配時候,忽略這些類型
void ignoreDependencyType(Class<?> type);
// 自動裝配時候,忽略這些接口
void ignoreDependencyInterface(Class<?> ifc);
// 給當(dāng)前類型,注入指定的實例。
void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue);
// 判斷當(dāng)前bean是否有資格作為自動裝配的候選者
boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor)
throws NoSuchBeanDefinitionException;
// 返回指定 bean 的注冊 BeanDefinition
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 返回此工廠管理的所有 bean 名稱
Iterator<String> getBeanNamesIterator();
// 清除合并的 bean 定義緩存,通常在更改原始 bean 定義后觸發(fā)
void clearMetadataCache();
// 凍結(jié)所有 bean 定義,表示注冊的 bean 定義將不會被修改或進一步后處理
void freezeConfiguration();
// 返回此工廠的 bean 定義是否被凍結(jié),即不應(yīng)該進一步修改或后處理。
boolean isConfigurationFrozen();
// 單例初始化方法,非常重要,我們開發(fā)中大部分bean初始化就是這個方法調(diào)用的哦。
void preInstantiateSingletons() throws BeansException;
}
好了關(guān)于工廠的定義已經(jīng)全部展示了,剩下的都是具體的實現(xiàn)。具體的實現(xiàn)就不單獨拿出來了。下面我們來看Spring中的上下文對象。
三、ApplicationContext 容器上下文
應(yīng)用上下文,是Spring中最最核心的類,也是功能最強大的類,Spring所有的工具基本都能通過上下文來獲取。
- 獲取環(huán)境變量
- 獲取Bean工廠
- 發(fā)送容器事件
下面我們看Spring中構(gòu)建上下文的幾種方式。
3.1 構(gòu)建上下文
3.1.1 參數(shù)化構(gòu)建
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
3.1.2 目錄掃描
掃描 com.acme
包以查找任何 帶@Component注釋的類,并且這些類在容器中注冊為 Spring bean 定義。
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
3.2 容器事件
事件 | 解釋 |
---|---|
ContextRefreshedEvent |
在初始化或刷新時發(fā)布ApplicationContext (例如,通過使用接口refresh() 上的方法ConfigurableApplicationContext )。這里,“初始化”意味著所有 bean 都已加載,后處理器 bean 被檢測并激活,單例被預(yù)實例化,并且ApplicationContext 對象已準備好使用。只要上下文沒有關(guān)閉,就可以多次觸發(fā)刷新,前提是所選擇的ApplicationContext 實際支持這種“熱”刷新。例如,XmlWebApplicationContext 支持熱刷新,但 GenericApplicationContext 不支持。 |
ContextStartedEvent |
使用接口上的方法 ApplicationContext 啟動時發(fā)布。在這里,“已啟動”意味著所有 bean 都接收到一個明確的啟動信號。通常,此信號用于在顯式停止后重新啟動 bean,但它也可用于啟動尚未配置為自動啟動的組件(例如,尚未在初始化時啟動的組件)。start()``ConfigurableApplicationContext``Lifecycle
|
ContextStoppedEvent |
使用接口上的方法 ApplicationContext 停止時發(fā)布。在這里,“停止”意味著所有 的 bean 都會收到一個明確的停止信號。可以通過 調(diào)用重新啟動已停止的上下文。stop()``ConfigurableApplicationContext``Lifecycle``start()
|
ContextClosedEvent |
在ApplicationContext 使用接口close() 上的方法ConfigurableApplicationContext 或通過 JVM 關(guān)閉掛鉤關(guān)閉時發(fā)布。在這里,“關(guān)閉”意味著所有的單例 bean 都將被銷毀。一旦上下文關(guān)閉,它就到了生命的盡頭,無法刷新或重新啟動。 |
RequestHandledEvent |
一個特定于 Web 的事件,告訴所有 bean 一個 HTTP 請求已得到服務(wù)。此事件在請求完成后發(fā)布。此事件僅適用于使用 Spring 的 Web 應(yīng)用程序DispatcherServlet 。 |
ServletRequestHandledEvent |
它的子類RequestHandledEvent 添加了 Servlet 特定的上下文信息。 |
3.2.1 ContextRefreshedEvent 容器刷新事件
容器啟動的最后一步,發(fā)送容器刷新事件,當(dāng)收到這個事件的時候,容器就已經(jīng)準備就緒了,你就可以正常使用了。
AbstractApplicationContext#finishRefresh
3.2.2 ContextClosedEvent 關(guān)閉事件
一旦應(yīng)用被關(guān)閉或者中斷就會觸發(fā)容器關(guān)閉事件。但是 kill -9
除外, kill
是可以的。這背后的原因,這是linux系統(tǒng)的機制,更多詳細請自行百度。
3.2.3 ContextStartedEvent 啟動事件
ContextStartedEvent 跟前面兩個的事件不同是,必須要顯示觸發(fā),比如下面這樣。
public static void main(String[] args) {
SpringApplication.run(Application.class,args).start();
}
3.2.4 ContextStoppedEvent 停止事件
ContextStoppedEvent 和 ContextStartedEvent 是一樣的,必須要顯示調(diào)用。
public static void main(String[] args) {
SpringApplication.run(Application.class,args).stop();
}
3.2.5 RequestHandledEvent
當(dāng)收到http請求時候觸發(fā),此事件僅適用于使用 Spring 的 Web 應(yīng)用程序DispatcherServlet。
3.2.6 ServletRequestHandledEvent
跟前這一樣,不同的是增加了Servlet的信息.
更多事件相關(guān),請看下一篇,Event專題
四、JavaConfig 配置
在之前Spring的配置都是基于xml方式,當(dāng)Jdk5之后支持注解后,Spring的配置方式增加了基于注解的配置。
那么你認為Java代碼注解配置好? 還是xml方式好呢?
我們看下官方的回答:
- 簡短的回答是“視情況而定”。
- 長答案是每種方法都有其優(yōu)點和缺點,通常由開發(fā)人員決定哪種策略更適合他們。
由于它們的定義方式,注解方式在其聲明中提供了大量上下文,從而使配置更短、更簡潔。
然而,XML 擅長在不觸及源代碼或重新編譯它們的情況下連接組件。一些開發(fā)人員更喜歡在源附近進行布線,而另一些開發(fā)人員則認為帶注釋的類不再是 POJO,此外,配置變得分散且更難控制。
無論選擇如何,Spring 都可以同時適應(yīng)這兩種風(fēng)格,甚至可以將它們混合在一起。
改部分介紹如何在 Java 代碼中使用注解來配置 Spring 容器。它包括以下主題:
4.1 @Configuration 配置類
Spring 新的 Java 配置,的主要使用的是 @Configuration注釋的類。
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的AppConfig類等價于下面的 Spring XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
4.2 帶@Bean注解的方法
當(dāng)@Bean方法在沒有用 @Configuration
注解修飾的類中聲明時 ,它們被稱為以“精簡”模式處理。
如下代碼示例。
@Component
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
4.3 @Configuration和@Bean的區(qū)別
與@Configuration不同的是,使用@Bean方法的模式, 不能聲明 bean 間的依賴關(guān)系。這句話怎么理解的。我們舉一個代碼的例子。
@Component
public class BeanConf {
@Bean("serverA")
public ServerA serverA() {
ServerA serverA = new ServerA("Configuration 方式");
System.out.println("ServerA:" + serverA.hashCode());
return serverA;
}
@Bean("serverB")
public ServerB serverB() {
ServerB serverB = new ServerB();
ServerA serverA = serverA();
System.out.println("Method ServerA:" + serverA.hashCode());
serverB.setServerA(serverA);
return serverB;
}
}
我們使用 Component
來修飾, ServerA:
這一行,會打印2次,第一次是 @Bean
解析Bean時候。第二次是
在 serverB方法調(diào)用時候執(zhí)行。此時ServerB中注入的ServerA并不是被容器管理的Bean。而是調(diào)用方法新建的ServerA。
好下面我們看另外一個例子。
@Configuration
public class BeanConf {
@Bean("serverA")
public ServerA serverA() {
ServerA serverA = new ServerA("Configuration 方式");
System.out.println("ServerA:" + serverA.hashCode());
return serverA;
}
@Bean("serverB")
public ServerB serverB() {
ServerB serverB = new ServerB();
ServerA serverA = serverA();
System.out.println("Method ServerA:" + serverA.hashCode());
serverB.setServerA(serverA);
return serverB;
}
}
與前面不同的是, ServerA:
這一行,會打印1次,就是解析 @Bean
的時候。而 serverB()方法中雖然調(diào)用了 serverA()方法,但是并不會執(zhí)行,而是從容器中直接拿到前面解析的Bean。
所以我們得出結(jié)論,我們盡量要用 @Configuration
來聲明配置,避免出現(xiàn)意外的問題。
五、基于注解容器配置
5.1 @Required
此注解指示必須在配置時通過 bean 定義中的顯式屬性值或通過自動裝配來填充受影響的 bean 屬性。如果受影響的 bean 屬性尚未填充,則容器將引發(fā)異常。
處理類: RequiredAnnotationBeanPostProcessor
注意: 這種方式已經(jīng)聲明廢棄了,不過也支持,但是不建議使用。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
5.2 @Autowired
聲明注入的,@Autowired 默認不允許為空,即跟 @Required
一樣,如果為空就中斷,但是也允許為空。
如果為空,不想中斷,可以這樣使用 @Autowired(required = false)
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
5.3 @Primary
- Primary翻譯: 主要的
由于按類型自動裝配可能會導(dǎo)致多個候選者,因此通常需要對選擇過程進行更多控制。實現(xiàn)這一點的一種方法是使用 Spring 的 @Primary注釋。@Primary: 當(dāng)多個 bean 是自動裝配到單值依賴項的候選對象時,應(yīng)該優(yōu)先考慮特定的 bean。如果候選中恰好存在一個主 bean,則它將成為自動裝配的值。
如下,MovieCatalog類型有兩個Bean。
@Configuration
public class MovieConfiguration {
@Bean("MovieCatalog1")
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean("MovieCatalog2")
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
當(dāng)要進行注入時候就會報錯,因為根據(jù)類型發(fā)現(xiàn)了兩個備選的Bean。而這種情況的解決辦法就是其中一個使用 @Primary
來修飾。此時容器就知道你到底要注冊那個了,當(dāng)被 @Primary
修飾的Bean會被正確注入。
此時可能有朋友會問,如果兩個一樣類型的Bean都用 @Primary
來修飾呢? 結(jié)果就是會報錯。如下。
No qualifying bean of type 'learn.spring.service.ServerA' available: more than one 'primary' bean found among candidates: [serverA1, serverA2]
5.4 @Qualifier
@Primary
當(dāng)可以確定一個主要候選者時,是一種通過類型使用多個實例的自動裝配的有效方法。當(dāng)您需要對選擇過程進行更多控制時,可以使用 Spring 的@Qualifier注解。您可以將限定符值與特定參數(shù)相關(guān)聯(lián),縮小類型匹配的范圍,以便為每個參數(shù)選擇特定的 bean。
@Configuration
public class MovieConfiguration {
@Bean("main")
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
一個最簡單的解釋就是 @Autowired + @Qualifier
= @Resource
。
5.5 CustomAutowireConfigurer
前面我們可以通過 @Qualifier 實現(xiàn)根據(jù)名字的注入, CustomAutowireConfigurer 允許我們自定義一個注解, 具備和 @Qualifier 一樣的功能。
首先我們聲明一個注解,保持和@Qualifier一樣的結(jié)構(gòu)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ConditionAutowire {
String value() default "";
}
然后使用CustomAutowireConfigurer來,配置我們自定義的注解。
/**
* 自定義一個注入工具
*
* @return 注入工具
*/
@Bean
public CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer customAutowireConfigurer = new CustomAutowireConfigurer();
customAutowireConfigurer.setCustomQualifierTypes(Collections.singleton(ConditionAutowire.class));
return customAutowireConfigurer;
}
這樣我們就能使用下面的代碼了。
@Component
public class ServerB {
ServerA serverA;
@Autowired
// @Qualifier("serverAA") 與下面代碼等價。
@ConditionAutowire("serverAA")
public void setServerA(ServerA serverA) {
this.serverA = serverA;
}
}
5.6 @Resource
Spring 還通過在字段或 bean 屬性設(shè)置器方法上使用 JSR-250@Resource注釋 ( )來支持注入。javax.annotation.Resource這是 Java EE 中的常見模式:例如,在 JSF 管理的 bean 和 JAX-WS 端點中。Spring 也支持 Spring 管理的對象的這種模式。
@Resource采用名稱屬性。默認情況下,Spring 將該值解釋為要注入的 bean 名稱。換句話說,它遵循按名稱語義,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果沒有明確指定名稱,則默認名稱派生自字段名稱或 setter 方法。如果是字段,則采用字段名稱。對于 setter 方法,它采用 bean 屬性名稱。以下示例將把名為 bean 的 beanmovieFinder注入到它的 setter 方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
5.7 @Value
@Value通常用于注入外部屬性
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
application.properties文件,添加上一下配置
catalog.name=MovieCatalog
5.7.1 默認值
- @Value(“${catalog.name:defaultCatalog}”)
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
5.7.1 支持SpringEL 表達式
當(dāng)@Value包含SpEL表達式時,該值將在運行時動態(tài)計算,如以下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL 還支持使用更復(fù)雜的數(shù)據(jù)結(jié)構(gòu):
- 注意如果使用EL表達式,就不是$而是#
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
5.8 初始化 & 銷毀方法
- @PostConstruct
- @PreDestroy
處理類: InitDestroyAnnotationBeanPostProcessor
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// 初始化執(zhí)行
}
@PreDestroy
public void clearMovieCache() {
// Bean銷毀執(zhí)行
}
}
可能會有人問
- 不是還有
InitializingBean
初始化和DisposableBean
接口能實現(xiàn)初始化和銷毀方法嗎?
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
public interface DisposableBean {
void destroy() throws Exception;
}
- 不是還可以通過
@Bean(initMethod = "init",destroyMethod = "destroy")
來聲明嗎?
是的當(dāng)然都可以,不過這也是有執(zhí)行順序的,順序如下。
5.9 @Scope
這個注解平時接觸的都很少,但是其實我們都在用,因為如果不顯示聲明,默認就是 @Scope(“singleton”)
這個怎么理解呢? 比如在Spring中默認都是單例 singleton
,這就意味著就是說在容器不關(guān)閉的情況下,不管你調(diào)用了幾次都是同一個實例。如果我們想讓每個Thread拿到自己的實例呢? 有沒有辦法呢?
當(dāng)然有,如下我們定一個Thread范圍的Bean, 首先給工廠定義自己的域范圍。
@Component
public class BeanFactoryConf implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("thread", new SimpleThreadScope());
}
@Bean
@Scope("thread")
public ThreadScopeBean threadScopeBean() {
return new ThreadScopeBean(Thread.currentThread().getName());
}
public static class ThreadScopeBean {
String name;
public ThreadScopeBean(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
}
然后使用多個線程來獲取這個Bean,最終我們會發(fā)現(xiàn),每個線程得到的實例都是不一樣的。符合Thread這個域的范圍。
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
ServerB bean = run.getBean(ServerB.class);
System.out.println(bean);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
BeanFactoryConf.ThreadScopeBean threadScope = run.getBean(BeanFactoryConf.ThreadScopeBean.class);
// thread-scope-1
// thread-scope-2
// thread-scope-0
System.out.println(threadScope);
}, "thread-scope-" + i).start();
}
}
注意: 上面這個例子,必須每次從容器中重新獲取Bean才會生效。
當(dāng)然這里是Thread范圍,其實還有Session范圍和request范圍,這兩個是我們使用最多的。他們兩個是如何實現(xiàn)的呢? 大家可以思考下,其實也很簡單。就是對工具類和ThreadLocal的利用。有知道原理的,可以下面評論。
5.9.1 HttpServletRequest 注入
這里解釋一個經(jīng)常被弄混淆概念,就是我們知道我們在容器中注入一個 HttpServletRequest
這個類,HttpServletRequest
不是一個 Bean
, 為什么能注入呢?
每次在使用的時候,都會獲取當(dāng)前的請求對象。他是如何實現(xiàn)的呢? 他不是Scope來實現(xiàn)的。而是通過。下面
這兩個行代碼一起來實現(xiàn)的。
- beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()) 這一行的意思是,當(dāng)發(fā)現(xiàn)你要注入的是SCOPE_REQUEST,時候會調(diào)用RequestScope@getObject來實例化。這個類不是單例不會被容器保存,也不是原型不會每次都來重新創(chuàng)建。
- beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()) 的意思是,當(dāng)這個類被注入到其他類的時候,要進行代理。
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
...
}
在進行自動注入的時候,如果發(fā)現(xiàn)實例是一個 ObjectFactory
就會生成代理類。
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
// 這里獲取到RequestObjectFactory
ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
}
else {
return factory.getObject();
}
}
return autowiringValue;
}
然后代理類中這樣處理,在執(zhí)行每個方法的時候,都從新獲取 ObjectFactory#getObject()
。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of proxy.
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
// 每次執(zhí)行方法,都從新獲取objectFactory.getObject()
// RequestObjectFactory中是使用ThreadLocal的方式來實現(xiàn)。
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
5.10 @Import
@Import
注解允許 @Bean
從另一個配置類加載定義,如以下示例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
現(xiàn)在,不需要同時指定ConfigA.class和ConfigB.class在實例化上下文時,只ConfigB需要顯式提供,如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
從 Spring Framework 4.2 開始,@Import還支持對常規(guī)組件類的引用,類似于AnnotationConfigApplicationContext.register方法。如果您想通過使用一些配置類作為入口點來顯式定義所有組件來避免組件掃描,這將特別有用。
這里我們定義一個注解,使用Import修飾,這樣當(dāng)使用這個注解時候,就會自動去注冊 DubboComponentScanRegistrar
到容器,然后去處理些dubbo組件掃描的邏輯。然后就可以你在DubboComponentScanRegistrar中來獲取到DubboComponentScan注解的信息。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] value() default {};
}
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 獲取DubboComponentScan注解中配置要掃描的目錄
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 掃描上面指定的目錄,生成BeanDefinition通過registry去注冊。
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerReferenceAnnotationBeanPostProcessor(registry);
}
}
5.11 @Profile
Bean 定義配置文件在核心容器中提供了一種機制,允許在不同環(huán)境中注冊不同的 bean?!碍h(huán)境”這個詞對不同的用戶可能意味著不同的東西,這個功能可以幫助許多用例,包括:
- 在開發(fā)中處理內(nèi)存中的數(shù)據(jù)源,而不是在 QA 或生產(chǎn)中從 JNDI 中查找相同的數(shù)據(jù)源。
- 僅在將應(yīng)用程序部署到性能環(huán)境時才注冊監(jiān)控基礎(chǔ)架構(gòu)。
- 為客戶 A 和客戶 B 部署注冊定制的 bean 實現(xiàn)。
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
5.11.1 自定義環(huán)境注解
可以將 @Profile
其用作元注釋以創(chuàng)建自定義組合注釋。以下示例定義了一個自定義 @Production
注釋,您可以將其用作 的替代品 @Profile("production")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
5.11.2 激活環(huán)境
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,您還可以通過 spring.profiles.active
屬性以聲明方式激活配置文件
以聲明方式,spring.profiles.active
可以接受以逗號分隔的配置文件名稱列表,如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
六、Aware
這個比較簡單,當(dāng)你看到實現(xiàn)了Aware結(jié)尾的接口,Spring都會給你自動給你注入對應(yīng)的Spring種內(nèi)置的組件。這個怎么理解呢,看下面。
6.1 BeanFactoryAware
獲取 BeanFactory
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
6.2 BeanNameAware
獲取 Bean
的名稱
public interface BeanNameAware extends Aware {
void setBeanName(String name);
}
6.3 MessageSourceAware
獲取國際化對象 MessageSource
public interface MessageSourceAware extends Aware {
void setMessageSource(MessageSource messageSource);
}
6.4 ApplicationContextAware
獲取容器上下文 ApplicationContext
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
6.5 ApplicationEventPublisherAware
獲取事件發(fā)送者 ApplicationEventPublisher
public interface ApplicationEventPublisherAware extends Aware {
void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}
6.6 ResourceLoaderAware
獲取資源加載器 ResourceLoader
public interface ResourceLoaderAware extends Aware {
void setResourceLoader(ResourceLoader resourceLoader);
}
6.7 ServletConfigAware
獲取 ServletConfig
public interface ServletConfigAware extends Aware {
void setServletConfig(ServletConfig servletConfig);
}
6.8 ServletContextAware
public interface ServletContextAware extends Aware {
void setServletContext(ServletContext servletContext);
}
七、生成候選組件的索引
雖然類路徑掃描非???,但可以通過在編譯時創(chuàng)建靜態(tài)候選列表來提高大型應(yīng)用程序的啟動性能。在這種模式下,作為組件掃描目標(biāo)的所有模塊都必須使用這種機制。
當(dāng) ApplicationContext檢測到這樣的索引時,它會自動使用它而不是掃描類路徑,這樣能提高速度。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.22</version>
<optional>true</optional>
</dependency>
</dependencies>
當(dāng)引用之后,再編譯期間生成配置文件。
這個的原理,其實就跟lombok類似,使用到的都是 APT
技術(shù),如果感興趣的話,可以看我這篇文章。
【lombok原理】無聊的周末一個人手寫一個lombok
都看到這里了,最后如果這篇文章,對你有所幫助,請點個關(guān)注,交個朋友。文章來源:http://www.zghlxwxcb.cn/news/detail-414479.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-414479.html
到了這里,關(guān)于第01篇:系統(tǒng)化學(xué)習(xí), 搞定Spring容器管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!