Whiteship's Note

'Spring/3.0'에 해당되는 글 26건

  1. 2010.08.10 [스프링 테스트] 웹 테스트용 WebApplicationContext에 request, session 스코프 빈 등록하기 (2)
  2. 2010.07.13 [스프링 3.0] 상속구조에서 @RequestMapping 퀴즈
  3. 2010.07.13 [스프링 3.0] 클래스-메서드 레벨 @RequestMapping 퀴즈
  4. 2010.04.13 [스프링 3.0] 소녀시대와 함께하는 스프링 @MVC (14)
  5. 2010.03.16 [스프링 3.0] @Value 이용해서 기본값 설정하기
  6. 2010.03.04 [스프링 3.0] @Async 테스트
  7. 2010.02.28 [스프링 3.0] FormattingConversionServiceFactoryBean에 들어있는 Converter와 Formatter (2)
  8. 2010.02.19 Spring Framework 3.0.1 나왔구나
  9. 2010.02.18 [스프링 3.0] PropertyEditorRegistry가 이길까 ConversionService가 이길까 (2)
  10. 2010.01.28 [스프링 3.0] 로깅은 SLF4J를 이용한 Log4J로 (1)
  11. 2010.01.12 [스프링 3.0] JSR-330 Provider 인터페이스 이용해서 싱글톤이 아닌 빈을 싱글톤 빈에 주입하기 (2)
  12. 2009.12.22 스프링 3.0의 MVC 간편화 (2)
  13. 2009.12.17 스프링 프레임워크 3.0이 GA 됐다. (4)
  14. 2009.12.11 [스프링 3.0] 스프링 bean과 일반 자바 객체가 호출하는 @Bean 메서드의 차이
  15. 2009.12.10 [스프링 3.0] @Async 사용하기
  16. 2009.11.16 [스프링 3.0] RC2 릴리즈~
  17. 2009.10.18 [스프링 3.0] @Valid 실습
  18. 2009.10.18 [스프링 3.0] @Valid 이론
  19. 2009.09.28 [스프링 3.0] 애노테이션 기반 스케줄링 (2)
  20. 2009.09.26 드이어 스프링 3.0 RC1이 나왔습니다. (3)
  21. 2009.08.20 [스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 4
  22. 2009.08.19 [스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 3
  23. 2009.08.13 [스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 2
  24. 2009.08.13 [스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 1
  25. 2009.06.10 [Spring 3.0] HiddenHttpMethodFilter (4)
  26. 2009.04.21 Spring Expression Language(SpEL) (2)

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

Spring/3.0 : 2010.08.10 12:23


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 테스트를 만들 수 있습니다.
top

  1. Favicon of http://kingori.egloos.com BlogIcon kingori 2010.08.10 17:49 PERM. MOD/DEL REPLY

    오, 중간 부분에 나온 HelloControllerTest 가 결국 제가 하고 싶었던 내용입니다. 책을 읽고 고민을 해 봐야겠네요.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.08.11 00:42 신고 PERM MOD/DEL

    음.. 그렇군요. 근데 제목엔 request 랑 session 스코프 빈을 등록하는게 목적인 것 같아 보이네요.ㅋㅋ

    암튼 저 Web 테스트는 토비님 책 중에서도 상당히 멋진 부분 중 일부 입니다. 그밖에도 주옥같은 부분들이 많은데 책 나오기 전까지 꾹꾹 참고 있어요.ㅋㅋ

Write a comment.


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

Spring/3.0 : 2010.07.13 15:09


아무도 안 풀 것 같지만... 자신이 @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 마스터!

top

Write a comment.


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

Spring/3.0 : 2010.07.13 12:12


이미 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 설정을 어떻게 고치면 동작하게 될지 적어보자.

정답은.. 토비님 책 또는 이번주 강의에서...
top

Write a comment.


[스프링 3.0] 소녀시대와 함께하는 스프링 @MVC

Spring/3.0 : 2010.04.13 23:49


아우 귀찮아 ㅠ.ㅠ 아우 귀찮아... ㅠ_ㅠ 
괜히 만들었어~~ 괜히 소녀시대로 장난쳤어~~

퀴즈1) 태연은 뭐다??
퀴즈2) HandleAdapter는 뉴~규?

정답은 이번주 토요일 KSUG 세미나에서...
top

  1. Favicon of http://blog.lckymn.com BlogIcon Kevin 2010.04.14 05:57 PERM. MOD/DEL REPLY

    제 댓글은 계속 짬 당하는듯...ㅠ_ㅠ

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.04.14 11:51 신고 PERM MOD/DEL

    혹시 몰라서 찾아봤는데 역시;; 찾아서 꺼냈습니다.ㅋ
    일단 티스토리 영어환자 플러긴을 꺼뒀습니다. 내용을 보니까 Ascii로만 작성된 댓글을 차단한다는 내용밖에 없네요.

  2. Favicon of http://blog.lckymn.com BlogIcon Kevin 2010.04.14 14:44 PERM. MOD/DEL REPLY

    아 아... 테스팅 테스팅 테스팅 잘 보이나요? :)

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.04.20 14:39 신고 PERM MOD/DEL

    난감하네요... 헐.. 대체 왜이럴까요.
    플러그인이 아니면 필터 설정에 걸리는 것 밖에 없을텐데 말이죠. @_@.

  3. Favicon of http://helols.pe.kr BlogIcon is윤군 2010.04.16 15:37 PERM. MOD/DEL REPLY

    사실.... 개인적으로.. 발표 부분중에서 제일 맘에 드는 부분임..ㅋ

    Favicon of http://whiteship.me BlogIcon 기선 2010.04.16 15:42 PERM MOD/DEL

    응 내일 봅세~

  4. Favicon of http://blog.lckymn.com BlogIcon Kevin 2010.04.20 19:50 PERM. MOD/DEL REPLY

    헉! 이제 잘 써지나요?ㅡ_ㅡ?

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.04.28 14:15 신고 PERM MOD/DEL

    이때까진 안된듯..

    Favicon of http://lckymn.com BlogIcon Kevin 2010.05.01 06:07 PERM MOD/DEL

    역시 지금도 홈페이지란에 블로그 주소를 넣으면 안 되는군요. 홈페이지 주소로 교체...

    Favicon of http://whiteship.me BlogIcon 기선 2010.05.02 11:11 PERM MOD/DEL

    다행히(?) 이 주소는 잘 되네요.ㅋ

  5. Favicon of http://google.com BlogIcon Kevin 2010.04.20 19:58 PERM. MOD/DEL REPLY

    왜 그런지 알았습니다.
    제 블로그 주소를 넣으니까 차단되었다고 나오면서
    등록이 안 되네요.
    희한하군요. 제 블로그가 왜 막힌건지?ㅡ_ㅡ?
    근데 이거 티스토리 버그 같습니다.
    새로 설치된 다른 티스토리에서도 같은 증상이 보이네요.

    혹시 구글 같은 사이트도 차단 되는지 시험해보니
    구글은 멀쩡하네요.

    티스토리에서 차단 하는 사이트 목록이나
    혹은 패턴에 제 사이트가 걸려든 모양입니다...ㅡ_ㅡ;

    근데 웃긴게 홈페이지 주소를 쓰니 정상적으로 등록되는데,
    블로그 페이지만 차단됐다고 뜨네요...ㅡ_ㅡ;

    전 그럼 이제 티스토리에 항의하러...ㅡ_ㅡ;;;

    Favicon of http://whiteship.me BlogIcon 기선 2010.04.21 07:31 PERM MOD/DEL

    오오오!! 드디어 보입니다.
    아흑.. 티스토리 왜이래 ㅠ_ㅠ

    워드프레스로 가고 싶어도 여기 올린게 하두 많아서 엄두를 못내고 있어요 @_@

    Favicon of http://lckymn.com BlogIcon Kevin 2010.05.01 06:09 PERM MOD/DEL

    이런...ㅡ_ㅡ;
    버그 신고하려고 보니, 무슨 개인정보 이용에 동의 어쩌고...
    짜증나서 관뒀습니다. @_@;

    Favicon of http://whiteship.me BlogIcon 기선 2010.05.02 11:10 PERM MOD/DEL

    ㅋㅋ글쿤요.

Write a comment.


[스프링 3.0] @Value 이용해서 기본값 설정하기

Spring/3.0 : 2010.03.16 18:04


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ValueTest {

    @Autowired Whiteship whiteship;

    @Test
    public void defaultName(){
        assertThat(whiteship.getName(), is("keesun"));
    }

}

@Component
public class Whiteship {

    @Value("keesun")
    private String name;

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

악.. 저녁 약속이 있어서 길게 정리는 못하겠네요. 


이것도 DI용 애노테이션이라는거..


top

Write a comment.


[스프링 3.0] @Async 테스트

Spring/3.0 : 2010.03.04 17:55


@Async 애노테이션을 사용한 메서드의 반환 타입은 둘 중 하나여야 합니다. void 거나.. java.util.concurrent.Future 타입이어야 한다네요. 뭐.. 그러니까 사실상 하나나 마찬가지이죠. 그렇다고 해서 다른 리턴타입으로 설정하면 비동기로 동작하지 않는 건 아닙니다. 다만;; 해당 메서드의 클라이언트 입장에서 보면 리턴값이 전부 null 이기 때문에 황당한 경우가 발생할테지만 말이죠.

스프링에서 Future 인터페이스의 구현체로 AsyncResult를 제공해줍니다. 이걸 이용해서 간단하게 Thread를 반환하는 비동기 메서드를 만들었습니다.

    @Async
    public Future<Thread> more() {
        return new AsyncResult<Thread>(Thread.currentThread());
    }

@Async는 @Transaction과 비슷하게 타입에 선언할 수도 있습니다. 그러면 해당 클래스의 모든 메서드가 비동기 메서드로 처리되겠죠.

<task:executor id="myExecutor" pool-size="5"/>

쓰레드 풀 갯수를 5개로 설정해 놓고 다음과 같이 테스트를 해봤습니다.

    @Test
    public void async() throws Exception {
        assertThat(beanService, is(notNullValue()));
        Set<Thread> threads = new HashSet<Thread>();

        for(int i = 0 ; i < 200 ; i++){
            collectThreadInfo(beanService.more(), threads);
        }
        assertThat(threads.size(), is(5));
        assertThat(threads.contains(beanService.more().get()), is(true));
    }

    private void collectThreadInfo(Future<Thread> future, Set<Thread> threads) throws Exception {
        threads.add(future.get());
    }

200번까지 안돌려도 상관없지만. 그냥.. 충분히 돌려서 쓰레드 풀에 있는 모든 쓰레드를 컬렉션에 모아둔 다음에 쓰레드 풀에서 만든 쓰레드 갯수를 확인하고 마지막으로 한 번 더 호출해서 반환 받은 Thread가 현재까지 사용한 쓰레드 중 하나인지 확인합니다.


top

TAG @Async, Spring

Write a comment.


[스프링 3.0] FormattingConversionServiceFactoryBean에 들어있는 Converter와 Formatter

Spring/3.0 : 2010.02.28 08:47


3.0에 새로 추가된  <mvc:annotation-driven>을 등록할 때 자동으로 등록되는 FormattingConversionServiceFactoryBean이 있을 때 기본으로 사용할 수 있는 Converter와 Formatter를 살펴보겠습니다.

사실 이 클래스는 프로젝트에서 확장할 가능성이 높은 클래스입니다. 사용하는 도메인에 대한 포매터를 제공할 가능성이 높기 떄문이죠. 그렇지 않고 그냥 쓴다는 건... 글쎄요. 스프링의 바인딩 기능을 제대로 활용하지 않고 있는 프로젝트일 가능성이 높습니다. 3.0 이전이었다면 모든 요청에 걸쳐 전역적으로 바인딩에 활용할 녀석을 PropertyEditorRegistrar(PER)를 구현한 클래스를 만들어  ConfigurableWebBindingInitializer에 빈으로 끼워넣었습니다.

그래서인지 <mvc:annotation-driven> 에도 conversion-service 속성을 제공하여 자신이 확장한 FCSFB를 끼워넣을 수 있게 해줍니다.

그런데.. 그냥 기본으로 사용할 때 대체 어떤 것들이 들어있는지는 API에도 명확히 나와있지 않습니다.
A factory for a FormattingConversionService that installs default formatters for common types such as numbers and datetimes.

Subclasses may override installFormatters(FormatterRegistry) to register custom formatters.

숫자나 날짜 같은 흔히 사용하는 타입에 대한 기본 포매터를 설치해준다. 확장할 땐 머시기 메서드를 써라.

흠.. 뭐가 있는지 알아야 쓰지 말입니다. @_@; 그래서 FCSFB 객체를 콘솔에 출력해봤습니다.

ConversionService converters =
    @org.springframework.format.annotation.DateTimeFormat java.lang.Long -> java.lang.String: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b, @org.springframework.format.annotation.NumberFormat java.lang.Long -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.DateTimeFormat java.util.Calendar -> java.lang.String: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b
    @org.springframework.format.annotation.DateTimeFormat java.util.Date -> java.lang.String: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b
    @org.springframework.format.annotation.NumberFormat java.lang.Double -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.NumberFormat java.lang.Float -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.NumberFormat java.lang.Integer -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.NumberFormat java.lang.Short -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.NumberFormat java.math.BigDecimal -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    @org.springframework.format.annotation.NumberFormat java.math.BigInteger -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.Character -> java.lang.Number : org.springframework.core.convert.support.CharacterToNumberFactory@69236cd5
    java.lang.Number -> java.lang.Character : org.springframework.core.convert.support.NumberToCharacterConverter@552c8fa8
    java.lang.Number -> java.lang.Number : org.springframework.core.convert.support.NumberToNumberConverterFactory@1cee1ede
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.lang.Long: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b, java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Long: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Calendar: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b
    java.lang.String -> @org.springframework.format.annotation.DateTimeFormat java.util.Date: org.springframework.format.support.FormattingConversionServiceFactoryBean$NoJodaDateTimeFormatAnnotationFormatterFactory@794e113b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Double: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Float: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Integer: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.lang.Short: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigDecimal: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigInteger: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@47ad6b4b
    java.lang.String -> java.lang.Boolean : org.springframework.core.convert.support.StringToBooleanConverter@4dbb9a58
    java.lang.String -> java.lang.Character : org.springframework.core.convert.support.StringToCharacterConverter@57922f46
    java.lang.String -> java.lang.Enum : org.springframework.core.convert.support.StringToEnumConverterFactory@67dacccc
    java.lang.String -> java.lang.Number : org.springframework.core.convert.support.StringToNumberConverterFactory@3fe2670b
    java.lang.String -> java.util.Locale : org.springframework.core.convert.support.StringToLocaleConverter@28db23f1
    java.lang.String -> java.util.Properties : org.springframework.core.convert.support.PropertiesToStringConverter@14be49e0
    org.springframework.core.convert.support.ArrayToArrayConverter@53f78b68
    org.springframework.core.convert.support.ArrayToCollectionConverter@9ac5f13
    org.springframework.core.convert.support.ArrayToObjectConverter@744d76b4
    org.springframework.core.convert.support.ArrayToStringConverter@1395dd5b
    org.springframework.core.convert.support.CollectionToArrayConverter@5e6214f5
    org.springframework.core.convert.support.CollectionToCollectionConverter@2eb0a3f5
    org.springframework.core.convert.support.CollectionToObjectConverter@4a5f2db0
    org.springframework.core.convert.support.CollectionToStringConverter@4edc41c5
    org.springframework.core.convert.support.IdToEntityConverter@20e183e9, org.springframework.core.convert.support.ObjectToObjectConverter@359b46dc
    org.springframework.core.convert.support.MapToMapConverter@4b14b82b
    org.springframework.core.convert.support.ObjectToArrayConverter@14004204
    org.springframework.core.convert.support.ObjectToCollectionConverter@65493102
    org.springframework.core.convert.support.ObjectToStringConverter@2830ae41
    org.springframework.core.convert.support.StringToArrayConverter@3e5dc994
    org.springframework.core.convert.support.StringToCollectionConverter@58e41bc3

흠.. 저런 것들이 있군요.
top

  1. Favicon of http://toby.epril.com BlogIcon 토비 2010.02.28 19:53 PERM. MOD/DEL REPLY

    곧 출간될 스프링 3.0 책 13장에 보면 아주 자세히 나와있....

    Favicon of http://whiteship.me BlogIcon 기선 2010.03.01 16:45 PERM MOD/DEL

    앗.. 감사합니다. ㅋㅋ

Write a comment.


Spring Framework 3.0.1 나왔구나

Spring/3.0 : 2010.02.19 11:52


http://blog.springsource.com/2010/02/18/spring-framework-3-0-1-released/

번역하기는 귀찮아서 짧게 요약만 합니다.

- 의존성 포함한 다운로드까지 제공해달라는 사람들이 좀 있어서 메이븐과 Ivy로 가져온 써드파티 라이브러리들까지 압축한 뭉탱이 파일 다운로드도 제공함.

- 3.0.1에서 새롭게 지원하는 라이브러리: 타일즈 2.2, 하이버네이트 3.5(CR1, JPA 2.0 구현체)

- ApplicationListener 감지 기능 향상: 프록시, 팩토리 메서드, Generic 정보를 확ㄹ용한 이벤트 선언 감지 등

- 포인트컷-기반 프록시, EntityManager 프록시, @Transactional 프록시를 직렬화할 수 있게 되었다. 특히 웹 애플리케이션 환경에서 유용할 듯

- FactroryBean과 HttpMessageConverter 같은 Generic 인터페이스의 Class 매개변수 선언을 좀 더 완하했다.

- JdbcTemplate의 쿼리 메서드에 가변인자를 사용했다.

- 보너스: 스프링의 JSP 태그라이브러리에 <spring:eval> 패그를 추가했다. JSP에서 SpEL 표현식을 사용할 수 있으며 스프링 3.0의 포매팅 시스템을 사용하여 포매팅된 결과를 보여줄 수도 있다. 기본적으로 JSTL의 <c:out>과 <fmt:*> 기능을 통합한 것으로 볼 수 있겠다.


top

Write a comment.


[스프링 3.0] PropertyEditorRegistry가 이길까 ConversionService가 이길까

Spring/3.0 : 2010.02.18 17:24


public class Whiteship {

    String name;

    public Whiteship(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


public class SpringSprout {

    private Whiteship whiteship;

    private int num;

    public Whiteship getWhiteship() {
        return whiteship;
    }

    public void setWhiteship(Whiteship whiteship) {
        this.whiteship = whiteship;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

이렇게 두 개의 클래스가 있을 때 Whiteship을 SpringSprout로 주입하는 빈 설정을 다음과 같이 했다.

    <bean class="sandbox.convert.SpringSprout">
        <property name="whiteship" value="keesun"/>
        <property name="num" value="1"/>
    </bean>

동작할까 안할까?

당연히 안한다. Whiteship 타입이 필요한데 keesun이라는 문자열을 던져주다니 장난 하는게냐 라고 예외를 던질꺼다.

하지만 되게 만들 수 있다. 이전까지는 PropertyEditorSupport 클래스를 확장하여 아주 손쉽게 구현할 수 있었다.

public class WhiteshipPE extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        return ((Whiteship)getValue()).getName();
    }

    @Override
    public void setAsText(String name) throws IllegalArgumentException {
        setValue(new Whiteship(name));
    }
}

이런 코드는 특히 MVC에서 바인딩 할때 매우 유용하다. 이걸 안쓰면 request에서 일일히 꺼내서 노가다를 해야하는데 난 절대로 그러고 싶지 않다.

그런데 PE의 문제는 쓰레드-세이프하지 않다는 것이다. 그래서 PE를 등록할 때 조심해야 한다. 특히 전역 변수를 가지는 PE를 싱글톤으로 바인더에 등록해버리지 않았나 조심해야 한다. 반드시 그때 그때 new를 사용해서 등록해 주도록 하고 PE 생성자에 필요한 레퍼런스를 전달해주는게 안전하다. 아마 이 내용도 사부님 책에 들어갈 것 같으니 자세한 내용은 그 책을 참고하도록 하자.

PE의 대안이자 좀 더 범용적인 변환기 역할로 3.0에 도입된 것이 ConversionService이고 ConversionServiceFactoryBean에 Converter, ConveterFacrtory, Formatter 등을 등록해놓고 변환을 부탁할 수 있게 되었다.

public class WhiteshipConverter implements Converter<String, Whiteship> {
    public Whiteship convert(String source) {
        return new Whiteship(source);
    }
}

그런데;; PE와 역할이 중복되는데;;; 둘 다 빈으로 등록해둘수 있다.


    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <bean class="sandbox.convert.CustomPERegister">
            </bean>
        </property>
    </bean>

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="sandbox.convert.WhiteshipConverter"/>
            </set>
        </property>
    </bean>

결국 문제는 대체 String을 Whiteship으로 바꿔야 할 때 PE를 쓸 것이냐 CS를 쓸 것이냐이다. 무엇을 쓰게 될지... 그게 궁금했었다. 그런데 마침 오늘 사부님이 퀴즈를 냈고;; 난 스케줄을 팽개치고 매달렸다;;

1차 문제 상황

CS와 PE가 등록되어 있을 때 PE가 이겼다.
하지만 난 CS가 이기는 경우를 예제에서 본적이 있었다. 그래서 도무지 납득이 되지 않았고 어떻게 설정해야 가능한지 궁금했다.

CS만 등록했을 땐 CS로 동작했다.
PE만 등록했을 땐 PE로 동작했다.
둘다 등록했을 땐 PE가 동작헀다.

2차 문제 상황

Whiteship만 가지고 테스트를 했었는데 기본 타입도 추가해봤다. int num이 그것이다. 이제 더 큰 문제가 생겼다.
Whiteship만 PE가 이기고 나머지 기본 타입은 CS가 이겼다.
정말 깜놀이었다. 이사실을 발견하지 않았다면 난 그냥 클래스로더를 공부했을 것이다.

어디선가 기본 PE를 덮어쓰거나 없애버린 거 아닌지 궁금했고 소스 코드를 뒤져보기 시작했다.
시간이 손살같이 지난간다.
우울해지기 시작한다.

3차 결론

소스 코드를 뒤적거리다가 ConvertingPropertyEditorAdapter 클래스를 찾았다.
이 클래스는 ConversionService를 사용해서 PE를 노출시켜주는 어댑터다.
겉으로는 PE를 등록한것 같지만 실제로는 ConvesionService를 사용하는 것이다.ㅋ

스프링에서 내부에서 저걸 사용해서 기본 PE들을 등록하도록 바꿨는지는 확인하지 않았다.
왠지 답이 아닌것 같다. OTL..
내가 졌다.

트릭같은 기법을 써서라도 PE를 누르고 아니 속이고 CS가 동작하게 했으니...그만 놓아줄 수 있을 것 같다.
어서 사부님 책이 나와주길....

ps: 위에 나온 코드는 급조한 코드오니 조심할 부분을 건너뛴 곳도 있습니다. 주의하세요.
top

  1. Favicon of http://toby.epril.com BlogIcon 토비 2010.02.18 17:44 PERM. MOD/DEL REPLY

    자세히 보면 이미 글에 답이 있음

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2010.02.19 09:33 신고 PERM MOD/DEL

    흠... 그럼 맞춘걸로 치겠습니다. ㅋㅋㅋㅋㅋ

Write a comment.


[스프링 3.0] 로깅은 SLF4J를 이용한 Log4J로

Spring/3.0 : 2010.01.28 17:08


스프링을 사용하면 기본적으로 JCL(자카르타 커먼스 로깅)을 사용하게 되는데 JCL이 실제 로거를 선택하는 시점이 런타임이라 클래스로더 문제라던가 런타임시 오버헤드가 생길 수 있는데 이것을 개선한 구현체 SLF4J로 갈아타면 그러너 문제 걱정을 덜 수 있겠습니다. 보너스로 문자열 연결로 발생하는 오버 헤드도 개선할 수 있으며 귀찮은 if문 추가하는 코딩에서 벗어날 수 있는 문법? API?를 제공해줍니다. 그래서인지 스프링도 3.0부터는 본격적으로 spring-core가 의존하는 JCL을 SLF4J로 교체하고 사용하는 예제 및 레퍼런스 설명을 보여주고 있습니다..

갈아타는 방법은 다음과 같습니다.

1. 스프링에서 참조하는 JCL 라이브러리를 빼버리기.
2. JCL-over-SLF4J 추가하기.
3. SLF4J API 추가.
4. SLF4J-log4j 추가.
5. log4j 추가.

이전에 스프링소스 블로그와 레퍼런스에 올라온 방법은 이걸 그대로 메이븐 의존성으로 옮겨적고 있지요.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                 </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>

1번은 단순히 JCL 라이브러리에 의존할 생각이 없기 때문에 빼버리는 것이고, 그때 JCL에 의존하는 클래스들이 깨질 텐데 그것을 JCL-over-SLF4J를 이용해서 겉은 JCL 같지만 내부에서는 SLF4J API를 호출하도록 일종의 어댑터 나 다리 역할을 해주는 라이브러리를 추가하고, 인터페이스 격인 3. SLF4J API를 추가한 뒤 실제 사용할 로거로 SL4J를 구현한 4. SLF4J-Log4J 라이브러리를 추가합니다. 마지막으로 최종적으로 사용할 로거 5. Log4J를 추가한 것입니다. 이때 Log4J가 불필요하게 참조하는 jmx, mail, jms 등의 라이브러리를 제외시켜줍니다.

만약에 Log4J가 아니라 다른 로거를 사용할 거라면 4번과 5번을 교체하면 될테고 JCL 뿐 아니라 log4j에 대한 직접 호출도 slf4j로 오게 하려면 2번 대신 log4j-over-slf4j을 추가하면 되겠습니다.

굉장히 장황니다. @_@;; 하지만 뭐.. 좋다는데.. 갈아타긴 해야겠죠.

출처: SLF4J in 10 slides, by Ceki Gülcü

위와 같은 설정은 스프링 3.0 예제와 레퍼런스에서 사용하고 있습니다. 정말 장황합니다. 일단 여기서 1, 2번은 필수다 하지만 레퍼런스와 블로그 글에도 나와있듯이 그 이하 3, 4, 5는 Logback이라는 SLF4J API 구현체로 한방 설정이 가능합니다.

logback은 크게 세 가지 모듈로 나뉘는데 그 중에 SLF4J API 구현체인 classic 모듈이 있고, Log4J를 개선한 core 모듈이 있습니다. logback 모듈을 가져오면 위에서처럼 log4j가 끌고오는 부가적인 라이브러리도 없고 깔끔하게 log4j API를 사용할 수 있습니다.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                 </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>com.springsource.ch.qos.logback.classic</artifactId>
            <version>0.9.9</version>
        </dependency>

따라서 굳이 Log4J가 아닌 다른 로거로 갈아탈 계획이 없다면 이렇게만 설정해도 되겠습니다. 로깅 설정만 거의 1/3로 줄어듭니다.

사용법은

1. SLF4J API를 이용해서 로거를 만들고..

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger log = LoggerFactory.getLogger(AccountJsonBindingTests.class);

2. 다음과 같이 로깅하면 됩니다.

log.info("name is {}", name);

+를 사용해서 문자열 연산을 할 필요도 없고, if문을 줘서 문자열 연산을 막을 필요도 없습니다.

3. log4j 설정은 프로퍼티 파일이나 XML로 할 수 있는데 스프링 3.0 예제는 보통 XML을 사용하더군요. src와 test 소스 폴더 하위의 클래스패스 루트에 각각 다음과 같은 log4j.xml 파일을 둡니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//LOGGER" "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <!-- Appenders -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p: %c - %m%n" />
        </layout>
    </appender>

    <!-- 3rdparty Loggers -->
    <logger name="org.springframework.core">
        <level value="info" />
    </logger>

    <logger name="org.springframework.beans">
        <level value="info" />
    </logger>
   
    <logger name="org.springframework.context">
        <level value="info" />
    </logger>

    <logger name="org.springframework.web">
        <level value="info" />
    </logger>

    <!-- Root Logger -->
    <root>
        <priority value="warn" />
        <appender-ref ref="console" />
    </root>
   
</log4j:configuration>

src와 test 간의 차이는 마지막 부분의 <root> 안의 <priority>가 src에서는 warn이고 test에서는 info라는 것 뿐이 없습니다. 이 설정에서는 Log4J를 사용하고 있는데.. logback API를 이용해서 설정해도 당근 잘 동작합니다.

top

  1. Favicon of http://www.timberlandbaratas.com BlogIcon botas timberland 2012.12.25 14:47 PERM. MOD/DEL REPLY

    La ville de Lille a été condamnée jeudi à indemniser une famille après la disparition d'une tombe dans un cimetière géré par la commune, http://www.timberlandbaratas.com timberland, a-t-on appris auprès de l'avocat des descendants du défunt, http://www.timberlandbaratas.com Timberland. France Amara appelée à se prononcer sur la "censure" d'un rapport de l'Igas France Un gar, http://www.timberlandbaratas.com Hombre Timberland?on de 17 ans mis en examen pour viol sur une fille de 12 ansRelated articles:


    http://unitedkorea.tistory.com/216 Le syndicat Sud-PTT a affirmé mercredi que la Poste avait supprimé 13

    http://persone.tistory.com/46 Terra Nova

Write a comment.


[스프링 3.0] JSR-330 Provider 인터페이스 이용해서 싱글톤이 아닌 빈을 싱글톤 빈에 주입하기

Spring/3.0 : 2010.01.12 22:26


결국 어디선가는 룩업을 해야합니다. 그냥 주입해서 될 일이 아닙니다. getter injection을 사용할 수도 있지만 AOP, Proxy 등 다소 장황해집니다. 룩업은 하되 가장 표준적이면서, 편리하고, 써드파티 라이브러리 의존성을 낮추는 방법을 얼마전 사부님 블로그를 통해 알게 되었습니다. 그 방법은 바로 구글 쥬스에 있던 걸 표준화 한 JSR-330의 Provider.

먼저 빈 설정입니다. 빈 두개. White는 싱글톤, Ship은 프로토타입 스코프로 설정합니다.

@Configuration
public class ProviderTestAppConfig {

    @Bean White getWhite(){
        return new White();
    }

    @Bean @Scope("prototype") Ship getShip(){
        return new Ship();
    }
}

다음은 위에 등록한 실제 빈 클래스들..

public class Ship {
}

public class White {

    @Autowired
    private Provider<Ship> shipProvider;

    public void hi(){
        System.out.println(shipProvider.get());
    }

}

바로 이 부분이 가장 눈여겨 봐야 할 코드입니다. @Autowired 대신에 @Inject를 써도 됩니다. 그게 중요한게 아니라 스프링이 Provider 인터페이스 구현체를 자동으로 만들어 주입해준다는 것이 중요합니다. 어떤 클래스가 어떻게 해주는지는 귀찮아서 찾아보지 않았습니다. 사부님 책에는 자세한 설명이 나올지도?! +_+

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationContextLoader.class)
public class ProviderTest {

    @Autowired White white;

    @Test
    public void get(){
        white.hi();
        white.hi();
        white.hi();
        white.hi();
    }

}


이건 뭐 기본적으로 자바5는 사용한다는 전제하에 제공되는 것이기 때문에 자바5 도입이 불가능하거나 거부감이 있으신 분들은 스프링의 ObjectFactroy나 ServiceLocatorFactoryBean를 검토해보시는게 좋겠습니다.

ps1: 빈 설정에서 @Scope("prototype")을 때어내도 같은 결과가 나오지 않을까 궁금하다는 생각을 하신 분이 계신가요?? 정답은... 안갈챠드려요. 직접 해보세요. 캬캬캬캬.

ps2: AnnotationContextLoader 이 클래스는 저랑 성윤이가 만든거고 스프링에 없습니다. http://jira.springframework.org/browse/SPR-6567 여기서 소스 및 테스트를 받으실 수 있답니다. vote 좀;;

ps3: 근데 이런 글에 누가 관심이나 있나요.. 싱글톤만 쓰는데;; @_@; 싱글톤이 아닌 빈을 어떻게 왜 써먹는걸까요..? 논의는 봄싹 그룹스에서. 음하핫
top

  1. Favicon of http://toby.epril.com BlogIcon 토비 2010.01.13 19:34 PERM. MOD/DEL REPLY

    초보자 책인데 그런 내용이 나올리가 없자나?

    기선 2010.01.14 09:56 PERM MOD/DEL

    저자 맘이죠.ㅋㅋ

Write a comment.


스프링 3.0의 MVC 간편화

Spring/3.0 : 2009.12.22 11:42


원문: http://blog.springsource.com/2009/12/21/mvc-simplifications-in-spring-3-0/
봄싹위키: http://springsprout.org/wiki/1556.do

유겐아렌이 언급했듯이, 모든 자바 개발자는 부담없이 스프링 3.0으로 버전을 올릴 수 있다. 이제 스프링 3이 나왔고 나는 여러분이 아직 모르고 있을 법한 MVC 기능을 소개하고자 한다. 이 기능들이 여러분에게 유용하고 웹 애플리케이션 개발을 빠르게 시작하는데 도움이 되기를 바란다.

또한 이 글은 "스프링 3 간편화" 시리즈의 시작으로 앞으로 몇일 또는 몇주 동안 이와 비슷한 글들이 올라올 것으로 예상한다.

설정 간편화

스프링 3은 mvc 네임스페이스를 도입하여 스프링 MVC 설정을 대폭 간편화했다. 지금까지 다른 개선사항들은 스프링 MVC 애플리케이션을 구성하고 실행하는것을 간편화 시켜주지는 않았다. mvc-basic 예제를 통해 살펴보도록 하자.

mvc-basic 예제는 스프링 MVC 기능의 기본 구성을 보여주도록 만들었다. 프로젝트는 spring-samples SVN 저장소에서 얻을 수 있으며 메이븐으로 빌드하고 이클립스로 import할 수 있다. web.xml 부터 시작하여 거기에 있는 설정부터 살펴보자. 주목할 것은 DispatcherServlet이 단일 마스터 스프링 설정 파일로 설정되어 있고 그 안에서 모든 애플리케이션 구성요소를 초기화한다. URL Rewrite를 설정하여 모든 요청을 깔끔하고 REST-스러운 URL로 DispatcherServlet에 보낸다.

마스터 설정 app-config.xml에서 일반적인 구성을 살펴볼 수 있다. 컴포넌트 스캔으로 클래스패스에서 애플리케이션 구성요소를 찾는다. MessageSource를 설정하여 지역화된 리소스 번들을 설정한다. 마지막으로 애플리케이션의 스프링 MVC 설정을 import한다.

mvc-config.xml 안에서 스프링 3의 첫번째 새 기능을 볼 수 있다.

<!-- Configures the @Controller programming model -->
<mvc:annotation-driven />

이 태그는 요청을 @Controller로 디스패치할 때 필요한 HadlerMapping과 HandlerAdapter를 등록한다. 또한 클래스패스에 무엇이 있는지에 따른 감각적인 기본값(sensible defaults)을 제공한다. 그러한 기본값은 다음과 같다.

- 자바빈 PropertyEditor 보다 더 견고한 대체제인 스프링 3 타입 ConversionService 사용하기
- @NumberFormat으로 숫자 필드 포매팅 지원
- @DateTimeFormat을 사용하여 Date, Calendar 포매팅 지원. Joda Time이 클래스패스에 있다면 Joda Time도 포매팅 지원.
- JSR-303 공급자가 클래스패스에 있다면 @Controller 입력값을 @Valid를 사용하여 검증
- JAXB가 클래스패스에 있다면 XML 읽기/쓰기 지원
- Jackson이 클래스패스에 있다면 JSON 읽기/쓰기 지원

꽤 멋지지 않은가? 훗

계속해서 mvc-config.xml 다음 줄에서 또다른 새로운 기능을 살펴보자.

<!-- Forwards requests to the "/" resource to the "welcome" view -->
<mvc:view-controller path="/" view-name="welcome" />

mvc:view-controller는 랜더링할 뷰를 선택하는 ParameterizableViewController를 등록한다. "/"를 요청하면 welcome 뷰를 랜더링하도록 설정했다. 실제 뷰 템플릿은 /WEB-INF/views 디렉토리의 .jsp가 랜더링 된다.

계속해서 mvc-config에서 살펴보지 않은 새로운 기능을 보도록 하자.

<!-- Configures Handler Interceptors -->
<mvc:interceptors>
    <!-- Changes the locale when a 'locale' request parameter is sent; e.g. /?locale=de -->
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>

mvc:interceptors 태그는 모든 컨트롤러에 적용할 HandlerInterceptor를 등록하게 해준다. 이전에는 반복적으로 모든 HandlerMapping 빈에 interceptor들을 등록했었다. 또한 이 태그를 사용하여 인터셉터를 적용할 URL 경로를 제한할 수 있다.

자 지금까지 살펴본 것을 종합하여, 예제를 배포하면 다음과 같은 welcome 뷰가 랜더링 될 것이다.


다른 지역 링크를 클릭하여 LocaleChangeInterceptor가 사용자 지역을 교체하도록 해보자.

데이터 바인딩 간편화

다음으로 설명할 새 기능은 @Controller 바인딩과 검증이다. 몇주 전에 글을 올렸다시피 이 부분에 새로운 기능이 많이 있다.

예제에서, @Controller Example 링크를 클릭하면 다음과 같은 폼이 랜더링 된다.


이 폼은 지역 정보 변경에 따라 국체화 필드 포매팅이 적용된다. 예를 들어, en에서 de로 바꾸면 날짜 형식을 12/21/10에서 12.12.10으로 바꾼다. 이런 동작과 폼 검증 규칙은 모델 애노테이션을 기반으로 한다.

public class Account {

    @NotNull
    @Size(min=1, max=25)
    private String name;

    @NotNull
    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal balance = new BigDecimal("1000");

    @NotNull
    @NumberFormat(style=Style.PERCENT)
    private BigDecimal equityAllocation = new BigDecimal(".60");

    @DateTimeFormat(style="S-")
    @Future
    private Date renewalDate = new Date(new Date().getTime() + 31536000000L);

}

폼 서브밋은 다음의 AccountController 메서드가 처리한다.

@RequestMapping(method=RequestMethod.POST)
public String create(@Valid Account account, BindingResult result) {
    if (result.hasErrors()) {
        return "account/createForm";
    }
    this.accounts.put(account.assignId(), account);
    return "redirect:/account/" + account.getId();
}

이 메소드는 바인딩과 검증 작업 이후에 호출되며, Account 검증은 @Valid 애노테이션에 의해 수행된다. 만약 어떠한 검증 에러라도 존재한다면 createForm을 다시 랜더링 한다. 그렇지 않을 경우 Account는 저장되고 사용자는 http://localhost:8080/mvc-basic/account/1로 리다이렉트 된다.

또 다른 멋진 기능을 사용해보려면 /account/99 같이 존재하지 않는 계정을 요청해보기 바란다.

요약

스프링 3은 다양한 새 기능과 여러 기존 분야에 대한 간편화를 제공한다. 이 글을 통해 여러분에게 유용한 새로운 스프링 MVC 개선 사항들을 파악했기를 바란다. 맨 처음에 언급했듯이 더 많은 "스프링 3 간편화" 시리즈를 통해 계속해서 스프링 최신 버전의 새롭고 흥미로운 기능들을 소개할테니 기대하기 바란다.

즐거운 연휴되시길!

top

  1. Favicon of http://helols.pe.kr BlogIcon is_Yoon 2009.12.22 11:45 PERM. MOD/DEL REPLY

    이 태그는 요청을 @Controller로 디스패치할 때 필요한 HadlerMapping과 HandlerAdapter를 등록한다. 또한 클래스패스에 무엇이 있는지에 따른 감각적인 기본값(sensible defaults)을 제공한다. 그러한 기본값은 다음과 같다.

    이문장 안읽고 코드만 보고 했다가 삽질했드랬죠 ;;ㅋㅋ

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.12.22 12:28 신고 PERM MOD/DEL

    캬캬캬. 그런 경우가 일상다반사지.

Write a comment.


스프링 프레임워크 3.0이 GA 됐다.

Spring/3.0 : 2009.12.17 11:12


원문: http://www.springsource.org/node/2266
봄싹 위키: http://springsprout.org/wiki/1495.do

긴 여정을 거쳐, 드디어 스프링 3.0 GA (배포)를 이용할 수 있게 됐다는 것을 발표하게 되어 기쁘다! 스프링소스는 지금 축제 중이다. 여러분도 같이 파티에 참여하기 바란다. :)

가장 최근 소식으로, 스프링 3.0 GA는 이제 (지난주에 배표된 GlassFish v3과 같은) 자바 EE 6 파이널 런타임 환경과 호환가능하며 이미 (EcliseLink 2.0 같은) JPA 2.0 파이널을 지원하고 있다. 또한 (JSR-250 v1.1에) 새로 추가된 @ManagedBean 컴포넌트 스캐닝을 지원하며 애노테이션-주도 의존성 주입에 활용할 (JSR-330) @Inject와도 잘 동작한다.

여러분의 편의를 위해 스프링 3.0 전체 주요 기능을 요약했다.

* 스프링 EL(SpEL): 빈 정의에서 사용할 코어 표현식 파서로, 내부 빈 구조와 환경적인 데이터 구조 속성 값에  #{...} 문법을 사용하여 접근할 수 있다.

* 애노테이션-기반 컴포넌트 지원 확장: (스프링 JavaConfig로 알려져 있는)설정 클래스 개념과 애노테이션 팩토리 메서드를 도입했다. 스프링은 또한 환경 값을 @Value 표현식으로도 주입할 수 있게 해주며, 동적인 #{...} 표현식과 정적인 ${...} 대체공간(placeholder)을 이용하여 설정 구성(configruation settings)을 참조할 수 있게 해준다.

* 강력한 스테레오타입 모델: 메타 애노테이션을 사용한 '숏컷' 애노테이션 생성을 지원한다. 예를 들어 기본 스코프와 기본 트랜잭션 특성을 가지고 있는 커스텀 스테레오타입을 만들 수 있다. @Service, @Scope("request"), @Transactional(readOnly=true)를 모두 가지고 있는 @MyService 애노테이션 한개를 만들어 사용할 수 있다.

* 표준화된 의존성 주입 애노테이션: 스프링 3.0은 자바 애노테이션-기반 의존성 주입에 관한 JSR-330 스펙을 완전히 지원한다. @Inject와 관련된 qualifier와 providier 모델을 스프링 내부의 @Autowired에 대한 대체제로 지원한다.

* 제약 애노테이션 기반의 선언적인 모델 검증: JSR-303 빈 검증기(validation provider)에 대한 스프링-스타일 구성을 했다. 애노테이션을 이용한 검증 옵션을 스프링 MVC에서 이용할 수 있다. 스프링의 바인딩 결과 기능을 통해 제약사항 위반에 대한 일관적인 뷰를 제공한다.

* 강화된 바인딩과 애노테이션-기반 포매팅: 표준 PropertyEditor에 대한 대체제로 Converter와 Formatter SPI를 제공한다. 포맷팅은 JSR-303 제약과 비스한 스타일로 애노테이션을 이용할 수 있다. 예를 들어 @DateTimeFormat을 사용할 수 있다. 또한 새로운 mvc 네임스페이스를 사용하여 스프링 MVC에 포맷팅과 검증기를 편하게 설정할 수 있으니 확인해 보기 바란다.

* 방대한 REST 지원: REST스타일 요청 매핑, @PathVariable 매개변수를 사용한 URI 변수 추출, 컨텐츠 교섭(content negotiation)을 통한 뷰 판단과 같은 고유의 REST를 스프링 MVC에서 사용할 수 있다. 클라이언트쪽 REST 지원은 RestTemplate 클래스를 이용할 수 있다.

* 풍부한 고유 포틀릿 2.0 지원: 스프링 MVC는 포틀릿 2.0 환경과 포틀릿 2.0의 새로운 이벤트와 리소스 요청 모델을 지원한다. 일반적인 포틀릿 요청 특징에 따른 구체화된 매핑 기능을 제공한다. @ActionMapping, @RenderMapping, @ResourceMapping, @EventMapping.

* 객체/XML 매핑(OXM): 스프링 웹 서비스에 있었지만, 지금은 스프링 프레임워크 코어로 이동했다. JAXB2, Castor 등을 지원하는 마샬링 언마샬링 추상화를 제공한다. 스프링 MVC와 스프링 JMS에 XML 페이로드(payload) 연동 옵션을 제공한다.

* 차세대 스케줄링 기능: cron을 지원하는 TaskScheduler와 Trigger 매터니즘을 새로 추가했다. 스프링 3.0은 편리한 task 네임스페이스와 @Async, @Scheduled 애노테이션을 제공한다. 고유의 쓰레드 풀 또는 서버가 관리하는 쓰레드 풀에서 실행될 수 있다.

이러한 큰 테마들 이외에, 스프링 2.5에 업그레이드 할 때 특히 좋아할 수백개의 구체적인 개선 사항사항이 있으니 변경사항과 자바독을 확인하기 바란다.

시스템 요구사항에 따르면, 스프링 3.0은 다양한 환경을 다룬다. 두 가지 주요 특징으로, 스프링 3.0은 자바 SE 5 이상서블릿 2.4 이상을 지원한다. 예를 들어 톰캣 5.x와 6.x를 지원한다. 또한 웹스피어 6.1과 웹로직 9.2와 같은 유명한 엔터프라이스 서버와도 호환가능하다. GlassFish v3은 이미 지원하고 있다.

마무리 하자면, 스프링 3는 여러분의 서버를 업그레이할 필요 없이 완전히 새로운 컴포넌트 모델 기능과 JSR-330 주입과 JSR-303 검증같은 표준을 제품 환경에 가져다 준다! 여러분이 해야 할 일은 스프링을 사용중인 애플리케이션의 라이브러리를 스프링 3.0으로 바꾸는 것 뿐이다.

줄겨라 그리고 다음에 이어질 구체적은 스프링 3 기능에 대한 글과 스프링 3.0에서 돌아가는 예제들을 기대하기 바란다!

ps: 반가운 마음에 급하게 번역하느라.. 좀 날림입니다. 이해해 주세요;
top

  1. Favicon of http://blog.outsider.ne.kr BlogIcon Outsider 2009.12.17 12:14 PERM. MOD/DEL REPLY

    역시 빠른... ㅎㅎ 잘 읽고 감.. ㅎ

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.12.17 12:31 신고 PERM MOD/DEL

    오타나 오역은 위키에서 편집해 주세요.ㅋ

  2. Favicon of http://helols.pe.kr BlogIcon is_Yoon 2009.12.17 18:13 PERM. MOD/DEL REPLY

    @RequestVariable ===> @PathVariable 오타 ..ㅋ위키수정하러 감..ㅋ

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.12.17 18:17 신고 PERM MOD/DEL

    쌩큐 고쳤어!

Write a comment.


[스프링 3.0] 스프링 bean과 일반 자바 객체가 호출하는 @Bean 메서드의 차이

Spring/3.0 : 2009.12.11 20:51


public class JavaConfigTest {

    AnnotationConfigApplicationContext ac;

    @Before
    public void setUp(){
        ac = new AnnotationConfigApplicationContext(AppConfig.class);
        assertThat(ac, is(notNullValue()));
    }

    @Test
    public void getBean(){
        SampleBean bean1 = ac.getBean("sampleBean", SampleBean.class);
        SampleBean bean2 = ac.getBean("sampleBean", SampleBean.class);
        assertThat(bean1, is(notNullValue()));
        assertThat(bean2, is(notNullValue()));
        assertThat(bean1, is(bean2));

        AppConfig config1 = new AppConfig();
        SampleBean bean3 = config1.sampleBean();
        assertThat(bean3, is(not(bean2)));

        AppConfig config2 = ac.getBean("appConfig", AppConfig.class);
        SampleBean bean4 = config2.sampleBean();
        assertThat(bean4, is(bean2));
    }

}

스프링 3.0 애노테이션 기반 설정을 익히기 위해서 처음 만들어본 테스트입니다. 이 테스트에서 알 수 있는 건 바로 이 글의 제목에서처럼 @Bean이 붙어있는 메서드를 어떤 객체를 이용해서 호출하느냐에 따라 그 결과가 다를 수 있다는 겁니다.

@Configuration
public class AppConfig {

    @Bean
    public SampleBean sampleBean(){
        return new SampleBean();
    }
}

이건 설정한 빈이고 SampleBean은 뭐 암거나;; @_@;

결론은 new 로 만든 AppConfig와 스프링에서 가져온 AppConfig의 @Bean이 붙은 메서드가 반환해주는 값이다르다는 겁니다. 전자는 일반적인 Java 문맥대로 sampleBean()에서 new SampleBean()으로 새로운 객체를 만들어서 받은 것이고, 후자는 sampleBean() 메서드 호출을 가로채서 기존의 bean을 반환해준 겁니다.

이 경우는 매우 간단한 경우에 속합니다. 하나는 스프링이 관리하는 빈이었고, 하나는 일반 자바 객체였으니까요. 그런데 만약에 둘 다 스프링의 빈이라면? 그 중에 하나는 @Configuration, 다른 하나는 @Component라면?

@Component로 설정한 빈 내부에 위와 똑같은 설정이 들어있다면? 반환하는 객체의 값이 조금 다르나면??

빈 스캔은 어찌하나?

정말 복잡하군요;; 하나씩 차근 차근 해보겠습니다.



top

Write a comment.


[스프링 3.0] @Async 사용하기

Spring/3.0 : 2009.12.10 13:59


참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch25s05.html#scheduling-annotation-support-async

봄싹 사이트에서 스터디 개설/변경, 모임 개설/변경 시에 알림 서비스로 이메일, 구글 메신저, 트위터로 알림 메시지를 전송합니다. 그런데, 사용자가 많아지다보니 해당 작업을 완료하는데 걸리는 시간이 너무 길어졌습니다. 불편해졌죠. 사실 관리자 권한이 있는 사람들만 만드는거라, 스터디 참여자 입장에서는 그런 불편을 알 수가 없을테지만, 저희는 내부적으로 좀 불편해 하고 있었습니다.

그러던 중 보게 된 것이 바로 @Async.. 딱 원하던 기능입니다.

필요한 빈 설정은

    <!-- ============================================================= -->
    <!--  Spring 3.0 @Task @Async                                      -->
    <!-- ============================================================= -->
    <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>

    <task:executor id="myExecutor" />

    <task:scheduler id="myScheduler" />

적용하려면, 원래 작업을 별도의 클래스로 빼내고 그것을 참조하도록 수정하는 것이 좋겠습니다. 안그럼 동작하질 않더라구요,

@Service
public class BeanService {

    public void normal(){
        System.out.println("===========================");
        System.out.println(Thread.currentThread());
        System.out.println("===========================");
        System.out.println("do it");
        this.more();
    }

    @Async
    public void more() {
        System.out.println("===========================");
        System.out.println(Thread.currentThread());
        System.out.println("===========================");
        System.out.println("more");
    }

}

예를 들어, 위와 같은 경우 normal() 내부에서 more()를 호출할 경우에는 @Async가 적용되지 않습니다.
more()를 직접 호출할 떄는 적용됩니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AsyncAnnoTest {

    @Autowired BeanService beanService;

    @Test
    public void async(){
        assertThat(beanService, is(notNullValue()));
        beanService.normal();
        beanService.more();
    }

}

대충 만든 테스트로 돌려보면 다음과 같은 결과를 볼 수 있습니다.





top

TAG @Async

Write a comment.


[스프링 3.0] RC2 릴리즈~

Spring/3.0 : 2009.11.16 22:54


요즘 스프링에 관심이 슬슬 가시기 시작하네요. 벌써 3일이나 지나서야 RC2로 버전을 올리다니.. 예전 같았으면 공개되자마자 버전을 올렸을텐데 말이죠. 하긴.. 뭐 3일 정도 늦었지만, 그래도 봄싹은 아마도 한국에서 가장 빨리 스프링 최신 버전을 적용한 프로젝트가 될 것 같습니다. 후훗

중요 변경 사항을 살펴보겠습니다.

* updated to final versions of JSR-330 "javax.inject" and JSR-303 "javax.validation" APIs
* full compliance with the JSR-330 TCK (i.e. full compliance with the final specification)
* support for Hibernate Validator 4.0 GA (as the JSR-303 reference implementation)
* added support for load-time weaving in JBoss 5.x
* added support for recent EHCache 1.6 configuration properties to EHCacheFactoryBean
* added AnnotatedBeanDefinitionReader helper for programmatic registration of annotated classes
* added AnnotationConfig(Web)ApplicationContext for convenient registration/scanning of classes
* added GenericXmlApplicationContext with flexible configuration options for its XML support
* AbstractApplicationContext can also start up in case of system properties access failure
* internal MergedBeanDefinitionPostProcessors apply after all other post-processors
* inner beans detected as ApplicationListeners as well (only supported for inner singletons)
* child bean definition's scope attribute can be inherited from parent bean definition now
* introduced SmartLifecycle interface with auto-startup and shutdown order support
* introduced LifecycleProcessor delegate, customizable through "lifecycleProcessor" bean
* MessageListenerContainers and Quartz SchedulerFactoryBean start up on refresh instead of init
* added initialize-database tag to jdbc namespace for populating external data sources with data
* PathMatchingResourcePatternResolver leniently ignores non-existing root directories
* DefaultConversionService understands "on"/"off", "yes"/"no", "1"/"0" as boolean values
* CustomEditorConfigurer supports PropertyEditor instances again (with deprecation warning)
* revised MethodParameter's annotation accessor methods
* ClassUtils is now parameterized with Class<?> and Class<T> where appropriate
* DataBinder now accepts var-args to set allowed, disallowed, and required fields
* DataBinder auto-grows nested paths on traversal (avoiding NullValueInNestedPathException)
* fixed enum binding regression with WebRequestDataBinder (as used by @MVC data binding now)
* fixed FieldError to expose rejected input value as String value instead of as array
* JSR-303 Validator will only register validation failures if no binding failure happened
* ContentNegotiatingViewResolver works with ignoreAcceptHeader and defaultContentType as well
* added Spring MVC namespace, with convenient mvc:annotation-driven configuration element
* default number and datetime formatters configured when using the Spring MVC namespace
* full support for datetime formatting using the Joda Time library (automatically enabled)
* added convenient @NumberFormat and @DateTimeFormat annotations for declarative formatting
* implicit T.valueOf(S) and constructor T(S) lookup if no explicit S->T converter matches
* AbstractExcelView is compatible with Apache POI 3.0 as well as 3.5 now
* TilesConfigurer only sets up EL support if JSP 2.1 is present (for JSP 2.0 compatibility)
* re-introduced Struts 1.x support ("org.springframework.web.struts") in deprecated form
* deprecated scheduling support for JDK 1.3 Timer ("org.springframework.scheduling.timer")
* deprecated remoting support for JAX-RPC (in favor of JAX-WS)


top

Write a comment.


[스프링 3.0] @Valid 실습

Spring/3.0 : 2009.10.18 12:57


1. 라이브러리 추가.

        <!-- Validation -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>com.springsource.org.hibernate.validator</artifactId>
            <version>4.0.0.GA</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>com.springsource.javax.xml.bind</artifactId>
            <version>2.1.7</version>
        </dependency>

2. 빈 설정.

    <bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="cacheSeconds" value="0" />
        <property name="webBindingInitializer">
            <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
                   <property name="validator" ref="validator" />
               </bean>
        </property>
    </bean>
   
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

3. 애노테이션 기반 검증 설정

    @Column(length = 100)
    @NotEmpty(message="제목을 입력하세요!")
    private String title;
    @Column(columnDefinition="TEXT")
    @NotEmpty(message="제목을 입력하세요!")
    private String contents;

4. 컨트롤러 코드 수정.

    @RequestMapping(value = "/notice/update/{id}", method = RequestMethod.POST)
    public String updateForm(@Valid Notice notice, BindingResult result, SessionStatus status) {
        if (result.hasErrors()) {
            return "notice/update";
        } else {
            noticeService.update(notice);
            status.setComplete();
            return "redirect:/notice/" + notice.getId() + ".do";
        }
    }

끝~!! 기본 에러 메시지는 애노테이션에서 변경할 수 있습니다.

이제 JSR 303 애노테이션과 그 확장 애노테이션에 뭣들이 있는지 살펴봐야겠네요.
top

Write a comment.


[스프링 3.0] @Valid 이론

Spring/3.0 : 2009.10.18 11:45


http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch05s07.html#validation.mvc

스프링 3.0에서 검증(validation) 설정에 대한 자바 표준인 JSR-303을 지원하기 시작했습니다.

public class Person {

    @NotNull
    @Max(64)
    private String name;
   
    @Min(0)
    private int age;

}

이런식으로 도메인 모델에 검증 메타데이터를 정의할 수 있는거죠. 이렇게 정의해둔 정보들은 JSR-303 Validator가 검증할 때 활용합니다. 이 검증기는 하이버네이트가 구현한것도 있고, 스프링 3.0에서 제공하는 것도 있습니다.

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

스프링에서 제공하는 검증기는 위에 있는 클래스입니다 이 클래스는 javax.validation.Validator 인터페이스와 org.springframework.validation.Validator 인터페이스를 구현했기 때문에 필요한 곳에서 적당한 인터페이스 타입으로 참조해서 사용하면 됩니다.

이밖에도 커스텀 @Constraint 만드는 방법과 DataBinder에서 스프링 검증기 사용하는 방법이 나와있으나 그 부분은 생략하겠습니다. 저는 지금 스프링 3.0 MVC에 적용하는 방법을 익히려고 정리중입니다.

이전까지는 @Controller 기반 컨트롤러를 사용할 때는 Validation 과정을 일일히 손수 코딩해줘야했습니다. 그러나 이제는 스프링이 자동으로 검증로직을 호출도록 설정할 수 있습니다. 그 방법이 바로 이 글의 제목인 @Valid죠.

@Controller
public class MyController {

    @RequestMapping("/foo", method=RequestMethod.POST)
    public void processFoo(@Valid Foo foo) { ... }

}

스프링 바인딩이 끝나면, 해당 객체에 설정된 Validator를 호출해 줍니다. 이때 호출해야 할 Validator를 설정하는 방법은 두 가지가 있습니다. 하나는 @Controller의 @InitBinder 마다 설정하는 방법이고, 다른 하나는 글로벌한 InitBinder라고 볼 수 있는 WebBindingInitializer에 설정하는 방법입니다.

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(new FooValidator());
    }
   
    @RequestMapping("/foo", method=RequestMethod.POST)
    public void processFoo(@Valid Foo foo) { ... }
   
}

이 방법과~

<!-- Invokes Spring MVC @Controller methods -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
        <!-- Configures Spring MVC DataBinder instances -->
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="validator" ref="validator" />
        </bean>
    </property>
</bean>

<!-- Creates the JSR-303 Validator -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
               
이런 방법이 있군요.

그럼? 스프링이 검증 해준 다음은 어떻게 되는걸까요?? 검증 에러는 어떻게 받아오나? 검증 에러가 있으면 알아서 이전 폼을 다시 보여주나? 핸들러에서는 그럼 서브밋 됐을 때의 로직만 구현하는 되는건가? 실험해봐야겠군요.
top

Write a comment.


[스프링 3.0] 애노테이션 기반 스케줄링

Spring/3.0 : 2009.09.28 18:17


참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch25s05.html

웹 애플리케이션을 띄울 때 구글 토크 봇을 로그인 시켜두려고 스케줄링을 이용하려 했습니다. 찾아보니까 애노테이션 기반으로 설정할 수 있는 기능이 추가됐더군요.

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>

<task:executor id="myExecutor" pool-size="5"/>

<task:scheduler id="myScheduler" pool-size="10"/>}

이렇게 task:annotation-driven 엘리먼트를 XML에 추가해주면 빈에 설정되어 있는 @Schedule과 @Async 애노테이션을 활성화 시켜줍니다.

@Schedule 애노테이션은 cron, fixedDelay, fixedRate 세 가지 속성 중 하나를 이용해서 설정해야 합니다. 반드시 이 셋 중에 하나는 설정되어 있어야 합니다.

@Async 애노테이션은 해당 메서드 호출을 비동기로 처리해주고 싶을 때 사용할 수 있습니다. 즉 이 애노테이션으로 스케줄링이 적용된 메서드를 호출하면 결과는 바로 리턴되고 실제 실행은 스프링의 TaskExecutor에 의해 별도의 Task 내부(이 녀석이 별도의 쓰레드겠죠)에서 실행됩니다.

막상 해보니 라이브러리 때문에 에러가 나더군요.

        <dependency>
            <groupId>edu.emory.mathcs.backport</groupId>
            <artifactId>com.springsource.edu.emory.mathcs.backport</artifactId>
            <version>3.1.0</version>
        </dependency>

그래서 필요한 라이브러리를 추가해주고 돌려보니까 잘 돌아갑니다.

그런데 해보고 나니까 굳이 반복 실행할 필요가 없는 메서드라;;; -_-;; @PostConstruct 애노테이션 붙여서 끝냈습니다.

이 간단한것을... 스캐쥴링은 머하러 찾아봤담;;
top

  1. 대한민국토리 2009.09.28 23:34 PERM. MOD/DEL REPLY

    그러게요. 글 처음에는 스케쥴링이 꼭 필요한 기능인 줄로만 느껴졌는데, 글 말미에는 스케쥴링이 필요없는 걸로 맺음 하셔서 읽고 나서 당황했어요^^

    덕분에 스케쥴링에 대해 찾아보고 좋았습니다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2009.09.29 00:10 PERM MOD/DEL

    ㅎㅎㅎ반전이죠.

Write a comment.


드이어 스프링 3.0 RC1이 나왔습니다.

Spring/3.0 : 2009.09.26 08:14


http://www.springsource.org/node/2004

주요 변경 사항은 다음과 같습니다.

Changes in version 3.0.0.RC1 (2009-09-25)
-----------------------------------------

* upgraded to CGLIB 2.2, AspectJ 1.6.5, Groovy 1.6.3, EHCache 1.6.2, JUnit 4.7, TestNG 5.10
* introduced early support for JSR-330 "javax.inject" annotations (for autowiring)
* introduced early support for JSR-303 Bean Validation (setup and MVC integration)
* added default editors for "java.util.Currency" and "java.util.TimeZone"
* refined PathMatchingResourcePatternResolver's treatment of non-readable directories
* PathMatchingResourcePatternResolver understands VFS resources (i.e. works on JBoss 5.x)
* revised AccessControlContext access from BeanFactory
* AbstractBeanDefinitionParser can deal with null return value as well
* PropertyOverrideConfigurer's "ignoreInvalidKeys" ignores invalid property names as well
* PropertyPlaceholderConfigurer supports "${myKey:myDefaultValue}" defaulting syntax
* BeanFactory's default type conversion falls back to String constructor on target type
* BeanFactory tries to create unknown collection implementation types via default constructor
* BeanFactory supports ObjectFactory as a dependency type for @Autowired and @Value
* BeanFactory supports JSR-330 Provider interface as a dependency type for @Inject
* BeanFactory prefers local primary bean to primary bean in parent factory
* protected @Autowired method can be overridden with non-annotated method to suppress injection
* private @Autowired methods with same signature will be called individually across a hierarchy
* @PostConstruct processed top-down (base class first); @PreDestroy bottom-up (subclass first)
* ConfigurationClassPostProcessor detect @Bean methods on registered plain bean classes as well
* support for default "conversionService" bean in an ApplicationContext
* MBeanServerFactoryBean returns JDK 1.5 platform MBeanServer for agent id "" (empty String)
* changed NamedParameter/SimpleJdbcOperations parameter signatures to accept any Map value type
* refined logging in JMS SingleConnectionFactory and DefaultMessageListenerContainer
* introduced "ui.format" package as an alternative to PropertyEditors for data binding
* @RequestMapping annotation now supported for annotated interfaces (and JDK proxies) as well
* @RequestParam and co support placeholders and expressions in their defaultValue attributes
* @Value expressions supported as MVC handler method arguments as well (against request scope)
* JSR-303 support for validation of @MVC handler method arguments driven by @Valid annotations
* refined response handling for @ExceptionHandler methods
* @ResponseStatus usage in handler methods detected by RedirectView
* all @SessionAttributes get exposed to the model before handler method execution
* @Event/ResourceMapping uniquely mapped to through event/resource id, even across controllers
* MultipartRequest is available as a mixin interface on (Native)WebRequest as well
* removed outdated "cacheJspExpressions" feature from ExpressionEvaluationUtils
* introduced common ErrorHandler strategy, supported by message listener container
* Jpa/JdoTransactionManager passes resolved timeout into Jpa/JdoDialect's beginTransaction
* HibernateJpaDialect applies timeout onto native Hibernate Transaction before begin call
* Spring's Hibernate support is now compatible with Hibernate 3.5 beta 1 as well
* Spring's JPA support is now fully compatible with JPA 2.0 as in EclipseLink 2.0.0.M7
* SpringJUnit4ClassRunner is now compatible with JUnit 4.5, 4.6, and 4.7
* SpringJUnit4ClassRunner once again supports collective timeouts for repeated tests
* deprecated @NotTransactional annotation for test classes in favor of @BeforeTransaction

봄싹 사이트에 어서 적용해 봐야겠네요.
top

  1. 대한민국토리 2009.09.28 23:37 PERM. MOD/DEL REPLY

    2번항목의 JSR-330 에 대해서 언급한 블로그가 있길래 링크 겁니다.

    http://chanwook.tistory.com/789#comment4556871

    생각보다 관심가지고 계신 분들이 많은데요...^^

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2009.09.29 00:14 PERM MOD/DEL

    아.. 찬욱이네요.ㅎㅎ
    전 DI 표준 보다 JSR-303 벨리데이션 쪽이 더 관심이 갑니다.

  2. Favicon of http://www.timberlandbaratas.com BlogIcon zapatos timberland 2012.12.25 12:12 PERM. MOD/DEL REPLY

    This airport can offer you outdoor parking options. There are about 567 parking spaces accessible. Fees differ in accordance with the model of vehicle and they are paid for 24-hour period, http://www.timberlandbaratas.com Timberland. Car hire agencies introduced at the airport comprise Avis, http://www.timberlandbaratas.com zapatos timberland, Budget, http://www.timberlandbaratas.com Timberland Online, National, and Hertz. Bus services between Port Louis and the airport run quite often each 30-60 minutes during the day, http://www.timberlandbaratas.com Mujer Timberland. Taxis are accessible outside the terminal structure always. About 60 taxis serve the Mauritius airport. An information desk is put centrally in the terminal structure, http://www.timberlandbaratas.com outlet timberland. There are some ATMs out off the arrival hall, http://www.timberlandbaratas.com timberland niños, plus currency exchange service is accessible in the check-in hall located in departures area. Also, there are bank offices in the departures areas and arrivals. A post office is placed out off the arrivals hall.Related articles:


    http://skywalker.tistory.com/332 Le patron de la Société des courses de Compiègne (Oise) a démenti samedi avoir bénéficié d'un

    http://ebhanglobaltopics.tistory.com/162 Selon un communiqué de presse de l'organisateur chilien du Dakar-2011

Write a comment.


[스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 4

Spring/3.0 : 2009.08.20 00:00


참고: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch14s04.html

oxm 네임스페이스를 스프링 XML 설정 파일에 추가하여 사용할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/oxm
    http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

현재 지원하는 태그는 다음과 같다.
    •    jaxb2-marshaller
    •    xmlbeans-marshaller
    •    jibx-marshaller

각각의 태그는 해당 섹션에서 자세히 설명하겠다. 예를 들어, 다음은 JAXB2 마샬러를 설정한 모습니다.

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

top

Write a comment.


[스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 3

Spring/3.0 : 2009.08.19 23:52


참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch14s03.html

14.3 Marshaller와 Unmarshaller 사용하기

다음은 마샬링, 언마샬링에서 사용할 JavaBean이다.

public class Settings {
    private boolean fooEnabled;

    public boolean isFooEnabled() {
        return fooEnabled;
    }

    public void setFooEnabled(boolean fooEnabled) {
        this.fooEnabled = fooEnabled;
    }
}

다음 Application에서는 위의 객체를 settings.xml로 저장하고 다시 그것을 읽어들인다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {
    private static final String FILE_NAME = "settings.xml";
    private Settings settings = new Settings();
    private Marshaller marshaller;
    private Unmarshaller unmarshaller;

    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    public void saveSettings() throws IOException {
        FileOutputStream os = null;
        try {
            os = new FileOutputStream(FILE_NAME);
            this.marshaller.marshal(settings, new StreamResult(os));
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

    public void loadSettings() throws IOException {
        FileInputStream is = null;
        try {
            is = new FileInputStream(FILE_NAME);
            this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ApplicationContext appContext =
            new ClassPathXmlApplicationContext("applicationContext.xml");
        Application application = (Application) appContext.getBean("application");
        application.saveSettings();
        application.loadSettings();
    }
}

위에서 설정할 Marshaller와 Unmarshaller는 다음 스프링 설정 파일에서 설정한다.

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="castorMarshaller" />
        <property name="unmarshaller" ref="castorMarshaller" />
    </bean>
    <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
</beans>

위 설정에서 사용한 Marshaller는 Castor 라이브러리인데, 이 것은 부가적인 설정이 필요 없기 때문에 빈 설정이 간단하다. 도한 CastorMarshaller가 스프링의 Mashaller와 Unmashaller를 모두 구현하고 있다. 따라서 이 빈이 Application의 Marshaller와 Unmashaller에 설정된다. 위 애플리케이션은 다음과 같은 XML 파일을 만들어 낸다.

<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>


top

Write a comment.


[스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 2

Spring/3.0 : 2009.08.13 18:18


요약, 번역, 참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch14s02.html

14.2 Marshaller와 Unmarshaller

14.2.1. Marshaller

스프링은 모든 마셜링 과정을 org.springframework.oxm.Marshaller 인터페이스로 추상화 시켰다. 주요 메서드는 다음과 같다.

public interface Marshaller {

    /**
     * Marshals the object graph with the given root into the provided Result.
     */
    void marshal(Object graph, Result result)
        throws XmlMappingException, IOException;
}

(실제로는 boolean supports(Class<?> clazz)  이것도 있음.)

메서드에 전달받은 Object graph를 javax.xml.transform.Result 객체로 변환해 준다. Result는 태깅 인터페이스로 기본적으로 XML을 추상화한 것이다. 다음과 같은 다양한 구현체들이 있다.

Result implementation    Wraps XML representation
DOMResult    org.w3c.dom.Node
SAXResult    org.xml.sax.ContentHandler
StreamResult    java.io.File, java.io.OutputStream, or java.io.Writer

노트: marshal 메서드가 Object 타입의 객체를 받긴 하지만, 대부분 임의의 객체를 전달하진 않는다. 맵핑 파일에 맵핑한 객체 또는 애노테이션을 표시해둔 객체, 마샬러에 등록한 객체 또는 임의의 상위 클래스를 지닌 객체를 넘긴다. 자세한 내용은 O/X 기술 선택 챕터를 참조하라.

14.2.2. Unmarshaller

Mashaller와 비슷하게 org.springframework.oxm.Unmarshaller 인터페이스도 있다.

public interface Unmarshaller {

    /**
     * Unmarshals the given provided Source into an object graph.
     */
    Object unmarshal(Source source)
        throws XmlMappingException, IOException;
}

(이 녀석도 boolean supports(Class<?> clazz) 이걸 가지고 있음)

javax.xml.transform.Source 타입의 객체를 넘겨주면 Object를 반환해준다. 다음과 같은 Source 구현체들이 있다.
 
Source implementation    Wraps XML representation
DOMSource    org.w3c.dom.Node
SAXSource    org.xml.sax.InputSource, and org.xml.sax.XMLReader
StreamSource    java.io.File, java.io.InputStream, or java.io.Reader

14.2.3. XmlMappingException

스프링은 사용중인 O/X 맵핑 툴의 예외 계층 구조를 XmlMappingExcpetion 계층 구조로 변환해준다. 사용중인 툴이 마샬링과 언마샬링 예외를 구분하고 있지 않더라도,,. 스프링에서 이것을 구분해 준다.


top

Write a comment.


[스프링 3.0 OXM] 14. Marshalling XML using O/X Mappers 1

Spring/3.0 : 2009.08.13 18:06


요약, 번역, 참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch14.html

14.1 도입

객체/XML 변환 과정, 즉 XML 마샬링(Marshalling) 또는 XML 직렬화(Serialization)에 대해 다룬다.

marshaller는 객체 그래프를 XML로 변환해주고..
unmarshaller는 반대로 XML을 객체 그래프로 변환해 준다.

스프링 OXM을 사용할 때의 장점
- 간편한 설정: JAXB context나 JiBK bidning factory 없이도 간단하게 설정할 수 있다. XML 설정시 oxm 네임스페이스를 제공해준다.
- 일관된 인터페이스: 스프링이 제공하는 Marshaller와 Unmashaller 인터페이스를 사용하면 된다. 클라이언트 코드 변경 없이 O/X 맵핑 프레임워크를 교체할 수 있다.
- 일관된 예외 구조: 제각각인 예외들을 스프링의 XmlMappingExcption 이하의 계층 구조로 변환해 준다. 런타입 예외로 기존의 예외를 감싸기 때문에 에러 정보를 분실하진 않는다.


top

Write a comment.


[Spring 3.0] HiddenHttpMethodFilter

Spring/3.0 : 2009.06.10 10:47


어제 밤에 스프링 ROO가 제공하는 REST 코드를 보다가 잠들었는데, 아침에 사부님 댓글을 보니, DELETE와 PUT method를 현재 브라우저와 HTML에서는 완전히 지원하지를 않더군요. 그래서 스프링 레퍼런스를 봤더니 역시나..

org.springframework.web.filter.HiddenHttpMethodFilter

이 녀석을 제공해주고 있었습니다. 사용하는 방법은 간단합니다.

    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

이렇게 web.xml에 추가해주면 되죠. 어젯밤엔 졸려서 이부분을 그냥 지나간것 같습니다.

해주는 일은 post method로 넘겨온 파라메터 중에 _method가 가지고 온 값으로 HTTP method를 실제 원하는 method로 바꿔주는 겁니다.

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

스프링 폼 태그를 이용해서 저렇게 method를 명시해주면 히든 필드 _method에 delete라는 값을 요청에 같이 포함해서 보내주며 실제 보내는 요청은 post가 됩니다. 하지만 이 필터가 _method의 값인 delete를 읽고서 HTTP method를 DELETE로 변경해주고, 그 결과 아래에 있는 핸들러가 이 요청을 처리하게 됩니다.

@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
    this.clinic.deletePet(petId);
    return "redirect:/owners/" + ownerId;
}

REST 스타일을 적용할 때 꼭 필요한 라이브러리 인 듯 합니다.
top

  1. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2009.06.10 13:19 PERM. MOD/DEL REPLY

    ssimini에서도 restful을 지원할때 별도의 파라미터(_http__method)를 이용 하빈다...
    restful 정말 매력적인듯 하네요.

    Favicon of http://whiteship.me BlogIcon 기선 2009.06.10 17:44 PERM MOD/DEL

    오호 ssimini도 REST를 지원하는군요~

  2. Favicon of http://kwon37xi.egloos.com BlogIcon 권남 2009.07.06 15:35 PERM. MOD/DEL REPLY

    저기 HiddenHttpMethodFilter 랑 org.tuckey.web.filters.urlrewrite.UrlRewriteFilter 가 무슨상관인지 잘 모르겠어요... org.tuckey.web.filters.urlrewrite.UrlRewriteFilter 를 설정하면 자동으로 HiddenHttpMethodFilter가 적용되는건가요??

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.07.06 15:39 신고 PERM MOD/DEL

    UrlRewriteFilter랑은 아무 관계가 없는 것 같습니다. 다만 이 글을 올릴 때 제가 전에 못 봤었던 부분을 같이 긁어다 붙인것 같네요.

Write a comment.


Spring Expression Language(SpEL)

Spring/3.0 : 2009.04.21 17:12


참조: 7. Spring Expression Language (SpEL)

번역, 요약 및 편역 합니다.

7.1 도입

스프링 EL(SpEL)은 실행시(runtime)에 객체 그래프 질의와 조작을 지원하는 강력한 EL(Expression Language)이다. 통합 EL(Unified EL)과 언어 문법이 비슷하지만, 메서드 호출과 기본 문자열 템플릿 같은 추가 기능을 제공한다.

OGNL, MAVEL, JBoss EL 같은 다른 자바 EL도 있지만, 스프링 EL은 스프링 커뮤니티에 모든 스프링 포트폴리오 제품에서 사용할 수 있는 단일 EL을 제공하기 위해 만들었다. 이 언어의 기능은 스프링 포트폴리오의 요구사항을 기반으로 도출했다. 그 중에는 이클립스 기반의 스프링소스 툴 스위트(STS)에서 코드 완성에 필요한 도구도 포함되어 있다.

SpEL이 스프링 포트폴리오에 표현 평가(evaluation) 기반을 제공하기는 하지만, 그렇다고 해서 스프링에 강력하게 묶여있지는 않다. 독립적으로 사용할 수 있다. 독립적으로 사용하는 예제가 이 번 장에 많이 나와있다. 그러려면 약간의 부트스트랩 역할을 하는 (파서 같은)기반 클래스들을 만들어야 한다. 스프링 사용자들은 그런 기반 시설을 사용할 필요가 없고 그저 표현식을 사용하면 된다. 일반적인 SpEL 사용법은 XML 또는 애노테이션 기반 빈 설정에 사용하는 것인데 빈 정의할 때 EL 사용하기 절에서 살펴볼 수 있다.

이번 장은 EL 기능과 API와 그리고 문법을 살펴본다. 

7.2. 기능 개요

EL은 다음의 기능을 제공한다.

      Literal expressions: 상수 표현
      Boolean and relational operators: 불린과 관계형 연산자
      Regular expressions: 정규 표현식
      Class expressions: 클래스 표현식
      Accessing properties, arrays, lists, maps: 속성, 배열, 리스트, 맵에 접근하기
      Method invocation: 메서드 호출
      Relational operators: 관계형 연산자
      Assignment: 대입
      Calling constructors: 생성자 호출
      Ternary operator: 3항 연산자
      Variables: 변수
      User defined functions: 사용자 정의 함수
      Collection projection: 콜렉션 보기
      Collection selection: 콜렉션 선택
      Templated expressions: 템플릿 표현식

7.3. 스프링의 Expression 인터페이스를 사용한 표현식 평가

이번 절에서는 간단한 SpEL 인터페이스와 그 EL 사용법을 살펴보겠다. EL 레퍼런스에서 자세한 내용을 참조하라.

다음 코드는 SpEL API를 사용하여 상수 문자열 표현식 'Hello World' 값을 구한다.

ExpressionParser parser = new SpelAntlrExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

message 변수의 값은 'Hello World'다.

여러분이 주로 사용할 SpEL 클래스와 인터페이스는 org.springframework.expression 패키지와 그 하위 패키지 spel.antlr과 spel.support에 있다.

EL은 ANTLR 문법을 사용하며 lexer와 파서(parser)를 구성할 때 사용한다. ExpressionParser 인터페이스는 표현식 파싱을 책임진다. 이 예제에서 표현식은 홑따옴표로 둘러쌓여있는  문자열 상수다. Expression 인터페이스는 앞에서 정의한 표현식 값을 도출하는 책임을 지니고 있다. parser.parseExpression을 호출할 때는 ParseException이 발생할 수 있고 exp.getValue를 호출할 때는 EvaluationException이 발생할 수 있다.

SpEL은 메서드 호출, 속성 접근, 생성자 호출 같은 다양한 기능을 제공한다.

다음 예제는 메서드 호출의 예로, concat 메서드를 호출한다.

ExpressionParser parser = new SpelAntlrExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();

SpEL은 . 기호를 사용하여 속성의 속성에도 접근할 수 있다. 예를 들어 prop1.prop2.prop3에 접근할 수 있고 값을 설정할 수 있다.

ExpressionParser parser = new SpelAntlrExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.bytes.length");  // invokes 'getBytes().length'
int length = (Integer) exp.getValue();

문자열의 생성자도 호출할 수 있다.

ExpressionParser parser = new SpelAntlrExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);

여기서 public <T> T getValue(Class<T> desiredResultType) 이 메서드를 주목해서 살펴보자. 이 메서드를 사용하면 결과 값을 캐스팅하지 않아도 된다. 값이 T 타입 또는 등록된 타입 컨버터로 캐스팅이 되지 않을 때는
EvaluationException가 발생한다.

좀 도 흔한 SpEL 사용처는 특정 객체 인스턴스에 대한 표현식일 것이다. 다음 예제는 Inventor 클래스의 인스턴스에서 Name 속성을 가져온다.

// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

//  The constructor arguments are name, birthday, and nationaltiy.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelAntlrExpressionParser();
Expression exp = parser.parseExpression("name");

EvaluationContext context = new StandardEvaluationContext();
context.setRootObject(tesla);

String name = (String) exp.getValue(context);

마지막 줄에서 'name' 변수는 "Nikola Tesla"가 설정된다. StandardEvaluationContext 클래스에 "Name" 속성을 찾아볼 객체를 알려준다. 같은 표현식을 새로운 루트 객체로 설정해가며 여러 번 재사용할 수 있다. Expression은 리플렉션을 사용한다.

노트

SpEL을 독립적으로 사용할 때는 파서와 Evaluation context를 만들어야 한다. 하지만 설정 파일의 일부로 SpEL을 사용할 때는 파서와 Evaluation context, root 객체 등을 암묵적으로 스프링이 설정해 준다.

마지막으로 간략히 살펴볼 예제는 불린 연산자를 사용하는 것이다.

Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(context, Boolean.class);  // evaluates to true

7.3.1 EvaluationContext 인터페이스

EvaluationContext 인터페이스는 사용하여 속성, 메서드, 필드를 찾을 수 있고 타입 변환을 수행하는데 이용할 수 있다. 구현하여 제공하는 클래스로 StandardEvaluationContext가 있는데, 객체를 다룰 때 리플렉션을 사용하고, 최적화를 위해 Method, Field, Constructor 인스턴스를 캐싱한다.

StandardEvaluationContext의 setRootObject 메서드를 사용해서 식을 적용할 루트 객체를 명시한다. 또한 setVariable과 registerFunction을 사용하여 표현식에서 사용할 변수와 함수를 명시할 수 있다. 변수와 함수 사용법은 EL 레퍼런스의 변수와 함수 절을 참조하라. StandardEvaluationContext는 또한 커스텀 ConstructorResolver, MethodResolver, PropertyAccessor등을 등록하여 SpEL이 표현식을 다루는 방법을 확장할 수 있다. 자세한 내용은 이들 클래스의 JavaDoc을 참조하라.

7.3.1.1 타입 변환

SpEL은 기본으로 스프링 코어에서 사용할 수 있는 변환 서비스(org.springframework.core.convert.ConversionService)를 사용한다. 이 변환 서비스는 기본 변환기를 내장하고 있지만 커스텀 변환기를 추가하는 것도 가능하다. 또한 핵심 기능으로 제네릭(Generic)을 인식한다. 즉 표현식에서 제네릭 타입을 사용하면, 스프링은 타입 일관성을 유지하기 위해 해당 타입으로 변환을 시도한다는 것이다.

실제로 어떻게 될까? setValue()를 사용하여 문자열을 Boolean 타입의 리스트에 넣는다. SpEL은 해당 리스트의 요소가 Boolean 타입이어야 한다는 것을 인식하고 적절하게 문자열을 Boolean 타입으로 변환해준다.

class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}
       
Simple simple = new Simple();

simple.booleanList.add(true);

StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);

// false is passed in here as a string.  SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");

// b will be false
Boolean b = simple.booleanList.get(0);
       
7.4. 빈 정의할 때 EL 사용하기 절

SpEL 표현식은 XML과 애노테이션 기반 설정에서 BeanDefinition에 대한 메타데이터를 정의할 때 사용할 수 있다. 두 경우 모두 표현식을 정의할 때 #{표현식} 문법을 사용한다.

7.4.1. XML 기반 설정

속성 또는 생성자-인자 값을 아래와 같이 표현식에 설정할 수 있다.

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

'systemProperties' 변수는 미리 정의되어 있다. 따라서 아래 처럼 표현식에서 사용할 수 있다. 미리 정의되어 있는 변수는 #기호를 붙일 필요가 없다.

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

또한 다른 빈의 속성을 그 이름으로 참조할 수도 있다.

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>


<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>

7.4.2. 애노테이션-기반 설정

@Value 애노테이션을 필드, 메서드 그리고 메서드와 생성자의 매개변수에 사용하여 기본 값을 설정할 수 있다.

다음 예제는 필드 변수에 기본 값을 설정한다.

public static class FieldValueTestBean

  @Value("#{ systemProperties['user.region'] }")
  private String defaultLocale;

  public void setDefaultLocale(String defaultLocale)
  {
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale()
  {
    return this.defaultLocale;
  }

}

이번에는 같은 작업을 세터 메서드에 한다.

public static class PropertyValueTestBean

  private String defaultLocale;

  @Value("#{ systemProperties['user.region'] }")
  public void setDefaultLocale(String defaultLocale)
  {
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale()
  {
    return this.defaultLocale;
  }

}

자동 연결(Autowiring)을 사용하는 메서드와 생성자에도 @Value 애노테이션을 사용할 수 있다.

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
                          @Value("#{ systemProperties['user.region'] } String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
public class MovieRecommender {

    private String defaultLocale;
   
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
                            @Value("#{ systemProperties['user.country'] } String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

7.5. EL 레퍼런스

생략

7.6. 예제에서 사용한 클래스

생략



top

  1. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2009.04.27 01:41 PERM. MOD/DEL REPLY

    강력해 지는 만큼 복잡하고 어려워 지는군요... ㅠ

    Favicon of http://whiteship.me BlogIcon 기선 2009.04.27 09:41 PERM MOD/DEL

    SpEL은 모든 스프링 제품에서 공통으로 사용할 용도로 만들었다고 하니, 꼭 알아둬야 할 것 같아요.

Write a comment.