스프링 3.1 설정 강화

http://www.infoq.com/presentations/Configuration-in-Spring-3-1

저처럼 서론 보기 귀찮으신 분들은 19분부터 보는 것이 좋습니다.

크게 세 가지로 나눠 볼 수 있을 것 같습니다.

  • Environment 추상화 지원
  • 빈 프로파일 지원
  • @Configuration 강화

자세히 적을 시간이 없어서 대충 보면서 요약한 메모를 그냥 올려두겠습니다. 죄송;;

Environment 지원
– Unified 프로퍼티 관리
– 빈 정의 프로파일

dev -> qa -> staging -> prod

Environment.getProperty(String)

Environment 인터페이스
PropertySource 추상화
– EnvironmentAware 인터페이스
– EnvironmentAwarePropertyPlaceholderConfigurer
– EnvironmentAwarePorpertyOverrideConfigurer
– SpEL 지원
– ProfileResolver

${‘prop.key’}
EnvironmentAwarePropertyPlaceholderConfigurer 이걸 쓰면 Environment 적용됨.

#{environment[‘prop.key’]}
EnvironmentAwarePropertyPlaceholderConfigurer 생략 가능.

조건적으로 등록되는 빈 정의(빈 정의 프로파일)
– 개발 단계와 배포 단계가 다를 수도 있지만
– 배포 환경 끼리도 서로 다를 수도 있다.

<beans profile="developmenr">
    <bean ~~ />
</beans>

@Profile("developmenr’)
@Component || @Configuration
public class MyComponent {..}

메타 애노테이션으로 활용 가능

java -DspringProfiles="p1,p2…
<context-param>, <init-param>
ConfigurableEnvironment.setActiveProfiles("p1", "p2")

내부 <beans>
– 프로파일 용
– 비슷한 기본값 묶을 때 사용
– 내부 빈끼리는 id 중복 허용
– 하지만 같은 곳에 들어있는 빈끼리는 중복되면 안 됨.

테스트 지원
– MockEnvironment
– MockProperySource

Pitfalls
– QA 단계에서 본 빈 그래프랑 제품 단계 때의 빈 그래프가 다르다.
– 잘못해서 테스트 단계 설정이 배포될 수 있다.
해결책
– 빈 그래프는 같아야 돼. 그냥 속성만 다른건 괜찮아.
– 빌드 시스템 수준에서 안전하게 빌드할 것
– 제품 단계에 개발 단계에 필요한 JAR를 추가하지 말 것.

STS에서 툴 지원
– 내부 <beans/> XML 설정 인식
– 프로파일 가시화

c:namespace
c(onstructor)
p:namespace에 대응하는 것.

자바 기반 앱 설정
– 다른 설정은?
– 컴포넌트 스캔은?

어려운 점
– 스프링 네임스페이스를 매핑하는게 간단하지 않아.
— purely dclarative
— Reference 빈 or 메서드
— DSL in XML

@ComponentScan("패키지")

TestCOntext @Configuration 지원

Groovy 지원

http://maven.springframework.org/snapshots

http://git.springsource.org/sandbox/cbeams.git

[스프링 테스트] 웹 테스트용 WebApplicationContext에 request, session 스코프 빈 등록하기

Spring, Junit 에서 session, request scope bean 을 사용하기 

오리대마왕님께서 올려주신 글을 보니 ‘토비의 스프링 3’에 나오는 웹 테스트 코드가 생각나서 그냥 session 스코프 빈을 한번 등록해 봤습니다. 오리대마왕님 테스트의 주 목적도 이거였을 텐데.. 컨트롤러에 AOP를 등록하고, 현재 request를 담고 있는 HttpRequestHolder를 request 스코프로 만든 걸 봤는데… 테스트 목적에 비해 코드가 다소 장황한 것 같습니다. (사실 올려주신 코드에서 HttpRequestHolder를 request 스코프로 등록하지 않고 그냥 singleton으로 써도 결과는 똑같더군요. 현재 요청을 가로 채고 있던 녀석은 HttpRequestHolder를 request 스코프로 했기 떄문이 아니라 RequestContextHolder 때문인것 같더군요. 그리고 컨트롤러에 설정한 AOP도 그냥 스프링 인터셉터를 쓰시면 더 간단하게.. 처리가 가능한.. 암튼..)
public class SimpleRequestScopeTest {
    @Scope(value = “request”, proxyMode = ScopedProxyMode.TARGET_CLASS)
    static class RequestScopedBean {}
    @Test
    public void simpleReqestScopeBean() throws ServletException {
        ConfigurableDispatcherServlet dispatcherServlet = new ConfigurableDispatcherServlet();
        dispatcherServlet.setClasses(RequestScopedBean.class);
        dispatcherServlet.init(new MockServletConfig(“spring”));
        WebApplicationContext wac = dispatcherServlet.getWebApplicationContext();
        RequestScopedBean rsb = wac.getBean(RequestScopedBean.class);
        assertThat(rsb, is(notNullValue()));
    }
}
이게 다입니다. RequestScopedBean이라는 클래스를 만들고, 스코프를 설정해준 다음 ConfigurableDispatcherSevlet에 전달해서 빈으로 등록해주었고, DS에서 WAC를 가져온다음 RSB를 getBean 해서 null이 아닌지 확인하면 끝입니다.
단순 빈 등록만 확인하려면 WebApplicationContext를 직접 만들어 쓰면 되겠지만, 여기서는 웹 테스트 용도로 작성하는 테스트라고 가정하면, 요청 매핑이나, 실행 결과 ModelAndView등을 확인하려면 역시 DispatcherServlet을 써먹어야 합니다. 하지만 DispatcherServlet은 ModelAndVIew를 밖으로노출해주지 않죠. 그럴 필요도 없구요. 그래서 테스트용으로 DispatcherServlet을 확장한 것이 바로 저기에 보이는 ConfigurableDS입니다. 이 CDS를 사용한 초단단 웹 @MVC 테스트는 다음과 같습니다.
public class HelloControllerTest {
@Test
public void helloController() throws ServletException, IOException {
ConfigurableDispatcherServlet servlet = new ConfigurableDispatcherServlet();
servlet.setRelativeLocations(getClass(), “spring-servlet.xml”);
servlet.setClasses(HelloSpring.class);
servlet.init(new MockServletConfig(“spring”));
MockHttpServletRequest req = new MockHttpServletRequest(“GET”, “/hello”);
req.addParameter(“name”, “Spring”);
MockHttpServletResponse res = new MockHttpServletResponse();
servlet.service(req, res);
ModelAndView mav = servlet.getModelAndView();
assertThat(mav.getViewName(), is(“/WEB-INF/view/hello.jsp”));
assertThat((String)mav.getModel().get(“message”), is(“Hello Spring”));
}
}
(출처, 토비의 스프링 3 예제 코드)
그러나. 이것 마저도 복잡합니다. ConfigurableDispatcherServlet 를 만들고 설정한 다음, Request에 URL을 설정하고 그 결과로 나오는 MAV를 확인하는 패턴이 고정적입니다. 따라서 이걸 더 간편하게 사용할 수 있도록 만든 클래스가 있는데.. 그게 AbstractDispatcherServletTest 입니다. ConfigurableDispatcherServlet과 AbstractDispatcherServletTest는 토비의 스프링 3에서 직접 보실 수도 있고, 예제 코드를 다운 받아 보실 수도 있는데 설명이 들어있는 책으로 보시는걸 추천해 드립니다.
여기서는 간단한 사용법만 보여드리자면;;
public class SimpleMVCTest extends AbstractDispatcherServletTest {
@Test
public void simpleHandler() throws ServletException, IOException {
this.setClasses(SimpleHandler.class, SimpleViewHandler.class)
.runService(“/hi”);
assertThat(this.response.getContentAsString(), is(“hi”));
this.runService(“/view”);
assertThat(this.getModelAndView().getViewName(), is(“view.jsp”));
}
@Controller static class SimpleHandler {
@RequestMapping(“/hi”) @ResponseBody 
public String hi() { return “hi”; }
}
@Controller static class SimpleViewHandler {
@RequestMapping(“/view”)
public String view() { return “view.jsp”; }
}
}
(출처, 토비의 스프링 3 예제 코드)
이런식으로 초간단 MVC 테스트를 만들 수 있습니다.

[스프링 3.0] 상속구조에서 @RequestMapping 퀴즈

아무도 안 풀 것 같지만… 자신이 @RM을 얼마나 이해했는지 측정해보기 위해서는 좋은 방법이니까 시간나면 꼭 해보시기 바랍니다.

@RequestMapping(“/hier”)
public class SuperController {
@RequestMapping(“/list”)
public String list(Model model){
model.addAttribute(“message”, “hier super list”);
return “/WEB-INF/views/hello.jsp”;
}
}
@Controller
public class SubController extends SuperController {
}
1. 이때 /hier/list 요청을 하면 처리가 될까? 
@Controller
public class SubController extends SuperController {
@Override
public String list(Model model){
model.addAttribute(“message”, “hier sub! list”);
return “/WEB-INF/views/hello.jsp”;
}
}
SubController 코드를 이렇게 바꿨다. 
2. 이때 /hier/list를 요청했을 때 화면에 찍히는 ${message}의 값은 무엇인가?
@RequestMapping(“/hier2”)
public class Super2Controller {
}
@Controller
public class Sub2Controller extends Super2Controller {
@RequestMapping(“/list”)
public String list(Model model){
model.addAttribute(“message”, “hier list”);
return “/WEB-INF/views/hello.jsp”;
}
}
3. 이때 /hier2/list를 요청했을 때 핸들러가 실행될까?
public class Super3Controller {
@RequestMapping(“/list”)
public String list(Model model){
model.addAttribute(“message”, “hier2 super list”);
return “/WEB-INF/views/hello.jsp”;
}
}
@Controller
@RequestMapping(“/hier3”)
public class Sub3Controller extends Super3Controller {
}
4. 이때 /hier3/list 요청이 처리 될까?
@Controller
@RequestMapping(“/hier3”)
public class Sub3Controller extends Super3Controller {
@Override
@RequestMapping(“/list”)
public String list(Model model){
model.addAttribute(“message”, “hier2 sub~! list”);
return “/WEB-INF/views/hello.jsp”;
}
}
5. 구현체를 이렇게 바꾸면 에러가 날까? 
6. 그렇치 않다면? ${message}의 값은 어떻게 될까?
@RequestMapping(“/hier4super”)
public class Super4Controller {
@RequestMapping(“/all”)
public String list(Model model){
model.addAttribute(“message”, “hier4 super list”);
return “/WEB-INF/views/hello.jsp”;
}
}
@Controller
@RequestMapping(“/hier4”)
public class Sub4Controller extends Super4Controller {
@Override
@RequestMapping(“/list”)
public String list(Model model){
model.addAttribute(“message”, “hier4 sub list”);
return “/WEB-INF/views/hello.jsp”;
}
}
7. /hier4suprer/all 이라는 요청은 처리 될까?
8. /hier4/list 라는 요청은 처리 될까?
정답은 토비님 책 또는 이번주 강의에서..
오늘의 퀴즈 2종 세트를 다 맞추시는 분은 @ReuqestMapping 마스터!

[스프링 3.0] 클래스-메서드 레벨 @RequestMapping 퀴즈

이미 2.5부터 추가된 기능이고 가장 자주 사용하고 있는 애노테이션 @ReqeustMapping.. 과연 얼마나 알고 있을까? 

public class Book2Controller {
@RequestMapping(“/book2/add”)
public String book2Add(Model model){
model.addAttribute(“message”, “book2 add”);
return “/WEB-INF/views/hello.jsp”;
}
@RequestMapping(“/book2/get”)
public String book2Get(Model model){
model.addAttribute(“message”, “book2 get”);
return “/WEB-INF/views/hello.jsp”;
}
}
1. 이 클래스를 <bean />을 사용해서 빈으로 등록하면 /book2/add 이나 /book2/get 요청 핸들러가 동작할까?
2. 만약 1번에서 false를 선택했다면 위 코드의 @RM이 동작하게 만드는 방법 두가지는 무엇일까?
@Controller
@RequestMapping(“/book3”)
public class Book3Controller {
@RequestMapping
public String add(Model model){
model.addAttribute(“message”, “book3 add”);
return “/WEB-INF/views/hello.jsp”;
}
@RequestMapping
public String get(Model model){
model.addAttribute(“message”, “book3 get”);
return “/WEB-INF/views/hello.jsp”;
}
}
3. 이렇게 매핑 했을 때 /book3/add 와 /book3/get은 동작할까?
4. 만약 3번에서 false를 선택했다면 Book3Controller의 @RequestMapping 설정을 어떻게 고치면 동작하게 할 수 있을까? (역시 두가지)
@Controller
@RequestMapping(“/book3/*”)
public class Book3Controller {
@RequestMapping
public String add(Model model){
model.addAttribute(“message”, “book3 add”);
return “/WEB-INF/views/hello.jsp”;
}
@RequestMapping
public String get(Model model){
model.addAttribute(“message”, “book3 get”);
return “/WEB-INF/views/hello.jsp”;
}
}
5. 위와 같이 설정했을 때 /book3/a/b/c/add 는 동작할까 안할까?
6. 만약 5번에서 동작하지 않는다고 대답했다면.. Book3Controller의 @RM 설정을 어떻게 고치면 동작하게 될지 적어보자.
정답은.. 토비님 책 또는 이번주 강의에서…