리액티브 프로그래밍이란?

리액티브 설계 원칙 (리액티브 선언문)

  1. Message Driven
  • 비동기 메시지 기반의 통신을 통해 구성요소들간의 느슨한 결합, 격리성을 보장합니다.
  1. Elastic (탄력성)
  • 시스템의 작업량이 변화하더라도 일정한 응답, 즉 시스템의 트래픽이 많든 적든 시스템에서 요구하는 응답성을 일정하게 유지하는 것입니다.
  1. Resilient (회복성)
  • 시스템에 장애가 발생하더라도 응답성을 유지하는 것을 의미한다. 시스템의 구성요소들이 독립적으로 분리되기 떄문에 장얘가 발생하더라도 전체 시스템은 여전히 응답 가능하고, 장애가 난 부분만 복구하면 된다는 의미입니다.
  1. Responsive (응답성)
  • 비동기 메시지 기반 통신을 바탕으로 한 회복성과 탄력성을 기반으로 즉각적으로 응답 가능한 시스템을 의미합니다.

Reactive 설계원칙

리액티브 프로그래밍(Reactive Programming)이란?

  • 비동기 메시지 기반으로 통신하는 높은 응답성을 가지는 Reactive System을 구축하는 데 필요한 프로그래밍 모델입니다.
  • 데이터 소스의 변경이 있을떄 마다 데이터를 전파하는 이벤트 기반 아키텍쳐를 사용합니다.
  • 선언형 프로그래밍 방식을 사용해 실행할 동작을 구체적으로 명시하지 않고, 목표만 선언합니다.
Read more

Spring Batch Step 파헤쳐보기

Step 개념

  • Batch Job 을 구성하는 독립적인 단계로 하나의 Job은 하나 이상의 Step으로 구성됩니다.
  • Spring Batch Job 구현체중 하나인 SimpleJob을 까보면 Job 은 내부 멤버변수로 Step List를 들고 있음을 확인할 수 있습니다.
    1
    2
    3
    4
    public class SimpleJob extends AbstractJob {
    private List<Step> steps = new ArrayList<>();
    //...
    }
    [2] Spring Batch Step

StepExecution 이란?

  • Step에 대한 한번의 시도를 의미하는 객체로서 Step 실행 중에 발생한 정보들을 저장하고 있는 객체입니다.
  • DB의 BATCH_STEP_EXECUTION 테이블과 1:1로 매핑됩니다.
  • Job이 실패해서 재수행하는 경우에는 실패한 Step에 대해서만 재시작하고, 성공한 Step은 생략합니다. ( 하지만 allowStartIfComplete 설정값을 변경하면 성공한 Step도 재시작되게 변경할 수 있습니다 )

예를 들어 Step 1,2,3이 존재하는데 Step2번이 실행중에 실패한다면 Step 1은 성공 , Step 2는 실패처리 됩니다. 그리고 Step 3는 실행되지 않습니다.
이 상태에서 Job을 재시작하면 Step2부터 재시작하여 Step2,Step3 가 수행됩니다.

  • Job을 구성하는 모든 Step의 실행 정보인 StepExecution이 완료 처리되어야만 JobExecution이 완료처리됩니다.

즉 Spring Batch 도메인 용어를 정리하면 하나의 Job은 여러개의 Step으로 구성되고, Job이 JobParameter를 주입받아 실행되는 객체가 JobInstance객체입니다.

JobInstance를 실행한 정보가 JobExecution이고, Job이 실행되면서 Job을 구성하는 Step들의 실행정보가 StepExecution입니다.

[1] Spring Batch Job과 Step에 관련된 도메인 객체들

Step간 데이터 공유하기 - ExecutionContext

  • ExecutionContext를 활용하면 Job내 Step간 데이터를 공유할 수 있습니다. 혹은 실패한 Step에서 Step 재시작시 실패 이전까지 작업했던 상태값들을 가져올 수 있습니다.
  • ExecutionContext는 Spring Batch에서 관리하는 key-value (Map) 컬렉션입니다.
  • StepExecution, JobExecution 객체의 멤버변수로 선언되고 , 각각 DB의 BATCH_JOB_EXECUTION_CONTEXT , BATCH_STEP_EXECUTION_CONTEXT 테이블에 1:1 매핑됩니다.
  • StepExecutionExecutionContext는 Step안에서만 공유됩니다. 즉 특정 Step에서만 접근이 가능합니다. 실패한 Step이 재시작된 경우도 이전까지 작업한 내용을 불러들일수 있습니다.
  • JobExeuctionExecutionContext는 모든 Step안에서 공유됩니다.
ExecutionContext

예시 코드

아래와 같이 ExecutionContextStepContribution 또는 ChunkContext를 통해 접근하고, 값을 넣어줄 수 있습니다.
넣어준 값은 BATCH_JOB_EXECUTION_CONTEXT , BATCH_STEP_EXECUTION_CONTEXT 테이블에 각각 직렬화되어 저장됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExecutionContextTasklet1 implements Tasklet {

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {

ExecutionContext jobExecutionContext = contribution.getStepExecution().getJobExecution().getExecutionContext();
ExecutionContext stepExecutionContext = contribution.getStepExecution().getExecutionContext();

jobExecutionContext.put("jobName", "developer"); //1. BATCH_JOB_EXECUTION_CONTEXT 에 저장 (모든 STEP에서 공유)
stepExecutionContext.put("stepName", "software"); // 2. BATCH_STEP_EXECUTION_CONTEXT 에 저장 (특정 STEP에서 공유)

return RepeatStatus.FINISHED;
}
}
BATCH_JOB_EXECUTION_CONTEXT 테이블
BATCH_STEP_EXECUTION_CONTEXT 테이블

Spring Batch에서 제공하는 Step 구현체(5)

  • Step 인터페이스를 AbstractStep이라는 추상 클래스에서 구현하고,AbstractStep 추상클래스를 구현하는 구조입니다.
  • Batch에서 제공하는 Step의 구현체는 아래와 같은 5개의 구현체가 존재합니다.
  1. Tasklet Step
  2. Partition Step
  3. Job Step
  4. Flow Step
  5. Decision Step
Spring Batch에서 제공하는 Step 구현체 종류

TaskletStep

  • RepeatTemplate을 사용해서 Tasklet 코드 block을 트랜잭션 경계 내(성공시 커밋,실패시 롤백)에서 반복해서 실행합니다.
    언제까지 반복해서 실행할것인가에 대한 판단은 Tasklet 객체에서 반환하는 RepeatStatus값에 의해 결정됩니다. RepeatStatus.FINISHED 와 같이 특정 RepeatStatus를 반환할떄까지 계속해서 실행합니다.

  • TaskletStep은 아래와 같은 tasklet 인터페이스를 구현하는 tasklet 구현체를 멤버변수로 가지고 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * Strategy for processing in a step.
    */
    public interface Tasklet {

    /**
    * @return an {@link RepeatStatus} indicating whether processing is
    * continuable. Returning {@code null} is interpreted as {@link RepeatStatus#FINISHED}
    *
    * @throws Exception thrown if error occurs during execution.
    */
    @Nullable
    RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;

    }
  • TaskletStep의 코드를 확인해보면 Tasklet 호출횟수를 조정하기 위한 RepeatTemplate 와 실행되야할 작업 그 자체 (tasklet) 구현체를 멤버변수로 들고있는것을 확인할 수 있습니다.

1
2
3
4
5
6
7
public class TaskletStep extends AbstractStep {

private RepeatOperations stepOperations = new RepeatTemplate();
//...
private Tasklet tasklet;
//...
}
  • 자주 사용되는 tasklet 구현체는 Spring Batch에서 이미 구현해놓았습니다. 이 중 ChunkOrientedTasklet구현체을 활용해 Chunk 단위로 배치 작업을 쪼개서 처리할 수 있습니다.
    Spring Batch에서 제공하는 Step 구현체 종류
Read more

Spring Batch Job의 구성

Spring Batch Job

  • 하나의 논리적인 배치작업을 의미합니다.
  • 배치작업을 어떻게 구성하고, 실행할것인지 명세해놓은 객체입니다.
  • 배치 작업을 구성하기 위한 최상위 인터페이스이며, Spring Batch가 기본 구현체를 제공합니다.
  • 여러 Step으로 구성될 수 있으며, 반드시 하나 이상의 Step으로 구성해야 합니다.
    ( Job - Step의 관계는 일대다관계입니다)

Job의 구현체

  • Spring Batch에서 제공하는 Job의 구현체는 2가지 종류가 있습니다.
  1. Simple Job : 순차적으로 Step을 실행시키는 Job을 의미합니다.
  2. Flow Job : 조건별로 분기를 따라 Step을 구성하여 실행시키는 Job을 의미합니다.

Job 구성방법

  • Spring Batch에서는 Job과 Step을 Spring Bean으로 관리한다. 때문에 @Configuration 설정 클래스에서 Job 객체를 등록해줄 수 있습니다.
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
@Bean
public Job helloJob() { // 1. Job 구성
return jobBuilderFactory.get("helloJob")
.start(helloStep1())
.next(helloStep2())
.build();
}
@Bean
public Step helloStep1() {// 2. Step 구성
return stepBuilderFactory.get("helloStep1")
.tasklet(((contribution, chunkContext) -> {
System.out.println("##### step1");
return RepeatStatus.FINISHED;
}))
.build();
}
@Bean
public Step helloStep2() {
return stepBuilderFactory.get("helloStep2")
.tasklet(((contribution, chunkContext) -> {
System.out.println("###### step2");
return RepeatStatus.FINISHED;
}))
.build();
}

Job Launcher 와 Job Parameter

  • JobLaunder 배치 작업을 실행하는 역할을 합니다.
  • JobParameter와 Job을 인자로 받아서 실행합니다.
[1] Job Launder Sequence Diagram
  • 예시코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class JobRunner implements ApplicationRunner {

private final Job job;
private final JobLauncher jobLauncher;

public JobRunner(Job job, JobLauncher jobLauncher) {
this.job = job;
this.jobLauncher = jobLauncher;
}

@Override
public void run(ApplicationArguments args) throws Exception {
JobParameters jobParameters = new JobParametersBuilder() // 1. Job Parameter를 생성합니다.
.addString("name", "user1")
.toJobParameters();

jobLauncher.run(job,jobParameters); // 2. Job Launcher가 주어진 Job과 Job Parameter를 받아서 Job을 실행합니다.

}
}

Job Instance

  • Job이 실행될떄 생성되는 Job의 논리적 실행 단위 객체. 즉 작업 실행을 의미한다.
  • Job과 JobInstance는 왜 구분해야 할까?
    • Job의 설정과 구성은 동일하지만 Job이 실행되는 시점에 처리하는 내용은 다르기 때문에 Job의 실행을 구분해야 합니다.
    • JobInstance는 Job과 JobParameter의 조합으로 생성하며, 처음 시작하는 Job과 Job Parameter의 경우는 새로운 Job Instance를 생성합니다.
    • 하나의 Job은 여러번 실행될 수 있으므로 Job과 JobInstance의 연관관계는 1:N의 관계입니다.
  • BATCH_JOB_INSTANCE 테이블에 매핑됩니다.
Job Runner에 의해 Job 실행시 BATCH_JOB_EXECUTION 테이블
  • JobInstance의 경우 동일한 Job과 Job Parameter의 조합인 경우 예외(JobInstanceAlreadyCompleteException)를 던집니다.즉 동일한 Job을 동일한 Job Parameter로 돌리면 중복 JobInstance로 판정됩니다.
    1
    2
    3
    4
    5
    6
    Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={name=user1}.  If you want to run this job again, change the parameters.
    at org.springframework.batch.core.repository.support.SimpleJobRepository.createJobExecution(SimpleJobRepository.java:139) ~[spring-batch-core-4.3.9.jar:4.3.9]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]

Job Parameter

  • 말그대로 Job을 실행할 때 함께 포함되어 사용되는 parameter 가진 도메인 객체입니다
  • 하나의 Job안에 존재하는 여러 JobInstance를 구분하기 위한 용도이다. 즉 JobInstance와 JobParameter는 1:1 관계입니다. (테이블 관계에서는 1:N)
  • BATCH_JOB_EXECUTION_PARAM 테이블과 매핑됩니다.
  • Spring Batch에서는 STRING, DATE, LONG, DOUBLE 타입을 지원을 하며, BATCH_JOB_EXECUTION_PARAM 테이블에서 타입별로 칼럼을 가지고 있습니다.

Job Parameter 생성방식

  1. Application 생성 시점에 외부 환경변수로 주입되는 방법

빌드한 Jar파일에 다음과 같이 외부 매개변수로 KEY-VALUE 형태로 값을 주입해줄 수 있다. 이떄 문자형이 아닌 경우는 별도로 (타입명)을 지정해주어야 합니다. 혹은 program argument로 key=value형태로 넣어줄 수 있습니다.

1
java -jar spring-batch-0.0.1-SNAPSHOT.jar name=cs seq\(long\)=2L date\(date\)=2023/10/27 age\(double\)=3 
  1. Application Code에서 직접 생성하는 방법
    1
    2
    3
    4
    5
    6
    JobParameters parameters = new JobParametersBuilder()
    .addString("name", "chansoo")
    .addLong("seq", 1l)
    .addDate("date", new Date())
    .addDouble("age", 16.5)
    .toJobParameters();
Job Runner에 의해 Job 실행시 BATCH_JOB_EXECUTION_PARAMS 테이블
  • 값을 주지 않으면 DATE_VAL의 경우는 기본값으로 1970-01-01 09:00:00, STRING_VAL의 경우 빈 문자열('') , LONG & DOUBLE_VAL의 경우는 0이 들어갑니다.
  1. SpEL 문법을 통해 생성하는 방법

Job Parameter를 꺼내오는 법

  1. Tasklet Based Step
  • tasklet의 경우는 StepContribution, ChunkContext 클래스에서 모두 JobParameter를 꺼내올 수 있습니다.
  • 차이점은 StepContribution은 JobParameters 타입의 객체를 반환하고, ChunkContext의 경우 Map 타입의 객체를 반환하는 차이점이 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Bean
    public Step step1() {
    return stepBuilderFactory.get("step1")
    .tasklet((contribution, chunkContext) -> {
    JobParameters params = contribution.getStepExecution().getJobExecution()
    .getJobParameters();
    String name = params.getString("name");
    Long seq = params.getLong("seq");
    Date date = params.getDate("date");
    Double age = params.getDouble("age");

    Map<String, Object> jobParameters = chunkContext.getStepContext()
    .getJobParameters();
    return RepeatStatus.FINISHED;
    })
    .build();
    }
Read more

Dynamic Proxy

Dynamic Proxy

  • Proxy class는 기존 코드에 영향을 주지 않으면서 타깃의 기능을 확장하거나 , 접근 방법을 제어할 수 있는 유용한 방법임에도 불구하고 ,
    다음과 같은 단점을 가지고 있다.

    1. 매번 새로운 Proxy class를 만들어야 한다.
    2. 모든 Method를 일일히 구현해서 타겟 객체에게 위임해주어야 한다.
    3. 부가 기능 코드가 중복될 가능성이 존재한다. 즉 부가 기능역할을 하는 코드가 여러 메소드에서 사용된다면 중복코드가 계속 들어갈 것이다.
  • 아래와 같은 인터페이스가 존재한다고 가정하자.

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Hello {

    String sayHello(String name);

    String sayHi(String name);

    String sayThank(String name);
    }

    구체 class는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HelloTarget implements Hello{
@Override
public String sayHello(String name) {
return "Hello," + name;
}

@Override
public String sayHi(String name) {
return "Hi," + name;
}

@Override
public String sayThank(String name) {
return "Thank," + name;
}
}

구체 class에 대한 Proxy 클래스는 구체 class와 동일한 인터페이스를 구현하고 , 부가기능 적용후 , 구체 클래스에게 위임한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HelloUppercase implements Hello{

private final Hello target; // 타깃 클래스

public HelloUppercase(Hello target) {
this.target = target;
}

@Override
public String sayHello(String name) {
return target.sayHello(name).toUpperCase();
}

@Override
public String sayHi(String name) {
return target.sayHi(name).toUpperCase();
}

@Override
public String sayThank(String name) {
return target.sayThank(name).toUpperCase();
}
}
  • 위처럼 부가기능이 모든 method에 대해 중복되서 나타는 문제점이 나타나는 것을 확인할 수 있다.

  • Dynamic Proxy를 통해 이런 문제를 해결할 수 있다. Dynamic Proxy란 ProxyFactory에 의해서 Reflection을 통해 런타임에 생성되는 Proxy객체를 말한다.

JDK Dynamic Proxy

  • JDK Dynamic Proxy는 타겟클래스가 구현한 인터페이스와 동일한 타입으로 만들어지는데, ProxyFactory에게 인터페이스 정보만 제공해주면 해당 인터페이스를 구현한 클래스의 객체를 자동으로 만들어준다.

  • 물론 부가 기능 코드는 직접 작성해야되는데, 이는 Proxy 객체와는 독립적으로 InvocationHandler 인터페이스를 구현한 오브젝트에 포함된다.

1
2
3
4
5
6
7
8
9

package java.lang.reflect;

public interface InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

  • Dynamic Proxy 객체는 클라이언트의 모든 요청을 Reflection으로 변환해서 InvocationHandler 구현 객체의 invoke 메소드로 넘긴다.

즉 다음과 같은 실행 흐름을 가진다.

1
2
3
4
5
6
7
8
- 프록시 생성 

ProxyFactory -> JDK Dynamic Proxy

- 프록시 동작 과정

특정 메소드 호출 -> JDK Dynamic Proxy -> InvocationHandler.invoke()
-> JDK Dynamic Proxy 가 결과값받아 처리
  • 위와 같은 실행흐름을 가지기 떄문에 모든 method 호출은 InvocationHandler.invoke()를 거치게된다. 즉 중복코드를 제거할 수 있다.

InvocationHandler를 앞서 만든 Proxy Class의 부가기능 코드에 적용해보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UppercaseHandler implements InvocationHandler {
private final Hello target; // 타깃에게 요청을 위임해야하기 때문에 타깃을 주입받는다.

public UppercaseHandler(Hello target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String ret = (String) method.invoke(target,args); // 타깃 클래스에게 위임
return ret.toUpperCase(); // 공통 부가 기능 수행
}
}

이제 실제로 Dynamic Proxy 를 생성해주는 코드를 보면 Proxy의 정적 팩토리 메소드를 통해 생성할 수 있다.

1
2
3
4
5
6
7
Hello proxy(){
return (Hello) Proxy.newProxyInstance(
getClass().getClassLoader(), // 동적으로 생성되는 프록시 클래스 로딩에 사용될 클래스 로더
new Class[]{Hello.class}, // 구현할 인터페이스
new UppercaseHandler(new HelloTarget()) // 부가기능 코드
);
}

Dynamic Proxy의 장점

  1. 인터페이스의 메소드가 늘어나도 , 클래스로 직접 구현한 프록시와 다르게 수정이 일어나지 않는다.

  2. 부가 기능 코드는 InvocationHandler 구현체에 들어있어서 , 타겟의 종류와 상관없이 적용가능하다.
    꼭 특정타입의 타겟이 아니라 , 다른 종류의 타겟에도 적용이 가능하다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class UppercaseHandler implements InvocationHandler {

    private final Object target; // 특정 타입이 아니라 , Object 타입

    public UppercaseHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object returnValue = method.invoke(target, args);
    if (returnValue instanceof String){
    return ((String) returnValue).toUpperCase();
    }
    return returnValue;
    }
    }

Dynamic Proxy 객체를 Spring Bean으로 어떻게 등록해야할까?

  • Spring 은 지정된 클래스 이름을 가지고 , Reflection을 통해서 해당 클래스의 객체를 생성한다.
1
Date now = (Date) Class.forName("java.util.Date").newInstance();

반면 Dynamic Proxy 객체 생성 방식은 Proxy 클래스의 newProxyInstance 정적 팩토리 메소드에 의해 생성되며 , 클래스 자체도 런타임에 결정된다. Spring은 어떤 클래스 타입을 해당 Proxy객체가 가질지 , 컴파일 타임에는 알수가 없다.

Factory Bean 을 통해 Dynamic Proxy를 Spring Bean으로 등록한다.

  • 부제 그대로 Spring은 Factory Bean을 통해 Dynamic Proxy 를 Spring Bean으로 등록한다.

  • Factory Bean이란 Spring을 대신해서 객체의 생성 로직을 담당하도록 만들어진 빈을 말하며 , Spring이 제공해주는 FactoryBean 인터페이스를 구현함으로서 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.springframework.beans.factory;

public interface FactoryBean<T> {

@Nullable
T getObject() throws Exception;

@Nullable
Class<?> getObjectType();

default boolean isSingleton() {
return true;
}
}

FactoryBean 인터페이스는 3가지 method로 구성되어있는데 ,
getObject() method에 Spring Bean으로 등록할 객체 생성로직이 들어가고 , 해당 객체 어떤 클래스 타입인지는 getObjectType() method에 들어간다.
isSingleton method는 getObject() method가 반환해주는 객체가 항상 동일한 객체인지 , 즉 싱글톤인지 여부를 명시한다.

아래와 같이 정적 팩토리 메소드를 통해서만 객체 생성을 할 수 있는 경우에 FactoryBean을 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
public class Message {
String text;

private Message(String text){
this.text = text;
}

public static Message newMessage(String text){
return new Message(text);
}
}

private 생성자이긴 하지만 Reflection으로 강제로 객체 생성을 하려면 할 수는 있다.
하지만 대부분의 경우 이렇게 private 생성자로 막아두는 경우는 사용하지말라는 이유가 있기 떄문에, 개발자가 열어둔 정적 팩토리 메소드를 통해서 생성하는게 안전하다.

따라서 아래와 같이 FactoryBean을 통해서 의도한 정적팩토리 메소드를 통해 객체 생성 & Spring Bean 등록을 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MessageFactoryBean implements FactoryBean<Message> {

private final String text;

public MessageFactoryBean(String text) {
this.text = text;
}

@Override
public Message getObject() throws Exception {
return Message.newMessage(text);
}

@Override
public Class<?> getObjectType() {
return Message.class;
}

@Override
public boolean isSingleton() {
return false; // Message 정적 팩토리 메소드는 매번 새로운 Message 객체를
// 반환함으로 false로 설정하지만 , 실제로 만들어진 Bean 객체는 싱글톤으로 Spring이 관리해줄 수 있다.
}
}

Factory Bean 설정

  • 일반 Bean과 다르게 Factory Bean의 경우 bean class property는 FactoryBean 이지만 , 실제로 반환되는 타입은 getObjectType method에 명시된 타입이다. 즉 위 예제에서는 Message 타입이 반환된다.
1
2
3
4
<bean id="message" class="MessageFactoryBean"> 
<!-- FactoryBean 타입으로 빈 클래스 프로퍼티 설정 -->
<constructor-arg name="text" value="Factory Bean"></constructor-arg>
</bean>
1
2
3
4
5
6
7
8
class MessageFactoryBeanTest {

@`Test
public void 반환타입은_GET_OBJECT_TYPE_메소드_타입이_반환된다(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
assertInstanceOf(Message.class,applicationContext.getBean("message"));
}
}

Proxy Factory Bean 방식의 한계점

  1. 한번에 여러 개의 클래스에 공통적인 부가 기능을 제공하는 것은 불가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UppercaseHandler implements InvocationHandler {

private final Object target;

public UppercaseHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 하나의 클래스에 부가 기능을 적용하고 있다.
Object returnValue = method.invoke(target, args);
if (returnValue instanceof String){
return ((String) returnValue).toUpperCase();
}
return returnValue;
}
}
  1. 하나의 타깃에 여러 부가기능을 적용하기 힘들다. 부가 기능 개수만큼 설정이 추가되야 한다.
Read more

Front Controller Pattern

Front controller pattern

front controller pattern 은 주로 web 환경에서 client 요청이 들어오면 먼저 공통적인 로직을 처리하는 하나의 controller를 두고, 해당 controller가 적합한 controller 를 호출하는 패턴이다.

front controller의 정확한 정의는 다음과 같다.

Front Controller is defined as “a controller that handles all requests for a Web site”. It stands in front of a web-application and delegates requests to subsequent resources. It also provides an interface to common behavior such as security, internationalization and presenting particular views to certain users

(ref - https://www.baeldung.com/java-front-controller-pattern)

Front controller pattern의 장점

  • front controller 가 결국 모든 controller이전에 수행됨으로 controller에서 발생할 수 있는 공통로직을 front controller에서 처리하고, 중복코드를 제거하고 유지보수성을 높여준다.
  • front controller를 제외한 나머지 controller는 servlet을 사용하지 않아도 된다.

Front controller pattern 은 MVC pattern과 함께 자주 쓰인다. 대표적으로 spring framework에서 사용하고 있으며,org.springframework.servlet.dispatcherServlet이 front controller이다.

UML diagram

front controller pattern은 front controller와 요청을 위임할 class (controller) 로 구성된다. 이떄 요청을 위임할 class는 공통 abstract class 또는 interface를 상속하고 있다.

Read more

MVC pattern

Model-View-Controller,MVC pattern은 사용자 인터페이스로부터 비즈니스 로직을 분리해서 application의 시각적인 요소에 변경사항이 비즈니스 로직에 영향없이 변경될 수 있도록 도와주는 패턴이다.

MVC 패턴은 Model , View , Controller로 구성되는데

  • Model : application의 데이터 , bussiness logic , business rule 을 뜻한다.
    *Model 을 뷰에 담을 데이터라고 한정적 정의하는 경우도 있고 (ref- https://developer.mozilla.org/ko/docs/Glossary/MVC)
    dao , service 계층의 business logic 을 모두 포함해 model 이라고 하는 경우도 있습니다.
    (ref - https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
    중요한 포인트는 UI와 application 비즈니스 로직을 분리시켜 서로 독립적으로 개발이 가능하도록 했다는 점입니다.
    dao, service , view에 대한 명확한 계층 구분은 multi-tier architecture에서 다루고 있습니다. (ref - https://en.wikipedia.org/wiki/Multitier_architecture)
  • View : 사용자 인터페이스 요소 (UI)
  • Controller : Model과 View 사이의 상호동작을 관리한다. 사용자의 요청에 따라 모델/뷰를 업데이트한다.

Read more

Spring validation - BindingResult

BindingResult

spring은 입력데이터에 대한 validation과 예외처리를 지원해준다. org.springframework.validation.BindingResult 가 validation 기능을 지원해주는 주요 객체 중 하나이다. BindingResult는 입력 form의 필드값 중에 오류가 있으면 오류정보를 담아둔다.

Read more

Servlet Filter 와 Spring Interceptor

Servlet Filter

  • servlet filter는 http 요청이 dispatcher servlet에 전달되기전에 호출되는 객체로서, 호출되는 순서는 아래와 같다.

HTTP message –> WAS –> Filter –> dispatcher servlet

  • dispatcher servlet: Tomcat과 같은 servlet container 안에 HTTP 요청을 가장 먼저 받아, 적합한 controller에게 요청을 forwarding 해주는 일종의 front controller
    (https://docs.spring.io/spring-framework/docs/3.0.0.M4/spring-framework-reference/html/ch15s02.html)

  • 주로 logging, encryption , input validation 과 같은 공통 관심사에 기능을 적용하고자 할떄 활용된다.

  • Filter chain : Filter는 chain 형태로 구성되어, 여러개의 필터를 끼울 수 있다.

Read more

spring type converter

spring 은 converter 인터페이스를 제공하여, 개발자가 spring에 추가적인 type 변환이 필요하면 이를 구현해서 등록할 수 있도록 지원해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package org.springframework.core.convert.converter;

/**
* A converter converts a source object of type {@code S} to a target of type {@code T}.
*/
@FunctionalInterface
public interface Converter<S, T> {

/**
* Convert the source object of type {@code S} to target type {@code T}.
* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
* @return the converted object, which must be an instance of {@code T} (potentially {@code null})
* @throws IllegalArgumentException if the source cannot be converted to the desired target type
*/
@Nullable
T convert(S source);
Read more