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

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

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

Задача: Сумма двух

Дано массив целых чисел и целая сумма. Нужно найти индексы двух чисел, сумма которых равна заданной ...

ANDROMEDA

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 .