@PostConstruct를 활용한 Static객체에서  Non-Static변수 사용하기

 

스프링(스프링부트)을 사용하면서 어플리케이션 레벨에서 공통으로 사용되는 변수들은 보통 application.properties나 application.yml 에 선언하고 실제로 사용하는 클래스에는 아래의 코드처럼  @Value 어노테이션을 사용합니다.

 

아마 대부분의 개발자들은 이렇게 사용하고 있을텐데요. 
당연히, 이 변수들은 Non-Static 변수들입니다. 클래스의 인스턴스가 생성될 때까지는 변수에 Value가 할당되지 않습니다. 

 

그런데 개발을 하다보면, Static객체에 Non-Static변수를 사용하는 경우가 발생합니다.
어떤 경우가 있을까요?

 

아마, 클래스 전역에 사용해야 하는 로그출력등에 사용되는 라이브러리가 있을 수 있습니다.

 

얼마전 사내에서 개발되고 있는 프로젝트들에게 fluentd를 도입해서 
어플리케이션 로그들을 하나로 통합하는 파일럿을 진행을 했었는데, 스프링에서 원격의 fluentd에 로그을 보내는 방식이 
우리가 일반적으로 사용하는 Log4J과 동일한 방식으로 API를 제공하는
 fluent-logger라는 라이브러리를 사용했습니다. 

 

fluentd: https://www.fluentd.org

 

Fluentd | Open Source Data Collector

"Logs are streams, not files. I love that Fluentd puts this concept front-and-center, with a developer-friendly approach for distributed systems logging." Adam Wiggins, Heroku co-founder

www.fluentd.org

Gradle의 의존성 라이브러리 설정
// https://mvnrepository.com/artifact/org.fluentd/fluent-logger
compile group: 'org.fluentd', name: 'fluent-logger', version: '0.3.4'

GitHub: https://github.com/fluent/fluent-logger-java

 

fluent/fluent-logger-java

A structured logger for Fluentd (Java). Contribute to fluent/fluent-logger-java development by creating an account on GitHub.

github.com

 

어플리케이션의 공통관심사를 관장하는 AOP 컴포넌트 클래스에 아래의 코드만 추가해서 
원하는 로그를 Map객체로 매핑해서 쉽게 fluentd로 전송할 수 있습니다.
Log4J사용법과 동일합니다.

 

 

아래처럼 사용할 클래스 전역에 Static으로 선언하고 필요한 곳에서 로그을 찍듯이 사용하면 객체가 json형태로 바로(물론, fluent-logger 내부적으로는 버퍼를 통해서 비동기방식으로 처리됨) 전송됩니다.

import java.util.HashMap;
import java.util.Map;
import org.fluentd.logger.FluentLogger;

public class Main {
    private static FluentLogger LOG = FluentLogger.getLogger("app");

    public void doApplicationLogic() {
        // ...
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("from", "userA");
        data.put("to", "userB");
        LOG.log("follow", data);
        // ...
    }

 

그런데, 실제로 사용할때는 fluentd 인스턴스는 어플리케이션 서버와 같은 곳에 위치하지 않고, 다른 서버에 위치하게 됩니다. 
때문에 위의 샘플코드처럼 FluentLogger.getLogger("app")처럼 동일한 서버에 있는 fluentd를 호출하는 방식으로 사용하지 못합니다. 

 

원격에 있는 fluent에 로그를 전송하기 위해서는  Logger클래스에 서버정보를 추가로 매개변수값으로 넘겨야 합니다.

// for remote fluentd
private static FluentLogger LOG = FluentLogger.getLogger("app", "원격fluentd주소", 원격fluentd접속포트);

 

쓰다보니, 주제가 fluentd-logger라이브러리 내용으로 넘어가 버렸네요.

 

본 주제로 다시 돌어와서,
fluentd도 log4j와 마찬가지로 static 객체를 선언하고 사용합니다. 

그런데 추가되는 매개변수인 원격fluentd주소와 원격fluentd접속포트정보는

어플리케이션 레벨에서 전역으로 사용하는 성격이라  당연히 아래와 같이 @Value를 통해 non-static으로 사용하게 됩니다. 

@Slf4j
@Aspect
@Component
public class ApiCommonAopAdvice {

    @Value("${fluentd.tagprefix}")
    private String tagPrefix;

    @Value("${fluentd.host}")
    private String host;

    @Value("${fluentd.port}")
    private Integer port;

    @Value("${fluentd.tag}")
    private String tag;
  
    private static FluentLogger LOG = FluentLogger.getLogger("app", host, port);  // 에러발생
...
...
   

문제는 이렇게 사용하면 static한 FluentLogger 객체에 non-static한 변수값을 인자로 넘길수 없게 됩니다. 

 

static변수에 non-static을 사용할 수 없기 때문이죠. @value로 값을 주입받는 변수는 당연히 non-static입니다. 
그렇다고 인자를 클래스마다 static으로 선언하거나, 하드코딩을 하는 방법은 더욱이 안될 일입니다.

 

이럴 경우, @PostConstruct 어노테이션을 사용하면 static 객체에 non-static한 변수를 사용할 수 있습니다.

 

 

@PostConstruct 가 붙은 메서드는 클래스의 빈 객체 초기화가 완료되고 의존성주입까지 모두 완료된 이후 실행되기 때문에 
호출되는 시점에는 @Value로 지정된 변수들이 모두 초기화되고 값이 할당된 상태가 됩니다. 

 

즉, 객체가 생성된 이후(빈생성 이후) 객체생성의 초기화를 보장하고, 그 이후의 별도의 초기화 작업을 위한 목적으로 사용하는 @PostConstruct  어노테이션을 사용해서 일종의 지연효과를 통해  static객체에 non-static 변수를 사용할 수 있게 됩니다. 

 

아래의 코드처럼 static 객체의 초기화를 빈객체가 생성된 이후에 실행되도록 변경하면 됩니다. 

@Slf4j
@Aspect
@Component
public class ApiCommonAopAdvice {

    @Value("${fluentd.tagprefix}")
    private String tagPrefix;

    @Value("${fluentd.host}")
    private String host;

    @Value("${fluentd.port}")
    private Integer port;

    @Value("${fluentd.tag}")
    private String tag;

	private static FluentLogger LOG;

    @PostConstruct
    private void initialize() {
      this.LOG = FluentLogger.getLogger(tagPrefix, host, port);
    }
  
    ...
    ...
}

 

객체의 생성후 필요한 다른 초기화 작업도 이 @PostConstruct 어노테이션이 붙은 메서드에서 해주면 되겠습니다.

 

@PostConstruct 

    • 객체의 초기화(빈 생성)와 객체의 의존성 주입(ID)까지가 모두 완료된 상태를 보장
    • 이후, 별도의 초기화 작업이 필요한 경우 사용하는 어노테이션
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기