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 .