정상혁정상혁

SpringSource Tool Suite의 Java Agent based reloading은 개발할 때 .class파일을 고치면 그 파일만 리로딩을 시켜주는 기능입니다. JRebel과도 유사한데 아직 "Experimental' 표시가 붙어있고, 생긴지가 얼마안되는 기능이라서 JRebel만큼 성숙한 기술인지는 잘 모르겠습니다. 그래도 모든 경우에 다 reloading이 되지 않더라도 한번이라도 서버내렸다 올리는 시간을 절약할 수 있으면 그만큼 이득이기 때문에 없는 것보다는 도움이 됩니다.

이런 도구들이 있으면 좋기는 하지만 WAS올리기 전에 테스트 코드로 왠만한 건 다 검증을 하고, WAS 올려서는 JSP같은 View만 고치는 개발 방식이 바람직합니다. 테스트 코드를 잘 짜고 있다면 '개발 중에 WAS 리로딩한다고 시간이 많이 들어요..' 와 같은 이야기가 별로 안 나올 것입니다. 테스트 코드 작성은 익숙해지면 시간이 별로 안 걸리는 일입니다. 그리고 테스트 코드를 만들면 로직이 있는 모듈의 가까운 위치에서 다양한 케이스를 반복해서 테스트해 볼 수 있고, 에러 추적과 디버깅이 훨씬 편해집니다. 아무리 리로딩이 잘 지원된다고 해도, 화면을 띄어서 손으로 데이터를 매번 입력하는 시간을 없애줄 수는 없고, 리로딩이 시간보다 훨씬 긴 에러 추적과 디버깅 시간을 줄여줄 수는 없습니다. 그래서 결국에는 테스트코드 작성하는 것이 개발시간을 더 빠르게 합니다.

STS를 설치할 때 SpringSource Tool Suite를 한번에 받아서 설치했다면 Tc server도 같이 설치되지만, 이미 깔려진 Eclipse 위에 update를 했다면 tc Server는 아래 URL에서 별도로 받아야 합니다.

Java agent based Reloading 설정

  1. 'Servers' Tab에서 New메뉴로 새 서버를 추가한다. 1_new-server.jpg

  2. tc Server를 선택한다.

    • SpringSource tc Server v2.0 혹은 v2.1 또는 v2.5를 선택한다.

    • 참고로 SpringSource Tool Suite 2.6.1부터 tc server v2.5가 포함되어 있고, "VMware vFabric tc Server v2.5"라는 이름으로 "VM Ware" 분류 폴더 아래에 포함되어 있다.

    • 20_tc-server-select.jpg

  3. 처음 설정하는 것이라면, Tc sever가 설치된 위치를 지정합니다.

    • 21_tc-server-select.jpg

  4. 처음 설정하는 것이라면 "Create new instance"를 선택한다.

    • 4_new-server.jpg

  5. 같이 설치할 모듈을 지정합니다. 간단하게 'base’와 'nio’만 선택해 된다.

    • 5_new-server.jpg

  6. 설정된 Server의 "Overview" 탭에서 "Enable Java Agent-based reloading"을 선택한다.

    • 6_servers.jpg

  7. 시험 삼아서 서버를 시작해 본다..

    • 7_servers-start

  8. 서버가 시작되면서 "Agent based reloading is active"라는 메시지가 처음에 뜨면 제대로 설정이 된 것이다.

    • 8_start-log.jpg

Java agent based reloading 테스트 해보기

테스트용 프로젝트를 생성해서 reloading이 되는지 확인하는 과정이다.

  1. Ctrl +N을 누르고 "Spring Template Project"를 선택한다.

    • 9_new-spring-template.jpg 2."Spring MVC project"를 선택한다.

    • 10_new-spring-mvc-project.jpg 3.프로젝트명과 상위 패키지이름을 적는다.

    • 11_new-spring-mvc-project.jpg

  2. 생성된 프로젝트를 Run AS→ Run On Server로 실행한다.

    • 12_run-on-the-server.jpg

  3. 실행할 서버는 Java Agent Based Reloading을 설정한 tc Server로 지정한다.

    • 13_run-on-the-server.jpg

  4. 서버가 올라간 다음에 템플릿의 HomeController 클래스를 수정해보고, 메소드의 내용을 수정한 다음에 아래와 같이 전체 "Realoding…​" 메시지가 보이며 applicationContext loading 없이 해당 클래스만 리로딩 되는 것을 확인한다. 14_modify-controller

테스트 결과

  • Dynamic web project가 아니고 external web module로 추가한 경우 - 잘 됨

  • Servlet만으로 된 프로젝트 - 잘됨

  • Application Context 파일 수정 - 되기도 하고 안 되기도 함

  • Spring MVC의 controller에 @RequestMapping이 달린 새로운 메소드를 추가한 것을 인식 - 잘 안 됨

정상혁정상혁

Spring 3.0부터 추가된 SpEL(Spring E-pression Language)를 응용한 사례입니다.

application context 파일 선언만으로 특정 빈의 속성에 서버 이름을 넣으려면 어떻게 해야하냐는 질문을 받았습니다.

질문자는 아래의 bean설정에 "serverName"이라고 지정된 곳에 각각 실행되는 서버마다 다른 이름을 넣고 싶어했습니다.

persistentMessageStore 선언
<bean id="persistentMessageStore" class="org.springframework.integration.jdbc.JdbcMessageStore">
   <property name="region" value="serverName" />
....

</bean>

나름대로의 FactoryBean을 따로 만들거나 JavaConfig을 써도 쉽게 풀리는 문제이지만, SpEL을 활용하는 것이 기존 설정을 가장 적게 바꾸는 방식입니다.

아래와 같이 java.net.InetAddress.getLocalHost를 bean으로 등록합니다.

<bean id="localHost" class="java.net.InetAddress" factory-method="getLocalHost"/>

SPel이 잘 먹는지는 다음와 같이 테스트 할 수 있습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HostNameTest {
    @Value("#{localHost.hostName}") String hostName;
    @Test
    public void hostNameShouldBePrinted() throws Exception {
        String expectedHostName = InetAddress.getLocalHost().getHostName();
        assertThat(hostName,is(expectedHostName));
        System.out.println(hostName);
    }
}

위의 테스트 코드에서는 @ContextConfiuguration선언 뒤에 Application context 파일 위치를 지정하지 않았으므로, default로 참조되는 같은 패키지 디렉토리의 HostNameTest-context.xml에 'localHost' bean이 선언되어 있어야 겠죠.

제일 처음의 코드1의 bean선언에서도 마찬가지로 #{localHost.hostName} 을 참조할 수 있습니다.

<bean id="persistentMessageStore" class="org.springframework.integration.jdbc.JdbcMessageStore">
   <property name="region" value="#{localHost.hostName}" />
....

</bean>

SpEL을 활용하면 아래와 같이 bean사이의 연결을 더 유연하게 할 수도 있지만, 컴파일타임에 검증되지 않는다는 단점이 있습니다. 이를 보완하려면 오타를 검증할 수 있는 테스트 코드가 있어야 합니다.

다른 방법으로는, JavaConfig를 활용해서 직접 명시적으로 메소드를 호출해서 hostName을 주입해도 됩니다.

@Bean JdbcMessageStore persistentMessageStore(){
    JdbcMessageStore store = new JdbcMessageStore ();
    store.setRegion(localHost.getHostName());
    return store;
}

@Bean public InetAddress localHost() throws UnknownHostException {
    return  InetAddress.getLocalHost();
}
정상혁정상혁

파일 update를 처리하는 Servlet도 Spring의 MockHttpSerlvetRequest와 MockHttpServletResponse로 테스트 할 수 있습니다.

다음 링크에 있는 소스를 참고했습니다.

request의 content 속성에 파일내용 등을 포함시켜 주면 됩니다.

아래 예제에서 Assert부분은 화면에 출력되는 문자열을 검사하는 방식입니다. 테스트 하고자하는 목적에 따라 파일업로드가 되었을 때의 특정 위치에 파일이 생성된 것을 확인한다거나, 그 뒤에 호출되는 클래스를 행위검증하는 방식등을 다양하게 응용하실 수 있을 것입니다.

publicclassUploadServletTest {
    UploadServlet servlet =newUploadServlet() ;
    MockHttpServletRequest request =newMockHttpServletRequest();
    MockHttpServletResponse response =newMockHttpServletResponse();

    private static final String ENDLINE ="\r\n";

    private static final String BOUNDARY ="qWeRtY";

   @Test
    public void testUploadNormalFile() throws Exception {
        // given
        String fileContent ="for upload test";
        String fileName ="message.txt";
        String reqContent = createContentWithFile(fileName, fileContent);
        request.setContent(reqContent.getBytes());
        request.setContentType("multipart/form-data; boundary="+ BOUNDARY);
        request.setMethod("POST");

        // when
        servlet.service(request, response);

       // then
       String output = response.getContentAsString();
      assertTrue("정상적으로 업로드 되었을 때에는", output.contains("File size"));
    }

    @Test
   public void testUploadEmptyFile() throws Exception {
       // given
       String fileContent ="";
       String fileName ="message.txt";
       String reqContent = createContentWithFile(fileName, fileContent);
       request.setContentType("multipart/form-data; boundary="+ BOUNDARY);
       request.setMethod("POST");
       request.setContent(reqContent.getBytes());

       // when
       servlet.service(request, response);

      // then
      String output = response.getContentAsString();
      assertThat("빈 파일이 올라갔을 때에는", output, is("No binary data contains"));
    }

    private String createContentWithFile(String fileName, String fileContent) {
        StringBuilder reqContent =newStringBuilder();
        reqContent.append("--"+ BOUNDARY + ENDLINE);
        reqContent.append("Content-Disposition: form-data; name=\"myfile\";"
        +" filename=\""+ fileName +"\""+ ENDLINE);
        reqContent.append(ENDLINE);
        reqContent.append(fileContent);
        reqContent.append(ENDLINE);
        reqContent.append("--"+ BOUNDARY +"--"+ ENDLINE);
        return reqContent.toString();
    }
}

참고로 Spring에서는 MockMultipartHttpServletRequest와 MockMultipartFile 같은 첨부파일에 특화된 테스트 전용 클래스를 제공하기는 하지만, Spring MVC를 사용하지 않는 그냥 Servlet에서는 위의 방식처럼 MockHttpServletRequest을 사용해야 합니다.