티스토리 뷰

아주 기본적인건데 삽질을 많이 했다. 나름 spring과 오랜 시간을 보내며 조금 안다고 생각을 했는데 한참 부족한것 같다. 지난번에 Springboot Batch Tasklet에 대해 글을 쓰며 두가지 방식에 소개를 했었는데 이와 관련해 @Value로 application.yml 파일에서 값을 가져오는 부분의 이슈를 발견했다. 무슨 문제인지 보자. 

 

application.yml

test:
  oingdaddy: Oing is the prettiest in the world!

 

test.oingdaddy 라는 값을 작성하는 tasklet에서 가져다 쓰고 싶은 경우이다. 일반적인 Spring에서 그러하듯 @Value("${test.oingdaddy}) 를 통해서 값을 가져올 것이다. 

 

Case1. 하나의 클래스에서 Tasklet Job을 모두 처리

SampleTasklet.java

@Configuration
public class SampleTasklet {

    @Value("${test.oingdaddy}")
    private String oing;
    
    //중략

    @Bean
    public Step sampleTaskletStep(SampleStepListener stepListener) {
    
        System.out.println(oing + "#########################################");
        
        return stepBuilderFactory.get("sampleTaskletStep")
                .tasklet((contribution, chunkContext) -> {
                    for(int inx = 0 ; inx < 20 ; inx++) {
                        log.info("[step] : " + inx);
                    }
                    return RepeatStatus.FINISHED;
                })
            .build();
    }
}

이렇게 일반적으로 사용하는 경우는 oing이라는 변수에 application.yml에 정의한 값이 잘 들어가진다. 

 

Case2. 두개의 클래스에서 Tasklet Job을 모두 처리 (Tasklet 분리)

SampleTasklet.java

@Configuration
public class SampleTasklet {
    
    //중략
    
    @Bean
    public Step sampleTaskletStep() {
        return stepBuilderFactory.get("sampleTaskletStep").
                tasklet(new OuterTasklet())
                .build();
    }
}

 

OuterTasklet.java

@Component
public class OuterTasklet implements Tasklet {
    
    private static final Logger log = LoggerFactory.getLogger(OuterTasklet.class);
    
    @Value("${test.oingdaddy}")
    private String oing;
    
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        
        System.out.println("#####################" + oing);
        
        IntStream.range(1, 20).forEach(
                i -> {
                    log.info("[step1] : {}", i);
                    try { Thread.sleep(100); } catch (InterruptedException e) { }
                }
        );
        return RepeatStatus.FINISHED;
    }
}

전에 설명한대로 똑같은 로직인데 OuterTasklet이라는 클래스로 Tasklet 업무만 따로 뺀 경우이다. test.oingdaddy도 OuterTasklet에서 불러서 사용을 하려고 한다. 두 클래스가 각각 @Configuration, @Component 가 정의되어 있어서 bean이 loading이 되는 것은 문제가 없을것이라 판단했다. 하지만 Case2는 test.oingdaddy 값을 가져올 수 없었다

 

 

이렇게 @Value로 받고 싶은 값이 null 로 출력이 된다.

 

호출을 하는 녀석(SampleTasklet.java)에서 @Value를 정의하면 잘 가져온다. 하지만 호출을 당하는 녀석(OuterTasklet.java)에서는 값을 가져오지 못한다. 

 

왜 안될까.. 고민을 많이 했다. 글을 쓰는 지금에야 Case1과 Case2를 비교하며 접근을 하고 있지만 당시에는 Case2만 다루고 있어서 더욱 삽질을 많이 한것 같다. 구글링을 해보니

'yaml 파일은 properties 파일과 다르게 값을 그대로 못가지고 와서 YamlParser 같은걸 만들어줘야한다', 'model을 정의해서 그것에 값을 받고 그 model을 주입받는 형식으로 해라', 'factory 클래스를 만들어서 해봐라', '@PostConstruct를 사용해봐라', '@DependsOn을 써봐라' 등등 많은 해결책이 있었지만 내가 적용을 잘 못한것인지 잘 안되었다.

 

그중 되는것이 하나 있었는데 바로 이 경우였다. 

@Component
public class OuterTasklet implements Tasklet {
    
    private static final Logger log = LoggerFactory.getLogger(OuterTasklet.class);
     
    private static String oing;

    @Value("${test.oingdaddy}") 
    public void setServerUrl(String oing) { 
        this.oing = oing; 
    } 
    
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        
        System.out.println("#####################" + oing);
        
        IntStream.range(1, 20).forEach(
                i -> {
                    log.info("[step1] : {}", i);
                    try { Thread.sleep(100); } catch (InterruptedException e) { }
                }
        );
        return RepeatStatus.FINISHED;
    }
}

 

 

오우 잘 되네? 이게 잘 되는 이유는 oing이라는 변수를 static으로 생성을 했기 때문이다. static은 bean보다 생성이 빨리 되어 먼저 생성이 된 이후 생성이 된 bean에서 사용이 되기 때문에 이렇게 하면 정상적으로 결과가 출력이 된다. 만약 private String oing 이라고만 변수를 선언했으면 똑같이 null이 나온다. 변수를 static으로 선언해서 사용해도 괜찮다면 이렇게 그냥 사용하면 된다

 

하지만 static으로 선언해서 사용하기 싫다. 또 고민을 했다

 

더 찾아보니 bean간에 injection을 모두 마친 후에 @Value 라는 녀석이 작업을 시작한다고 한다. @Value 라는 녀석을 아예 사용하지 않고 해결할 방법은 없을까 하다가 다음과 같은 방법으로 결정을 했다. 


해결방법

SampleTaklet.java

@Configuration
public class SampleTasklet {

    @Autowired
    private Environment env;

    //생략
    
    @Bean
    public Step sampleTaskletStep() {
        return stepBuilderFactory.get("sampleTaskletStep").
                tasklet(new OuterTasklet(env))
                .build();
    }
}

OuterTasklet.java

@Component
public class OuterTasklet implements Tasklet {
    
    private static final Logger log = LoggerFactory.getLogger(OuterTasklet.class);
    
    private Environment env;
    
    public OuterTasklet(Environment env) {
        this.env = env;
    }
    
    
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        
        System.out.println("#####################" + env.getProperty("test.oingdaddy"));
        
        IntStream.range(1, 20).forEach(
                i -> {
                    log.info("[step1] : {}", i);
                    try { Thread.sleep(100); } catch (InterruptedException e) { }
                }
        );
        return RepeatStatus.FINISHED;
    }
}

호출하는 쪽에서 Environment를 주입받아서 이것을 호출받는 쪽으로 넘겨준다. 그리고 받은 값을 지역변수에 넣어 준다음에 사용을 하면 된다. Environment의 객체이기 때문에 getProperty() 를 통해서 application.yml에 담겨진 값을 꺼내서 사용을 할 수 있다. 새로운 클래스의 추가나 복잡한 로직의 추가 없이 깔끔하게 잘 동작을 한다. 

 

 

역시 오잉이가 제일 예쁘다.

 

 

별것도 아닌게 왜 안되지? 라고 접근했다가 spring 공부를 덕분에 많이 했다. 

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
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
26 27 28 29 30 31