본문 바로가기

Development/Spring

빈 설정 메타정보 (Config) 작성 테스트

빈(Bean) 설정 메타정보를 작성하는 방법에는 여러 가지가 있으며, 적당히 조합하여 사용할 수도 있다.


우선 메타 정보는 적절한 Reader 만 있으면 포맷에 상관없이 작성 가능하다.(xml, annotation, java, properties ...)




<테스트에 사용할 POJO 클래스>



@Setter
public class Hello {
private String name;

private Printer printer;


public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}

}

public interface Printer {
void print(String msg);
}
public class StringPrinter implements Printer {
private StringBuffer buffer = new StringBuffer();

@Override
public void print(String msg) {
this.buffer.append(msg);
}

public String toString() {
return this.buffer.toString();
}
}
public class ConsolePrinter implements Printer {
@Override
public void print(String msg) {
System.out.println(msg);
}
}





스프링의 설정 메타정보는 BeanDeifinition 인터페이스로 표현되는 추상정보이다. 앞서 말한 '적절한 Reader'들이 작성한 메타정보들을 BeanDefinition으로 정의해 준다.


1) 우선 메타정보를 작성 해보기 전에 첫 테스트를 한번 해보자.

- StaticApplicationContext가 디폴트 메타정보를 사용해서 싱글톤 빈을 등록해준다.(사용자가 메타정보를 작성하지 않음, 테스트용으로 적합)

@Test
public void 빈_생성_호출_테스트() {
//register bean
StaticApplicationContext ac = new StaticApplicationContext();
ac.registerSingleton("hello1", Hello.class);

//call bean
Hello hello1 = ac.getBean("hello1", Hello.class);

assertNotNull(hello1);
}

테스트는 잘 성공한다.



2) 앞서 '적절한 리더'들이 BeanDeifinition을 작성해 준다고 했다. 그전에 직접 BeanDefinition을 작성하여 테스트를 해보자

- hello1 은 1)에서 사용한 방법으로 빈을 등록했고, hello2는 BeanDefinition을 직접 작성하여 빈을 등록했다.

- hello2에는 "name" 속성을 spring으로 지정해 주었다.


@Test
public void 빈_생성_호출_테스트_BeanDefinition_직접작성() {
StaticApplicationContext ac = new StaticApplicationContext();
//register bean

//bean 1
ac.registerSingleton("hello1", Hello.class);

//bean 2
BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
ac.registerBeanDefinition("hello2", helloDef);


//call bean and Test
Hello hello1 = ac.getBean("hello1", Hello.class);
Hello hello2 = ac.getBean("hello2", Hello.class);

//DI test
assertEquals(hello2.sayHello(), "Hello Spring");

//같은 클래스에 대해서 다른 bean설정 정보를 작성한 경우, 2개는 다른 빈인가 ?
assertNotEquals(hello1, hello2);

//빈 설정정보는 총 몇개인가?
assertEquals(2, ac.getBeanDefinitionCount());
}

- 이 테스트에서 알 수 있는 점.

같은 Class에 대하여 다른 설정 메타 정보를 작성하여 등록할 경우 2개의 빈은 서로 다른 빈이다.



3) 빈 생성 후 다른 빈을 주입 해보자.

- Hello Bean에 Printer를 생성하여 주입해 주었다.

@Test
public void 빈_생성_주입_테스트() {
StaticApplicationContext ac = new StaticApplicationContext();
//register bean
ac.registerBeanDefinition("printer", new RootBeanDefinition(StringPrinter.class));

BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
helloDef.getPropertyValues().addPropertyValue("Printer", new RuntimeBeanReference("printer"));
ac.registerBeanDefinition("hello", helloDef);

//call bean
Hello hello = ac.getBean("hello", Hello.class);
hello.print();

assertThat(ac.getBean("printer").toString(), is("Hello Spring"));
}


4) XML 설정 파일을 이용한 메타정보 작성

- XML 파일로 메타정보의 내용을 작성하여 빈을 등록한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans ...

<bean id="hello" class="pojo.Hello">
<property name="name" value="Spring"/>
<property name="printer" ref="printer"/>
</bean>

<bean id="printer" class="pojo.StringPrinter"/>

</beans>

@Test
public void 빈_생성_주입_테스트_XML() {
GenericApplicationContext ac = new GenericApplicationContext();

//register bean
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac);
reader.loadBeanDefinitions("hello-config.xml");
ac.refresh();

//call bean

Hello hello = ac.getBean("hello", Hello.class);
hello.print();

assertThat(ac.getBean("printer").toString(), is("Hello Spring"));

}

- <bean> 이라는 태그를 통해 정의하며, <property> 태그로 속성들을 정의할 수 있다.



5) Java 코드를 이용한 메타 정보


설정 파일.java

@Configuration
public class OtherHelloConfig {

@Bean
public OtherHello theOtherHello() {
OtherHello otherHello = new OtherHello();
otherHello.setPrinter(printer());

return otherHello;
}

@Bean
public Printer printer() {
return new StringPrinter();
}
}
@Test
public void 빈_생성_주입_테스트_자바코드에의한설정() {
ApplicationContext ac = new AnnotationConfigApplicationContext(OtherHelloConfig.class);

OtherHello hello = ac.getBean("theOtherHello", OtherHello.class);
OtherHello hello2 = ac.getBean("theOtherHello", OtherHello.class);
assertNotNull(hello);

OtherHelloConfig config = ac.getBean("otherHelloConfig", OtherHelloConfig.class);
assertNotNull(config);

assertEquals(hello, config.theOtherHello());
assertEquals(hello, hello2);
assertEquals(hello2, config.theOtherHello());
System.out.println(hello.sayHello());
assertEquals(hello.sayHello(), hello2.sayHello());

}

@Bean 이라는 애노테이션을 메소드에 정의한다. -> XML 의 <bean> 태그에 대응한다고 보면 된다. 이때 Bean 이름은 메소드의명이 된다 위에서는 theOtherHello 라고 정의 했기 때문에, getBean을 할때 "theOtherHello"를 찾아야한다. 

또한 여기서 자바코드로 빈 생성 테스트를 하며, 같은 메타정보로 부터 2번빈을 호출 할때 싱글톤인지도 확인 하였다.



6) 자동인식을 이용한 빈 등록

- 스테레오 타입과 빈스캐너를 이용해, 설정 정보를 따로 정의 하지 않아도, 빈으로 사용할 클래스에 특정 애노테이션을 붙여, 자동으로 빈으로 등록 시킨다. => 빈 스캐닝

빈 스캐너는 @Compoent 애노테이션, 또는 이를 기반으로 하는 애노테이션을(ex, @Controller, @Service .. ) 가진 클래스들을 선별한다. -> 스테레오타입 애노테이션

빈 이름은 아래와 @Compoent("name")으로 지정가능하며, 기본값은 클래스 이름에 첫 글자가 소문자인 형태이다.

이때, 컴포넌트 스캔을 할 대상 패키지를 정해줄 수 있다 ("com.ex.pojo")


@Setter
@Component("ComponentHello")
public class AnnotatedHello {

private String name;

private Printer printer;


public String sayHello() {
return "ComponentHello " + name;
}

public void print() {
this.printer.print(sayHello());
}
}

@Test
public void 빈_생성_주입_테스트_ComponentScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext("pojo");

AnnotatedHello hello = ac.getBean("ComponentHello", AnnotatedHello.class);

assertNotNull(hello);
}


7) XML을 이용한 메타정보 + 자동인식

XML에 필요한 빈들을 정의도 하고, 자주쓰이지만 별다른 속성이 필요없느 빈들은 빈 스캔을 통해 등록할 수있다.

이때는 XML에 아래와 같이 정의해준다.

<context:component-scan base-package="pojo"/> //base-pacakage :컴포넌스 스캔을 할 대상 패키지



<?xml version="1.0" encoding="UTF-8"?>
<beans ...
<context:component-scan base-package="pojo"/>

<bean id="hello" class="pojo.Hello">
<property name="name" value="Spring"/>
<property name="printer" ref="printer"/>
</bean>

<bean id="printer" class="pojo.StringPrinter"/>

</beans>

@Test
public void 빈_생성_주입_테스트_XML() {
GenericApplicationContext ac = new GenericApplicationContext();

//register bean
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac);
reader.loadBeanDefinitions("hello-config.xml");
ac.refresh();

//call bean

Hello hello = ac.getBean("hello", Hello.class);
hello.print();

assertThat(ac.getBean("printer").toString(), is("Hello Spring"));


AnnotatedHello annotatedHello = ac.getBean("ComponentHello", AnnotatedHello.class);
assertNotNull(annotatedHello);
}