1.10 類路徑掃描和組件管理
本章中的大多數示例都使用XML來指定在Spring容器中生成每個BeanDefinition
的配置元數據。前面的部(基於註解的容器配置)分展示怎樣去提供一些配置元素數據通過源碼級別的註解。然而,即使在這些示例中,基本bean定義也是在XML文件中顯式定義的,而註解只驅動依賴項注入。本節介紹通過掃描類路徑來隱式檢測候選組件的選項。候選組件是符合過濾條件的類,並在容器中註冊了相應的Bean定義。這消除了使用XML進行bean註冊的需要。相反,你可以使用註釋(例如@Component
)、AspectJ類型表達式或你自己的自定義篩選條件來選擇哪些類具有在容器中註冊的bean定義。
Spring3.0開始,通過Spring
JavaConfig
項目提供的許多Spring核心特性部分。這允許你使用Java定義bean而不是使用傳統的XML文件。查看@Configuration
、@Bean
、@Import
和@DependsOn
註解例子怎樣去使用這些新特性。
1.10.1 @Component和其他構造型註解
@Repository
註解是任何滿足存儲庫角色或構造型(也稱為數據訪問對象或DAO)的類的標記。該標記的用途包括自動轉譯異常,如“異常轉換”中所述。
Spring提供提供更進一步的構造型註解:@Component
、@Service
和@Controller
。@Component
是一個通用的構造型註解對於任何Spring管理的組件。@Repository
、@Service
和@Controller
是特殊化的@Component
為更多特定的使用場景(在持久化、服務、表現層中)。因此,你可以使用@Component
註解組件類,但是通過使用@ Repository
、@Service
或@Controller
註解組件類,你的類更適合通過工具進行處理或與aspects相關聯。例如,這些構造型註釋成為切入點的理想目標。@Repository
、@Service
和@Controller
在Spring框架未來的發佈中可能增加額外的語義。因此,如果你在使用@Component
或@Service
之間選擇為你的服務層,@Service
是更簡潔的選擇。類似地,如前所述,@Repository
已經被支持作為持久化層中自動異常轉換的標記。
1.10.2 使用元註解和組合組件
通過Spring提供的許多註解在你自己代碼中能夠被作為元註解使用。元註解也是註解它能夠被應用到其他註解上。例如,前面提到的@Service
註解使用@Component
進行元註解,類似下面例子:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //1
public @interface Service {
// ...
}
-
@Component
導致@Service
的處理方式與@Component
相同。
你可以聯合元註解去創建組合註解
。例如,Spring MVC的RestController
註解由@Controller
和@ResponseBody
組成。
此外,組合註解可以選擇從元註解中重新聲明屬性,以允許自定義。當你想去僅僅暴露一個元數據的屬性子集的時候特別地有用。例如,Spring的@SessionScope
註解硬編碼作用域名稱session
但是仍然允許proxyMode
的自定義。下面的列表顯示SessionScope
註解的定義:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
你可以使用@SessionScope
無需聲明proxyMode
類似下面:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
你可以覆蓋proxyMode
值,類似下面例子:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
更多詳情,查看Spring註解編程模型wiki頁。
1.10.3 自動地檢查類和註冊BeanDefinition
Spring可以自動檢測構造型類並向ApplicationContext註冊相應的BeanDefinition實例。例如,下面兩個類進行自動檢查:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
去自動檢查這些和註冊對於bean,你需要增加@ComponentScan
到你的@Configuration
類,basePackages
屬性是兩個類的共同的父包。(另外,你可以指定一個逗號或分號或空格分隔的列表,其中包括每個類的包路徑):
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
為了簡潔,前面的例子可以使用註解的value屬性(也就是,
@ComponentScan("org.example")
)。
以下替代方法使用XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
< context:component-scan>
使用隱式地激活< context:annotation-config>
功能。當使用< context:component-scan>
時,通常情況無需包含< context:annotation-config>
元素。類路徑包掃描需要在類路徑中對應的目錄實體存在。使用Ant構建JAR時,請確保未激活JAR任務的文件專用開關。此外,在某些環境中,基於安全策略可能不會公開類路徑目錄-例如,在JDK 1.7.0_45及更高版本上的獨立應用程序(這需要在清單中設置“信任的庫” —請參見https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources) 。
在JDK9的模塊路徑,Spring的類路徑掃描通常按預期工作。然而,確保你的組件類在你的
module-info
描述被暴露。如果你期望Spring調用你的類非公共的成員,確保他們被打開
(也就是,他們在module-info
描述符中使用了一個opens
聲明而不是exports
聲明)。
此外,當你使用component-scan
元素時,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
兩者隱式地包含。也就是說兩個組件被自動檢查和連接在一起-所有這些都沒有XML提供的任何bean配置元數據。
你可以通過設置
annotation-config
屬性值為false禁止AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
註冊。參考代碼:
com.liyong.ioccontainer.starter.AutoCheckAndRegisterBeanDefinitionIocContainer
1.10.4 使用Filer去自定義掃描
默認情況下,僅使用@Component
、@Repository
、@Service
、@Controller
、@Configuration
進行註解的類或使用@Component
進行註解的自定義註解是唯一被檢測到的候選組件。然而,你可以修改和拓展這個行為通過使用自定義過濾器。增加@ComponentScan
註解(在XML配置中子元素 <context:include-filter />
或<context:exclude-filter />
)的includeFilters
或excludeFilters
屬性。每個過濾器元素需要type
和expression
屬性。下面表格描述過濾器可選項:
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation |
在目標組件的類型級別上呈現或元呈現的註釋(備註:元組件描述形式)。 |
assignable | org.example.SomeClass |
目標組件可分配給(擴展或實現)的類(或接口)。 |
aspectj | org.example..*Service+ |
目標組件要匹配的AspectJ類型表達式。 |
regex | org.example.Default.* |
要與目標組件的類名匹配的正則表達式。 |
custom | org.example.MyTypeFilter |
org.springframework.core.type.TypeFilter 接口的自定義實現. |
下面的例子展示配置文件忽略所有@Repository
註解且使用stub
倉庫替換。
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
下面清單顯示相同的作用的XML配置:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
你還可以通過在註解上設置
useDefaultFilters = false
或通過將use-default-filters =“ false”
作為元素的屬性來禁用默認過濾器。這有效地禁用了自動檢測使用@Component
、@Repository
、@Service
、@Controller
、@RestController
或@Configuration
註解或元註解的類的功能。代碼示例:
com.liyong.ioccontainer.starter.CustomizeScanByFilterIocContainer
1.10.5 在組件中定義bean元數據
Spring組件有助於bean定義元數據到容器。在類上,你可以使用@Configuration
定義同時在方法上使用@Bean
即可定義元數據。下面例子展示怎樣使用:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
前面的類是一個Spring組件,它有一個在它的doWork()
方法特定於應用的代碼。但是,它也提供了一個具有工廠方法的bean定義,該工廠方法引用了方法publicInstance()
。@Bean
註解標識工廠方法和其他bean定義的屬性,例如,通過@Qualifier
註解指定限定符。其他方法級別註解能夠被指定為@Scope
、@Lazy
和自定義限定符註解。
除了它的組件初始化角色外,你可以在注入點放置
@Lazy
註解標記@Autowired
或@Inject
。在這種情況下,它導致注入了惰性解析代理。
如前所述,自動裝配字段和方法是被支持的同時附加的支持@Bean
方法的自動裝配。下面的例子展示怎樣去做:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
這個示例自動將String方法參數country連接到另一個名為privateInstance的bean上的age屬性的值。Spring表達式語言元素定義屬性值通過符號\#{ <expression> }
。對應@Value
註解,表達式解析器已經預先配置當解析表達式文本的時候查找bean的名稱。
自Spring4.3以後,你可以聲明一個類型InjectionPoint
(或其更具體的子類:DependencyDescriptor
)工廠方法參數來訪問觸發當前bean創建的請求注入點。請注意,這僅適用於實際創建bean實例,不會注入已經存在的實例。這個特性大多數用在單例bean作用域。對於其他作用域,factory方法只能看到在給定作用域中觸發創建新bean實例的注入點(例如,依賴觸發懶加載單例bean的創建)。在這種情況下,可以將提供的注入點元數據與語義一起使用。下面的例子展示怎樣使用InjectionPoint
:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常規Spring組件中的@Bean
方法的處理方式與Spring 的@Configuration
類中的@Bean
方法不同。不同點是@Component
這些類不會被CGLIB增強去攔截方法和字段的調用。CGLIB代理是調用@Configuration類中的@Bean
方法中的方法或字段來創建對協作對象的bean元數據引用的方法。這些方法不會通過常規的Java語法調用,而是通過容器,去提供常規的生命週期管理和Spring bean的代理,即使通過對@Bean
方法的編程調用來引用其他bean。相反,在普通@Component
類中的@Bean
方法中調用方法或字段具有標準Java語義,沒有其他特定的CGLIB處理或約束。說明:@Component
中調用@Bean
註解的方法和常規的方法調用一樣,而在@Configuration
中的@Bean
調用則是通過容器去查找Bean和生成bean的元數據。
你可以聲明
@Bean
方法為static的,這個配置類實例在容器中沒有被創建時也允許調用這個方法(備註:脫離了Configuration
實例)。當定義後置處理器bean(例如,BeanFactoryPostProcessor
或BeanPostProcessor
類型)時這是特殊的場景,因為在容器生命週期中更早的獲取bean實例並且應該在那時避免觸發配置類中的其他部分。說明:把@Bean
方法標記為static後脫落了bean實例管理所以在需要提前觸發場景可以使用,這樣避免未被實例化的bean其他@Bean
方法被觸發。靜態
@Bean
方法的調用永遠不會被容器攔截,即使在@Configuration
類中也是如此(在這個章節前面被描述),由於技術限制:CGLIB子類僅僅能夠覆蓋非靜態類。因此,直接調用另一個@Bean
方法具有標準的Java語義,從而導致直接從工廠方法本身直接返回一個獨立的實例。
@Bean
方法的Java語言可見性不會對Spring容器中的最終bean定義產生直接影響。你可以在非@Configuration
類中自由聲明自己的工廠方法,也可以在任何地方聲明靜態方法。常規的在@Configuration
類中的@Bean
方法需要可覆蓋的。也就是說,他們不能被聲明為private
或final
。
@Bean
方法在給定的組件或配置類的基類中也是可以被發現的,以及在Java 8默認方法中由組件或配置類實現的接口中聲明的方法。這為組合複雜的配置安排提供了很大的靈活性,從Spring 4.2開始,通過Java 8默認方法甚至可以進行多重繼承。最後,一個類可以包含同一個bean的多個
@Bean
方法,根據運行時可用的依賴關係,可以安排使用多個工廠方法。這與在其他配置方案中選擇“最貪婪”的構造函數或工廠方法的算法相同。在構造時會選擇具有最大可滿足依賴性的變量,這類似於容器在多個@Autowired
構造函數之間進行選擇的方式。參考代碼:
com.liyong.ioccontainer.starter.XmlBeanDefinationWithinComponetIocContainer
1.10.6 命名自動檢查組件
在掃描過程中自動檢測到組件時,其Bean名稱由該掃描程序已知的BeanNameGenerator
策略生成。默認情況,任何Spring的構造型註解(@Component
、 @Repository
、 @Service
、 @Controller
)包含一個名字value
,從而提供名稱和對應bean的定義。
如果註解包含沒有名字的value
或者其他的檢查組件(例如通過自定義過濾器),默認bean名稱生成器返回沒有大寫字母的非限定類名稱。例如,如果下面的組件類被檢查到,他們的名字將會是myMovieLister
和movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果你不想依賴默認的bean命名策略,你可以提供一個自定義的bean命名策略。首先,實現BeanNameGenerator
接口,並且確保包含無參構造函數。然後,當配置掃描器的時候,提供全限定類名稱,類似下面例子註釋和bean定義。
如果由於多個自動檢測到的組件具有相同的非限定類名而導致命名衝突(具有相同名稱但位於不同包中的類),你可能需要配置一個
BeanNameGenerator
,該名稱默認為生成的Bean名稱的完全限名的類名稱(備註:類路徑全限定名稱)。從Spring Framework 5.2.3開始,位於org.springframework.context.annotation
包中的FullyQualifiedAnnotationBeanNameGenerator
可以用於此目的。
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作為一般規則,當其他組件可能顯式引用該名稱時,請考慮使用註釋指定該名稱。另一方面,只要容器負責連接(連接其他組件),自動生成的名稱就足夠了。
代碼示例:
com.liyong.ioccontainer.starter.BeanNameGeneratorIoCContainer
1.10.7 為自動檢查組件提供作用域
一般情況下Spring管理的組件,對於自動檢查組件默認情況下是singleton
作用域。然而,有時候你需要不同的作用域時可以通過@Scope
註解指定。你可以提供在註解中的作用域名稱,類似下面的例子:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
註解僅在具體的bean類(用於帶註解的組件)或工廠方法(對於@Bean
方法)上進行內省。與XML的bean定義對比,這裡沒有bean定義繼承的概念,並且類級別的繼承層次結構與元數據目的無關。
更多詳情在Spring上下文中在特定的web作用域,例如request
或session
,查看Request、 Session、 Application和 WebSocket 作用域。與這些作用域的預構建註解一樣,你可以通過使用Spring的元註解方法構建你自己的作用域註解:例如,自定義元註解@Scope("prototype")
,儘可能地聲明一個自定義scoped-proxy
模式。
你可以實現
ScopeMetadataResolver
接口,去提供一個自定義策略為作用域解析,而不是依賴基於註解的方法。確保包含一個無參構造函數。當配置掃描器的時候你需要提供全限定類名,類似下面註解和bean定義例子:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
當使用一些非單例bean作用時,可能需要為作用域對象聲明代理。這個原因在 Scoped Beans as Dependencies中描述。為了這個目的,在component-scan
元素上可以使用scoped-proxy
屬性。有三種可以的值時:no
、interfaces
和targetClass
。例如,下面的配置是標準的JDK動態代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8 為註解提供限定元數據
@Qualifie
註解在基於註解細粒度調整限定符中已經討論。本節中的示例演示了在解析autowire
候選對象時使用@Qualifier
註解和自定義qualifier
註解來提供細粒度控制。因為這些例子是基於在XML定義的基礎上的,因此限定符元數據是在XML中通過使用qualifier
或bean子元素meta在候選者bean定義上被提供的。當依靠類路徑掃描為組件自動檢測時,可以在候選者類中為限定符元數據提供類型級別的註解。下面三個例子演示了這個技術:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")//自定義限定符
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline//自定義限定符
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
與大多數基於註解的替代方法一樣,請記住,註解元數據綁定到類定義本身,而XML的使用允許相同類型的多個bean在其限定符元數據中提供變體,因為元數據是針對每個實例而不是針對每個類提供的。
代碼示例:
com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer
1.10.9 生成候選者組件索引
雖然類掃描非常快,在編譯的時候通過創建一個靜態的候選者清單去改善大型項目的啟動性能。在這種模式下,作為組件掃描目標的所有模塊都必須使用此機制。
現有的
@ComponentScan
或<context:component-scan>
指令必須保持不變,以便請求上下文掃描某些包中的候選者。當ApplicationContext檢測索引的時候,它自動地使用索引而不是去掃描類路徑。
去生成這個索引,增加一個附加的依賴到每個模塊,它包含組件是指令掃描的目標。下面的例子顯示怎樣使用Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.6.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
在Gradle 4.5已經之前的版本,依賴需要在compileOnly配置中聲明,類似下面的例子:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.6.RELEASE"
}
在Gradle 4.5已經之前的版本,在annotationProcessor配置中被聲明,類似下面的例子:
dependencies { annotationProcessor "org.springframework:spring-context-indexer:{spring-version}" }
處理過程在jar文件中生成一個META-INF/spring.components文件。
在IDE中使用這種模式時,必須將
spring-context-indexer
註冊為註解處理器,以確保在更新候選組件時索引是最新的。當在類路徑中找到
META-INF/spring.components
時索引被自動地激活。如果某些庫(或用例)的索引部分可用,但無法為整個應用程序構建索引,你可以通過將spring.index.ignore
設置為true來回退到常規的類路徑掃描(就像根本沒有索引一樣),要麼在系統屬性或者類路徑的根目錄下spring.properties
文件中配置。
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公眾號:
技術交流群: