🍃 Розділ 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.

Пов’язані теми