안녕하세요.
#yeoneeblog 여니입니다:)
1. 의존성 주입 종류
의존성 주입 종류는 Constructor, Setter, Field가 있습니다.
1) Constructor Injection(생성자 주입)
생성자로 객체를 생성하는 시점에 필요한 빈을 주입합니다. 먼저 생성자의 인자에 사용되는 빈을 찾습니다. 그 후에 찾은 인자 빈의 생성자를 호출합니다. 즉 먼저 빈을 생성하지 않습니다. 수정자 주입, 필드 주입처럼 빈을 생성해 놓고 주입하는 방식과는 다른 것입니다.
public class ExampleCase {
private final ChocolateService chocolateService;
private final DrinkService drinkService;
@Autowired
public ExampleCase(ChocolateService chocolateService, DrinkService drinkService) {
this.chocolateService = chocolateService;
this.drinkService = drinkService;
}
}
2) Setter Injection(수정자 주입)
우선 주입(inject) 받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩터리에 등록하고 수정자 인자에 사용하는 빈을 찾거나 만듭니다. 그 이후에 주입 받으려는 빈 객체의 수정자를 호출하여 주입합니다.
public class ExampleCase{
private ChocolateService chocolateService;
private DrinkService drinkService;
@Autowired
public void setChocolateService(ChocolateService chocolateService){
this.chocolateService = chocolateService;
}
@Autowired
public void setDrinkService(DrinkService drinkService){
this.drinkService = drinkService;
}
}
3) Field Injection(필드 주입)
수정자 주입 방법과 동일하게 먼저 빈을 생성한 후에 어노테이션이 붙은 필드에 해당하는 빈을 찾아서 주입하는 방법입니다. 즉 먼저 빈을 생성한 후에 필드에 대해서 주입하는 것입니다.
public class MemberController {
@Autowired
private CommonService cService;
@Autowired
private ServletContext application;
@Autowired
private ChatService chService;
}
이렇게 자주 사용하는 필드 주입을 사용하게 되면 @Autowired어노테이션이 붙은 것들을 모두 확인하게 되고,
찾지 않아도 되는 것들까지 찾으며 자신이 원하는 빈을 찾게 되니 낭비가 됩니다.
우리는 필드 주입 방식을 보통 많이 사용하는데, 아래와 같은 단점이 있습니다.
2. 필드 주입 방식 단점
1) 단일 책임의 원칙 위반
필드 주입 망식은 일단 의존성을 주입하기 쉽습니다. @Autowired 선언 아래 3개든 10개든 막 추가할 수 있으니... 여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감 같은 걸 느끼게 해줍니다. Constructor의 파라미터가 많아짐과 동시에 하나의 클래스가 많은 책임을 떠안는다는 걸 알게됩니다. 이때 이러한 징조들이 리팩토링을 해야한다는 신호가 될 수 있습니다.
2) 의존성이 숨는다.
DI(Dependency Injection) 컨테이너를 사용한다는 것은 클래스가 자신의 의존성만 책임진다는게 아닙니다. 제공된 의존성 또한 책임집니다. 그래서 클래스가 어떤 의존성을 책임지지 않을 때, 메서드나 생성자를 통해(Setter나 Contructor) 확실히 커뮤니케이션이 되어야만합니다. 하지만 Field Injection은 숨은 의존성만 제공해줍니다.
3) DI 컨테이너의 결합성과 테스트 용이성
DI 프레임워크의 핵심 아이디어는 관리되는 클래스가 DI 컨테이너에 의존성이 없어야합니다. 즉, 필요한 의존성을 전달하면 독립적으로 인스턴스화 할 수 있는 단순 POJO여야합니다. DI 컨테이너 없이도 유닛테스트에서 인스턴스화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있습니다. 컨테이너의 결합성이 없다면 관리하거나 관리하지 않는 클래스를 사용할 수 있고, 심지어 다른 DI 컨테이너로 전환할 수 있습니다.하지만, Field Injection을 사용하면 필요한 의존성을 가진 클래스를 곧바로 인스턴스화 시킬 수 없습니다.
+ 포조(Plain Old Java Object, POJO)가 뭘까?
[ 위키백과 ]
Plain Old Java Object, 간단히 POJO는 말 그대로 해석을 하면 오래된 방식의 간단한 자바 오브젝트라는 말로서 Java EE 등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 "무거운" 객체를 만들게 된 것에 반발해서 사용되게 된 용어이다.
public class MyPojo {
private String name;
private int age;
public String getName() {
return name;
}
public String getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
EJB 등에서 사용되는 Java Bean이 아닌 Getter와 Setter로 구성된 가장 순수한 형태의 기본 클래스를 POJO라 하며, 위와 같이 가장 기본적인 형태의 Java 객체를 POJO라 합니다.
+ 진정한 POJO란?
토비의 스프링에서는 진정한 POJO를 아래와 같이 정의했다고 합니다.
"진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고
필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다."
4) 불변성
Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없습니다. 그래서 객체가 변할 수 있습니다.
+ 생성자 주입방식을 사용하는 이유
즉, 생성자 주입방식은 아래와 같은 장점을 가진다
1. 의존관계 설정이 되지 않으면 객체생성 불가 -> 컴파일 타임에 인지 가능, NPE 방지
2. 의존성 주입이 필요한 필드를 final 로 선언가능 -> Immutable
3. (스프링에서) 순환참조 감지가능 -> 순환참조시 앱구동 실패
4. 테스트 코드 작성 용이
덧붙여 필드 인젝션은 아래와 같은 장점을 가진다고 할 수 있다.
- 딱히, 편하다는 것 말고는 없다
아래와 같은 경우에는 Constructor Injection(생성자 주입방식)과 Setter Injection(수정자 주입방식), Field Injection(필드 주입방식)을 모두 섞어서 쓰고 있었는데요, 정상적으로 작동하니 이게 잘못된지 모르고 그냥 쓰는 경우가 많을 겁니다.
@Slf4j
@Controller
@SessionAttributes({"loginUser","nextUrl"})
public class MemberController {
@Autowired
private CommonService cService;
private MemberService mService;
private MemberValidator memValidator;
private BCryptPasswordEncoder bcrypotPasswordEncoder;
@Autowired
private ServletContext application;
@Autowired
private ChatService chService;
public MemberController() {
}
@Autowired
public MemberController(MemberService mService , MemberValidator memValidator , BCryptPasswordEncoder bcrypotPasswordEncoder) {
this.mService = mService;
this.memValidator = memValidator;
this.bcrypotPasswordEncoder = bcrypotPasswordEncoder;
}
@Autowired
public void setMemberService(MemberService mService) {
this.mService = mService;
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.addValidators(memValidator);
}
이걸 lombok(롬복)을 이용해서 @RequiredArgsConstructor 어노테이션을 통해서 간단한 방법으로 주입방식을 바꿔보겠습니다.
3. lombok(롬복) 이용하여, 필드 주입방식을 생성자 주입방식으로 바꾸어 사용하기
1) @RequiredArgsConstructor 어노테이션을 사용한 생성자 주입 방법
생성자 주입의 단점은 생성자를 작성하기 번거롭다는 것인데, lombok(롬복)을 사용하여 간단한 방법으로 생성자 주입 방식의 코딩을 할 수 있습니다.
2) @RequiredArgsConstructor
final이 붙거나 @NotNull 이 붙은 필드의 생성자를 자동 생성해주는 lombok(롬복) 어노테이션
<@RequiredArgsConstructor 어노테이션 활용한 전>
@RequiredArgsConstructor를 사용하지 않으면 원래는 이렇게 생성자 주입을 해야합니다.
public class MemberController {
private MemberService mService;
private MemberValidator memValidator;
private BCryptPasswordEncoder bcrypotPasswordEncoder;
@Autowired
public MemberController(MemberService mService , MemberValidator memValidator , BCryptPasswordEncoder bcrypotPasswordEncoder) {
this.mService = mService;
this.memValidator = memValidator;
this.bcrypotPasswordEncoder = bcrypotPasswordEncoder;
}
}
<@RequiredArgsConstructor 어노테이션 활용한 후>
@Slf4j
@Controller
@RequiredArgsConstructor
public class MemberController {
private final CommonService cService;
private final MemberService mService;
private final MemberValidator memValidator;
private final BCryptPasswordEncoder bcrypotPasswordEncoder;
private final ServletContext application;
private final ChatService chService;
}
@RequiredArgsConstructor 를 사용하고 난 뒤, 코드가 매우 깔끔하게 변경된 것을 볼 수 있습니다.
'⋆ 。゜☁︎ 。⋆ 。゜☾゜。⋆⋆ 。゜☁︎ 。⋆ 。゜☾゜。⋆ > Spring' 카테고리의 다른 글
[SpringBoot] 로그 레벨(Log Level)이란? 로그레벨 설정하기 (0) | 2023.10.19 |
---|---|
[SpringBoot] log4j의 additivity 옵션 (0) | 2023.10.19 |
[SpringBoot] @Controller와 @RestController 차이점 (1) | 2023.10.19 |
[SpringBoot] 로그설정 - log4j2 (1) | 2023.10.19 |
[Springboot] Springboot 프로젝트 생성 및 셋팅하기 (1) | 2023.09.24 |