類路徑掃描和託管元件
本章中的大多數示例都使用 XML 來指定配置元資料,這些元資料在 Spring 容器中生成每個 BeanDefinition。上一節(基於註解的容器配置)演示瞭如何透過原始碼級註解提供大部分配置元資料。然而,即使在這些示例中,“基礎” bean 定義仍明確定義在 XML 檔案中,而註解僅用於驅動依賴注入。
本節介紹了一種透過掃描類路徑隱式檢測候選元件的選項。候選元件是與篩選條件匹配並向容器註冊了相應 bean 定義的類。這消除了使用 XML 進行 bean 註冊的需要。相反,您可以使用註解(例如 @Component)、AspectJ 型別表示式或您自己的自定義篩選條件來選擇哪些類向容器註冊 bean 定義。
|
您可以使用 Java 而不是 XML 檔案來定義 bean。請檢視 |
@Component 和進一步的 Stereotype 註解
@Repository 註解是任何滿足倉庫角色或
Spring 提供了進一步的構造型註解:@Component、@Service 和 @Controller。@Component 是任何 Spring 管理元件的通用構造型。@Repository、@Service 和 @Controller 是 @Component 的專用版本,用於更具體的用例(分別在持久層、服務層和表現層)。因此,您可以使用 @Component 註解您的元件類,但透過使用 @Repository、@Service 或 @Controller 註解它們,您的類更適合工具處理或與方面關聯。例如,這些構造型註解是切入點的理想目標。@Repository、@Service 和 @Controller 在 Spring Framework 的未來版本中也可能帶有額外的語義。因此,如果您在服務層選擇使用 @Component 或 @Service,那麼 @Service 顯然是更好的選擇。同樣,如前所述,@Repository 已被支援作為持久層中自動異常翻譯的標記。
使用元註解和組合註解
Spring 提供的許多註解都可以在您自己的程式碼中用作元註解。元註解是可以應用於另一個註解的註解。例如,前面提到的 @Service 註解是用 @Component 元註解的,如下例所示
-
Java
-
Kotlin
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
| 1 | @Component 元註解使得 @Service 被視為與 @Component 相同。 |
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
| 1 | @Component 元註解使得 @Service 被視為與 @Component 相同。 |
您還可以組合元註解以建立“組合註解”。例如,Spring MVC 中的 @RestController 註解由 @Controller 和 @ResponseBody 組成。
此外,組合註解可以選擇性地重新宣告元註解中的屬性以允許自定義。當您只想公開元註解屬性的子集時,這尤其有用。例如,Spring 的 @SessionScope 註解將作用域名稱硬編碼為 session,但仍然允許自定義 proxyMode。以下列表顯示了 @SessionScope 註解的定義
-
Java
-
Kotlin
@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;
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
然後您可以如下所示使用 @SessionScope 而無需宣告 proxyMode
-
Java
-
Kotlin
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
您也可以覆蓋 proxyMode 的值,如下例所示
-
Java
-
Kotlin
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有關更多詳細資訊,請參閱 Spring 註解程式設計模型 wiki 頁面。
自動檢測類並註冊 Bean 定義
Spring 可以自動檢測構造型類並向 ApplicationContext 註冊相應的 BeanDefinition 例項。例如,以下兩個類符合此類自動檢測的條件
-
Java
-
Kotlin
@Service
public class SimpleMovieLister {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
-
Java
-
Kotlin
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自動檢測這些類並註冊相應的 bean,您需要將 @ComponentScan 新增到您的 @Configuration 類中,其中 basePackages 屬性配置為這兩個類的共同父包。或者,您可以指定一個逗號、分號或空格分隔的列表,其中包含每個類的父包。
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
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 及更高版本上的獨立應用程式(這需要在清單中設定“Trusted-Library”——請參閱 stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在模組路徑(Java 模組系統)上,Spring 的類路徑掃描通常按預期工作。但是,請確保您的元件類已在 |
此外,當您使用 <context:component-scan> 元素時,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都被隱式包含。這意味著這兩個元件都被自動檢測並連線在一起——所有這些都不需要 XML 中提供任何 bean 配置元資料。
您可以透過包含 annotation-config 屬性並將其值設定為 false 來停用 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 的註冊。 |
屬性佔位符和 Ant 風格模式
@ComponentScan 中的 basePackages 和 value 屬性支援 ${…} 屬性佔位符,這些佔位符將針對 Environment 解析,以及 Ant 風格的包模式,例如 "org.example.**"。
此外,可以指定多個包或模式,無論是單獨指定還是在單個字串中指定——例如,{"org.example.config", "org.example.service.**"} 或 "org.example.config, org.example.service.**"。
以下示例為 @ComponentScan 中的隱式 value 屬性指定 app.scan.packages 屬性佔位符。
-
Java
-
Kotlin
@Configuration
@ComponentScan("${app.scan.packages}") (1)
public class AppConfig {
// ...
}
| 1 | app.scan.packages 屬性佔位符將針對 Environment 進行解析 |
@Configuration
@ComponentScan(["\${app.scan.packages}"]) (1)
class AppConfig {
// ...
}
| 1 | app.scan.packages 屬性佔位符將針對 Environment 進行解析 |
以下列表表示一個定義 app.scan.packages 屬性的屬性檔案。在前面的示例中,假設此屬性檔案已註冊到 Environment——例如,透過 @PropertySource 或類似機制。
app.scan.packages=org.example.config, org.example.service.**
使用過濾器自定義掃描
預設情況下,只有用 @Component、@Repository、@Service、@Controller、@Configuration 或本身用 @Component 註解的自定義註解標記的類才被檢測為候選元件。但是,您可以透過應用自定義過濾器來修改和擴充套件此行為。將它們新增為 @ComponentScan 註解的 includeFilters 或 excludeFilters 屬性(或作為 XML 配置中 <context:component-scan> 元素的 <context:include-filter /> 或 <context:exclude-filter /> 子元素)。每個過濾器元素都需要 type 和 expression 屬性。下表描述了篩選選項
| 過濾器型別 | 示例表示式 | 描述 |
|---|---|---|
annotation (預設) |
|
在目標元件的型別級別 |
assignable |
|
目標元件可賦值給的類(或介面)(繼承或實現)。 |
aspectj |
|
一個 AspectJ 型別表示式,將由目標元件匹配。 |
regex |
|
一個正則表示式,將由目標元件的類名匹配。 |
custom |
|
|
以下示例顯示了 @ComponentScan 配置,該配置排除了所有 @Repository 註解幷包含了“Stub”倉庫
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"],
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
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-scan/> 元素的屬性來停用預設過濾器。這有效地停用了對使用 @Component、@Repository、@Service、@Controller、@RestController 或 @Configuration 註解或元註解的類的自動檢測。 |
命名自動檢測元件
當元件作為掃描過程的一部分被自動檢測時,它的 bean 名稱由該掃描器已知的 BeanNameGenerator 策略生成。
預設情況下,使用 AnnotationBeanNameGenerator。對於 Spring 構造型註解,如果您透過註解的 value 屬性提供名稱,該名稱將用作相應 bean 定義中的名稱。此約定也適用於使用 @jakarta.inject.Named 註解而不是 Spring 構造型註解時。
自 Spring Framework 6.1 起,用於指定 bean 名稱的註解屬性名稱不再必須是 value。自定義構造型註解可以宣告具有不同名稱(例如 name)的屬性,並使用 @AliasFor(annotation = Component.class, attribute = "value") 註解該屬性。有關具體示例,請參閱 ControllerAdvice#name() 的原始碼宣告。
|
自 Spring Framework 6.1 起,對基於約定的構造型名稱的支援已棄用,並將在框架的未來版本中移除。因此,自定義構造型註解必須使用 |
如果無法從此類註解或其他任何檢測到的元件(例如透過自定義過濾器發現的元件)派生出顯式 bean 名稱,則預設的 bean 名稱生成器將返回未大寫的非限定類名。例如,如果檢測到以下元件類,則名稱將為 myMovieLister 和 movieFinderImpl。
-
Java
-
Kotlin
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
-
Java
-
Kotlin
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果您不想依賴預設的 bean 命名策略,您可以提供一個自定義的 bean 命名策略。首先,實現 BeanNameGenerator 介面,並確保包含一個預設的無引數建構函式。然後,在配置掃描器時提供完全限定的類名,如下面的示例註解和 bean 定義所示。
如果您遇到由於多個自動檢測到的元件具有相同的非限定類名(即,具有相同名稱但位於不同包中的類)而導致的命名衝突,您可能需要配置一個 BeanNameGenerator,它將預設使用完全限定類名作為生成的 bean 名稱。位於 org.springframework.context.annotation 包中的 FullyQualifiedAnnotationBeanNameGenerator 可用於此類目的。 |
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作為一般規則,當其他元件可能顯式引用它時,考慮使用註解指定名稱。另一方面,當容器負責連線時,自動生成的名稱就足夠了。
為自動檢測元件提供作用域
與一般的 Spring 管理元件一樣,自動檢測元件的預設和最常見作用域是 singleton。但是,有時您需要透過 @Scope 註解指定不同的作用域。您可以在註解中提供作用域名稱,如下例所示
-
Java
-
Kotlin
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 註解僅在具體的 bean 類(對於帶註解的元件)或工廠方法(對於 @Bean 方法)上進行內省。與 XML bean 定義相反,沒有 bean 定義繼承的概念,並且類級別的繼承層次結構與元資料目的無關。 |
有關 Spring 上下文中特定於 Web 的作用域(例如“request”或“session”)的詳細資訊,請參閱請求、會話、應用程式和 WebSocket 作用域。與那些作用域的預構建註解一樣,您也可以透過使用 Spring 的元註解方法來組成您自己的作用域註解:例如,一個自定義註解用 @Scope("prototype") 元註解,可能還宣告一個自定義作用域代理模式。
要提供自定義作用域解析策略,而不是依賴於基於註解的方法,您可以實現 ScopeMetadataResolver 介面。請務必包含一個預設的無引數建構函式。然後,您可以在配置掃描器時提供完全限定的類名,如下面的註解和 bean 定義示例所示 |
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
在使用某些非單例作用域時,可能需要為作用域物件生成代理。原因在作為依賴項的作用域 Bean 中進行了描述。為此,元件掃描元素上提供了一個 scoped-proxy 屬性。可能的值有三個:no、interfaces 和 targetClass。例如,以下配置會生成標準的 JDK 動態代理
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
使用註解提供限定符元資料
@Qualifier 註解在使用限定符微調基於註解的自動裝配中進行了討論。該部分中的示例演示了使用 @Qualifier 註解和自定義限定符註解來在解析自動裝配候選時提供細粒度控制。由於這些示例基於 XML bean 定義,因此限定符元資料透過 XML 中 bean 元素的 qualifier 或 meta 子元素在候選 bean 定義上提供。當依賴類路徑掃描自動檢測元件時,您可以透過在候選類上使用型別級註解來提供限定符元資料。以下三個示例演示了此技術
-
Java
-
Kotlin
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
-
Java
-
Kotlin
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
-
Java
-
Kotlin
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
| 與大多數基於註解的替代方案一樣,請記住註解元資料繫結到類定義本身,而 XML 的使用允許同一型別的多個 bean 提供其限定符元資料的變體,因為該元資料是按例項而不是按類提供的。 |
在元件中定義 Bean 元資料
Spring 元件還可以向容器貢獻 bean 定義元資料。您可以使用與在 @Configuration 註解類中定義 bean 元資料相同的 @Bean 註解來實現。以下示例展示瞭如何做到這一點
-
Java
-
Kotlin
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Component
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
上述類是一個 Spring 元件,其 doWork() 方法中包含應用程式特定的程式碼。然而,它也貢獻了一個 bean 定義,該定義具有一個引用 publicInstance() 方法的工廠方法。@Bean 註解標識了工廠方法和其他 bean 定義屬性,例如透過 @Qualifier 註解提供的限定符值。可以指定的其他方法級註解有 @Scope、@Lazy 和自定義限定符註解。
|
除了元件初始化作用外,您還可以將 |
如前所述,支援自動裝配欄位和方法,並額外支援 @Bean 方法的自動裝配。以下示例展示瞭如何做到這一點
-
Java
-
Kotlin
@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);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
該示例將 String 方法引數 country 自動裝配到名為 privateInstance 的另一個 bean 上的 age 屬性的值。Spring Expression Language 元素透過 #{ <expression> } 符號定義屬性的值。對於 @Value 註解,表示式解析器預配置為在解析表示式文字時查詢 bean 名稱。
自 Spring Framework 4.3 起,您還可以宣告型別為 InjectionPoint(或其更具體的子類:DependencyDescriptor)的工廠方法引數,以訪問觸發當前 bean 建立的請求注入點。請注意,這僅適用於 bean 例項的實際建立,而不適用於現有例項的注入。因此,此功能對於原型作用域的 bean 最有意義。對於其他作用域,工廠方法只會看到觸發在給定作用域中建立新 bean 例項的注入點(例如,觸發建立延遲單例 bean 的依賴項)。在此類場景中,您可以謹慎使用提供的注入點元資料。以下示例展示瞭如何使用 InjectionPoint
-
Java
-
Kotlin
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
常規 Spring 元件中的 @Bean 方法與 Spring @Configuration 類中的對應方法處理方式不同。區別在於 @Component 類未透過 CGLIB 增強來攔截方法和欄位的呼叫。CGLIB 代理是 @Configuration 類中 @Bean 方法內呼叫方法或欄位時建立協作物件的 bean 元資料引用的方式。此類方法不是以正常的 Java 語義呼叫的,而是透過容器來提供 Spring bean 通常的生命週期管理和代理,即使透過程式設計呼叫 @Bean 方法來引用其他 bean。相反,在普通 @Component 類中 @Bean 方法內呼叫方法或欄位具有標準 Java 語義,沒有特殊的 CGLIB 處理或其他限制。
|
您可以將 對靜態
在給定元件或配置類的基類上以及在元件或配置類實現的介面中宣告的 Java 預設方法上,也發現了 最後,單個類可以包含同一 bean 的多個 |