«1. Введение

Как разработчики программного обеспечения, мы всегда ищем передовой опыт использования данной технологии или библиотеки. Естественно, иногда возникают споры.

Один из таких споров касается размещения аннотации Spring @Service. Поскольку Spring предоставляет альтернативные способы определения bean-компонентов, стоит обратить внимание на расположение аннотаций стереотипов.

В этом уроке мы рассмотрим аннотацию @Service и выясним, как лучше всего ее поместить в интерфейсы, абстрактные классы или конкретные классы.

2. @Service на интерфейсах

Некоторые разработчики могут решить поместить @Service на интерфейсы, потому что они хотят:

    Явно показать, что интерфейс должен использоваться только для целей уровня службы. Определить новые реализации службы и иметь они автоматически определяются как компоненты Spring во время запуска

Давайте посмотрим, как это будет выглядеть, если мы аннотируем интерфейс:

@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. . Мы оставили @Service только на интерфейсе AuthenticationService.

Итак, давайте запустим наш контекст Spring с помощью базовой настройки Spring Boot:

@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 для абстрактных классов не является обычным явлением.

Давайте проверим его, чтобы увидеть, достигает ли он нашей цели, заставляя 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, зависящего только от абстрактного типа. Это хорошая практика, о чем также свидетельствует принцип замещения Лискова.

Итак, мы снова запускаем наше приложение 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.

Поэтому Spring автоматически обнаружит эти классы в пути к классам и автоматически определит их как управляемые компоненты.

Итак, давайте на этот раз поместим @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 для классов реализации позволяет автоматически обнаруживать их при сканировании компонентов.

6. Заключение

В этой статье мы рассмотрели различные места использования аннотации Spring @Service и узнали, где хранить @Service для определения bean-компонентов Spring сервисного уровня, чтобы они автоматически обнаруживались при сканировании компонентов.

В частности, мы увидели, что размещение аннотации @Service на интерфейсах или абстрактных классах не имеет никакого эффекта и что только конкретные классы будут обнаружены при сканировании компонентов, когда они аннотированы @Service.