AOP가 적용된 Target 메서드를 호출 할 때 실제 메서드가 호출되는 것이 아니라 Advice가 요청을 대신 랩핑(Wrraping) 클래스로써 받고 그 랩핑 클래스가 Target을 호출
Spring AOP : Proxy 기반 AOP
Spring AOP의 특징
프록시 기반의 AOP 구현체
스프링 빈에만 AOP를 적용할 수 있음
모든 AOP 기능을 제공하는 것이 목적이 아니라 스프링 IoC와 연동
프록시 패턴
프록시 패턴을 적용하는 이유 : 기존 코드 변경없이 접근 제어 또는 부가 기능 추가
만약 성능(시간)을 측정하는 기능을 추가해야 한다면?
@Service
public class EventServiceImpl implements EventService {
@Override
public void createEvent() {
long begin = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Created an event");
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void publishEvent() {
long begin = System.currentTimeMillis();
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Published an event");
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void deleteEvent() {
System.out.println("Deleted an event");
}
}
성능(시간)을 측정하는 부가적인 기능을 추가할 경우 기존 코드를 수정해야 하는 상황이 발생
프록시 패턴을 이용하여 기존 클래스를 두고 프록시 클래스를 작성하여 기능을 위임하고 부가적인 기능을 작성할 수 있음
@Primary
@Service
@RequiredArgsConstructor
public class ProxyEventServiceImpl implements EventService {
private final EventServiceImpl eventServiceImpl;
@Override
public void createEvent() {
long begin = System.currentTimeMillis();
eventServiceImpl.createEvent();
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void publishEvent() {
long begin = System.currentTimeMillis();
eventServiceImpl.publishEvent();
System.out.println(System.currentTimeMillis() - begin);
}
@Override
public void deleteEvent() {
eventServiceImpl.deleteEvent();
}
}
프록시 클래스를 활용하여 기능을 위임
하지만 이 경우에 모든 클래스에 대해 프록시 클래스를 작성해야 하는 비용이 발생
추가로 프록시 클래스 내에도 동일한 기능에 대한 중복 코드가 발생하고 다른 클래스에서 재사용이 어려움
Spring AOP 등장 배경
이러한 문제를 Spring IoC Container가 제공하는 기반 시설과 Dynamic Proxy를 사용하여 해결
동적 프록시 : 동적으로 프록시 객체를 생성하는 방법
Java가 제공하는 방법 : Interface 기반 Proxy 생성
Spring IoC : 기존 Bean을 대체하는 Dynamic Proxy Bean을 만들어 등록 시켜줌
plugins {
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.24"
}
group = "com.ruthetum"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-batch")
implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.h2database:h2")
runtimeOnly("com.mysql:mysql-connector-j")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.springframework.batch:spring-batch-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
Spring Batch 기본 용어
간단한 Job을 선언하는 방법을 확인하기 전에 기본적인 Spring Batch의 용어들에 대해 간단하게 설명드리겠습니다.
JoLauncher : Job을 실행시키는 컴포넌트
Job : 배치 작업
JobRepository : Job 실행과 Job, Step을 저장
Step : 배치 작업의 단계
ItemReader, ItemProcesser, ItemWriter : 데이터를 읽고 처리하고 쓰는 구성
Job은 1개 이상의 Step으로 구성되어 배치 작업을 수행합니다.
후에 설명을 드리겠지만 Step 같은 경우 Chunk 기반의 작업을 수행하는 경우와 Tasklet 기반의 작업을 수행하는 경우로 구분됩니다.
일반적으로 Chunk 기반 스텝을 많이 사용합니다.
Tasklet 스탭은 하나의 트랜잭션 내에서 작동하고, 단순한 처리를 할 때 사용하게 됩니다.
2-2. Simple Job 생성하기
기본 용어를 이해한 상태에서 간단한 Job을 구성해보도록 하겠습니다.
먼저 Java와 Spring Batch 4 기준으로 간단한 Job을 구성하게 되면 아래와 같은 형태로 작성되어집니다.
@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // 생성자 DI를 위한 lombok 어노테이션
@Configuration
public class SimpleJobConfiguration {
private final JobBuilderFactory jobBuilderFactory; // 생성자 DI 받음
private final StepBuilderFactory stepBuilderFactory; // 생성자 DI 받음
@Bean
public Job simpleJob() {
return jobBuilderFactory.get("simpleJob")
.start(simpleStep1())
.build();
}
@Bean
public Step simpleStep1() {
return stepBuilderFactory.get("simpleStep1")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
})
.build();
}
}
각 컴포넌트 및 라인에서 의미하는 바는 아래와 같습니다.
@Configuration - Spring Batch의 모든 Job은 @Configuration으로 등록해서 사용합니다.
jobBuilderFactory.get("simpleJob") - simpleJob 이란 이름의 Batch Job을 생성합니다. - job의 이름은 별도로 지정하지 않고, 이렇게 Builder를 통해 지정합니다.
stepBuilderFactory.get("simpleStep1") - simpleStep1 이란 이름의 Batch Step을 생성합니다. - jobBuilderFactory.get("simpleJob")와 마찬가지로 Builder를 통해 이름을 지정합니다.
tasklet((contribution, chunkContext)) - Step 안에서 수행될 기능들을 명시합니다. - Tasklet은 Step안에서 단일로 수행될 커스텀한 기능들을 선언할때 사용합니다. - 여기서는 Batch가 수행되면 log.info(">>>>> This is Step1") 가 출력되도록 합니다.
기본적으로 Spring 및 Spring Batch에서 활용되는 어노테이션 및 주요 컴포넌트는 대부분 동일합니다.
다만 Spring Batch의 버전이 올라가면서 몇몇 어노테이션 지원 및 Spring Batch에서 배치를 실행하기 위해 정의하는 Job에 대한 설정 방법에 일부 수정이 있습니다.
[Batch 4 vs. Batch 5] JobBuilderFactory, StepBuilderFactory deprecated
먼저 Job과 Step을 선언하는 경우JobBuilderFactory, StepBuilderFactory는 Batch 5를 기준으로 지원하지 않게 되었습니다.
이에 따라 더이상 Builder Factory를 사용하지 않고JobBuilder,StepBuilder를 사용합니다.
[Batch 4 vs. Batch 5] JobRepository, TransactionManager
Spring Batch 5에서는 내부의 필요한 객체를 명시적으로 표시해주는 형태로 변경되었습니다.
이에 따라 기존에는 생략했던 JobRepository, TransactionManager를 선언하여 Job을 구성하게 됩니다.
변경점을 고려하여 위에서 작성했던 예제 코드를 Kotlin과 Srping Batch 5 기준으로 작성하면 아래와 같이 변경됩니다.
@Configuration
class SimpleJobConfiguration(
private val jobRepository: JobRepository,
private val transactionManager: PlatformTransactionManager,
) {
private val log = logger()
@Bean
fun simpleJob(): Job {
return JobBuilder("simpleJob", jobRepository)
.start(simpleStep1())
.build()
}
@Bean
fun simpleStep1(): Step {
return StepBuilder("simpleStep1", jobRepository)
.tasklet(simpleTasklet(), transactionManager)
.build()
}
fun simpleTasklet(): Tasklet {
return Tasklet { _, _ ->
log.info(">>>>> This is Step1");
RepeatStatus.FINISHED
}
}
}
실행 결과는 아래와 같습니다.
정상적으로 Job과 Step이 실행됨을 확인할 수 있습니다.
2-3. MySQL 환경에서 Spring Batch 실행해보기
이전 과정에서 굉장히 간단하게 Spring Batch가 수행되었습니다. Spring Batch는 어플리케이션 코드만 작성하면 되는구나! 라고 생각하실수 있으실텐데요.
실제로는 그렇지 않습니다. Spring Batch에선 메타 데이터 테이블들이 필요합니다.
Spring Batch의 메타 데이터는 다음과 같은 내용들을 담고 있습니다. - 이전에 실행한 Job이 어떤 것들이 있는지 - 최근 실패한 Batch Parameter가 어떤것들이 있고, 성공한 Job은 어떤것들이 있는지 - 다시 실행한다면 어디서 부터 시작하면 될지어떤 Job에 어떤 Step들이 있는지 - Step들 중 성공한 Step과 실패한 Step들은 어떤것들이 있는지
일반적인 웹 애플리케이션(Tomcat + Spring MVC)의 경우 큰 데이터를 읽고, 가공하고, 저장한다면 서버는 CPU 및 I/O 등의 자원을 소모하기 때문에 다른 요청을 처리하지 못 하게 됩니다. 또한 하루에 1번 또는 단발적으로 수행되는 이러한 요청을 API로 구성하는 것은 리소스적으로 낭비입니다.
이렇게 단발적으로 대용량 데이터를 처리하는 애플리케이션을 배치 애플리케이션이라고 합니다.
배치 애플리케이션의 경우 데이터가 너무 많아서 처리 중 실패가 나는 경우 실패한 지점부터 작업을 처리하거나, 같은 파라미티로 같은 함수를 실행할 경우 실행 여부에 따라 작업을 제어해야할 수도 있습니다.
즉, 비즈니스 로직 외에 부가적으로 신경써야 할 부분들이 많다는 것을 알 수 있습니다.
여기서 한가지 생각해볼것이, 웹 애플리케이션을 개발할 때 저희는 비지니스 로직에 최대한 집중할 수 있습니다. 그건 왜일까요? 바로 Spring MVC를 사용하기 때문입니다. Spring MVC를 사용함으로 비지니스 로직에 최대한 집중할 수 있었습니다.
그럼 Spring에서 이런 배치 어플리케이션을 지원하는 모듈이 없을까요? Spring 진영에선 Spring Batch가 있습니다.
배치 애플리케이션
사용자의 개입 없이 일괄적으로 작업을 처리하는 애플리케이션 (대체로 대용량 데이터를 처리)
서비스를 운영하는 관점에서 주기적으로 작업을 처리하기 위해 배치 애플리케이션 사용
배치 애플리케이션이 필요한 경우
필요한 데이터를 모아서 처리해야할 때 (e.g. 월별 거래 명세서 생성)
일부러 지연시켜 처리할 때 (e.g. 주문한 상품을 바로 배송 처리하지 않고, 일정 시간 뒤 처리)
자원을 효율적으로 활용해야할 때 (e.g. 트래픽이 적은 시간 대에 서버 리소스를 활용)
1.1. Spring Batch?
Spring Batch 프로젝트는 Accenture와 Spring Source의 공동 작업으로 2007년에 탄생했습니다.
Spring Batch는 Spring의 특성을 그대로 가져왔습니다. 그래서 DI, AOP, 서비스 추상화 등 Spring 프레임워크의 3대 요소를 모두 사용할 수 있으면서, Accenture의 Batch 노하우가 담긴 아키텍처를 사용할 수 있습니다.
본격적인 글을 작성하기에 앞서 본 시리즈의 게재 목적과 함께 작성 배경을 정리해보고자 합니다.
0.1. 배경
현재 업계를 보면 많은 회사들에서 목적 및 상황에 맞게 다양한 언어와 기술을 사용하고 있습니다.
다만 국내의 회사들 중 흔히 서버 혹은 백엔드 개발자로 분류되는 직군에서는 여러가지 사유로 인해 스프링 프레임워크를 많이 사용하고 있습니다.
그리고 데이터를 효과적으로 처리하기 위해 배치 애플리케이션을 구현할 때에도 이러한 배경 및 배경이 갖고 있는 다양한 장점 때문에 스프링 배치는 좋은 옵션으로 선택받습니다.
하지만 스프링 배치의 경우 스프링 MVC에 비해 비교적 사용 및 사용자가 적고, 이에 따른 인과 결과로 인해 국내에 양질의 자료가 많이 존재하지 않습니다.
개인적으로 생각할 때 2019~2020년 경에 향로 선생님이 블로그에 연재하신 Spring Batch 가이드가 국내에 존재하는 자료들 중 매우 친절하고 자세하게 설명이 되어있다고 생각합니다.
하지만 과거에 연재된 만큼 현재 작성 시점 기준과 비교해봤을 때 스프링 배치의 최신 버전이 변경됐고(4.x → 5.x), 현재 저는 Kotlin을 애용하고 있었기에 블로그에 게시된 내용을 바로바로 예제로 작성하는 과정에서 일부 지연이 있었습니다.
따라서 이번 기회에 위의 두 가지 사안을 반영해서 향로 선생님께서 작성하신 Spring Batch 가이드를 2024년 기준으로 오마주하는 형태로 내용을 정리하고자 합니다.
0.2. 작성 방향
내용은 향로 선생님의 글을 기반으로 아래의 내용이 포함될 예정입니다.
원본 글의 요약
버전 업그레이드에 따른 변경사항 적용
Kotlin 예제 코드
추가적으로 학습한 내용
원본 글에 대한 인용 및 요약은 아래와 같이 음영 처리된 상자로 표시합니다.
Spring Batch 프로젝트는 Accenture와 Spring Source의 공동 작업으로 2007년에 탄생했습니다. Accenture는 수년간의 노력으로 그들만의 배치 프레임워크를 만들었고, 그를 통해 얻은 경험을 가지고 있었습니다. 즉, Accenture의 배치 노하우 & 기술력과 Spring 프레임워크가 합쳐져 만들어진 것이 Spring Batch입니다.
본 게시글을 효과적으로 읽기 위해서는 각 게시글의 원본(향로 선생님의 글) 내용을 우선적으로 읽어보시는 것을 권장드립니다.
0.3. 작성 범위
현재 목표로 하는 커버 범위는 게재된 Spring Batch 가이드 내에서 아래 글들을 대상으로 계획하고 있습니다.