Spring Framework 3.2 – Themes And Trends 일부 정리

기본

Spring Framework 3.2 Webinar 인 Themes And Trends 영상을 보고 Spring Framework 3.2 에 대한 내용 중 일부를 정리한 포스트입니다:

Meta 어노테이션

빈을 주입하거나 @Bean 메소드에서 @Autowired, @Value 어노테이션을 메타 어노테이션으로 사용할 수 있습니다. 즉, 커스텀 어노테이션을 만들면서 @Value, @Autowired, 그리고 커스텀 어노테이션을 추가하면 해당 기능이 추가됩니다. 그리고 @Lazy 등을 이용해 기능을 더 추가할 수도 있다고 하네요.

비동기 지원

request.startAsync()

request.startAsync()

코드를 사용해서 명시적으로 비동기 코드를 사용할 수 있습니다. 위와 같이 지정하면..

  • 컨테이너 쓰레드가 종료되고 새로운 쓰레드로 처리되며 완료 후, 다시 컨테이너로 디스패치 됩니다.

java.util.concurrent.Callable<?>

Spring MVC 핸들러에서 Callable<?>을 리턴값으로 사용 가능합니다. Callable을 리턴할 경우 새로운 쓰레드에서 실행됩니다.

@RequestMapping(&amp;quot;…&amp;quot;)
public @ResponseBody Callable callable() {
  return new Callable() {
    @Override
    public String call() throws Exception {
      // Long Running DB job, 3rd party REST API call, etc.
      return &amp;quot;Hello world&amp;quot;;
    }
  }
}

org.springframework.web.*.DeferredResult<?>

Spring MVC가 제어하지 않는 쓰레드를 통해 비동기 처리(JMS, AMQP, Redis 등)를 합니다. 먼저 DeferredResult를 저장하고, 외부 이벤트 발생 시, 실행하게 됩니다. 이때, 스프링의 AsyncTask를 사용할 수도 있습니다:

// save
@RequestMapping(&amp;quot;…&amp;quot;)
public @ResponseBody DeferredResult deferredResult() {
  DeferredResult result = new DeferredResult();
  this.responseBodyQueue.add(result);
}

// process
@Scheduled(fixedRate=2000)
public void processQueues() {
  for (DeferredResult result : this.responseBodyQueue)  {
    result.setResult(&amp;quot;Deferred result&amp;quot;);
    this.responseBodyQueue.remove(result);
  }
}

서버단 롱폴링 처리 프로세스

  1. 브라우저 요청
  2. 핸들러 메소드가 DeferredResult 리턴
  3. 서블릿 컨테이너 종료(응답은 살아있음)
  4. JMS, AMQP, Redis 같은 외부 이벤트 발생
  5. DeferredResult 세팅
  6. 요청이 서블릿 컨테이너로 돌아감
  7. 처리가 다시 진행되고 요청 완료

서블릿 3 에서 비동기 설정

서블릿과 모든 필터에 비동기 지원 플래그 추가
<async-supported>true</async-supported>

<filter-mapping> 자식 엘리먼트인 <disaptcher> 엘리먼트에 ASYNC 추가
<dispatcher>REQUEST, ASYNC, FORWARD</dispatcher>

Java Config 에서 설정을 도와주는 WebApplicationInitializer  추가됨

public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class&amp;lt;?&amp;gt;[] getRootConfigClasses() {
    return new Class&amp;lt;?&amp;gt;[] { RootConfig.class };
  }

  @Override
  protected Class&amp;lt;?&amp;gt;[] getServletConfigClasses() {
    return new Class&amp;lt;?&amp;gt;[] { WebMvcConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { &amp;quot;/*&amp;quot; };
  }

  @Override
  protected void customizeRegistration(Dynamic registration) {
    registration.setAsyncSupported(true);
  }

}

컨텐츠 네고시에이션

file extension, Accept header, request parameter, default value, custom strategy 를 기반으로 컨텐츠 네고시에이션이 가능하고, 전략의 우선순위도 설정 할 수 있습니다.

REST 에러 보고

전역 @ExceptionHandler 를 지정할 수 있는 @ControllerAdvice 어노테이션이 추가됐습니다.

@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler
  public @ResponseBody String handleBusinessException(BusinessException ex) {
    return &amp;quot;Error details&amp;quot;;
  }

  // May also add globally applicable @InitBinder(e.g. Date) and @ModelAttribute methods
}

Matrix 변수

URI path에 세미콜론을 사용하면.. GET /pets/42;q=21 다음과 같은 코드로 가져올 수 있습니다.

@PathVariable String petId, @MatrixVariable int q

Matrix 변수 설정을 사용하기 위해선 RequestMappingHandlerMapping 의 removeSemicolonContent 프로퍼티의 값을 false 로 지정해야 합니다.(디폴트 true)

TestContext 프레임워크에서 Web Application 지원

// default to &amp;quot;file:src/main/webapp&amp;quot;
@WebAppConfiguration

// detects &amp;quot;WacTests-context.xml&amp;quot; in same package
// or static nested @Configuration class
@ContextConfiguration
public class WacTests {
  //...
}

Spring MVC Test Framework

발표자가 생각하는 좋은 테스트 이디엄(idiom)을 소개해 주고 있습니다.

// Server-side test
@Runwith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(&amp;quot;servlet-context.xml&amp;quot;)
public class SampleTests {
  @Autowired
  private WebApplicationContext wac;
  private MockMvc mvc;

  @Before
  public void setup() {
    this.mvc = webAppContextSetup(this.wac).build();
  }

  @Test
  public void getFoo() throws Exception {
  this.mvc.perform(get(&amp;quot;/foo&amp;quot;).accept(&amp;quot;application/json&amp;quot;))
          .andExpect(status().isOk())
          .andExpect(content().mimeType(&amp;quot;application/json&amp;quot;))
          .andExpect(jsonPath(&amp;quot;$.name&amp;quot;).value(&amp;quot;Lee&amp;quot;));
  }
}

// Client-side test
RestTemplate restTemplate = new RestTemplate();

MockRestServletServer mockServer = MockRestServiceServer.createServer(restTemplate);

mockServer.expect(requestTo(&amp;quot;/greeting&amp;quot;))
    .andRespond(withSuccess(&amp;quot;Hello world&amp;quot;, &amp;quot;text/plain&amp;quot;));

// use RestTemplate …

mockServer.verify();

postMessage를 이용한 크로스도메인간 iframe 리사이징

기본

회사에서 외주를 주고 있는 사이트가 변경돼서, <iframe>으로 해당 사이트를 임베드 해야하는 이슈가 있었습니다. <iframe> 해서 추가하는 거야 일도 아닌데 포함시킬려는 페이지의 사이즈에 맞게 사이즈를 조절해 줘야 하더군요.

바꾸기 전에는 같은 도메인이었기 때문에 iframe 안에서 부모쪽의 함수를 호출하면 되는 부분이었는데 도메인이 아예 달라져서 이렇게 호출하려고 하면 크로스도메인 관련 오류가 떨어집니다.

이 문제를 해결하기 위해 몇 가지 트릭이 존재하는데 그 중에서 제가 찾은 가장 모던한 방법은 window.postMessage를 사용하는 겁니다.

일반적으로 서로 다른 페이지의 스크립트가 통신하기 위해선 프로토콜, 포트, 호스트(도메인)가 같아야만 가능한데 window.postMessage는 이런 제약들을 우회할 수 있다고 합니다. 그러면 보안이 문제가 될 수도 있는데 이게 다른 페이지의 메소드를 호출하는건 아니고 한쪽에선 메시지를 보내고 다른쪽에서 그 메시지를 처리하는 일종의 이벤트 이고 받는 쪽도 origin을 지정할 수 있기 때문에 보안에 큰 문제는 없어 보입니다. (단, jQuery postMessage 플러그인을 사용할 경우 window.postMessage를 지원하지 않는 브라우저에서는 origin이 무시됩니다.)

문제는 IE 8+ 부터 지원한다는 건데 다행히 jQuery postMessage 플러그인을 사용하면 IE6도 커버가 됩니다. 코드도 간단합니다. ajax 요청과 크게 다르지 않네요. window.postMessage를 지원하지 않는 경우엔 window.location에 해시(#)를 붙여 메시지를 보내고 window.location.hash 값을 모니터링하는 식으로 처리합니다. 기발하네요.