Spring @Service註解應放置在哪裡?

    1.簡介

    作為軟件開發人員,我們一直在尋找使用給定技術或庫的最佳實踐。自然,有時會有辯論。

    這樣的爭論之一就是關於Spring的@Service註釋的放置。由於Spring提供了定義bean的替代方法,因此值得注意構造型註釋的位置。

    在本教程中,我們將研究@Service批註,並檢查將其放在接口,抽像類或具體類上是否最有效

    2.接口上的@Service

    一些開發人員可能決定將@Service放在接口上,因為他們想要:

    • 明確表明接口只能用於服務級別的目的
    • 定義新的服務實現,並在啟動過程中將它們自動檢測為Spring Bean

    讓我們看一下我們對接口進行註解的樣子:

    @Service
    
     public interface AuthenticationService {
    
    
    
     boolean authenticate(String username, String password);
    
     }

    我們注意到, AuthenticationService現在變得更具自我描述性。 @Service標記建議開發人員僅將其用於業務層服務,而不用於數據訪問層或任何其他層。

    通常,這很好,但是有一個缺點。通過將Spring的@Service放在接口上,我們創建了一個額外的依賴項,並將我們的接口與外部庫耦合。

    接下來,為了測試新服務bean的自動檢測,讓我們創建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. @Service類上的@Service

    在抽像類上使用@Service註解並不常見。

    讓我們對其進行測試,看看它是否達到了使Spring自動檢測實現類的目的。

    我們將從頭定義一個抽像類開始,並在其上添加@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的實例。正如Liskov替代原則所建議的那樣,這是一個好習慣。

    因此,我們再次運行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. @Service具體類的服務

    與上面看到的相反,註釋實現類而不是抽像類或接口是一種很常見的做法。

    這樣,我們的目標主要是告訴Spring該類將是@Component並用特殊的@Service型對其進行標記,在本例中為@Service

    因此,Spring將自動從類路徑中檢測這些類,並將它們自動定義為託管Bean。

    因此,這次讓我們將@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.結果

    最終,我們看到了唯一的工作方式是將@Service放入實現類中,以使其能夠自動檢測。除非單獨註解這些類,否則Spring的組件掃描不會選擇這些類,即使它們是從另一個@Service註釋的接口或抽像類派生的也是如此。

    另外, Spring的文檔還指出,在實現類上使用@Service可以使組件掃描自動檢測到它們。

    六,結論

    在本文中,我們研究了使用Spring的@Service批註的不同位置,並了解了保留@Service以定義服務級別的Spring Bean,以便在組件掃描期間自動檢測到它們。

    具體來說,我們看到將@Service批註放置在接口或抽像類上沒有任何效果,並且當使用@Service批註時,組件掃描將僅提取具體的類。