빈 생명주기( Bean Lifecycle )
# 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 에플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다
- 스프링 빈은 간단하게 다음과 같은 라이프사이클을 가진다
객체 생성 -> 의존관계 주입
- 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.
따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다
# 스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료
- 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
- 소멸전 콜백 : 빈이 소멸되기 직전에 호출
빈 생명주기 콜백 지원 3가지 방법
# 인터페이스
클래스명 implements InitializingBean, DisposableBean
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메세지");
}
@Override
public void destroy() throws Exception {
}
- 단점 :
스프링 전용 인터페이스에 의존적으로 설계해야한다
초기화 소멸 메서드의 이름을 변경할 수 없다
내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다
인터페이스를 사용하는 초기화, 종료 방법은 스프링 초창기에 나온 방법들이고 지금은 거의 사용하지 않는다.
# Bean 설정 정보에 초기화 메서드, 종료 메서드 지정
@Bean(initMethod = "init", destroyMethod = "close")
- 해당하는 메서드를 클래스 내부에 넣으면 된다
- 장점 :
메서드 이름 자유롭게 가능하다
스프링 빈이 스프링 코드에 전혀 의존하지 않는다
코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 적용 가능하다
- 단점 :
@Bean으로 등록하면 destroyMethod에 default값으로 infer_method라는게 있는데 (inferred)
외부 라이브러리 대부분 close나 shutdown을 종료 메서드로 사용
inferred은 추론으로 close나 shutdown이라는 함수가 있으면 추론해서 불러준다
추론기능이 사용하기 싫으면 ""로 등록해주어야 한다 ( blank )
# @PostConstruct @PreConstruct 어노테이션 지원
@PostConstruct
public void init(){
connect();
call("초기화 연결 메세지");
}
@PreDestroy
public void close(){
}
- 특징
javax에서 지원하는 어노테이션이기에 다른 패키지에서도 지원한다
매우 편리하며, 스프링에 종속적인 기술이 아니라 자바 표준이라 다른 컨테이너에서도 잘 동작한다
컴포넌트 스캔과도 어울리며, 유일한 단점으로는 외부 라이브러리에서는 적용하지 못한다는 것
외부 라이브러리를 초기화 종료해야하면 @Bean의 initMethod destroyMethod를 사용하면 된다
그래서 2번 기능(Bean 설정 정보를 통한 메서드 지정방식)도 알아야 한다!!
Assertion.assertThat 기능 비교 ( org.assertj )
# AssertJ는 많은 assertion(직역: 주장, 행사)을 제공하는 자바 라이브러리이다. 에러 메세지와 테스트 코드의 가독성을 매우 높여주고 각자 좋아하는 IDE에서 쓰기 굉장히 쉽다.
# JUnit5의 경우, assertEquals(expected, actual)과 같이 두 개의 인자를 받아서 비교를 하지만,
AssertJ는 메소드 체이닝을 통해 가독성을 높여주는 특징이 있습니다. assertEquals()는 왼쪽이 expected인지 actual인지 혼동될 여지가 있지만, assertThat()은 actual 인자 하나만 요구하고 그 뒤로 메소드 체이닝을 하므로 actual과 expected를 명확하게 구분지어준다는 장점이 있다
# Test Fail Message
- JUnit5의 경우, 마지막 인자값에 선택적으로 메시지를 넣어줌으로써 테스트 실패 메시지를 명시할 수 있는데, AssertJ에서는 as()를 호출하여 사용합니다. 단, assertion이 수행되기 전에 사용해야합니다.
assertThat(a).as("check %s's age", a).isEqualTo(b);
# Filtering assertions
- AssertJ를 사용하면 간단하게 테스트할 데이터들을 필터링할 수 있습니다.
class MemberTest {
private Member dexter = new Member("dexter", 12, ADMIN);
private Member james = new Member("james", 30, ADMIN);
private Member park = new Member("park", 23, USER);
private Member lee = new Member("lee", 33, USER);
private List<Member> members = newArrayList(dexter, james, park, lee);
@Test
void sample() throws Exception {
// 1 type 필드가 ADMIN인 멤버만 필터링
assertThat(members)
.filteredOn("type", ADMIN)
.containsOnly(dexter, james);
// 2 람다식을 활용하여 필터링이 가능합니다
assertThat(members)
.filteredOn(m -> m.getType() == USER)
.containsOnly(park, lee);
// 3 메서드를 통해 다양한 조건을 넣을 수 있습니다.
assertThat(members).
filteredOn("type", in(ADMIN, USER))
.containsOnly(dexter, james, park, lee);
// 4 메서드를 통해 다양한 조건을 넣을 수 있습니다.
assertThat(members)
.filteredOn("type", not(ADMIN))
.containsOnly(park, lee);
// 5 필터링은 연쇄적으로 진행할 수 있습니다.
assertThat(members)
.filteredOn("type", ADMIN)
.filteredOn(m -> m.getAge() > 20)
.containsOnly(james);
}
}
# Collection Data Extracting
- List에서 멤버들의 필드를 테스트하려면?
// 기존 동작 방식
@Test
void no_extracting() throws Exception{
List<String> names = new ArrayList<>();
for (Member member : members) {
names.add(member.getName());
}
assertThat(names).containsOnly("dexter", "james", "park", "lee");
}
@Test
void extracting() throws Exception{
// 위와 동일하게 동작
assertThat(members)
.extracting("name")
.containsOnly("dexter", "james", "park", "lee");
// 여러 데이터의 경우 튜플로 동작
assertThat(members)
.extracting("name", "age")
.contains(
tuple("dexter", 12),
tuple("james", 30),
tuple("park", 23),
tuple("lee", 33)
);
}
# Soft Assertions
- 보통 테스트를 진행할 때 앞의 assertThat이 실패하면 해당 테스트는 중지된다
- Soft Assertions을 이용하면 우선 모든 assertThat을 실행하고 해당 실패 내역을 확인할 수 있다
@Test
void softAssertion() throws Exception{
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(dexter.getAge()).as("Dexter Age Test").isEqualTo(11);
softAssertions.assertThat(james.getAge()).as("James Age Test").isEqualTo(31);
softAssertions.assertThat(park.getAge()).as("Park Age Test").isEqualTo(24);
softAssertions.assertThat(lee.getAge()).as("Lee Age Test").isEqualTo(32);
softAssertions.assertAll();
}
# File Assertions
- 파일에 대한 테스트를 제공한다
- 해당 데이터가 파일인지, 존재하는 지, 등에 대한 테스트 진행 가능하고, 파일 내용을 읽어 테스트를 진행할 수 있다
@Test
void file() throws Exception{
File file = writeFile("Temp", "You Know Nothing Jon Snow");
// 1 해당 파일이 존재하는지, 파일이 맞는지, 상대 경로인지
assertThat(file).exists().isFile().isRelative();
// 2 해당 값의 시작, 끝 값과 포함하는 갓 등을 테스트
assertThat(contentOf(file))
.startsWith("You")
.contains("Know Nothing")
.endsWith("Jon Snow");
}
# Exception Assertions
- 예외를 테스트하는 방법이다.
// 1 assertThatThrownBy에 해당 예외가 발생하는 코드를 작성한다
// 예외 종류, 메시지, 스택 트레이스등에 대한 정보를 테스트할 수 있습니다.
@Test
void exception() throws Exception {
assertThatThrownBy(() -> throwException())
.isInstanceOf(Exception.class)
.hasMessage("예외 던지기!")
.hasStackTraceContaining("Exception");
}
// 2 catchThrowable에서 예외가 발생하는 코드를 작성하고
// 해당 throwable을 위와 같은 방법으로 테스트할 수 있습니다.
@Test
void thrownTest() throws Exception{
Throwable throwable = catchThrowable(() -> throwException());
assertThat(throwable).isInstanceOf(Exception.class).hasMessage("예외 던지기!");
}
private void throwException() throws Exception {
throw new Exception("예외 던지기!");
}
# Custom Comparision Assertions
- 따로 Comparison을 커스텀하여 테스트를 진행한다
@Test
void equalTest() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
// 테스트 실패
// assertThat(item1).isEqualTo(item2);
assertThat(item1).isNotEqualTo(item2);
assertThat(item1)
.usingComparator(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
return o1.getName().compareTo(o2.getName());
}
})
.isEqualTo(item2);
assertThat(item1)
.usingComparator((a, b) -> a.getName().compareTo(b.getName()))
.isEqualTo(item2);
}
# Field Comparisons
- 위와 같이 Comparator를 커스텀할 수도 있지만, 기본적인 필드 비교는 지원해준다
@Test
void comparison() throws Exception{
Item item1 = new Item("item1", 1000);
Item item2 = new Item("item1", 1000);
Item item3 = new Item("item1", 3000);
// 1 해당 객체의 필드 값이 같은지 비교해준다
assertThat(item1).isEqualToComparingFieldByField(item2);
// price field가 달라 오류
//assertThat(item1).isEqualToComparingFieldByField(item3)
// 2 주어진 필드만 같은지 비교
assertThat(item1).isEqualToComparingOnlyGivenFields(item2, "name");
assertThat(item1).isEqualToComparingOnlyGivenFields(item3, "name");
// 3 주어진 필드만 제외하고 같은지 비교
assertThat(item1).isEqualToIgnoringGivenFields(item2, "price");
assertThat(item1).isEqualToIgnoringGivenFields(item3, "price");
// 4 Null필드 값을 제외하고 같은지 비교
Item item4 = new Item(null, 1000);
assertThat(item1).isEqualToIgnoringNullFields(item4);
}
# isSameAs
- 단위 테스트에서 객체들의 주소값을 비교해주는 메서드
assertThat(a).isSameAs(b);
assertThat(a).isNotSameAs(c);
# isEqualTo
- isEqualTo는 대상의 내용자체를 비교한다.
String a = "monster";
String b = "monster";
assertThat(a).isEqualTo(b)
# isInstanceOf
- 비교값이 매쳐의 타입과 동일한지 여부를 체크한다. any와는 다르게 매쳐의 값은 비교값과 연관없는 경우에도
사용할 수 있다.
#스프링 프레임워크 #스프링 빈 스코프 #빈 lifecycle #빈 등록 방식 #Assertion
'서버개발' 카테고리의 다른 글
[Spring / JPA] 프로젝트 설계를 위한 어노테이션 (0) | 2022.02.14 |
---|---|
[JPA / DB] JPA 정의, Entity 관계모델(E-R Model) (0) | 2022.02.05 |
[Spring] 싱글톤과 컴포넌트스캔, 메타 어노테이션 (0) | 2022.01.19 |
[Spring] Spring 프레임워크와 Bean 등록, 컴포넌트 어노테이션 (0) | 2022.01.16 |
[Spring] Spring 기본개념 (IoC와 DI, 객체지향 4대원칙, SOLID 등) (1) | 2022.01.11 |