🍃 Раздел 5 · Вопрос #25

В чём разница между @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:

  1. При старте контекста Spring автоматически регистрирует PersistenceExceptionTranslationPostProcessor (являющийся BeanPostProcessor).
  2. Этот процессор ищет бины, помеченные аннотацией @Repository.
  3. Вокруг каждого @Repository создается AOP-прокси с советником PersistenceExceptionTranslationAdvisor.
  4. Любые низкоуровневые ошибки вендора перехватываются и конвертируются в единую непроверяемую (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 каверзных вопроса с ответами

  1. Какая из стереотипных аннотаций имеет реальное поведение в рантайме, а не только семантическое значение? Ответ: Аннотация @Repository. При ее наличии Spring оборачивает бин в прокси с помощью PersistenceExceptionTranslationPostProcessor для автоматической трансляции низкоуровневых исключений баз данных в иерархию DataAccessException.

  2. Что произойдет, если заменить @Service на @Component над классом сервиса? Ответ: С точки зрения создания и внедрения бина ничего не изменится: Spring точно так же зарегистрирует его в контексте. Однако ухудшится читаемость архитектуры, и перестанут срабатывать AOP-аспекты, привязанные к предикату @within(org.springframework.stereotype.Service).

  3. В чём разница между @Controller и @RestController? Ответ: Метод @Controller возвращает имя View (шаблона HTML), которое затем разрешается через ViewResolver. @RestController — это мета-аннотация, объединяющая @Controller и @ResponseBody. Ее методы возвращают чистые данные доменной модели, которые сериализуются в тело ответа (обычно JSON) через HttpMessageConverter.

  4. Почему иерархия исключений 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.

Связанные темы