2. 스프링 시작하기

Mando·2023년 11월 19일
0
post-thumbnail
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

위 코드는 확장성이 떨어진다.

  • 표준 출력 대신 표준 에러로 변경하고 싶다면?
  • 단순 텍스트 출력이 아니라 HTML 태그로 출력하고 싶다면?

다시 애플리케이션에 대한 요구사항을 정리해보자

  • 메시지를 바꿀 수 있는 단순하고 유연한 메커니즘을 제공해야 한다.
  • 출력 결과를 다른 형태로 렌더링하기 쉬워야 한다.

이를 충족시키기 위해서 메시지를 외부에서 관리하고 런타임에 메시지 내용을 받아오는 형식으로 변경하였다.

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();
}
  • MessageProvider과 MessageRenderer는 의존성이 존재한다.
  • MessageProvider에게 메시지를 가져오는 책임을 위임한다.
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;
    }
}

실행자는 인터페이스를 통해 기능을 실행한다.

Untitled

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();
    }
}

스프링으로 리팩터링 하기 - XML 사용

위의 경우에 컴포넌트 결합도를 낮추기 위해 컴포넌트 접착 코드가 많이 필요하다.

또한, 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();
    }
}

정리

  1. 책임 분리란 무엇인가 : HelloWolrd를 출력할 때 메세지를 가져오는 부분과 메시지를 출력하는 부분으로 나눌 수 있다.
  2. 기존에 인터페이스를 사용하더라도 구현체의 변경이 일어나면 어쩔 수 없이 인터페이스를 사용하는 쪽의 클라이언트 코드는 변경이 불가피하다 라는 생각을 가지고 있었는데, 이 또한 factory 클래스를 통해 코드의 변경 없이 구현체를 바꿀 수 있다.

0개의 댓글