스테리오타입 어노테이션 (Sterotype Annotation) 이란?
- Spring에서는 @로 시작하는 Annotation을 굉장히 많이 사용합니다.
- Annotation을 많이 사용하기 때문에 여러개를 동시에 쓰는 상황이 반복되거나, 기존 어노테이션의 동작을 약간 수정하면서도 비슷한 역할을 하는 새로운 어노테이션을 만들어야 하는 상황이 발생할 수 있습니다.
- 이 경우에 여러개의 Annotation을 합쳐 스테리오타입 어노테이션 (Sterotype Annotation)을 만들 수 있습니다.
- 스테리오타입 어노테이션 (Sterotype Annotation)을 사용하면 여러 개의 어노테이션을 적용해야 하는 상황을 단순화 하기 때문에 코드의 유지보수성을 높일 수 있습니다.
Spring Bean을 만드는 스테리오타입 어노테이션 (Sterotype Annotation)
- Spring Bean은 Spring Container에 의해 관리되는 객체를 말합니다.
- 쉽게 말하면 객체를 직접 만들고 의존성을 주입하는게 아니라 스프링이 이걸 대신 해 주면 그 객체는 Bean이라고 합니다.
- Spring에 Bean을 등록하기 위해서는 당연히 Spring에 알려줘야 하는데요. 옛날에는 XML을 사용하기도 했지만 요즘은 거의 대부분 Annotation을 사용합니다.
- 하지만... 헷갈리게도 Spring Bean을 등록하기 위한 Annotation은 1개가 아닙니다. 물론 기본적으로 Spring에 Bean으로 등록한다는 목적은 같지만, 이 외에도 추가적인 동작을 하거나 의미적으로 다른 경우가 있습니다.
예를 들어 @Service를 살펴보겠습니다. 모든 Spring Bean의 스테리오타입 어노테이션은 (Sterotype Annotation)은 @Component가 바탕입니다. 실제로 @Service를 살펴보면 @Component가 붙어 있는데요.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 까꿍
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
하지만 @Service는 일반적인 Bean과 달리 비즈니스 로직을 처리하는 객체에 붙이는게 대부분입니다. 어쩌구Service, 어쩌구UseCase 등에 붙입니다.
@Service의 경우 의미가 다를 뿐이지 대신 @Component를 붙인다고 달라지는게 1도 없습니다. 하지만 @Repository, @Controller, @Configuration은 의미적인 차이 외에도 추가적인 동작을 하는데요. 이는 밑에서 자세히 알아보겠습니다.
요약
의미 | 추가적인 동작 | |
@Component | - | - |
@Controller | Web 요청을 처리함 | Spring MVC에서 Controller로 인식함 |
@Service | 비즈니스 로직을 처리함 | - |
@Repository | 데이터 관련 동작을 처리함 | DB의 예외를 Spring의 예외로 바꿔줌 |
@Configuration + @Bean | 설정 관련 | CGLib으로 Singleton을 보장 |
추가로 하는 것 - @Controller
- HTTP 요청이 들어오면 Spring MVC Container는 특정 함수를 호출해 요청을 처리합니다.
- 이때 여러 편의성을 위해 Spring MVC Controller는 다양한 기능이 있습니다.
- String을 return하면 ViewResolver가 자동으로 해당 이름의 View를 찾아 줌
- 자바 객체를 return하면 JSON으로 변환하거나 View에서 사용할 수 있도록 전달해 줌
- 함수의 Parameter는 자동으로 URL의 Param과 연결 시켜 줌
- 이 모든 기능을 사용하기 위해서는 Spring MVC Controller가 되야 하는데... @Controller만 붙이면 바로 Spring Web MVC Controller가 될 수 있습니다!
추가로 하는 것 - @Repository
- 데이터를 접근하다 보면 다양한 문제가 생깁니다.
- 네트워크가 이상할 수도 있고, Lock 관련 문제일 수도 있고, Transaction 관련 문제일 수도 있습니다.
- 순수 자바 (JDBC 등)에서 데이터 접근 과정에 문제가 생기면 던지는 예외에는 2가지 문제점이 있습니다.
- 1 - 이런 예외는 보통 복구 불능인데 (할 수 있는게 없는데) Checked Exception임
- 2 - 일반적인 예외를 던지고 (SQLException 등) 그 안에 에러코드가 들어 있음
- @Repository를 붙이면 이 문제를 해결해 줍니다.
🐜 1 - 이런 예외는 보통 복구 불능인데 (할 수 있는게 없는데) Checked Exception임
- Java에서는 많은 예외가 try - catch로 반드시 처리해야 하는 Checked Exception입니다.
- 일부의 경우 Checked Exception도 말이 됩니다.
- 예를들어 평균을 계산해야 하는데 유저가 0을 넣어서 나누기 0 하는 중에 Arithmetic Exception이 발생하면 전체 요청이 실패하는게 아니라 이 예외를 처리해서 0을 return 하는게 더 좋을 수도 있습니다. 그리고 이걸 까먹지 않게 반드시 처리하도록 언어 차원에서 강제하면 좋을 수도 있습니다.
try {
return sum / count;
} catch (ArithemtaticException e) {
return 0
}
- 하지만 DB에서 나는 예외를 생각해 보세요.
- Timeout이 나거나, COL이 없거나, TABLE이 없거나, Lock이 안 잡히거나, Transaction이 Rollback 되거나 기타 DB에 다른 문제가 생겼는데 여기서 뭘 할 수 있을까요?
- DB의 장애는 절대 일어나면 안 되고, 장애가 난 경우 가짜 데이터를 내릴 수도 없으니 이 예외를 처리할 필요가 없습니다.
- 처리를 해야 한다고 해도 전역적인 @ControllerAdvice로 처리할 수 있는데 모든 DAO에서 처리를 강제하는건 실용적이지 않습니다.
🐜 2 - 일반적인 예외를 던지고 (SQLException 등) 그 안에 에러코드가 들어 있음
- Unique Constraint를 어겨서 예외가 발생했습니다.
- 근데 그 예외가 SQLException입니다. 물론 안에 코드랑 설명은 있지만... 이 예외를 처리하려면 예외 코드나 설명에 대한 지식이 있어야 합니다.
- 만약 여기서 DB를 바꾼다면? 예외 코드나 설명이 또 달라집니다.
- 그래서 Spring에서는 DataAccessException을 최상위로한 DAO 관련 여러 예외를 추상화시켰습니다.
- 이제 Unique Constraint를 어기면 SQLException이 아닌 DataIntegrityViolationException이 나옵니다.
- DataIntegrityViolationException은 당연히 DataAccessException의 자식 관계입니다.
그래서 @Repository가 붙은 경우 Spring에서 SQLException 같은 일반적인 예외를 쉽게 알 수 있는 잘 추상화된 Spring의 예외로 변환해 주고, Unchecked Exception으로 바꿔서 선택적으로 처리할 수 있게 해 줍니다.
추가로 하는 것 - @Configuration
@Configuration은 내부의 @Bean이 붙은 함수를 호출해서 Spring Bean을 얻는 역할을 합니다.
@Configuration
public ExampleConfig {
@Bean
public Example example() {
return Example();
}
}
하지만 이상하지 않으신가요?
- Spring이 Bean을 얻고 싶을 때 마다 @Bean이 붙은 함수를 호출함
- 하지만 Spring의 Bean은 Singleton Scope인 경우가 있음
- Spring이라고 자바 코드를 마음대로 바꿀 수 있는게 아닌데 매번 호출해야 한다면 Single Scope를 어떻게 보장하지?
정답은 @Configuration에 있습니다. @Configuration이 붙으면 Spring에서 CGLib이라는 바이트 코드를 조작할 수 있는 라이브러리를 사용해서 원래 Class를 상속한 Proxy Class를 대신 Spring Bean으로 등록합니다.
위의 예시의 경우 아래와 같은 형식이 (정확하진 않겠지만) 생깁니다.
public class ExampleConfig$$EnhancedBySpringCGLIB extends ExampleConfig {
private Example example; // 1번 만들면 재사용하기 위해 요기에 저장
@Override
public Example example() {
if (this.example == null) {
this.example = super.example(); // 1번도 만들지 않은 경우 만들고 저장
}
return this.example; // 저장된 인스턴스를 재사용
}
}
'👨💻 프로그래밍 > Java, Kotlin, Spring' 카테고리의 다른 글
자주 쓰는 Kotlin Live Template 공유 (0) | 2024.06.02 |
---|---|
Kotlin Sequence (스퀸스) 에 대해 알아보자 (1) | 2024.02.04 |
Kotlin의 inline 함수 알아보기 (0) | 2023.10.09 |
Spring에서 같은 type의 Bean 구분하는 방법 (0) | 2023.06.06 |
자바 21 - 자바 상속(Inheritance) (0) | 2021.05.21 |