반응형
Spring
11. 스프링은 무엇인가?
- 스프링은 좋은 객체지향 프로그래밍을 할 수 있게 도와주는 프레임워크이다.
12. 프레임워크 vs 라이브러리
- 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다.
- JUnit 라이프 사이클 속에서 내코드를 작성
- 어플리케이션의 flow를 누가 제어하는냐가 프레임워크인지 라이브러리인지 구분할 수 있다.
- 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.
- public static void main(String[] args) { new 함수 호출 등}
13. 스프링과 객체지향은 어떤 관계인가?
- 스프링의 IOC, DI는 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
- 스프링은 다음 기술로 다형성과 OCP,DIP를 가능하게 지원한다
- DI(Dependency Injection): 의존관계, 의존성 주입
- DI 컨테이너 제공
- 결과 : 클라이언트 코드의 변경 없이 기능 확장 -> 쉽게 부품을 교체하듯이 개발
- 위 기술을 활용하여 클라이언트의 코드 변경 없이 기능 확장할 수 있다
- 쉽게 부품을 교체하듯이 개발할 수 있다.
14. 스프링은 왜 만들어졌을까?
- 좋은 객체 지향 개발을 하려고 순수한 자바로 OCP DIP 원칙들을 지키면서 개발을 하기는 너무 할 일이 많다. 그래서 스프링프레임워크 (DI 컨테이너)를 만들게 된다.
15. 스프링의 특징은 무엇인가?
16. IOC란 무엇인가?
- 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라 한다.
17. DI란 무엇인가?
- 애플리케이션 실행 시점(런타임) 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
18. IOC 컨테이너 DI컨테이너
- AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC컨테이너 또는 DI 컨테이너라 한다.
- 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
- 스프링 컨테이너 (ApplicationContext)가 DI 컨테이너의 역할을 한다.
19. 스프링 컨테이너란 무엇인가?
- ApplicationContext(인터페이스) 를 스프링 컨테이너라 한다.
- 스프링컨테이너를 BeanFactory, ApplicationContext로 구분해서 이야기 하지만 BeanFactory를 직접 사용하는 경우는 BeanFactory(인터페이스)를 상속받고, 그외 여러가지 인터페이스(메세지소스, 환경변수)를 상속 받는다.
- 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이 너를 통해서 사용한다.
- 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
- 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
- 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService )
- 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너 를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드 를 사용해서 찾을 수 있다.
- 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.
- 코드가 약간 더 복잡해진 것 같은데, 스프링 컨테이너를 사용하면 어떤 장점이 있을까?
20. 스프링 빈이란 무엇인가?
- 스프링 컨테이너에서 관리하는 객체를 스프링 빈이라고 한다.
- 스프링 빈 조회시 상속관계에 주의한다.
- 부모 타입으로 조회하면, 자식 타입도 함께 조회된다.
- 그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
21. 스프링 컨테이너의 생성
- 스프링 컨테이너 생성
-
new AnnotationConfigApplicationContext(AppConfig.class)
- 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 여기서는 AppConfig.class 를 구성 정보로 지정했다.
-
- 스프링 빈 등록
- 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
- 빈이름은 항상 중복되지 않아야 한다.
- @Bean은 메소드 이름을 빈이름으로 사용한다.
- 스프링 의존관계 주입
- 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
- 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이 차이는 뒤에 싱글톤 컨테이너에서 설명한다.
다만 자바 코드로 스프링 빈(@Bean) 을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리
22. 싱글톤 컨테이너
- 스프링은 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다. - 스프링컨테이너는 등록된 빈 객체들을 싱글톤으로 관리한다.
- 싱글톤 패턴
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
싱글톤 방식의 주의점
- 스프링빈은 무상태로 설계해야한다.
- @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.
memberRepository() 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다 - 스프링 설정 정보는 항상 @Configuration 을 사용하자.
23. 컴포턴트 스캔
컴포넌트 스캔과 의존관계 자동 주입
- 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 등을 통해서 설정 정보에 직 접 등록할 스프링 빈을 나열했다.
- 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공
- @ComponentScan -> @Component로 등록된 클래스들을 찾아 자동으로 빈으로 등록해준다.
- 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다
- Component로 등록 된 Bean들의 의존관계를 주입해 줘야하기 때문에 Autowired로 주입해 준다.
- @Component 빈 이름은 class이름을 사용한다 (앞 글자만 소문자)
- @Bean은 메소드 이름을 빈이름으로 사용한다.
- @Autowired (의존관계 자동 주입)
- 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
- 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.
- 타입이 같은 빈이 여러개라면 에러가 날 것이다 ex) DiscountPolicy <- rate, fixed
컴포넌트 스캔 기본 대상
- @Component : 컴포넌트 스캔에서 사용
- @Controlller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
- 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
24. 의존관계 자동 주입
의존관계 주입 방법
- 생성자 주입
- 수정자 주입(Setter 주입)
- 필드 주입
- 일반 메서드 주입
생성자 주입
- 생성자를 통해서 의존 관계를 주입 받는 방법이다.
- 생성자 호출시점에 딱 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;
}
수정자 주입
- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다. 특징
- 선택 변경 가능성이 있는 의존관계에 사용
- 참고: @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.
- 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.
- 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.
- 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서 드를 통해서 값을 읽거나 수정하는 규칙
@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;
}
}
자바빈 프로퍼티 규약 예시
class Data {
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
필드 주입 (안티 패턴)
- 이름 그대로 필드에 바로 주입하는 방법이다.
- 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명 적인 단점이 있다.
- DI 프레임워크가 없으면 아무것도 할 수 없다.
- 순수자바 코드로 하는 테스트를 못함.
- 사용처 (사용하지 말자)
- 애플리케이션의 실제 코드와 관계 없는 테스트 코드 (SpringbootTest)
- 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용 (수동으로 @bean등록할 때?)
@Component
public class OrderServiceImpl implements OrderService {
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
}
참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스 프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
일반 메서드 주입
- 일반 메서드를 통해서 주입 받을 수 있다
- 한번에 여러 필드를 주입 받을 수 있다.
- 일반적으로 잘 사용하지 않는다. (Setter 주입이랑 유사하며, 다른 방식으로 사용하면 되기 때문)
@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;
}
}
참고: 어쩌면 당연한 이야기이지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동 작한다. 스프링 빈이 아닌 Member 같은 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.
옵션 처리
- 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
- 그런데 @Autowired 만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없 으면 오류가 발생한다.
- 자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
- @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
- Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.
결론: 생성자 주입을 사용해라
생성자 주입을 선택해라!
- 과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.
- 생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다
불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
- 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
- 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다. 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.
누락
- 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에 다음과 같이 수정자 의존관계인 경우
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 가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만, 지금은 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다. 이렇게 테스트를 수행하면 실행은 된다.
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
- 그런데 막상 실행 결과는 NPE(Null Point Exception)이 발생하는데, memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.
- 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl(); () <- 오류가 발생하는 부분, 생성자를 주입하라
orderService.createOrder(1L, "itemA", 10000);
}
final 키워드
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되 지 않는 오류를 컴파일 시점에 막아준다.
- 참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 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;
}
}
- 자바는 컴파일 시 점에 다음 오류를 발생시킨다.
java: variable discountPolicy might not have been initialized
롬복 라이브러리 적용
- 최근에는 생성자를 딱 1개 두고, @Autowired 를 생략하는 방법을 주로 사용한다.
- 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.
25. 빈 생명주기 콜백
스프링 빈의 이벤트 라이프사이클
- 스프링컨테이너생성 -> 스프링빈생성 -> 의존관계주입 -> 초기화콜백 -> 사용 -> 소멸전콜백 -> 스프링 종료
- 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
- 소멸전 콜백: 빈이 소멸되기 직전에 호출
- @PostConstruct(생성 전), @PreDestory(소멸전) 애노테이션 특징
- 최신 스프링에서 가장 권장하는 방법이다. 애노테이션 하나만 붙이면 되므로 매우 편리하다.
- 패키지를 잘 보면 javax.annotation.PostConstruct 이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.
- 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
- 컴포넌트 스캔과 잘 어울린다.
- 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 initMethod , destroyMethod 기능을 사용하자.
참고:
- 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애 플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다
- 객체의 생성과 초기화를 분리하자
- 생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다.
참조
계속 업데이트 예정
'프로그래밍 > 스프링 자바' 카테고리의 다른 글
TDD 방식으로 개발 예제 (Junit5) (0) | 2021.07.04 |
---|---|
[Java] JVM, 자바 메모리, GC (0) | 2021.07.03 |
객체 지향 프로그래밍이 정리 (OOP 정리) (0) | 2021.06.28 |
JUnit5 정리 (0) | 2021.06.28 |
TDD , 단위 테스트, 리팩토링 (0) | 2021.06.28 |