В чём разница между @Component, @Service, @Repository, @Controller
Все четыре аннотации (@Component, @Service, @Repository, @Controller) являются стереотипными аннотациями Spring (Stereotype Annotations). Любой класс, помеченный одной из них, а...
🟢 Junior Level
Все четыре аннотации (@Component, @Service, @Repository, @Controller) являются стереотипными аннотациями Spring (Stereotype Annotations). Любой класс, помеченный одной из них, автоматически обнаруживается процессом @ComponentScan и регистрируется как бин в ApplicationContext.
Аннотация @Component является родительской (базовой мета-аннотацией), а @Service, @Repository и @Controller — ее специализированными наследниками для слоистой архитектуры приложения.
Сводная таблица стереотипов
| Аннотация | Архитектурный слой | Назначение | Особое поведение в рантайме |
|---|---|---|---|
@Component |
Любой слой | Базовый компонент общего назначения (утилиты, хелперы, клиенты) | Нет (стандартный бин) |
@Service |
Сервисный слой | Бизнес-логика приложения, оркестрация операций | Нет (семантический маркер) |
@Repository |
Слой данных (DAO) | Доступ к данным и выполнение запросов к БД | Да: автоматическая трансляция исключений БД в иерархию DataAccessException |
@Controller |
Слой представления | Обработка HTTP-запросов и возврат HTML-страниц (View) | Да: связывание с DispatcherServlet и возврат шаблонов View |
@RestController |
REST API | Обработка REST-запросов и возврат JSON/XML (@Controller + @ResponseBody) |
Да: автоматическая сериализация ответов через HttpMessageConverter |
🟡 Middle Level
Иерархия мета-аннотаций
В исходном коде Spring аннотации @Service, @Repository и @Controller сами помечены аннотацией @Component:
// Исходный код Spring Framework:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // <-- Мета-аннотация!
public @interface Service { ... }
Благодаря механизму мета-аннотаций сканер @ComponentScan видит любой @Service как @Component.
@Controller vs @RestController
@Controllerпредназначен для классического Spring MVC. Возвращаемая методом строка интерпретируется механизмомViewResolverкак имя HTML-шаблона (Thymeleaf, FreeMarker):@Controller public class WebPageController { @GetMapping("/home") public String homePage() { return "home"; // Отрендерит файл resources/templates/home.html } }@RestController— это мета-аннотация, объединяющая@Controllerи@ResponseBody. Она указывает фреймворку, что возвращаемое значение метода нужно не отправлять во ViewResolver, а напрямую сериализовать в тело HTTP-ответа (по умолчанию в формат JSON с помощью библиотеки Jackson):@RestController // = @Controller + @ResponseBody public class UserApiController { @GetMapping("/api/users/{id}") public UserDto getUser(@PathVariable Long id) { return new UserDto(id, "Alex"); // Сериализуется в {"id": 1, "name": "Alex"} } }
🔴 Senior Level
Главное функциональное отличие: Трансляция исключений в @Repository
Единственная аннотация среди стереотипов, добавляющая реальное функциональное поведение в рантайме — это @Repository.
Зачем нужна трансляция исключений:
Разные СУБД и библиотеки доступа к данным выбрасывают специфичные низкоуровневые исключения:
- JDBC выбрасывает проверяемое
java.sql.SQLException. - Hibernate выбрасывает
org.hibernate.HibernateException. - JPA выбрасывает
jakarta.persistence.PersistenceException.
Если бы сервисный слой обрабатывал эти исключения напрямую, бизнес-логика оказалась бы намертво привязана к конкретной реализации БД.
Механизм PersistenceExceptionTranslationPostProcessor:
- При старте контекста Spring автоматически регистрирует
PersistenceExceptionTranslationPostProcessor(являющийсяBeanPostProcessor). - Этот процессор ищет бины, помеченные аннотацией
@Repository. - Вокруг каждого
@Repositoryсоздается AOP-прокси с советникомPersistenceExceptionTranslationAdvisor. - Любые низкоуровневые ошибки вендора перехватываются и конвертируются в единую непроверяемую (
unchecked) иерархию Spring —DataAccessException:
java.sql.SQLException / HibernateException
│
▼ (PersistenceExceptionTranslator)
DataAccessException (Spring Unchecked)
├── DuplicateKeyException (Нарушение UNIQUE)
├── DataIntegrityViolationException (Нарушение внешнего ключа FK / NOT NULL)
├── CannotAcquireLockException (Deadlock блокировок БД)
└── EmptyResultDataAccessException
Важно: Если заменить аннотацию
@Repositoryна@Componentили@Service, класс продолжит работать как бин, но автоматическая трансляция исключений отключится. Сервисному слою придется ловить «сырые» исключения драйвера БД!
Использование стереотипов в срезах AspectJ (Pointcuts)
Использование семантически корректных аннотаций позволяет строить элегантные правила для сквозных аспектов (AOP):
@Aspect
@Component
public class ArchitectureMonitoringAspect {
// Применить логирование только к сервисному слою:
@Around("@within(org.springframework.stereotype.Service)")
public Object profileServices(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
// Замерить метрики производительности запросов к БД:
@Around("@within(org.springframework.stereotype.Repository)")
public Object profileDatabaseCalls(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
🎯 Шпаргалка для интервью
30-секундный ответ
Все 4 аннотации служат для автоматической регистрации классов как бинов Spring через
@ComponentScan:
@Component— базовый стереотип для компонентов общего назначения.@Service— семантическая метка для бизнес-логики; в рантайме идентична@Component, но служит якорем для транзакций и аспектов.@Repository— слой работы с данными. Единственная аннотация, добавляющая поведение в рантайме: активирует AOP-прокси черезPersistenceExceptionTranslationPostProcessor, преобразующий исключения вендоров (SQLException,HibernateException) в унифицированную непроверяемую иерархиюDataAccessException.@Controller— обработчик Spring MVC, возвращающий View.@RestController— композиция@Controller+@ResponseBody, возвращающая сериализованные данные (JSON/XML).
4 каверзных вопроса с ответами
-
Какая из стереотипных аннотаций имеет реальное поведение в рантайме, а не только семантическое значение? Ответ: Аннотация
@Repository. При ее наличии Spring оборачивает бин в прокси с помощьюPersistenceExceptionTranslationPostProcessorдля автоматической трансляции низкоуровневых исключений баз данных в иерархиюDataAccessException. -
Что произойдет, если заменить
@Serviceна@Componentнад классом сервиса? Ответ: С точки зрения создания и внедрения бина ничего не изменится: Spring точно так же зарегистрирует его в контексте. Однако ухудшится читаемость архитектуры, и перестанут срабатывать AOP-аспекты, привязанные к предикату@within(org.springframework.stereotype.Service). -
В чём разница между
@Controllerи@RestController? Ответ: Метод@Controllerвозвращает имя View (шаблона HTML), которое затем разрешается черезViewResolver.@RestController— это мета-аннотация, объединяющая@Controllerи@ResponseBody. Ее методы возвращают чистые данные доменной модели, которые сериализуются в тело ответа (обычно JSON) черезHttpMessageConverter. -
Почему иерархия исключений
DataAccessExceptionв Spring сделана непроверяемой (unchecked)? Ответ: Большинство ошибок работы с БД (обрыв соединения, deadlock, нарушение ограничений целостности) являются фатальными или неустранимыми на уровне вызова конкретного метода. Делая исключения непроверяемыми (наследникамиRuntimeException), Spring избавляет разработчиков от захламления каждого метода принудительными блокамиtry-catchили секциямиthrows SQLException, позволяя централизованно перехватывать их в глобальных обработчиках (@ControllerAdvice).
Красные флаги (чего категорически нельзя говорить)
- ❌ «
@Serviceавтоматически делает все методы класса транзакционными» — нет, транзакции активируются только аннотацией@Transactional. Сам по себе@Serviceтранзакции не открывает. - ❌ «
@Repositoryи@Componentработают абсолютно одинаково под капотом» —@Repositoryвключает автоматическую трансляцию исключений. - ❌ «
@RestControllerне умеет возвращать HTTP-статусы ошибок» — статусы гибко возвращаются черезResponseEntity<T>или@ResponseStatus. - ❌ «Для регистрации бина обязательно использовать только
@Component» — использование профильных стереотипов (@Service,@Repository) обязательно по стандарту чистой архитектуры Spring.