public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
위 코드는 확장성이 떨어진다.
다시 애플리케이션에 대한 요구사항을 정리해보자
이를 충족시키기 위해서 메시지를 외부에서 관리하고 런타임에 메시지 내용을 받아오는 형식으로 변경하였다.
public class HelloWorld {
public static void main(String[] args) {
if (args.length > 0) {
System.out.println(args[0]);
}
else{
System.out.println("Hello Word!`");
}
}
}
이제 코드의 변경 없이 메시지를 바꿀 수 있습니다.
하지만, 이 코드의 문제점은 무엇일까요?
바로, 메시지 획득과 출력 메시지 렌더링 2가지 책임을 담당하고 있다는 것입니다.
이 경우에는 메시지를 획득하는 방법을 변경하면 메시지를 렌더링하는 코드도 변경이 된다는 문제점이 있습니다.
즉, 메시지를 획득하는 부분과 메시지를 출력하는 부분을 분리해야 합니다.
/**
* 메시지를 가져오는 역할을 담당하는 인터페이스
*/
public interface MessageProvider {
String getMessage();
}
/**
* 출력 메시지를 렌더링하는 책임을 담당하는 인터페이스
*/
public interface MessageRenderer {
void render();
void setMessageProvider(MessageProvider provider);
MessageProvider getMessageProvider();
}
public class HelloWorldMessageProvider implements MessageProvider {
@Override
public String getMessage() {
return "Hello World";
}
}
public class StandardMessageRenderer implements MessageRenderer {
private MessageProvider messageProvider;
@Override
public void render() {
if (messageProvider == null) {
throw new RuntimeException(
"You must set the property messageProvider of class : " + StandardMessageRenderer.class.getName()
);
}
System.out.println(messageProvider.getMessage());
}
@Override
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = messageProvider;
}
@Override
public MessageProvider getMessageProvider() {
return this.messageProvider;
}
}
실행자는 인터페이스를 통해 기능을 실행한다.
public class Main {
public static void main(String[] args) {
MessageRenderer messageRenderer = new StandardMessageRenderer();
MessageProvider messageProvider = new HelloWorldMessageProvider();
messageRenderer.setMessageProvider(messageProvider);
messageRenderer.render();
}
}
하지만, 이 경우에도 문제점이 존재한다.
바로, 인터페이스 구현체를 변경하려면 코드를 변경해야 한다.
이를 피하기 위해 애플리케이션 실행 도중에 속성 파일에서 구현 클래스를 읽어 인스턴스로 만드는 팩터리 클래스를 이용하면 된다.
public class MessageSupportFactory {
private static MessageSupportFactory instance;
private MessageRenderer renderer;
private MessageProvider provider;
private Properties props;
private MessageSupportFactory() {
props = new Properties();
try {
props.load(this.getClass().getResourceAsStream("msf.properties"));
String rendererClass = props.getProperty("renderer.class");
String providerClass = props.getProperty("provider.class");
renderer = (MessageRenderer) Class.forName(rendererClass).newInstance();
provider = (MessageProvider) Class.forName(providerClass).newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
}
static {
instance = new MessageSupportFactory();
}
public static MessageSupportFactory getInstance() {
return instance;
}
public MessageRenderer getMessageRenderer() {
return renderer;
}
public MessageProvider getMessageProvider() {
return provider;
}
}
public class Main {
public static void main(String[] args) {
MessageRenderer messageRenderer = MessageSupportFactory.getInstance().getMessageRenderer();
MessageProvider messageProvider = MessageSupportFactory.getInstance().getMessageProvider();
messageRenderer.setMessageProvider(messageProvider);
messageRenderer.render();
}
}
위의 경우에 컴포넌트 결합도를 낮추기 위해 컴포넌트 접착 코드가 많이 필요하다.
또한, MessageRenderer 구현체에 직접 MessageProvider 인스턴스를 제공해야 한다.
MessageSupportFactory
클래스를 ApplicationContext
인터페이스로 대체해보자.
ApplicationContext
스프링이 관리하는 모든 빈 인스턴스에 대해 공급자 역할을 한다.
ApplicationContext.getBean()
: 구성(XML 파일)을 읽어서 스프링의 ApplicationContext를 초기화하며 이후 구성이 완료된 빈 인스턴스를 반환한다.구성 클래스는 @Configuation
이 붙은 클래스이거나 @ComponentScan 을 붙여 애플리케이션 내의 빈 정의를 찾는 클래스이다.
구성 클래스에서 빈 구성을 읽을 수 있도록 AnnotationConfigApplicationContext
구현체로 변경한다.
@Configuration
public class HelloWorldConfiguration {
@Bean
public MessageProvider provider() {
return new HelloWorldMessageProvider();
}
@Bean
public MessageRenderer renderer() {
StandardMessageRenderer renderer = new StandardMessageRenderer();
renderer.setMessageProvider(provider());
return renderer;
}
}
public class HelloWorldSpringAnnotated {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
HelloWorldConfiguration.class);
MessageRenderer renderer = context.getBean("renderer", MessageRenderer.class);
renderer.render();
}
}