컴포넌트 스캔과 자동 의존관계 설정
먼저 빈(Bean)에 대해 알아보자.
빈(Bean)
스프링(Spring) 컨테이너가 관리하는 자바 객체를 빈(Bean)이라 한다.
스프링의 특징에는 제어의 역전(IoC)가 있다.
지금까지 사용자가 new연산을 통해 객체를 생성하고 메소드를 호출했지만 제어의 역전이 적용 된 경우에는 객체의 생성과 사용자의 제어권을 스프링에게 넘긴다.
사용자는 직접 new를 이용해 생성한 객체를 사용하지 않고, 스프링에 의하여 관리당하는 자바 객체를 사용한다. 이 객체를 '빈(bean)' 이라 한다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 설정한 것이다.
- 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라한다.
스프링 빈을 두가지 방법으로 등록할 수 있는데,
- 컴포넌트 스캔과 자동 의존관계 설정
- 자바 코드로 직접 스프링 빈 등록하기
@Component 어노테이션이 있으면 스프링 빈으로 자동 등록된다.
@Controller, @Service, @Repository도 컴포넌트 스캔이 안에 달려있기 때문에 스프링 빈으로 등록된다.
스프링 빈 등록은 @Component, 연결은 @Autowired 라고 볼 수 있다.
참고: 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유한다) -> @Configuration을 통해 수동으로 등록 가능
따라서 같은 스프링 빈이면 모두 같은 인스턴스다.
@Controller
1. @Controller 어노테이션의 역할
- @Controller 어노테이션은 말 그대로, 해당 클래스가 웹 애플리케이션의 컨트롤러임을 나타낸다.
- 스프링 컨테이너는 @Controller 어노테이션이 지정된 클래스를 빈으로 등록하고, 요청을 해당 컨트롤러에 매핑하여 처 리한다.
- @Controller 어노테이션은 클라이언트의 요청을 처리하는 비즈니스 로직과 뷰를 결합하여 전체적인 동작을 제어한다.
2. @Controller 어노테이션의 사용 방법
- @Controller 어노테이션이 지정된 클래스는 일반적으로 HTTP 요청을 처리하기 위한 메소드를 포함한다.
- 각 메소드는 @RequestMapping 어노테이션과 함께 사용하여 특정 URL 패턴에 대한 처리를 정의한다.
- @RequestMapping 어노테이션을 사용하여 요청 URL과 해당 메소드를 매핑시키고, 메소드는 요청을 처리하고 필요한 로직을 수행한 후에 응답을 생성한다.
- 컨트롤러 메소드에서는 ModelAndView, Model, ResponseEntity 등을 반환하여 응답 데이터와 뷰 정보를 제공한다.
3. @Controller 어노테이션의 추가 기능
- @PathVariable 어노테이션을 사용하여 경로 변수를 추출하고 활용할 수 있다.
- @RequestParam 어노테이션을 사용하여 요청 파라미터를 추출하고 활용할 수 있다.
- @ModelAttribute 어노테이션을 사용하여 요청 데이터를 객체에 바인딩하고, 자동으로 모델에 추가할 수 있다
4. @Controller 어노테이션의 예시
@Controller
@RequestMapping("/products")
public class ProductController {
@Autowired
private productService productService;
@RequestMapping("/{id}")
public ModelAndView getProduct(@PathVariable("id")int id) {
Product product = productService.getProductById(id);
ModelAndView modelAndView = new ModelAndView("productView");
modelAndView.addObject("products", products);
return modelAndView;
}
}
위의 예시에서는 "/products"로 시작하는 URL에 대한 요청을 처리하기 위한 컨트롤러이다.
@RequestMapping 을 사용하여 각 요청 URL과 해당 메소드를 매핑하고, ProductService를 이용해 비즈니스 로직을 수행한다.
* DI에는 필드 주입, setter 주입, 생성자 주입 3가지 방법이 있고
DI (Dependency Injection)
의존대상 B가 변하면, 그것이 A에 영향을 미친다. (이일민, 토비의 스프링 3.1)
public class A {
private B b = new B();
}
A라는 클래스가 있고, 이 클래스는 B라는 클래스를 필드로 가질때 B에 final 필드가 추가되는 변경이 일어난다면
new B()부분에서 컴파일 에러가 난다. B 내부의 변경이 일어났는데, A에도 영향을 미치게 된것이다.
이런 경우를 A가 B에 의존한다. 라고한다. 그렇다면 의존성 주입이란, 의존성을 외부에서 주입해준다는 의미가 될것이다. 예를 들어 다음과 같은 코드이다.
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
B가 변경되면 A의 내용도 변경되지만, 의존 대상을 직접 생성하는 것이 아닌 외부로 부터 주입을 받는다.
의존성을 주입하는 방법에는 세가지가 있다.
생성자 주입(Constructor Injection)
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
Setter 주입(Setter Injection)
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
인터페이스 주입(Interface Injection)
public interface BInjection {
void inject(B b);
}
public A implements BInjection {
private B b;
@Override
public void inject(B b) {
this.b = b;
}
}
그렇다면 왜 DI를 써야하는가?
- 의존성이 줄어든다 (변경에 덜 취약해진다.)
- 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워진다.
- 가독성이 높아진다.
- 재사용성이 높아진다