프록시 패턴
프록시 패턴이 어떻게 사용되는지 알아보겠다. 아래는 전체 테스트 코드이다.
public class ConfigurationTest {
@Test
void configuration() {
Common common = new Common();
Assertions.assertThat(common).isSameAs(common);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(MyConfig.class);
ac.refresh();
Bean1 bean1 = ac.getBean(Bean1.class);
Bean2 bean2 = ac.getBean(Bean2.class);
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
@Test
void proxyCommonMethod() {
MyConfigProxy myConfigProxy = new MyConfigProxy();
Bean1 bean1 = myConfigProxy.bean1();
Bean2 bean2 = myConfigProxy.bean2();
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
static class MyConfigProxy extends MyConfig {
private Common common;
//proxy pattern
@Override
Common common() {
if(this.common == null) this.common = super.common();
return common;
}
}
@Configuration
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean2 bean2() {
return new Bean2(common());
}
}
static class Bean1 {
private final Common common;
public Bean1(Common common) {
this.common = common;
}
}
static class Bean2 {
private final Common common;
public Bean2(Common common) {
this.common = common;
}
}
static class Common {
}
}
객체 직접 생성 및 사용
스프링 빈에서 프록시 패턴이 어떻게 사용되는지 알기 위해 만든 예시 코드이다. Bean1 클래스와 Bean2 클래스 각각 Common 클래스를 생성자에서 주입받고 있다.
static class Bean1 {
private final Common common;
public Bean1(Common common) {
this.common = common;
}
}
static class Bean2 {
private final Common common;
public Bean2(Common common) {
this.common = common;
}
}
static class Common {
}
그 후 아래와 같이 Bean1, Bean2, Common 클래스를 빈 컨테이너에 등록하도록 하겠다.
@Configuration
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean2 bean2() {
return new Bean2(common());
}
}
위에 예제 코드를 활용해서 다음과 같이 테스트 코드를 작성해 보겠다.
@Test
void configuration() {
Common common = new Common();
Assertions.assertThat(common).isSameAs(common);
MyConfig myConfig = new MyConfig();
Bean1 bean1 = myConfig.bean1();
Bean2 bean2 = myConfig.bean2();
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
빈 컨테이너를 호출하는 것이 아닌 직접 Bean1와 Bean2 객체를 생성한 후 그 안에 있는 common의 레퍼런스 값을 비교해주고 있다. 이때 bean1안에 있는 common의 레퍼런스 값과 bean2안에 있는 common의 레퍼런스 값이 다르게 나와 Test가 failed로 끝난다.

빈 컨테이너에 등록된 클래스는 싱글톤으로 사용되기 때문에 common의 레퍼런스 값이 같을 것이라고 생각했지만 Test가 failed가 발생했다. 여기서 알 수 있는 부분은 클래스를 빈 컨테이너에 등록했다고 해도 직접 클래스를 생성하면 빈 컨테이너를 사용하지 못한다는 점이다. 빈 컨테이너를 사용하여 다시 테스트를 진행해 보겠다.
Bean Container 사용
AnnotationConfigApplicationContext를 사용하면 등록한 Bean Container에서 객체를 가져와 사용할 수 있다. Bean Container에 등록된 객체를 사용하여 다시 테스트를 해보겠다.
@Test
void configuration() {
Common common = new Common();
Assertions.assertThat(common).isSameAs(common);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(MyConfig.class);
ac.refresh();
Bean1 bean1 = ac.getBean(Bean1.class);
Bean2 bean2 = ac.getBean(Bean2.class);
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
위와 같이 테스트 코드를 작성하고 실행하면 테스트가 성공적으로 끝난다. bean1와 bean2에서 사용한 common이 같은 레퍼런스 값을 사용하고 있기 때문이다. 그렇다면 한 가지 궁금증이 생길 수 있다. MyConfig 클래스를 보면 Bean1과 Bean2를 빈 컨테이너에 등록할 때 common() 메서드를 사용하고 있고 common() 메서드는 아래 코드와 같이 새로운 Common() 인스턴스를 만들기 때문에 다른 레퍼런스 값을 가져야 한다는 추측을 할 수 있다.
@Bean
Common common() {
return new Common();
}
어떻게 된 것일까?
Proxy Pattern
Spring이 Proxy Pattern을 사용한다는 것에서 해답을 찾을 수 있다. 아래 예제 코드를 참고해 보자.
static class MyConfigProxy extends MyConfig {
private Common common;
//proxy pattern
@Override
Common common() {
if(this.common == null) this.common = super.common();
return common;
}
}
@Configuration
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean2 bean2() {
return new Bean2(common());
}
}
Proxy Pattern을 활용하기 위해 MyConfig객체를 상속받은 MyConfigProxy객체를 만들었다. 이를 활용해서 테스트 코드를 작성해 보겠다.
@Test
void proxyCommonMethod() {
MyConfigProxy myConfigProxy = new MyConfigProxy();
Bean1 bean1 = myConfigProxy.bean1();
Bean2 bean2 = myConfigProxy.bean2();
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
bean1() 메서드와 bean2() 메서드를 실행할 때 common() 메서드가 같이 실행된다. 이때 실행되는 common() 메서드는 슈퍼 클래스(MyConfig)가 아닌 오버라이드된 서브 클래스(MyConfigProxy)가 실행된다. 따라서 bean1과 bean2의 common이 같은 레퍼런스 값을 갖고 있게 되고 테스트는 성공으로 끝나게 된다.
대부분 bean container에 등록된 클래스들은 위와 같이 proxy pattern을 사용한다. 이를 통해 싱글톤 패턴을 지킬 수 있으며 빈 컨테이너에 등록된 클래스가 하나의 레퍼런스 값을 갖게 된다. 그렇다면 빈 컨테이너에 등록된 클래스에 대해 proxy pattern을 사용하기 싫으면 어떻게 해야 할까? 다음과 같이 @Configuration 어노테이션에 proxy false 옵션을 설정하면 된다.
@Configuration(proxyBeanMethods = false)
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean2 bean2() {
return new Bean2(common());
}
}
위와 같이 @Configuration(proxyBeanMethods = false)로 설정한 후 테스트를 진행하면 결괏값이 false가 나오는 것을 알 수 있다.
@Test
void configuration() {
Common common = new Common();
Assertions.assertThat(common).isSameAs(common);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(MyConfig.class);
ac.refresh();
Bean1 bean1 = ac.getBean(Bean1.class);
Bean2 bean2 = ac.getBean(Bean2.class);
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}

@Configuration(proxyBeanMethods = true)가 defualt 값이기 때문에 따로 설정하지 않으면 빈 컨테이너에 proxy pattern이 적용된다.
'Spring' 카테고리의 다른 글
| [스프링부트] 조건부 자동 구성 (0) | 2024.04.19 |
|---|---|
| [스프링부트] 자동 구성 기반 애플리케이션 (0) | 2024.04.17 |
| [스프링부트] 빈 컨테이너 생성 (0) | 2024.04.08 |
| [스프링부트] 서블릿 컨테이너 만들기 (0) | 2024.04.05 |
| [스프링부트] 스프링부트 살펴보기 (0) | 2024.04.04 |