2023. 12. 14. 00:16ㆍspring
과제
어떤 POJO는 사용하기 전에 특정한 초기화 작업을 거쳐야 한다.
예를 들면 파일을 열거나, 네트워크/DB에 접속하거나, 메모리를 할당하는 등 선행 작업이 필요한 경우이다.
대개 이런 POJO는 그 생명을 다하는 순간에도 폐기 작업을 해줘야 한다.
IoC 컨테이너에서 빈을 초기화 및 폐기하는 로직을 커스터마이징 하라
해결책
자바 구성 클래스의 @Bean 정의부에서 initMethod, destroyMethod 속성을 설정하면 스프링은 이들을 각각 초기화, 폐기 콜백 메서드로 인지한다. POJO 메서드에 각각 @PostConstruct 및 @PreDestroy를 붙여도 마찬가지다.
또 스프링에서 @Lazy를 붙여 느긋한 초기화 (주어진 시점까지 빈 생성을 미루는 기법)를 할 수 있고 @DependsOn으로 빈을 생성하기 전에 다른 빈을 먼저 생성하도록 강제할 수 있다.
풀이
POJO 초기화/폐기 메서드는 @Bean으로 정의한다. 쇼핑몰 애플리케이션에서 체크아웃 기능을 구현해보자. 카트에 담긴 상품 및 체크아웃 시각을 텍스트 파일로 기록하는 기능을 Cashier 클래스에 추가하자
public class Cashier {
private String fileName;
private String path;
private BufferedWriter writer;
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setPath(String path) {
this.path = path;
}
public void openFile() throws IOException {
File targetDir = new File(path);
if (!targetDir.exists()) {
targetDir.mkdir();
}
File checkoutFile = new File(path, fileName + ".txt");
if (!checkoutFile.exists()) {
checkoutFile.createNewFile();
}
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(checkoutFile, true)));
}
public void checkout(ShoppingCart cart) throws IOException {
writer.write(new Date() + "\t" + cart.getItems() + "\r\n");
writer.flush();
}
public void closeFile() throws IOException {
writer.close();
}
}
openFile() 메서드는 우선 데이터를 써넣을 대상 디렉터리와 파일이 있는지 확인한 뒤, 주어진 시스템 경로에 있는 텍스트 파일을 열어 writer 필드에 할당한다.
checkout() 메서드를 호출할 때마다 날짜와 카트 항목을 이 텍스트 파일에 덧붙인다.
closeFile() 메서드는 파일을 닫고 시스템 리소스를 반납한다.
Cashier 빈 생성 이전에 openFile() 메서드를, 폐기 직전에 closeFile() 메서드를 각각 실행하도록 자바 구성 클래스에 빈 정의부를 설정한다.
@Configuration
@PropertySource("classpath:discounts.properties")
@ComponentScan("com.spring.study.chapter02.shop")
public class ShopConfiguration {
@Value("classpath:banner.txt")
private Resource banner;
@Bean(initMethod = "openFile", destroyMethod = "closeFile")
public Cashier cashier() {
String path = System.getProperty("java.io.tmpdir") + "/cashier";
Cashier c1 = new Cashier();
c1.setFileName("checkout");
c1.setPath(path);
return c1;
}
}
@Bean의 initMethod, destroyMethod 속성에 각각 초기화, 폐기 작업을 수행할 메서드를 지정한다.
@PostConstruct와 @PreDestroy로 POJO 초기화/폐기 메서드 지정하기
자바 구성 클래스 외부에(@Component를 붙여) POJO 클래스를 정의할 경우에는 @PostConstruct와 @PreConstruct를 붙여 해당 클래스에 초기화/폐기 메서드를 지정한다.
@Component
public class Cashier {
@Value("checkout")
private String fileName;
@Value("c:/Temp/cashier")
private String path;
private BufferedWriter writer;
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setPath(String path) {
this.path = path;
}
@PostConstruct
public void openFile() throws IOException {
File targetDir = new File(path);
if (!targetDir.exists()) {
targetDir.mkdir();
}
File checkoutFile = new File(path, fileName + ".txt");
if (!checkoutFile.exists()) {
checkoutFile.createNewFile();
}
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(checkoutFile, true)));
}
public void checkout(ShoppingCart cart) throws IOException {
writer.write(new Date() + "\t" + cart.getItems() + "\r\n");
writer.flush();
}
@PreDestroy
public void closeFile() throws IOException {
writer.close();
}
}
@Component를 붙였기 때문에 스프링의 관리 대상이 된다.
스프링은
openFile() 메서드에 @PostConstruct가 달려있기 때문에 빈 생성 이후에 이 메서드를 실행하고,
closeFile() 메서드에 @PreDestroy가 달려있기 때문에 빈 폐기 이전에 이 메서드를 실행한다.
@Lazy로 느긋하게 POJO 초기화하기
기본적으로 스프링은 모든 POJO를 시동과 동시에 초기화한다. 하지만 환경에 따라 빈을 처음으로 요청하기 전까지 초기화 과정을 미루는 게 더 나을 때도 있다.
느긋하게 초기화하면 시동 시점에 리소스를 집중 소모하지 않아도 되므로 전체 시스템 리소스를 절약할 수 있다.
@Component
@Scope("prototype")
@Lazy
public class ShoppingCart {
private List<Product> items = new ArrayList<>();
public void addItem(Product item) {
items.add(item);
}
public List<Product> getItems() {
return items;
}
}
@DependsOn으로 초기화 순서 정하기
POJO가 늘어나고 분산 선언된 많은 POJO가 서로를 참조하다 보면 경합 조건이 일어나기 쉽다.
그럴때 @DependsOn 애너테이션은 빈의 초기화 순서를 보장한다.
@Configuration
@Import(SequenceConfig.class)
public class SequenceGeneratorConfiguration {
@Value("#{uniqueSequence}")
private Sequence sequence;
@Bean
@DependsOn("datePrefixGenerator")
public Sequence sequence() {
sequence.setId(sequenceGenerator().getSequence());
return sequence;
}
@Bean
SequenceGenerator sequenceGenerator() {
SequenceGenerator seqgen = new SequenceGenerator();
seqgen.setPrefix("30");
seqgen.setSuffix("A");
seqgen.setInitial(100000);
return seqgen;
}
}
'spring' 카테고리의 다른 글
스프링 레시피 CH2.16 애스펙트 포인트컷 재사용하기 (1) | 2023.12.18 |
---|---|
스프링 레시피 CH2.9 후처리기를 만들어 POJO 검증/수정하기 (0) | 2023.12.16 |
스프링 레시피 CH2.7 프로퍼티 파일에서 로케일마다 다른 다국어 메시지를 해석하기 (0) | 2023.12.14 |
스프링 레시피 CH2.6 외부 리소스(텍스트, XML, 프로퍼티, 이미지 파일)의 데이터 사용하기 (1) | 2023.12.12 |
스프링 레시피 CH2.4 @Resource와 @Inject를 붙여 POJO 자동 연결 하기 (0) | 2023.12.11 |