~ 목차 ~
의존성과 의존성 관계 주입(Dependency Injection, DI)🦄
* 의존성
- 한 객체가 다른 객체를 사용할 때
- A클래스가 B클래스 또는 인터페이스를 사용하고 있는 경우
→ A가 B를 의존한다는 것이 의미하는 것은 의존 대상 B가 변하면, 그것이 A에 영향을 미친다는 것.
* 의존성 관계 주입(Dependency Injection, DI)
- 의존성이 강할 때 나타나는 문제점을 해결하기 위해 사용
- 의존성 주입 : 두 객체 간의 관계(의존성)를 맺어주는 것
- 방법 : 생성자 주입, 필드 주입, 수정자 주입
→ 객체를 주입받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해 넘겨받는다는 것 = 결합도 낮춤(유연한 구조).
<의존성 관계 주입 예시>
Class Minicar {
private Motor motor;
public Minicar() {
motor = new TripleMotor();
}
}
- 의존성
interface Motor {
public void booster();
}
- interface 설정
class TripleMotor implements Motor {
public void booster() {
....
}
}
- 의존성 관계 주입 : TripleMotor 클래스가 Motor 인터페이스를 구현
필드 주입 방식(Field Injection)🦄
* 필드주입 방식 (in Spring Framework)
- @Autowired : 필드에서 바로 @Autowired 어노테이션을 통해 의존성을 주입하는 방식
→ 매우 간단하다.
→ 주입된 객체를 Immutable 한 상태(불변)를 만들 수 없다,
Spring이 아니면 해당 필드에 Injection 할 수 있는 방법이 없다.
* 수정자 주입 방식(Setter Injection)
- Setter를 기반으로 한 의존성 주입(DI)
→ 주입받는 객체가 변경될 가능성이 있는 경우에 사용
→ 실제 개발에서는 의존 관계 주입의 변경이 필요한 상황이 거의 없으며,
수정자 주입을 사용하게 되면 불필요하게 객체의 수정 가능성을 열어두게 되는데,
이는 OOP(객체 지향 프로그래밍)의 5가지 개발 원칙 중에 OCP(Open-Closed Principal, 개방-폐쇄 원칙) 를 위반하게 됩니다.
* 생성자 주입 방식(Constructor Injection)
- 생성자 주입을 통해 변경의 가능정을 배제하고, 불변성을 보장 받을 수 있기 때문에 주입받을 필드를 final으로 선언 가능
- 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 Spring 프레임워크에서 지원
- final 키워드를 붙임으로써 Lombok과 결합되어 코드를 간결하게 작성
(Lombok에는 final 변수를 위한 생성자를 대신 생성해주는 @RequiredArgsConstructor 어노테이션이 존재합니다.)
→ 객체의 불변성(Immutable)을 확보
컴파일 시점에 누락된 의존성을 확인할 수 있다는 장점
NullPointerException을 방지( 객체 생성 시점에 필수적으로 빈 객체 초기화를 수행 )
※ OCP(Open-Closed Principle, 개방-폐쇄 원칙)
: 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다는 프로그래밍 원칙
<의존성 관계 주입 예시>
@Controller
public class FieldInjectionController {
@Autowired
private FieldInjectionService fieldInjectionService;
}
- 필드 주입 방식
@Controller
public class SetterInjectionController {
private SetterInjectionService setterInjectionService;
@Autowired
public void setSetterInjectionService(SetterInjectionService setterInjectionService) { this.setterInjectionService = setterInjectionService;
}
}
- 수정자 주입 방식
@Controller
public class ConstructorInjectionController {
private ConstructorInjectionService constructorInjectionService;
// 생성자가 1개인 경우 @Autowired를 생략해도 주입 가능하도록 지원
@Autowired
public ConstructorInjectionController(ConstructorInjectionService constructorInjectionService) { this.constructorInjectionService = constructorInjectionService;
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------------
@Controller
@RequiredArgsConstructor
public class ConstructorInjectionController {
private final ConstructorInjectionService constructorInjectionService;
}
- 생성자 주입 방식
* 생성자 주입 장점
- 순환 참조 방지( StakOverflowError 방지 )
- 만약 두 클래스가 서로의 메서드를 호출하고 있다면 코드 호출 시 new A(new B(new A(new B(new A...))) 같은 상황이 반복되기 때문에 StakOverflowError가 발생
- 수정자 주입(Setter Injection)과 필드 주입(Field Injection) 방식은 빈을 먼저 생성한 후에 어노테이션이 붙은 필드에 해당하는 빈을 찾아서 주입하는 방식. 즉, 빈을 먼저 생성한 후에 필드에 대해서 주입하기 때문에 빈 객체를 생성한 시점에는 순환 참조의 발생 여부를 알 수가 없다. (생성 -> 주입)
- 생성자 주입(Constructor Injection) 같은 경우에는 생성자로 객체를 생성하는 시점에 필요한 빈을 주입.
다시 말하면 빈 객체를 생성하는 시점에 생성자의 파라미터 빈 객체를 찾아서 먼저 주입한 뒤에 주입받은 빈 객체를 이용하여 생성하게 됨. (주입 -> 생성)
때문에 런타임 시점이 아니라 애플리케이션 구동 시점에서 순환 참조 오류를 발견할 수 있게 됨.
Builder Pattern을 사용하는 이유🦄
1. 객체를 생성하기 위해서는 생성자 패턴, 정적 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있다.
[ 빌더 패턴(Builder Pattern)의 장점 ]
- 필요한 데이터만 설정할 수 있음
- 유연성을 확보할 수 있음
- 가독성을 높일 수 있음
- 변경 가능성을 최소화할 수 있음
2. 클래스 변수는 변경 가능성을 최소화하는 것이 좋다.
why? 만약 값을 할당하는 시점이 객체의 생성뿐이라면 객체에 잘못된 값이 들어왔을 때 그 지점을 찾기 쉬우므로 유지보수성이 훨씬 높아짐.
→ 변경 가능성을 최소화하는 가장 좋은 방법은 변수를 final로 선언함으로써 불변성을 확보하는 것
final로 강제할 수 있다면 가장 바람직하지만 final을 붙일 수 없는 경우라면 Setter를 넣어주지 않으면 된다.
1. 생성자 패턴, 정적 메소드 패턴을 이용하여 객체 생성
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private int height;
private int iq;
public User (String name, int height, int iq) {
this.name = name;
this.height = height;
this.iq = iq;
}
public static User of(String name, int height, int iq) {
return new User(name, 0, 180, 150);
}
}
2. 빌더(Builder)로 동적으로 처리하여 객체 생성
User user = User.builder()
.name("망나니 개발자")
.height(180)
.iq(150).build();
3. final로 선언함으로써 불변성을 확보
@Builder
@RequiredArgsConstructor
public class User {
private final String name;
private final int age;
private final int height;
private final int iq;
}
Builder Pattern을 구현할 필요가 없는 경우🦄
1. 객체를 생성을 라이브러리로 위임하는 경우
- 엔티티(Entity) 객체나 도메인(Domain) 객체로부터 DTO를 생성하는 경우라면 직접 빌더를 만들고 하는 작업이 번거로우므로 MapStruct나 Model Mapper와 같은 라이브러리를 통해 생성을 위임할 수 있다.
2. 변수의 개수가 2개 이하이며, 변경 가능성이 없는 경우
- 변수가 늘어날 가능성이 거의 없으며, 변수의 개수가 2개 이하인 경우에는 정적 팩토리 메소드를 사용하는 것이 더 좋을 수도 있다.
→ 변수의 개수와 변경 가능성 등을 중점적으로 보고 빌더 패턴을 적용할지 판단하면 된다.
Builder Pattern 사용 방법 - Lombok🦄
1. builder 사용( 함수 내에서)
// ✨ 필요한 값만 정의한다면, 나머지는 null로 자동 설정
User build = new User.Builder()
.email(email)
.password(password)
.name(name)
.build();
* 빌더 패턴을 사용해서 필요한 값만 설정 가능하고, 어떤 값을 설정하는지 명확하게 알 수 있기 때문에 가독성을 높일 수 있습니다.
2. @builder 설정
- 클래스 전체 Builder 적용
@Data
@Builder // ✨ 클래스 전체 필드를 빌더로 사용 가능!
public class MemberVO {
private String memberId;
private String memberName;
private String memberAge;
private String memberPhone;
private String memberAddr;
private String memberSex;
private String memberEmail;
private String memberDepartment;
private String memberLeaveYN;
}
// 사용예제 - 입력한 데이터만 조회 (R)
@GetMapping("/{memberId}")
public ResponseEntity<MemberVO> getMember(@PathVariable String memberId) {
MemberVO member = memberService.selectMember(memberId);
MemberVO builder = MemberVO.builder()
.memberId(member.getMemberId())
.memberName(member.getMemberName())
.memberAge(member.getMemberAge())
.memberDepartment(member.getMemberDepartment())
.memberEmail(member.getMemberEmail())
.memberPhone(member.getMemberPhone())
.memberSex(member.getMemberSex())
.memberAddr(member.getMemberAddr())
.memberLeaveYN(member.getMemberLeaveYN())
.build();
return ResponseEntity.ok(builder);
}
- 특정 생성자에서만 Builder 적용 가능
@Data
public class MemberVO {
@Getter(AccessLevel.NONE) // 해당 필드에 대한 getter 생성하지 않음
private String memberId;
private String memberName;
private String memberAge;
private String memberPhone;
private String memberAddr;
private String memberSex;
private String memberEmail;
private String memberDepartment;
private String memberLeaveYN;
@Builder
public MemberVO(String memberName, String memberAge, String memberPhone, String memberAddr, String memberSex, String memberEmail, String memberDepartment){
this.memberName = memberName;
this.memberAge = memberAge;
this.memberPhone = memberPhone;
this.memberAddr = memberAddr;
this.memberSex = memberSex;
this.memberEmail = memberEmail;
this.memberDepartment = memberDepartment;
}
}
// 사용예제 - member_id, member_leave_yn 출력X : NULL로 표현
@GetMapping("/{memberId}")
public ResponseEntity<MemberVO> getMember(@PathVariable String memberId) {
MemberVO member = memberService.selectMember(memberId);
MemberVO builder = MemberVO.builder()
.memberName(member.getMemberName())
.memberAge(member.getMemberAge())
.memberDepartment(member.getMemberDepartment())
.memberEmail(member.getMemberEmail())
.memberPhone(member.getMemberPhone())
.memberSex(member.getMemberSex())
.memberAddr(member.getMemberAddr())
.build();
return ResponseEntity.ok(builder);
}
https://wildeveloperetrain.tistory.com/139
https://mangkyu.tistory.com/163
'Back-End > JAVA' 카테고리의 다른 글
[JAVA] 스프링 IP 주소 가져오기 (0) | 2024.05.17 |
---|---|
[JAVA][자바] 자바 메모리 구조, 자바 변수의 종류 (1) | 2023.11.07 |
[JAVA] 반복문 - FOR문, FOREACH문 (0) | 2023.11.02 |
[프로그래머스 JAVA]문자열에서 대소문자 바꿔서 출력하기 (0) | 2023.09.28 |
[Java] 출력 방법 알아보기 (0) | 2023.09.27 |