spring @bean

spring @Bean

스프링에 자주 사용되는 어노테이션으로 @Bean에 대해 살짝 맛만 볼라고 한다.
저번에 한번 얘기를 했는데 ConfigurationClassParser클래스 doProcessConfigurationClass 메소드에 여러 메타 어노테이션을 파싱하는 부분이 있다.

...

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

...

doProcessConfigurationClass 메소드의 Bean을 파싱하는 부분이다.
하지만 메타 정보만 갖고 있고 이때 인스턴스는 하지 않는다.(이 부분은 저번에도 얘기 한듯 하다)
그럼 우리가 흔히 쓰는 @Bean 은 언제 인스턴스 하는지 알아보자

@Bean
public ModelClass modelClass(){
    return new ModelClass();
}

예로 위와 같은 빈이 있다 가정하자
저번에 얘기 했듯이 Spring은 내부적으로 getBean을 호출하면서 그때 인스턴스도 같이 한다.(물론 안하는 Context도 있다. 안한다는 것보다 할 필요가 없는 듯 해서 그런거 같다.)
그 중에 @Bean 어노테이션들은 BeanMethodInterceptor aop 프록시를 통해 해당 빈을 호출한다.
그리고 나서 DefaultSingletonBeanRegistry클래스에 싱글톤 빈이라고 저장해둔다.(아마도 여기에 모든 싱글톤이 있는 듯하다)

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

저기에는 실제 인스턴스화된 빈들이 저장 되어 있는 곳이다.
왜 이얘길 하냐면 우리들이 흔히 쓰는 getBean을 호출 할때 singletonObjects 멤버 변수에서 꺼내서 주는 거다.

Object sharedInstance = getSingleton(beanName);

getBean을 호출 할때 저 함수를 호출한다. getSingleton 함수를 보자

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

첫 줄만 봐도 알 것이다. Map으로 저장된 빈들을 꺼내서 사용한다.

그럼 Bean의 스코프가 prototype 일 경우는 어떠할까?
실질적으로 하는 행동은 같다.
AbstractBeanFactory 클래스의 일부분이다.

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            try {
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                // Explicitly remove instance from singleton cache: It might have been put there
                // eagerly by the creation process, to allow for circular reference resolution.
                // Also remove any beans that received a temporary reference to the bean.
                destroySingleton(beanName);
                throw ex;
            }
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

else if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

코드만 봐도 비슷한 일을 하고 있다 대신 싱글톤일 경우엔 getSingleton 을 호출 하면서 싱글톤으로 등록 할 뿐이다.
저기 위에서 말했듯이 getBean을 호출 할 경우 getSingleton에서 싱글톤이 있는지 없는지 확인을 하는데 prototype일 경우엔 없으니 계속 Bean을 새로 호출 할 것이다.

스코프가 session이거나 request일 경우에도 비슷하게 동작 하지 않나 싶다. (확인을 안해서 믿지 마시길)

다 아는 내용 이겠지만 테스트를 해봤다.

  • singleton 일 경우
ModelClass hello = applicationContext.getBean("modelClass", ModelClass.class);
ModelClass hello2 = applicationContext.getBean("modelClass", ModelClass.class);
System.out.println(hello == hello2);
//true
  • prototype 일 경우
ModelClass hello = applicationContext.getBean("modelClass", ModelClass.class);
ModelClass hello2 = applicationContext.getBean("modelClass", ModelClass.class);
System.out.println(hello == hello2);
//false

이렇게 @Bean이 어떻게 동작하는지 조금 알아봤다.

spring boot logging

spring boot logging

spring boot의 로깅을 알아볼려한다. 예전에 백기선님이 스프링 캠프에서 발표한 내용을 참고하여 정리했다.
예전에 한번 봤었는데 기억이 가물가물에서 아예 정리를 해야겠다.

spring은 기본적으로 JCL을 사용한다.
spring bean 라이브러리에 DefaultSingletonBeanRegistry 클래스의 일부분

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

...

protected final Log logger = LogFactory.getLog(getClass());

...

if (logger.isDebugEnabled()) {
    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}

하지만 근래에는 JCL보다 slf4j를 더 많이 쓴다고 관련해서는 백기선님 발표자료를 참고.
JCL과 slf4j는 같은 추상 로깅 라이브러리다.
동작방식이 약간만 다를뿐…

그럼 어떻게 로깅을 통합하고 관리하는지 알아보자.
일단 slf4j에는 기본적으로 3가지 라이브러리가 있다.
첫 번째론 slf4j api 라이브러리가 있다.
이 라이브러리는 그냥 기본 인터페이스 껍대기라 한다.
거기서보면 실제 클래스패스에서 log 라이브러리를 가져오는 곳으로 보이는 함수가 있다.

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    // use Set instead of list in order to deal with bug #138
    // LinkedHashSet appropriate here because it preserves insertion order during iteration
    Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration<URL> paths;
        if (loggerFactoryClassLoader == null) {
            paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
        } else {
            paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
        }
        while (paths.hasMoreElements()) {
            URL path = paths.nextElement();
            staticLoggerBinderPathSet.add(path);
        }
    } catch (IOException ioe) {
        Util.report("Error getting resources from path", ioe);
    }
    return staticLoggerBinderPathSet;
}

logback classic 에 보면 org/slf4j/impl/StaticLoggerBinder 클래스가 있다.
아무튼 일단 slf4j api는 껍대기 인터페이스라 생각하면 되겠다.

두번째론 slf4j binding 이라는 라이브러리다.
실직적으로 slf4j api 의 구현체들이다.
위에서 언급한 logback classic 도 binding 이다.
logback classic 의 Logger 클래스의 일부분이다.

public final class Logger implements org.slf4j.Logger, LocationAwareLogger,
    AppenderAttachable<ILoggingEvent>, Serializable {
...

  public void debug(String format, Object arg) {
    filterAndLog_1(FQCN, null, Level.DEBUG, format, arg, null);
  }
}

이외에도
slf4j-log4j12 : log4j binding
slf4j-jdk14 : java.util.logging binding 기본 자바 패키징
slf4j-jcl : 아파치 common logging binding

등이 있다.
logback만 logback-classic이다. 흠

세번째는 slf4j bridge 라이브러리다.
레거시들을 위한 라이브러리다.
예전에 개발할때는 직접적으로 로그들을 선택해서 쓰거나 혹은 JCL을 썼다.
그래서 그 라이브러리들을 slf4j로 호출하도록 해주는 라이브러리이다.
그래야 내가 원하는 로그들로 갈테니…
아주 좋은 라이브러리다.

만약에 어떤 라이브러리에서 log4j를 직접적으로 쓴다고 가정하자.
그럼 log4j -> slf4j-log4j-bridge -> slf4j-api -> slf4j-binding(내가 원하는 로그 binding) -> 내가 원하는 로그 라이브러리(logback, jul, jcl)
이런 방식이다.
만약 같은 종류의 브릿지와 바인딩을 쓰면 어떻게 되나

만약 log4j를 쓴다고 가정하면 log4j -> slf4j-log4j(bridge) -> slf4j-api -> slf4j-log4j(binding) -> log4j -> sl4j->log4j(bridge) -> slf4j-api -> slf4j-log4j(binding)….. ????

무한로프에 빠지게 된다. 같은 종류의 브릿지와 바인딩을 쓰면 안된다.
그것만 주의하자!!

아래는 스프링 boot의 기본 로깅 이다.
spring-boot-logger-maven

한개의 binding 라이브러리와 세개의 bridge 라이브러리가 존재한다.
동일한 브릿지와 바인더는 보이지 않는다.

만약 boot의 기본 라이브러리인 logback 말고 log4j를 쓰고 싶다면 어떻게 할까
저기 발표내용에도 나와있지만
spring boot logging
여기에 자세히 나와있다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j</artifactId>
</dependency>

을 추가 해주자
spring-boot-log4j

그럼 위와 같이 변경이 되어있을 것이다.

slf4j-log4j 에도 org/slf4j/impl/StaticLoggerBinder 클래스가 존재한다.

log4j를 쓰면서 JCL 코드를 호출 해보았다.
SLF4JLocationAwareLog 클래스를 호출하였다.
slf4-bridge다. 정확한 명칭은 jcl-over-slf4j 이다. 그리고 slf4j-api의 구현체인 Log4jLoggerAdapter 클래스를 호출하였다.
그런다음에 실질적인 logj4를 호출 하였다.

순서를 대략 이렇다.
info 기존이다.

jcl-over-slf4j(bridge) 라이브러리의 SLF4JLocationAwareLog 클래스

public void info(Object message) {
    logger.log(null, FQCN, LocationAwareLogger.INFO_INT, String.valueOf(message), null, null);
}

slf4j-log412(binding) 의 Log4jLoggerAdapter 클래스(slf4j-api 구현체)

public void log(Marker marker, String callerFQCN, int level, String msg, Object[] argArray, Throwable t) {
    Level log4jLevel = toLog4jLevel(level);
    logger.log(callerFQCN, log4jLevel, msg, t);
}

log4j 의 Category 클래스

public void log(String callerFQCN, Priority level, Object message, Throwable t) {
    if(repository.isDisabled(level.level)) {
      return;
    }
    if(level.isGreaterOrEqual(this.getEffectiveLevel())) {
      forcedLog(callerFQCN, level, message, t);
    }
}

이런 방식으로 로그를 찍는다. 저기 백기선님이 설명을 잘해주셔서 도움이 많이 되었다.
만약 우리가 라이브러리를 만든다면 실직적으로 slf4j-api만 사용하면 된다.
나머지만 해당 어플리케이션 개발자가 무엇을 쓸지 선택하기만 하면 된다.

이렇게 로그에 대해서 알아봤다.

추가 : 만약 동일한 브릿지 바인딩이 클래스 패스에 있을경우 서버 시작시 에러를 낸다. jcl, log4j 테스트했음!

spring Aware 순서

spring Aware order

이건 메모 추후에 다신 확인

AbstractApplicationContext finishBeanFactoryInitialization(beanFactory)

AbstractAutowireCapableBeanFactory 클래스에서 아래와 같이 셋팅

BeanNameAware
BeanClassLoaderAware
BeanFactoryAware
    private void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }

ApplicationContextAwareProcessor 클래스에서 아래와 같이 셋팅

EnvironmentAware
EmbeddedValueResolverAware
ResourceLoaderAware
ApplicationEventPublisherAware
MessageSourceAware
ApplicationContextAware
    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof EnvironmentAware) {
                ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
            }
            if (bean instanceof EmbeddedValueResolverAware) {
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                        new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
            }
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof MessageSourceAware) {
                ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
        }
    }

XXXXXAwareProcessor를 참고 하면 됨 BeanPostProcessor를 구현한걸로 보임. 모두다 그렇지는 않음

Spring 4.2.5의 전체 목록. 일단 기본적인것만 확인 했다.

ApplicationContextAware, ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, BootstrapContextAware, EmbeddedValueResolverAware, EnvironmentAware, ImportAware, LoadTimeWeaverAware, MessageSourceAware, NotificationPublisherAware, PortletConfigAware, PortletContextAware, ResourceLoaderAware, SchedulerContextAware, ServletConfigAware, ServletContextAware