Перейти к основному содержимому

Где следует хранить аннотацию Spring @Service?

· 5 мин. чтения

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.foreach.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.foreach.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 .

Как всегда, все образцы кода и многое другое доступны на GitHub .