Spring Container
자바 애플리케이션은 애플리케이션 동작을 제공하는 객체들로 이루어져있다.
이때, 객체들은 독립적으로 동작하는 것보다 서로 상호작용하여 동작하는 경우가 많은데
이렇게 상호작용하는 객체를 '객체의 의존성'이라고 표현
스프링에서는 스프링 컨테이너가 객체들을 생성하고 객체끼리 의존성을 주입하는 역할을 함.
스프링 컨테이너가 생성한 객체들을 '빈'이라고 한다.
즉, 빈은 스프링에서 사용하는 어플리케이션 객체라고 이해할 수 있다.
빈의 생성 방법
1. xml 활용
컴파일 단계에서 스캔하는 xml에 bean을 명시적으로 입력해줌
2. 어노테이션 활용
xml 통해 빈을 생성하는 건 상당히 번거롭
@Component 라는 어노테이션을 지정하여 편리하게 빈 생성
(@Component는 스프링 컴포넌트 클래스를 지정하는 어노테이션)
의존성 주입
Spring Framework의 3가지 핵심 프로그래밍 모델 중 하나
외부에서 두 객체 간 관계를 정해주는 디자인패턴으로 인터페이스를 사이에 두고 클래스 레벨에서는 의존 관계가 고정되지 않도록 함
런타임시 관계를 동적으로 주입하여 결합도를 낮출 수 있게 하는 기법
DI (Dependency Injection)
의존성 주입은 IoC(Invesoin of Control, 의존성 역전) 원칙 하에 객체 간 결합을 약하게 해주고 유지보수가 좋은 코드를 만들어 줌
-> 즉 외부에서 생성된 객체를 이용
한 객체가 어떤 객체에 의존할것인지는 별도의 관심사이다.
DI컨테이너를 통해 서로 강하게 결합되어있는 두 클래스를 분리하고,
두 객체간 관계를 결정해줌으로서 결합도를 낮추고 유연성을 확보하고자 한다.
(이때 다른 빈을 주입받으려면 자기자신도 반드시 컨테이너의 빈이여야 한다.)
강한 결합
객체 내부에서 다른 객체를 생성하는 것은 강한 결합도를 가지는 구조이다
클래스 간 관계가 맺어질 땐 근본적으로 의존성 주입을 통해 문제를 해결할 수 있음
-> 다형성 필요 (큰 범주의 Interface가 필요하고, 그 범주의 Interface를 구현하는 구현 클래스를 만들면 된다!)
그 후 상세 클래스에서 외부에서 해당 구현 클래스를 주입받도록 한다
ex) 배터리(의존 객체)를 사용하는 장난감(어떤 객체)에게 배터리를 넣어주는 것을 의존성 주입이라고 생각하면 된다!
애플리케이션 실행시점에 필요한 빈을 생성하여, 의존성이 있는 두 객체를 연결하기위해 한 객체를 다른 객체로 주입시켜줘야 한다.
느슨한 결합
객체를 주입받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해 넘겨받는 것
이렇게 하면 런타임시 의존관계가 결정되어 결합도를 낮출 수 있다!
스프링의 의존성 주입
만약 Service와 Repository가 둘다 Bean(*스프링 컨테이너 상에서 생성된 객체)으로 등록되어 있다면, Service의 생성자만 만들어주면 스프링 IoC컨테이너가 Repository에 의존성을 알아서 해준다.
(*컨테이너 : 쉽게말해 bean객체들이 들어있는 통)
스프링은 다양한 의존성 주입방법을 제공한다.
@Autowired 어노테이션을 이용하면 Spring에게 의존성을 주입하라 라고 명령하는것과 같은데 생성자, 필드, 세터에 붙일수 있다.
1. 생성자 주입 (가장 권장)
생성자에 @Autowired를 붙여 의존성을 주입받을 수 있음
생성자주입은 생성자 호출시점에 (해당클래스의 인스턴스생성시) 1회 호출되는 것이 보장
-> 주입받은 객체가 변하지 않거나, 반드시 객체주입이 필요한 경우에 강제하기 위해 사용된다.
가장 권장하지만 생성자 주입을 위해 코드를 만드는 부분에서 번거로움 존재
그래서 많이들 사용하는 Lombok에서 @Getter, @Setter 어노테이션처럼 @RequiredArgsConstructor 어노테이션은 클래스에 선언된 final 변수들, 필드들을 매개변수로 하는 생성자를 자동으로 생성해주는 어노테이션이다
2. 필드 주입
멤버변수 선언부에 @Autowired을 붙인다.
필드주입을 이용하면 코드가 간결해져서 과거에 많이 사용했던 방법이다.
하지만 필드 주입은 외부에서 변경이 불가능하다.
(테스트코드의 중요성이 부각됨에 따라 필드의 필드객체를 수정할 수 없는 필드주입은 거의 사용하지 않게 됨)
또한 필드주입은 반드시 DI 프레임워크가 존재해야 하므로 사용을 지양해야한다!
3. Setter주입
Setter메소드에 @Autowired을 붙인다.
필드값을 변경하는 Setter를 통해서 의존관계를 주입하는 방법이다.
Setter주입은 생성자 주입과 달리 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.
생성자를 통한 주입을 사용해야 하는 이유
Spring에서 가장 권장하는 방법은 생성자를 통한 주입이다.
1. 생성자를 사용하면 필수적으로 사용해야 하는 의존성 없이는 인스턴스를 만들지 못하도록 강제 가능
만약 SampleController가 SampleRepository없이는 제대로 동작할 수 없다면 SampleController에서 생성자를 통해 강제로 SampleRepository를 무조건 주입받은 후에야 인스턴스를 생성할 수 있도록 해야할 것이다.
2. 생성자주입을 통해 변경의 가능성을 배제하고 불변성을 보장할 수 있다.
3. final 키워드 작성 및 롬복과의 결합
생성자주입을 사용하면 필드객체에 final키워드를 사용할 수 있다. 이는 컴파일 시점에 누락된 의존성을 확인할수 있다.
반면 생성자 주입을 제외한 다른 주입방법들은 객체생성 이후 (생성자호출이후) 에 호출되므로 final키워드를 사용할 수 없다. ( 객체생성 이후에 필드값을 변하게 하는것이니까!)
또한 final키워드를 붙임으로서 Lombok과 결합되어 코드를 더 간결하게 작성할 수 있다.
롬복에는 final변수를 위한 생성자를 대신해주는 @RequiredArgsConstructor가 있다.
@RequiredArgsConstructor
초기화되지 않은 final 필드나 @NotNull이 붙은 필드에 대해 생성자를 생성
주로 의존성 주입 (Dependency Injection) 편의성을 위해 사용
스프링 의존성 주입의 특징 중 한가지를 이용하는데 다음과 같다
어떠한 빈(Bean)에 생성자가 오직 하나만 있고, 생성자의 파라미터 타입이 빈으로 등록 가능한 존재라면 이 빈은 @Autowired 어노테이션 없이도 의존성 주입이 가능하다.
즉 새로운 필드를 추가할 때 다시 생성자를 만들어서 관리해야하는 번거로움을 없애줌!
(@Autowired 사용하지 않고 의존성 주입)
- Spring Framework의 DI(의존성주입) 중 Constructor Injection(생성자 주입)을 임의의 코드 없이 자동으로 설정
- @RequiredArgsConstructor이 어떻게 구동하는지 정확히 알아야 예상치 못한 오류를 막을 수 있음
추가적으로 롬복 어노테이션이(@Getter 혹은 @Setter 등) 사용할땐 편하지만, 단점도 있다.
setter 메서드가 필요없는 필드에 대해서도 setter 메서드를 강제로 생성하게 되니, 필드 값이 변경될 위험이 생긴다.
이런 부분들은 전부 리팩토링의 대상이지만, 롬복을 사용하게될 경우 리팩토링이 힘들어지는 부분도 있으니 너무 무분별하게 사용하는것은 좋지 않다!
References : https://esoongan.tistory.com/90, https://medium.com/webeveloper/requiredargsconstructor-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-dependency-injection-4f1b0ac33561
'Backend > springboot' 카테고리의 다른 글
[SpringBoot] @Autowired, IoC(Inversion of Control), DI(Dependency Injection) (0) | 2022.12.23 |
---|---|
[SpringBoot] Controller vs RestController / RESTful 웹서비스 (0) | 2022.11.05 |
[Spring Boot] JPA 엔티티 매핑/테이블 설정 (0) | 2022.10.09 |
[Spring Boot] JPA & DB 설정 및 동작 확인 (0) | 2022.10.02 |
[Spring Boot] H2 Database (0) | 2022.10.02 |