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(&quot;…&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 &quot;Hello world&quot;;
}
}
}
org.springframework.web.*.DeferredResult<?>
Spring MVC가 제어하지 않는 쓰레드를 통해 비동기 처리(JMS, AMQP, Redis 등)를 합니다. 먼저 DeferredResult를 저장하고, 외부 이벤트 발생 시, 실행하게 됩니다. 이때, 스프링의 AsyncTask를 사용할 수도 있습니다:
// save
@RequestMapping(&quot;…&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(&quot;Deferred result&quot;);
this.responseBodyQueue.remove(result);
}
}
서버단 롱폴링 처리 프로세스
- 브라우저 요청
- 핸들러 메소드가 DeferredResult 리턴
- 서블릿 컨테이너 종료(응답은 살아있음)
- JMS, AMQP, Redis 같은 외부 이벤트 발생
- DeferredResult 세팅
- 요청이 서블릿 컨테이너로 돌아감
- 처리가 다시 진행되고 요청 완료
서블릿 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&lt;?&gt;[] getRootConfigClasses() {
return new Class&lt;?&gt;[] { RootConfig.class };
}
@Override
protected Class&lt;?&gt;[] getServletConfigClasses() {
return new Class&lt;?&gt;[] { WebMvcConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { &quot;/*&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 &quot;Error details&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 &quot;file:src/main/webapp&quot;
@WebAppConfiguration
// detects &quot;WacTests-context.xml&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(&quot;servlet-context.xml&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(&quot;/foo&quot;).accept(&quot;application/json&quot;))
.andExpect(status().isOk())
.andExpect(content().mimeType(&quot;application/json&quot;))
.andExpect(jsonPath(&quot;$.name&quot;).value(&quot;Lee&quot;));
}
}
// Client-side test
RestTemplate restTemplate = new RestTemplate();
MockRestServletServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo(&quot;/greeting&quot;))
.andRespond(withSuccess(&quot;Hello world&quot;, &quot;text/plain&quot;));
// use RestTemplate …
mockServer.verify();