1. 引言
作為軟件開發人員,我們一直在尋找使用特定技術或庫的最佳實踐。當然,有時也會有爭論。
其中一個爭論是關於 Spring 框架中 @Service 註解的放置位置。由於 Spring 提供了替代定義 Bean 的方法,因此值得關注這些類型註解的位置。
在本教程中,我們將研究 @Service 註解,並探討 它最好放在接口、抽象類還是具體類上。
2. 在接口上使用 <em @Service>
一些開發者可能會決定在接口上使用 <em @Service>,因為他們希望:
- 明確表明接口僅用於服務級別目的
- 定義新的服務實現,並在啓動時自動將其識別為 Spring Bean
讓我們看看在註解接口後的效果:
@Service
public interface AuthenticationService {
boolean authenticate(String username, String password);
}如我們所見,AuthenticationService現在更加具有描述性。@Service標記建議開發者僅將其用於業務層服務,而不是用於數據訪問層或其他層。
通常情況下,這沒問題,但存在一些弊端。通過在 Spring 上使用 @Service 標記接口,我們創建了額外的依賴關係並使我們的接口與外部庫耦合。
接下來,為了測試我們新服務豆的自動檢測,讓我們創建一個AuthenticationService的實現:
public class InMemoryAuthenticationService implements AuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}我們應該注意,我們的新實現,InMemoryAuthenticationService,沒有在其上添加 @Service 註解。我們只在接口 AuthenticationService 上使用了 @Service。
讓我們使用基本的 Spring Boot 設置來運行我們的 Spring 容器:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AuthenticationService authService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}當我們運行我們的應用程序時,我們經常會遇到臭名昭著的 NoSuchBeanDefinitionException,Spring上下文無法啓動:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.baeldung.annotations.service.interfaces.AuthenticationService' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
...因此,將 @Service 放在接口上並不能充分實現 Spring 組件的自動檢測。
3. 在抽象類上使用 <em @Service>
使用 <em @Service> 註解在抽象類上並不常見。
讓我們測試一下,看看它是否能實現我們的目標,即讓 Spring 自動檢測我們的實現類。
我們將從頭開始定義一個抽象類,並在該類上添加 <em @Service> 註解:
@Service
public abstract class AbstractAuthenticationService {
public boolean authenticate(String username, String password) {
return false;
}
}接下來,我們擴展 AbstractAuthenticationService 以 在不進行註釋的情況下創建具體的實現:
public class LdapAuthenticationService extends AbstractAuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}據此,我們還更新了我們的 AuthApplication,以 注入新的服務類:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AbstractAuthenticationService authService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}我們應該注意到,我們沒有嘗試直接注入抽象類,這在技術上不可行。相反,我們打算獲取 LdapAuthenticationService 這種抽象類型的具體實例,僅依賴於抽象類型。這是一種良好的實踐,也符合利斯刻維替換原則。
因此,我們再次運行 AuthApplication:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.baeldung.annotations.service.abstracts.AbstractAuthenticationService' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
...如我們所見,Spring 容器並未啓動。它最終拋出相同的 NoSuchBeanDefinitionException 異常。
當然,在抽象類上使用 @Service 註解在 Spring 中沒有任何效果。
4. 在具體類上使用 <em @Service>
與之前看到的不同,在具體類上標註比在抽象類或接口上標註更為常見。
通過這種方式,我們的目標主要是告訴 Spring 這個類將作為 <em @Component 並用特殊的類型別名 <em @Service> 標記。
因此,Spring 將自動檢測 classpath 中的這些類,並自動將它們定義為管理 Bean。
所以,我們現在將 <em @Service> 放在我們的具體服務類上。 我們將有一個實現接口的類,以及另一個擴展我們之前定義的抽象類的類:
@Service
public class InMemoryAuthenticationService implements AuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}
@Service
public class LdapAuthenticationService extends AbstractAuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}我們應該注意到,我們的 AbstractAuthenticationService 並未在這裏實現 AuthenticationService。因此,我們可以獨立測試它們。
最後,我們將這兩個服務類都添加到 AuthApplication 中並進行測試:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AuthenticationService inMemoryAuthService;
@Autowired
private AbstractAuthenticationService ldapAuthService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}最終測試結果表明一切順利,Spring上下文啓動過程中沒有拋出任何異常。兩個服務均自動註冊為 Bean。
5. 結果
最終,我們發現唯一可行的方法是將 <em @Service</em> 放在我們的實現類上,使其能夠自動檢測到。<strong>Spring 的組件掃描除非單獨標註,否則不會拾取即使它們是從另一個<em @Service` 註解的接口或抽象類派生出來的類。
此外,Spring 的文檔 也指出,在實現類上使用 `` 可以使它們能夠通過組件掃描自動檢測到。
6. 結論
在本文中,我們探討了使用 Spring 框架中 <em @Service</em> 註解的不同使用場景,並學習瞭如何將 <em @Service</em> 保持在服務級別 Spring Bean 中,以便它們在組件掃描期間自動檢測。
具體來説,我們發現將 <em @Service</em> 註解應用於接口或抽象類沒有任何效果,只有標註了 <em @Service</em> 的具體類才會通過組件掃描被選中。