spring 관련 첫 글로 무엇이 좋을까 하다가, 의존성 주입에 대한 글을 쓰게 되었습니다.
가장 보편적인 케이스인 Controller라는 클래스에서 Service 클래스를 호출하는 코드를 통해 이해해보겠습니다.
- Service
@Slf4j
public class BeanTestService {
public void log() {
log.info("log test...");
}
}
- Controller
public class BeanTestController {
public void beanTest() {
BeanTestService.log();
}
}
BeanTestController에서 BeanTestService의 log라는 메서드를 사용하려고 호출해봤자, 사용하지 못합니다.
이러한 경우에 spring에서 정상적으로 Service 클래스를 사용하기 위해서는, 두 가지를 해줘야 합니다.
1. Controller와 Service를 spring 컨테이너에 Bean 등록
Controller와 Service를 Bean 등록하기 위해서는, @Component라는 어노테이션을 사용하면 간단히 등록할 수 있습니다.
그런데, 우리가 Controller에 흔히 사용하는 @Controller, @RestController와 Service에 사용하는 @Service, Repository에 사용하는 @Repository 등은 기본적으로 @Component가 포함이 되어 있습니다.
예를 들어, @Controller 어노테이션의 상세코드를 찾아보면, @Component가 포함되어 있음을 확인할 수 있습니다.
때문에 우리는 @Controller혹은 @RestController만 선언해도, spring에서 알아서 찾아서 Bean으로 등록해줍니다.
/**
* Indicates that an annotated class is a "Controller" (e.g. a web controller).
*
* <p>This annotation serves as a specialization of {@link Component @Component},
* allowing for implementation classes to be autodetected through classpath scanning.
* It is typically used in combination with annotated handler methods based on the
* {@link org.springframework.web.bind.annotation.RequestMapping} annotation.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 2.5
* @see Component
* @see org.springframework.web.bind.annotation.RequestMapping
* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@Component를 통해 Bean 등록을 할때, Bean 명칭을 따로 지정하지 않으면 기본적으로 클래스의 첫글자를 대문자->소문자로 변경한 것을 Bean 이름으로 간주하여 등록합니다.(B -> b로 변경, beanTestService 등록)
@Component
public class BeanTestService {
2. Controller에서, Service를 호출하고 의존성 주입을 통해 Service 객체에 spring 컨테이너에 있는 Service Bean을 주입.
Controller에서 Service를 호출하여 사용하기 위해서는, 아래와 같이 작성하여야 합니다. BeanTestService라는 클래스를 선언하고 beanTestService라는 bean을 찾아 @Autowired 어노테이션을 통해 주입을 해줍니다. 이를 의존성 주입(DI, Dependency Injection) 이라고 합니다.
@Autowired
BeanTestService beanTestService;
결론적으로 Controller에서 Service를 호출하여 정상적으로 사용하기 위해서는, 아래와 같이 작성되어야 합니다.
참고로 @Slf4j 어노테이션은 Lombok 라이브러리를 적용하려는 용도인데, 간단하게 java의 system.out.println을 대체하는 용도로 log를 사용한다고 이해하셔도 됩니다.
@Slf4j
@Service
public class BeanTestService {
public void log() {
log.info("log test...");
}
}
.
@RestController
public class BeanTestController {
@Autowired
BeanTestService beanTestService;
public void beanTest() {
beanTestService.log();
}
}
위의 정리된 내용은 Controller와 Service에만 해당되는것이 아니고, spring 전반을 관통하는 개념이라 아래와 같이 DI의 핵심을 일반화 할 수 있습니다.
---------------------------- 의존성 주입 핵심 ----------------------------
1. 본인 클래스가 호출되기 위해(사용되기 위해) spring 컨테이너에 Bean 등록
2. bean 등록된 클래스를 호출하기 위해서는, spring 컨테이너에 등록된 Bean을 조회해서, 객체에 주입해서 사용
그런데 의존성을 주입하는 방법은 위의 예시케이스와 같은 @Autowired만 있는 것이 아니라, 여러종류가 있습니다. 의존성을 주입하는 방법은 총 네가지로, 하나하나 살펴보겠습니다.
1) 필드 주입(@Autowired)
2) 생성자 주입
3) Setter 주입
4) 일반 메서드 주입
각 방법들은 다음과 같은 특징이 있습니다.
1) 필드주입(@Autowired)은 위의 예시에서 사용했던 예시를 그대로 사용할 수 있습니다. beanTestService라는 명칭으로 등록된 bean을 찾아 controller안에서 의존성을 주입해줍니다. 가장 간편하지만, 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있으며, 스프링과 같은 DI 프레임워크가 있어야 사용 가능한 특징이 있습니다.
@RestController
public class BeanTestController {
@Autowired
BeanTestService beanTestService;
public void beanTest() {
beanTestService.log();
}
}
2) 생성자 주입은 말 그대로 생성자를 통해서 의존 관계를 주입 받는 방법입니다. 생성자를 통해서 beanTestService를 주입받아 사용할 수 있습니다. 참고로 생성자가 1개만 있으면 생성자에서 @Autowired 생략할 수 있습니다. 생성자는 호출시점에 딱 1번만 호출되는 것이 보장되며, 의존관계가 불변한다는 특징이 있습니다. 따라서 의존성 주입은 기본적으로는 생성자 주입을 사용하는 것을 권장합니다.
@RestController
public class BeanTestController {
BeanTestService beanTestService;
@Autowired
public BeanTestController(BeanTestService beanTestService) {
this.beanTestService = beanTestService;
}
public void beanTest() {
beanTestService.log();
}
}
그런데 위와 같은 생성자 코드를 매번 작성하는것은 귀찮습니다. 이때 도움을 주는것이 바로 lombok 라이브러리에서 제공하는 @RequiredArgsConstructor 어노테이션입니다. 해당 어노테이션은 private final로 선언된 변수에 한하여 생성자를 자동으로 생성해줍니다. 이를 통해 private final BeanTestService beanTestService라는 한줄만 있어도, spring이 의존성 주입을 해줘 정상적으로 beanTestService의 메서드를 사용할 수 있습니다.
이 방식이 좋은 점은 final 키워드를 사용하기 때문에, 생성자에서 혹시라도 값이 설정되지 않았을때, 이러한 오류를 컴파일 단계에서 바로 확인 할 수 있습니다. 직접 로직을 돌려서 오류를 확인하는 것보다 컴파일 단계에서 오류를 확인할 수 있다는 것은 큰 장점입니다.
@RestController
@RequiredArgsConstructor
public class BeanTestController {
private final BeanTestService beanTestService;
// @Autowired
// public BeanTestController(BeanTestService beanTestService) {
// this.beanTestService = beanTestService;
// }
public void beanTest() {
beanTestService.log();
}
}
3) Setter 주입은 아래와 같이 setter 수정자 메서드를 통해 의존성을 주입하는 방법인데, 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하기 때문에, 변경 가능성이 있는 의존관계에 사용합니다. 그런데 의존성주입이란 것이 사실 불변하는 것이 좋은것이 대부분이기 때문에 권장하지 않습니다. 또한 set 메서드의 접근제어자를 public으로 항상 열어놔야 하는 단점도 있습니다.
@RestController
public class BeanTestController {
private BeanTestService beanTestService;
@Autowired
public void setBeanTestService(BeanTestService beanTestService) {
this.beanTestService = beanTestService;
}
public void beanTest() {
beanTestService.log();
}
}
4) 일반 메서드 주입은 아래와 같이 일반적인 메서드(inject라는 메서드)를 통해서도 의존성 주입을 할 수 있는 개념인데, 일반적으로는 사용하지 않습니다.
@RestController
public class BeanTestController {
private BeanTestService beanTestService;
@Autowired
public void inject(BeanTestService beanTestService) {
this.beanTestService = beanTestService;
}
public void beanTest() {
beanTestService.log();
}
}
'spring' 카테고리의 다른 글
spring 파라미터와 Request정보가 같이 필요할 때 처리방법 (1) | 2023.05.20 |
---|---|
spring Interceptor (0) | 2023.05.20 |
spring Filter (0) | 2023.05.20 |
spring Bean Validation 적용(@NotNull, @NotEmpty, @NotBlank, @Max, @Min) (0) | 2023.05.19 |
spring RestAPI 어노테이션 정리 (0) | 2023.05.14 |
spring Rest API Request(RestTemplate, Feign 비교) (0) | 2023.05.12 |
spring Bean 개념정리 2편(@Configuration, @Bean, @Qualifier, @Primary) (1) | 2023.05.10 |
spring Bean 개념정리 1편(Spring 컨테이너, @Component,싱글톤, Swagger, Bean 조회) (0) | 2023.05.04 |
댓글