Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 스프링 mvc2 - 검증
- 2024 정보처리기사 수제비 실기
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch13
- 스프링 mvc1 - 서블릿
- 2024 정보처리기사 시나공 필기
- 스프링 mvc1 - 스프링 mvc
- 스프링 고급 - 스프링 aop
- 스프링 mvc2 - 로그인 처리
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch8
- 게시글 목록 api
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch14
- @Aspect
- 자바의 정석 기초편 ch3
- 스프링 mvc2 - 타임리프
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch4
- 자바 기본편 - 다형성
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch9
- jpa 활용2 - api 개발 고급
- 스프링 입문(무료)
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch7
Archives
- Today
- Total
나구리의 개발공부기록
의존관계 자동주입, 의존관계 주입방법, 옵션처리, 생성자 주입 선택, 롬복과 최신 트랜드 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 핵심원리 - 기본편
의존관계 자동주입, 의존관계 주입방법, 옵션처리, 생성자 주입 선택, 롬복과 최신 트랜드
소소한나구리 2024. 1. 31. 12:00 출처 : 인프런 - 스프링 핵심원리 - 기본편(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 다양한 의존관계 주입방법
1) 의존관계 주입 방법
- 생성자 주입
- 수정자 주입(setter 주입)
- 필드 주입
- 일반 메서드 주입
(1) 생성자 주입
- 생성자를 통해서 의존 관계를 주입하는 방법(지금까지 계속 진행 해왔던 방법)
- 생성자 호출 시점에 딱 1번만 호출 되는 것이 보장되며 불변, 필수 의존 관계에 사용함
- 생성자가 딱 1개만 있으면 @Autowired를 생략해도 스프링 빈에 자동 주입되며 생략 하는 방식을 주로 사용함
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired // 생략해도 의존관계가 자동으로 주입이 됨
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
(2) 수정자 주입
- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입
- 선택, 변경 가능성이 있는 의존관계에 사용하며 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법임
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
** 참고
- @Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생하며 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정
- 생성자 주입과 수정자 주입이 동시에 있다면 생성자 주입이 먼저 실행 됨(객체를 생성할 때 애초에 생성자를 호출하기 때문)
- 수정자 주입은 실행 순서가 보장이 안되기 때문에 멀티쓰레드 환경에서 순서를 보장 할 수 없음
** 자바빈 프로퍼티 규약
- 자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만듦
- 자세한 내용은 자바빈프로퍼티 검색
- 자바빈 프로퍼티 규약 예시
class Data {
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
(3) 필드 주입
- 이름 그대로 필드에 바로 주입하는 방식
- 코드가 간결하여 과거에 많이 사용 했지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있음
- DI 프레임워크가 없으면 아무것도 할 수 없음
- 애플리케이션의 실제 코드와 관계없는 테스트 코드, 스프링 설정을 목적으로하는 @Configuration 같이 특별한 곳 말고는 사용 금지!
@Component
public class OrderServiceImpl implements OrderService {
@Autowired private final MemberRepository memberRepository;
@Autowired private final DiscountPolicy discountPolicy;
}
** 참고
- 순수한 자바 테스트 코드에는 @Autowired가 동작하지 않으므로 @SpringBootTest처럼 스프링 컨테이너를 테스트에 통합한 경우에만 사용 가능
- @Bean에서 파라미터에 의존관계는 자동 주입이되어 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결 할 수 있음
@Bean
OrderService orderService(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
return new OrderServiceImpl(memberRepository, discountPolicy);
}
(4) 일반 메서드 주입
- 일반 메서드를 통해서 주입
- 한번에 여러 필드를 주입 받을 수 있으나 일반적으로 잘 사용하지 않음
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
** 참고
- 당연한 이야기지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작함
- 스프링 빈이 아닌 일반 자바 클래스에서 @Autowired를 적용해도 아무 기능도 작동하지 않음
2. 옵션 처리
1) @Autowired의 옵션
- 주입할 스프링 빈이 없어도 의존관계 자동 주입이 동작해야 할 때가 있음
- 그러나 @Autowired만 사용하면 required옵션의 기본값이 true로 되어있어서 자동 주입 대상이 없으면 오류가 발생함
(1) 자동 주입 대상을 옵션으로 처리하는 방법
- @Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
- @Nullable : 자동 주입할 대상이 없으면 null이 입력됨
- Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력됨
** 참고
- @Nullable, Optional은 스프링 전반에 지원이 되기 때문에 생성자 자동 주입에서 특정 필드에만 적용하는 등으로 사용할 수 있음
- 해당 테스트에서 주입받는 Member가 스프링 빈이 아니기 때문에 IDE에서 @Autowired가 빨간줄로 표시되지만 테스트 자체는 정상적으로 동작함
(2) 옵션 테스트 및 출력 결과
- @Autowired가 제대로 동작하지 않는 상황을 테스트
- 스프링 빈이 아닌 Member클래스를 수정자 의존관계로 주입
- 테스트를 실행해보면 setNoBean은 애초에 호출 자체가 안되며 @Nullable 옵션을 사용한 테스트는 null이 입력되고 Optional을 사용한 테스트는 Optional.empty가 입력된 것을 확인할 수 있음
package hello.core.autowired;
public class AutowiredTest {
private static final Logger log = LoggerFactory.getLogger(AutowiredTest.class);
@Test
void autowired() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
// Bean으로 등록되지 않은 Member클래스를 @Autowired로 주입
@Autowired(required = false) // 호출 자체가 안됨
public void setNoBean1(Member noBean1) {
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("TestBean.setNoBean2 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("TestBean.setNoBean3 = " + noBean3);
}
}
}
/* 실행 결과
TestBean.setNoBean2 = null
TestBean.setNoBean3 = Optional.empty
*/
3. 생성자 주입을 선택해라
1) 생성자 주입을 권장하는 이유
- 과거에는 수정자주입과 필드주입을 많이 사용 했으나 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장함
(1) 불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 변경할 일이 없고 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됨(불변해야함)
- 수정자 주입 사용시 setXxx메서드를 public으로 열어두어야 하는데 누군가 실수로 변경할 수도 있고 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아님
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없기에 불변하게 설계 할 수 있음
(2-1) 누락
- 수정자 주입의 경우 순수한 자바 코드를 단위 테스트 하는 경우에 코드 누락이 발생할 수 있음
(2-2) 수정자 주입
- 의존관계를 수정자 주입(setter)로 작성
- @Autowired가 프레임워크 안에서 동작할 때에는 의존관계가 없으면 오류가 발생하지만 이번에는 자바 코드로만 단위 테스트를 수행
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
// 수정자 주입
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
// ... 구현된 코드 생략
}
(2-3) 순수한 자바코드로 테스트
- 순수한 자바 코드로만 수정자로 의존관계를 주입 후 OrderServiceImpl의 메서드를 테스트하기위해 코드를 작성할 때 Repository와 할인정책을 입력하지 않아도 컴파일 에러가 발생하지 않음
- 그러나 실행하면 Null Point Exception이 발생하는데 memberRepository, discountPolicy 모두 의존관계 주입이 누락이 되었기 때문임
- 만약 애플리케이션에 이렇게 적용이되어 애플리케이션이 실행이 되면 해당 코드에 사용자가 접근 했을 때 오류가 발생하기 때문에 상당히 치명적인 버그가 발생하게 되는 것임
package hello.core.order;
class OrderServiceImplTest {
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
Order order = orderService.createOrder(1L, "itemA", 10000);
}
}
(2-4) 생성자 주입과 final키워드
- 생성자 주입을 사용하면 위 테스트 코드에서처럼 주입 데이터를 누락했을 때 바로 컴파일 오류가 발생하여 개발 시점에 오류를 바로잡을 수 있으며 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있음
- 또한 생성자 주입을 사용하면 주입대상인 필드에 final 키워드를 사용할 수 있어 생성자에서 값을 설정하는 코드를 빼먹으면 컴파일오류가 발생하여 컴파일 시점에 오류를 막아줌
- 아래의 예시처럼 final 키워드를 사용하면 생성자의 값을 생성하기위한 필수 필드인 discountPolicy의 값이 누락이 되면 컴파일 시점에 오류 발생함
- 컴파일 오류는 세상에서 가장 빠르고 좋은 오류임
** 참고
- 생성자 주입을 제외한 나머지 주입방식은 모두 생성자 이후에 호출 되므로 필드에 final 키워드를 사용할 수 없음
- 오직 생성자 주입만 final 사용가능함
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
// this.discountPolicy = discountPolicy; 누락시 컴파일 오류
}
}
(2-5) 생성자 주입을 사용하여 테스트 진행
- OrderServiceImpl에 생성자를 사용하면 순수 자바로 테스트 코드를 작성할 때 필드를 빼먹지 않도록 모두 컴파일 오류가 발생함
- 올바른 테스트를 수행하기위해, 리포지토리와 할인정책을 생성하여 OrderServiceImpl의 객체를 생성할때 인수로 입력하면 오류 없이 테스트 코드를 작성할 수 있고 동작도 정상적으로 됨
package hello.core.order;
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 생성자를 통해서 각 객체를 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
// 기존 코드 생략
}
// 정상적인 테스트
package hello.core.order;
class OrderServiceImplTest {
@Test
void createOrder() {
MemberRepository memberRepository = new MemoryMemberRepository();
memberRepository.save(new Member(1L, "MemberA", Grade.VIP));
OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new RateDiscountPolicy());
Order order = orderService.createOrder(1L, "itemA", 10000);
assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
(3) 정리
- 생성자 주입 방식을 선택하는 이유는 여러가지가 있지만 생성자 주입 방식은 프레임워크에 의존하지 않고 순수한 자바 언어의 특징을 잘 살리는 방법임
- 기본으로 생성자 주입을 사용하고 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하는 것을 권장함 (동시에 사용 가능함)
- 그리고 필드 주입은 테스트코드외에는 사용하지 않는 것을 권장함
4. 롬복과 최신 트랜드
1) 최적화
- 개발을 해보면 대부분의 의존관계가 불변이여서 필드에 final을 사용하게 되는데 생성자도 만들어야하고 주입 받은 값을 대입하는 코드도 만들어야 하는 등 번거로운 부분이 많아짐
- 생성자주입을 필드주입처럼 편하게 사용하도록 최적화를 진행
(1) @Autowired 생략
- 아래의 코드처럼 생성자가 딱 1개만 있으면 자동 주입하는 @Autowired를 생각할 수 있음
- 그러나 여전히 생성자가 남아있음
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// @Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
// 기존 코드 생략
}
(2) 롬복 라이브러리 적용
- build.gradle에 아래의 환경설정 및 라이브러리 추가 후 gradle을 새로고침(코끼리 모양)
- 과거에는 설정 -> Annotation Processors 검색 -> Enable annotation processing 체크 후 재시작해야 했지만 인텔리제이가 롬복을 내장 탑재한 이후 해당 설정은 하지 않아도 동작함
// gourp, version 바로 아래에 추가
//lombok 설정 추가 시작
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//lombok 설정 추가 끝
dependencies {
// dependencies에 lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//lombok 라이브러리 추가 끝
}
(3) 롬복 동작 확인, HelloLombok클래스 생성
- @Getter, @Setter : 선언된 변수를 가지고 setter, getter를 만들어 줌
- @ToString : 선언된 변수를 가지고 toString을 전부 만들어 줌
- 롬복은 실무에서 정말 많이 사용함
@Getter
@Setter
@ToString
public class HelloLombok {
private String name;
private int age;
public static void main(String[] args) {
HelloLombok helloLombok = new HelloLombok();
helloLombok.setName("hello");
helloLombok.setAge(20);
System.out.println("helloLombok.getName() = " + helloLombok.getName());
System.out.println("helloLombok.getAge() = " + helloLombok.getAge());
// @ToString 적용
System.out.println("helloLombok = " + helloLombok);
}
}
(4) 롬복으로 OrderServiceImpl 최적화 적용
- 롬복의 @RequiredArgsConstructor 애노테이션을 사용 하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어 줌 (코드에는 보이지 않지만 실제 호출이 가능)
- build.classes에 컴파일된 클래스를 열어보면 기존의 생성자가 그대로 추가 되어있는 것을 확인 할 수 있음
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 생성자 주입 전체를 생략할 수 있음
/*
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
} */
// ... 기존 코드 생략
}
(5) 정리
- 최근에는 생성자를 딱 1개 두고 @Autowired를 생략하는 방법을 주로 사용함
- 여기에 Lombok 라이브러리의 @RequiredArgsConstructor를 함께 사용하여 기능은 제공하고 코드를 깔끔하게 할 수 있음