Spring @Configuration과 @Bean을 기반으로 프록시 객체 이해하기

· Backend, Spring

@Bean 어노테이션

어떤 클래스를 스프링 빈으로 등록하고자 할 때, 명시적으로 설정 클래스에서 수동으로 스프링 컨테이너에 빈을 등록하는 방법이다.

@Configuration 어노테이션

스프링 컨테이너는 @Configuration이 붙은 클래스를 자동으로 스캔해서 빈으로 등록한다.
@Configuration이 붙은 클래스들을 파싱해서 @Bean이 붙은 메소드들을 찾아내서 메소드 이름을 빈 이름으로 해서 빈으로 등록한다.

@Bean은 언제 쓰는가?

@Component와 @Configuration은 무엇이 다른가?

이런 질문을 할 수도 있다.

@Configuration 대신 @Component를 써도 똑같은거 아닌가요?! @Component 쓰면 안돼요?

안된다.

사실, @Configuration 어노테이션 안에는 @Component 어노테이션이 존재하기 때문에 @Configuration이 붙어있는 클래스가 스프링 빈으로 등록되는 것이긴 하다.

하지만 큰 차이점이 있는데, @Configuration을 따로 쓰는 이유는, CGLib으로 프록시 패턴을 적용해 수동으로 등록하는 스프링 빈이 반드시 싱글톤으로 생성됨을 보장하기 위해서이다.

내부적으로 CGLIB가 런타임 시점에 @Configuration이 붙은 클래스들의 프록시 객체를 생성해서 해당 프록시 객체를 빈으로 등록한다.

하지만, CGLIB의 프록시 객체 생성 작업은 상속으로 이루어진다. 따라서 상속 규칙을 지켜야만 한다.

결론은, @Bean은 싱글톤을 보장하지만 직접 작성한(혹은 라이브러리를 불러온) 팩토리 메소드들은 싱글톤을 보장하지 않으므로, 이러한 객체들을 전부 확실하게 싱글톤화 시켜주기 위해서 처리해주는 기능이다.

예제 따라하기

다른 블로그 예제를 보고 나도 만들어서 따라해봤다.

Bean 미적용

// SomeBean Class
package kr.lesh.beta.samples;

public class SomeBean {
}
package kr.lesh.beta.config;

import kr.lesh.beta.samples.SomeBean;
import kr.lesh.beta.samples.NothingBean;

public class AppConfig {
    SomeBean someBean() {
        return new SomeBean();
    }

    NothingBean nothingBean() {
        return new NothingBean(someBean());
    }
}
package kr.lesh.beta.config;


import kr.lesh.beta.samples.NothingBean;
import kr.lesh.beta.samples.SomeBean;
import org.springframework.stereotype.Controller;

@Controller
public class MainController {
    public MainController() {
        AppConfig appConfig = new AppConfig();
        SomeBean sb = appConfig.someBean();
        NothingBean nb = appConfig.nothingBean();
        System.out.println(sb);
        System.out.println(nb.getSomeBean());
    }
}

다른 인스턴스로 나온다. 싱글톤이 아니라는 뜻이다.

Bean 적용

package kr.lesh.beta.config;

import kr.lesh.beta.samples.SomeBean;
import kr.lesh.beta.samples.NothingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    SomeBean someBean() {
        return new SomeBean();
    }

    @Bean
    NothingBean nothingBean() {
        return new NothingBean(someBean());
    }
}
package kr.lesh.beta.config;


import kr.lesh.beta.samples.NothingBean;
import kr.lesh.beta.samples.SomeBean;
import org.springframework.stereotype.Controller;

@Controller
public class MainController {

    public MainController(SomeBean someBean, NothingBean nothingBean) {
        System.out.println(someBean);
        System.out.println(nothingBean.getSomeBean());
    }
}

같은 인스턴스로 나온다. 싱글톤이 적용됐다!

그렇다면, AppConfig 클래스는 CGLIB 프록시 객체가 되었을까? 찍어보자.

System.out.println(appConfig.getClass());
class kr.lesh.beta.config.AppConfig$$SpringCGLIB$$0

그렇다. 프록시 객체가 되었다.

레퍼런스

[Spring] 빈 등록을 위한 어노테이션 @Bean, @Configuration, @Component 차이 및 비교 - (1/2)
[Spring] @Configuration 안에 @Bean을 사용해야 하는 이유, proxyBeanMethods - (2/2)
[spring] @Configuration의 프록시 객체
JDK Dynamic Proxy와 CGLib를 알아보자 #2