類別路徑掃描與受管理組件
本章的大多數範例使用 XML 來指定組態中繼資料,這些中繼資料會在 Spring 容器中產生每個 BeanDefinition
。前一節(基於註解的容器組態)示範如何透過原始碼層級註解提供許多組態中繼資料。然而,即使在這些範例中,「基本」bean 定義仍然在 XML 檔案中明確定義,而註解僅驅動依賴注入。本節描述一個選項,透過掃描類別路徑來隱含地偵測候選組件。候選組件是符合篩選條件的類別,並且在容器中註冊了對應的 bean 定義。這消除了使用 XML 執行 bean 註冊的需要。相反地,您可以使用註解 (例如,@Component
)、AspectJ 類型運算式或您自己的自訂篩選條件來選擇哪些類別要註冊 bean 定義到容器中。
您可以使用 Java 而非 XML 檔案來定義 bean。請查看 |
@Component
和其他刻板印象註解
@Repository
註解是任何類別的標記,這些類別履行儲存庫的職責或刻板印象(也稱為資料存取物件或 DAO)。此標記的用途之一是自動翻譯例外,如例外翻譯中所述。
Spring 提供更多刻板印象註解:@Component
、@Service
和 @Controller
。@Component
是任何 Spring 管理組件的通用刻板印象。@Repository
、@Service
和 @Controller
是 @Component
的特殊化,用於更特定的用例(分別在持久層、服務層和表示層)。因此,您可以使用 @Component
註解您的組件類別,但是,透過使用 @Repository
、@Service
或 @Controller
註解它們,您的類別更適合由工具處理或與切面關聯。例如,這些刻板印象註解是切入點的理想目標。@Repository
、@Service
和 @Controller
也可能在 Spring Framework 未來的版本中攜帶額外的語意。因此,如果您在為您的服務層選擇使用 @Component
或 @Service
之間猶豫,@Service
顯然是更好的選擇。同樣地,如先前所述,@Repository
已被支援作為持久層中自動例外翻譯的標記。
使用 Meta-annotation 和組合註解
Spring 提供的許多註解可以用作您自己程式碼中的 meta-annotation。Meta-annotation 是可以應用於另一個註解的註解。例如,前面提到的 @Service
註解使用 @Component
進行 meta-annotation,如下例所示
-
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 相同的方式處理。 |
您還可以組合 meta-annotation 來建立「組合註解」。例如,Spring MVC 中的 @RestController
註解由 @Controller
和 @ResponseBody
組成。
此外,組合註解可以選擇性地重新宣告 meta-annotation 中的屬性以允許自訂。當您只想公開 meta-annotation 屬性的子集時,這可能特別有用。例如,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 Annotation Programming Model wiki 頁面。
自動偵測類別並註冊 Bean 定義
Spring 可以自動偵測刻板印象類別,並在 ApplicationContext
中註冊對應的 BeanDefinition
實例。例如,以下兩個類別符合自動偵測的條件
-
Java
-
Kotlin
@Service
public class SimpleMovieLister {
private 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 及更高版本上的獨立應用程式(這需要在您的 manifest 中進行 'Trusted-Library' 設定 — 請參閱 stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在模組路徑(Java 模組系統)上,Spring 的類別路徑掃描通常按預期工作。但是,請確保您的組件類別在您的 |
此外,當您使用 component-scan 元素時,AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
都會隱含地包含在內。這表示這兩個組件會自動偵測並連接在一起 — 全部都無需在 XML 中提供任何 bean 組態中繼資料。
您可以透過包含 annotation-config 屬性並將值設為 false 來停用 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 的註冊。 |
使用篩選器自訂掃描
預設情況下,使用 @Component
、@Repository
、@Service
、@Controller
、@Configuration
或本身使用 @Component
註解的自訂註解註解的類別是唯一偵測到的候選組件。但是,您可以透過應用自訂篩選器來修改和擴充此行為。將它們新增為 @ComponentScan
註解的 includeFilters
或 excludeFilters
屬性(或 XML 組態中 <context:component-scan>
元素的 <context:include-filter />
或 <context:exclude-filter />
子元素)。每個篩選器元素都需要 type
和 expression
屬性。下表描述了篩選選項
篩選器類型 | 範例運算式 | 描述 |
---|---|---|
annotation (預設) |
|
要在目標組件的類型層級存在或meta-present 的註解。 |
assignable |
|
目標組件可指派給的類別(或介面)(擴充或實作)。 |
aspectj |
|
要由目標組件匹配的 AspectJ 類型運算式。 |
regex |
|
要由目標組件的類別名稱匹配的 regex 運算式。 |
custom |
|
|
以下範例顯示了組態忽略所有 @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 註解或 meta-annotation 的類別。 |
在組件中定義 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> }
表示法定義屬性的值。對於 @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 8 預設方法上,也會發現 最後,單一類別可以為同一個 bean 保有多個 |
命名自動偵測到的組件
當組件作為掃描過程的一部分被自動偵測到時,其 bean 名稱由該掃描器已知的 BeanNameGenerator
策略產生。
預設情況下,使用 AnnotationBeanNameGenerator
。對於 Spring stereotype annotations,如果您透過註解的 value
屬性提供名稱,則該名稱將用作相應 bean 定義中的名稱。當使用以下 JSR-250 和 JSR-330 註解而不是 Spring stereotype annotations 時,此慣例也適用:@jakarta.annotation.ManagedBean
、@javax.annotation.ManagedBean
、@jakarta.inject.Named
和 @javax.inject.Named
。
從 Spring Framework 6.1 開始,不再需要使用名為 value
的註解屬性來指定 bean 名稱。自訂 stereotype annotations 可以宣告具有不同名稱(例如 name
)的屬性,並使用 @AliasFor(annotation = Component.class, attribute = "value")
註解該屬性。有關具體範例,請參閱 ControllerAdvice#name()
的原始碼宣告。
從 Spring Framework 6.1 開始,對基於慣例的 stereotype 名稱的支援已被棄用,並將在框架的未來版本中移除。因此,自訂 stereotype annotations 必須使用 |
如果無法從此類註解或任何其他偵測到的組件(例如透過自訂過濾器發現的組件)衍生出明確的 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>
作為一般規則,當其他組件可能正在明確參考它時,請考慮使用註解指定名稱。另一方面,當容器負責組裝時,自動產生的名稱就足夠了。
為自動偵測到的組件提供 Scope
與 Spring 管理的組件通常一樣,自動偵測到的組件的預設和最常見的 scope 是 singleton
。但是,有時您需要不同的 scope,可以透過 @Scope
註解來指定。您可以在註解中提供 scope 的名稱,如下面的範例所示
-
Java
-
Kotlin
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 註解僅在具體的 bean 類別(對於註解組件)或 factory 方法(對於 @Bean 方法)上進行內省。與 XML bean 定義相反,沒有 bean 定義繼承的概念,並且類別層次的繼承階層與元數據目的無關。 |
有關 Spring context 中特定於 web 的 scope 的詳細資訊,例如「request」或「session」,請參閱 Request、Session、Application 和 WebSocket Scopes。與這些 scope 的預建註解一樣,您也可以透過使用 Spring 的元註解方法來組合您自己的 scope 註解:例如,使用 @Scope("prototype")
進行元註解的自訂註解,可能還宣告自訂的 scoped-proxy 模式。
要提供自訂的 scope 解析策略,而不是依賴基於註解的方法,您可以實作 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>
當使用某些非 singleton scope 時,可能需要為 scoped 物件產生代理。原因在 Scoped Beans as Dependencies 中進行了描述。為此,component-scan 元素上提供了一個 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
註解在 使用 Qualifiers 微調基於註解的自動裝配 中進行了討論。該節中的範例示範了 @Qualifier
註解和自訂 qualifier 註解的使用,以便在解析自動裝配候選者時提供細緻的控制。由於這些範例基於 XML bean 定義,因此 qualifier 元數據是透過使用 XML 中 bean
元素的 qualifier
或 meta
子元素在候選 bean 定義上提供的。當依靠類別路徑掃描來自動偵測組件時,您可以使用候選類別上的類型層級註解來提供 qualifier 元數據。以下三個範例示範了此技術
-
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 提供其 qualifier 元數據的變體,因為該元數據是按實例而不是按類別提供的。 |