Whiteship's Note


Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext



보통 스프링 설정 파일이 최소한 두 개이상 있을 겁니다. xxx-servlet.xml 과 나머지로 나눌 수 있습니다. 그중에서 xxx-servlet.xml은 DispatcherServlet이 WebApplicationContext를 만들 때 사용하고, 나머지는  ContextLoaderListener 또는 ContextLoaderServlet이 일반적인 ApplicationContext를 만들 때 사용합니다.

이게 끝이 아닙니다. WebApplicationContext는 바로 이 ApplicationContext를 상속받아서 여러 서블릿들이 공통으로 사용하는 빈들을 사용할 수 있게 되는 겁니다. 따라서 만들어지는 순서도 중요한데, Listener가 아니라 ContextLoaderServlet을 사용했을 때는 load 머시기 설정 값에 1을 줘서 DispatcherServlet보다 먼저 만들게 해야 합니다. 그래서 WebApplicationContext를 만들 때 해당 ApplicationContext를 상속받아서 그 안에 있는 빈들을 사용할 수 있게 되겠죠.

이런 구조로 설계한 건, DispatcherServlet이 하나의 웹 애플리케이션에서 여러 개일 수 있기 때문입니다. 여러 개의 DispatcherServlet에서 공통으로 사용할 빈들을 상위에 있는 ApplicationContext에 선언해두고 공유할 수 있게 하는 거죠.

사용자 삽입 이미지

자. 그럼 여기서 문제

만약에 스프링 2.5 컨트롤러를 사용하고 있고 이 컨트롤러에 특정 @Aspect를 적용하고 싶을 때 해당 AOP 관련 설정(aop:autoproxy 머시기 엘리먼트 + @Aspect 빈 등록)은 xxx-servlet.xml과 applicationContext.xml 둘 중 어디에 둬야 할까요?

xxx-servlet.xml에는 컨트롤러 설정, 뷰 리졸버, 핸들러 맵퍼 등의 설정이 되어 있고, applicationContext.xml에는 서비스, DAO 등의 설정이 들어있습니다.
 
ㄱ. xxx-servlet.xml
ㄴ. applicationContext.xml
ㄷ. 어디에 두든지 상관없다.
ㄹ. 스프링 2.5 컨트롤러에는 Spring AOP를 사용할 수 없다.

정답은?? ㄱ 입니다. 왜냐면, applicationContext.xml을 사용해서 ApplicationContext를 만드는 순간에는 컨트롤러들이 미쳐 빈으로 등록되어 있지도 않기 때문에, 프록시를 만들 대상이 없습니다. 다시 말해, AOP 빈은 있지만, 이 AOP를 적용할 대상이 되는 빈이 없는겁니다. 따라서 컨트롤러들과 관련된 설정이 있는 xxx-servlet.xml에 해당 설정을 위치해야 합니다.

'Spring MVC > 3장 Spring MVC' 카테고리의 다른 글

Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext  (6) 2008.07.03
Service Layer  (2) 2006.12.13
Web Layer  (0) 2006.12.12
User Interface Layer  (0) 2006.10.09
Layers of Abstractions  (0) 2006.10.08
top


[수정]AbstractModelAndViewTests 사용하여 Controller 테스트하기



이전 글에서 내렸던 결론은 좀 더 생각해보니 확실히 틀렸습니다.

테스트를 할 단위가 컨트롤러 전체 범위라면 이전 글의 가정이 맞겠지만 하나의 테스트는 최소한 작은 부분을 테스트 하라고 했었습니다. 컨트롤러에는 다양한 종류의 lifecycle 메소드들이 있으며 그것들을 전부 handleRequest의 결과인 ModelAndView를 사용하여 테스트 한다는 것은 다소 위험한 발상인 것 같습니다.

Controller 테스트 != ModelAndView 테스트


따라서 handleRequest 전체가 아닌 onSubmit()이 테스트의 대상이 되어야 합니다. 하지만 이전 글에서는 handleRequest를 가지고 테스트를 했었습니다.

이것을 다음과 같이 수정할 수 있습니다.

public class CheckControllerTest extends AbstractModelAndViewTests{

    private CheckController controller;
    private MemberService mockMemberService;
    private String mail;
    private MemberCommand command;

    public void setUp() {
        mockMemberService = createMock(MemberService.class);
        controller = new CheckController();
        controller.setMemberService(mockMemberService);
        command = new MemberCommand();
    }

    public void testEmptyOrWhiteMail() throws Exception {
        mail = "";
        expect(mockMemberService.findByMail(mail)).andReturn(null);
        replay(mockMemberService);
        command.setMail(mail);
        ModelAndView mav = controller.onSubmit(null, null, command, null);
        assertEquals("redirect:join.html", mav.getViewName());
        assertViewName(mav, "redirect:join.html");
        verify(mockMemberService);
    }

    public void testExistMemberMail() throws Exception {
        Member member = new Member();
        mail = "keesun@mail.com";
        member.setMail(mail);
        member.setName("기선");

        expect(mockMemberService.findByMail(mail)).andReturn(member);
        replay(mockMemberService);
        command.setMail(mail);
        ModelAndView mav = controller.onSubmit(null, null, command, null);
        assertViewName(mav, "confirm");
        assertModelAttributeValue(mav, "member", member);
    }
}

이전 테스트와의 가장 큰 차이점은 먼저 handleRequest 전부가 아닌 obSubmit 메소드만을 테스트 했다는 것입니다. 이래야 이전 보다 더 세부적인 부분을 테스트 했기 때문에 좀 더 단위 테스트라 부를 만 한 것 같습니다.

또하나 차이점은 MockHttpServletRequest와 MockHttpServletResponse의 필요가 없어졌습니다. Requet에 바인딩할 데이터를 주는 대신에 command 객체를 사용하여 이미 바인딩 된 상태라고 가정을 했습니다. 이전 테스트의 경우 binder를 사용하여 request에 넣어준 데이터를 바인딩하는 과정까지 테스트를 하는 것이였기 때문에 지금보다 훨씬 넓은 범위를 테스트 했다고 느껴집니다.

top


AbstractModelAndViewTests 사용하여 Controller 테스트하기



컨트롤러의 주된 목적은 handleRequest안에 Request와 Respnse 객체를 넣어서 결국 ModelAndView 객체를 반환하는 것입니다.

사용자 삽입 이미지
따라서 다음과 같은 결론을 조심스래 내놓을 수 있습니다.

Controller 테스트 == ModelAndView 테스트


아직은 TDD에 익숙하지도 않고 테스트 클래스를 어떻게 작성해야 할지도 모르기 때문에;; 일단은 구현을 하고 그 것을 테스트 하는 방법을 익히는 식으로 공부하고 있습니다.

먼저 Controller 하나를 구현합니다.
public class CheckController extends SimpleFormController {

    private MemberService memberService;

    public CheckController() {
        setCommandClass(MemberCommand.class);
        setCommandName("memberCommand");
        setFormView("check");
        setSuccessView("confirm");
    }

    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }

    @Override
    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException exception) throws Exception {
        MemberCommand memberCommand = (MemberCommand)command;
        Member member = memberService.findByMail(memberCommand.getMail());
        if(member != null)
            return new ModelAndView(getSuccessView())
                .addObject("member", member);
        else
            return new ModelAndView("redirect:join.html");
    }
}

이 컨트롤러는 입력 받은 mail 주소를 사용하여 DB에서 검색을 하여 있으면 confirm 페이지로 없으면 join 페이지로 리다이렉션하는 컨트롤러 입니다.

이것을 테스트 하려면 다음과 같이 if 조건문에 걸릴 경우(true)와 걸리지 않는 경우(false)를 모두 테스트 해봐야 합니다. 이 때 테스트 할 것은 위에서도 말했지만 결국은 ModelAndView입니다.

JUnit의 TestCase를 직접 사용해도 되지만 spring-mock.jar 에 Spring 2.0에서 추가된 AbstractModelAndViewTests 클래스를 사용하여 다음과 같이 테스트를 할 수 있습니다.
public class CheckControllerTest extends AbstractModelAndViewTests{

    private CheckController controller;
    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private MemberService mockMemberService;
    private String mail;

    public void setUp() {
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
        mockMemberService = createMock(MemberService.class);
        controller = new CheckController();
        controller.setMemberService(mockMemberService);
    }

    public void testEmptyOrWhiteMail() throws Exception {
        mail = "";
        expect(mockMemberService.findByMail(mail)).andReturn(null);
        replay(mockMemberService);
        request.addParameter("mail", mail);
        request.setMethod("POST");
        ModelAndView mav = controller.handleRequest(request, response);
        assertEquals("redirect:join.html", mav.getViewName());
        assertViewName(mav, "redirect:join.html");
        verify(mockMemberService);
    }

    public void testExistMemberMail() throws Exception {
        Member member = new Member();
        mail = "keesun@mail.com";
        member.setMail(mail);
        member.setName("기선");

        expect(mockMemberService.findByMail(mail)).andReturn(member);
        replay(mockMemberService);
        request.addParameter("mail", mail);
        request.setMethod("POST");
        ModelAndView mav = controller.handleRequest(request, response);
        assertViewName(mav, "confirm");
        assertModelAttributeValue(mav, "member", member);
    }
}

EasyMock을 사용하여 MemberSerive의 Mock 객체를 사용했으며 spring-mock.jar의 MockHttpServletRequest와 MockHttpServletReponse를 사용했습니다.

여기서 주목할 것은 테스트의 대상인 ModelAndView 객체를 받아 올 때 호출한 메소드가 handleRequest라는 것입니다. 이 것은 Spring 의 Controller 클래스의 Workflow 때문이죠. 결국은 모든 컨트롤러들이 handleRequest로 요청을 처리하기 시작하기 때문입니다.


top


AbstractController



상속 구조
사용자 삽입 이미지
하는 일
모든 컨트롤러들의 기본이 되는 클래스로써 위 그림에 표시되어 있는 일들을 합니다.
여기서 한 가지 주목할 것은 Controller 인터페이스를 구현함에 따라 구현해야할 handleRequest 입니다.
이 메소드는 final로 다음과 같이 구현되어 있습니다.

    public final ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        // Delegate to WebContentGenerator for checking and preparing.
        checkAndPrepare(request, response, this instanceof LastModified);

        // Execute handleRequestInternal in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return handleRequestInternal(request, response);
                }
            }
        }
       
        return handleRequestInternal(request, response);
    }

이 메소드와 관련된 자세한 설명은 KSUG 제 2회 세미나에서 영회형이 설명해주셨지만 Spring MVC 118쪽에도 적혀있습니다. 워크 플로우와 메소드들의 라이프 사이클을 지키면서도 상속 받아 사용하는 녀석들에 때라 행동을 다르게 정의 할 수 있도록 Open-Closed Principle 을 구현한 사례에 해당합니다.

Workflow
1. DispatcherServelet에 의해 handleRequest() 호출됨.
2. 지원하는 요청의 타입을 조사(만약 지원하는 요청이 아닐 경우 ServletException 발생)
3. 세션이 필오하면 가져오려고 시도함.(없을 경우 ServletException 발생)
4. cacheSeconds 속성에 따라 필요하다면 캐슁 헤더를 설정함.
5. handleRequestInternal() 추상 메소드를 호출(이때 설정에 따라 HttpSession을 동기화 함)

'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


Non-String DataBinding 테스트하기



참조 : Spring MVC

객체가 속성으로 String 타입이 아닌 속성을 가지고 있을 때 데이터 바인딩을 하려면 별도의 조취가 취해져야 합니다. request에 담고 있는 데이터는 모두 문자열이기 때문에 이전 글에서 살펴보았던 간단한 DataBinding일 경우에는 정말 간단하게 바인딩을 할 수 있었습니다. 하지만 이번 경우에는 PropertyEditor의 도움을 받아서 역시 간단하게 문자열을 특정 타입으로 변환하여 바인딩 해줍니다.

다음의 표는 ServletRequestDataBinder가 기본으로 사용하는 PropertyEditor들 입니다.
사용자 삽입 이미지
따라서 위 표의 Result에 있는 타입들은 별도의 PropertyEditor를 바인더에 등록하지 않더라도 알아서 문자열을 해당 타입으로 변환하여 바인딩해줍니다.

간단한 클래스 작성

테스트 클래스 작성


'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


간단한 DataBinding 테스트하기



화면에서 입력되는 값을 객체의 프로퍼티로 세팅해주는 역할을 데이터 바인딩이라고 합니다. 그리고 Spring MVC에서는 ServletRequestDataBindier와 PropertiEditor를 사용하여 폼에서 입력 된 값을 바인딩합니다.

가장 간단한 경우가 String 타입의 속성만을 가지고 있는 객체로 바인딩하는 것입니다.

먼저 String 타입을 가지고 있는 커맨드 객체를 만듭니다. 모델 객체를 커맨드 객체로 사용할 것이라면 굳이 만들 필요는 없습니다.
public class Message {

    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

}
이 때 JavaBeans 스펙에 맞게 getter와 setter가 정의되어 있어야 합니다.

다음 ServletRequestDataBinder를 사용하여 테스트를 작성 합니다.
public class MessageTest {

    private Message message;
    private ServletRequestDataBinder binder;
    private MockHttpServletRequest request;

    @Before
    public void setUp() {
        message = new Message();
        binder = new ServletRequestDataBinder(message, "message");
        request = new MockHttpServletRequest();
    }

    @Test
    public void testBinding() {
        String title = "새로운 메시지 제목";
        request.addParameter("title", title);

        binder.bind(request);

        assertEquals(title, message.getTitle());
    }
}

JUnit 테스트를 실행하여 결과를 확인할 수 있습니다.
사용자 삽입 이미지

BaseCommandController 컨트롤러의 하위 클래스들은 모두 위에서 사용한 bind 메소드를 사용하며 Binder객체를 생성할 때 사용한 Command 객체의 이름은 바인딩 할 때 에러가 발생하면 그 에러를 기록할 Errors 객체에서 사용하게 됩니다. 아무런 이름도 주지 않으면 기본값으로 taget 이라는 이름을 가집니다.

    @Test
    public void testCommandName() {
        binder = new ServletRequestDataBinder(message);
        assertEquals("target", binder.getObjectName());
    }


'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


Testing Controller



매우 간단한 Controller를 테스트 하겠습니다.
앞에서 만든 MultiActionController를 테스트 하는 코드를 작성하겠습니다. 앞에서 작서해준 컨트롤러는 다음과 같이 View이름만 넘겨 주도록 만든 Stub 형태 입니다.

    public ModelAndView list(HttpServletRequest request, HttpServletResponse response){
        return new ModelAndView("issue/list");
    }

이 녀석을 EasyMock과 spring-mock.jar안에 있는 클래스들을 사용하여 테스트 클래스를 만들어서 IssueService로 부터 받아온 List<Issue>를 ModelAndView에 담아서 반환 하도록 구현할 것입니다.

1. 먼저 테스트 클래스를 작성하고 기본적으로 필요한 변수들을 설정합니다.
public class IssueControllerTest {

    private IssueController issueController;
    private IssueService mockIssueService;
    private MockHttpServletRequest request;
    private MockHttpServletResponse response;

    @Before
    public void setUp() {
        issueController = new IssueController();
        mockIssueService = createMock(IssueService.class);
        issueController.setIssueService(mockIssueService);
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
    }

2. 테스트를 작성합니다.
    @Test
    public void testList() {
        List<Issue> issueList = new ArrayList<Issue>();
        expect(mockIssueService.getAll()).andReturn(issueList);
        replay(mockIssueService);
        ModelAndView mav = issueController.list(request, response);
        assertEquals("issue/list", mav.getViewName());
        assertEquals(issueList, mav.getModel().get("issueList"));
        verify(mockIssueService);
    }

위 테스트는 컨트롤러의 list 메소드에서 반환되는 ModelAndView의 viewName과 "issueList"라는 key로 List<Issue> 객체를 가지고 있는지 확인합니다. 이 때 필요한 IssueService의 행위를 '녹화-> 재생->검사' 하는 작업을 거칩니다.

3. JUnit 테스트를 실행합니다.
사용자 삽입 이미지
list 메소드를 제대로 구현해두지 않았기 때문에 에러가 발생합니다.

4. list 메소드 구현하기
    public ModelAndView list(HttpServletRequest request, HttpServletResponse response){
        return new ModelAndView("issue/list", "issueList", issueService.getAll());
    }

5. 다시 JUnit 테스트 실행
사용자 삽입 이미지



top


MultiactionController



AbstractController를 상속받아서 간단하게 구현하는 Controller들이 많이 있습니다. 그러한 것들 중에는 서로 관련이 있는 컨트롤러들도 있습니다. 예를 들어 검색결과 리스트를 가져오는 컨트롤러, 전체 목록을 가져오는 컨트롤러, 목록에서 한 개의 아이템에 대한 정보를 가져오는 컨트롤러가 있을 수 있습니다. 이러한 것들을 하나의 컨트롤러에서 처리할 수 있습니다.

MultiactionController의 장점
- 컨트롤러의 갯수가 줄어듭니다.
- 여러 처리를 논리적인 그룹으로 묶어서 하나의 클래스에 담을 수 있습니다.
- 액션 메소드의 유동적인 맵핑이 가능합니다.

MultiactionController의 단점
- 바인딩과 Valitor를 사용할 수 있지만 폼 처리 work flow가 정의되어 있지 않습니다.
    - 따라서 바인딩과 Validation을 요청을 처리할 메소드 내부에서 하기(?)를 권하고 있습니다.
- 쉽게 커질 우려가 있습니다.
- 리플렉션을 사용하기 때문에 컴파일 에러를 확인할 수 없습니다.

사용하는 방법
1. MultiActionController  상속받기
public class IssueController extends MultiActionController {

}

2. 요청을 처리할 액션 메소드 구현
- ModelAndView 객체를 반환해야 합니다.
- HttpServletRequest와 HttpServletResponse 파라미터를 가지고 있어야 합니다.
- 옵션으로 HttpSession 또는 Command 객체를 파라미터로 가질 수 있습니다.
    public ModelAndView list(HttpServletRequest request, HttpServletResponse response){
       return new ModelAndView("issue/list");
   }

3. 등록하기
- MethodNameResolver bean 등록합니다. 참조
    - InternalPathMethodNameResolver를 default로 사용합니다. 따라서 이 녀석을 사용할 때는 굳이 등록하거나 DI하지 않아도 됩니다.
- 위에서 구현한 컨트롤러 baen 등록하기
    <bean name="/issue/*"  class="net.agilejava.nayoung.controller.IssueController" />
- 이 때 *을 사용하여 /issue/list.html, /issue/find.html 과 같은 요청들을 모두 위의 컨트롤러에서 처리하도록 합니다.

궁금한 점
- Binding과 Validation을 할 수는 있는데 화면에 바인딩 하거나 Validation할 때 발생한 Error를 못 보여 주는 것이 단점인 것 같은데 이 걸 어떻게 해결 할 수 있을지...




'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


EasyMock을 사용한 Service 계층 테스트2



참조 : http://www.easymock.org/EasyMock2_2_Documentation.html

reset() 메소드 활용하기

이 전글에서 원래 하나의 테스트 메소드에 넣어뒀던 내용을 세 개의 메소드로 쪼개두었습니다. 의도적으로 쪼갠 것은 아니였고 다만 expect() 메소드를 사용하여 MemberDao의 get() 메소드를 설정해 두었는데 Mock 객체의 특정 메소드를 여러 번 재정의 할 수가 없어서 에러가 발생했습니다.

이럴 때 reset으로 Mock 객체에 녹화 해둔 것들을 싹 지우고 다시 녹화를 할 수 있습니다. 즉 Mock 객체를 재사용할 수 있습니다.

@Test

public void testGetMember() {

       mail = null;

       expect(mockMemberDao.get(mail)).andReturn(null);

       replay(mockMemberDao);

       member = memberService.get(mail);

       assertNull(member);

       verify(mockMemberDao);

 

       reset(mockMemberDao);

 

       mail = "nonExistMail@mail.com";

       expect(mockMemberDao.get(mail)).andReturn(null);

       replay(mockMemberDao);

       member = memberService.get(mail);

       assertNull(member);

       verify(mockMemberDao);

 

       reset(mockMemberDao);

 

       mail = "existMail@mail.com";

       Member correctMember = new Member(mail);

       expect(mockMemberDao.get(mail)).andReturn(correctMember);

       replay(mockMemberDao);

       member = memberService.get(mail);

       assertNotNull(member);

       assertEquals(mail, member.getMail());

       verify(mockMemberDao);

}


이렇게 하면 메소드 하나에 여러 행위들을 설정하여 테스트 해볼 수 있습니다.

top


EasyMock을 사용한 Service 계층 테스트1



참조 : http://www.easymock.org/EasyMock2_2_Documentation.html

패키지 구조는 다음과 같습니다.
MemberService를 구현하려는데 아직 MemberDao는 구현되어 있지 않고 MemberDao라는 인터페이스만 존재합니다.
사용자 삽입 이미지
이 때 MemberServiceImpl 클래스를 TDD로 개발하기 위해 다음과 같이 작성했습니다.

public class MemberServiceImplTest {

 

       MemberService memberService;

       MemberDao mockMemberDao;

 

       @Before

       public void setUp() {

             memberService = new MemberServiceImpl();

             memberService.setMemberDao(mockMemberDao);

       }

 

       @Test

       public void testGetMember() {

             //Edge Case Test

             String mail = null;

             Member member = memberService.get(mail);

             assertNull(member);

 

             mail = "nonExistMail@mail.com";

             member = memberService.get(mail);

             assertNull(member);

 

             //Common Case Test

             mail = "existMail@mail.com";

             member = memberService.get(mail);

             assertNotNull(member);

             assertEquals(mail, member.getMail());

       }

}


위 테스트 코드를 실행하면 NullPointerExeption이 발생합니다. MemberDao 객체를 만들지도 않고 MemberService객체에 세팅하고 있기 때문이죠.

1. EasyMock을 사용하여 mock객체를 만들어서 세팅을 하겠습니다.
- EasyMock을 static import 합니다.

import static org.easymock.EasyMock.*;
- MemberDao를 MemberService에 세팅하기 전에 createMock()메소드를 사용하여 mock객체를 생성합니다.

       @Before

       public void setUp() {

             memberService = new MemberServiceImpl();

             mockMemberDao = createMock(MemberDao.class);

             memberService.setMemberDao(mockMemberDao);

       }


- 테스트를 돌려서 확인합니다.

사용자 삽입 이미지
MemberDao의 get() 메소드의 행위를 사전(테스트 하기 전)에 설정해주지 않았기 때문에 에러가 났습니다.

MemberServiceImple을 다음과 같이 구현해 두었습니다.

public class MemberServiceImpl implements MemberService {

 

       private MemberDao memberDao;

 

       public void setMemberDao(MemberDao memberDao) {

             this.memberDao = memberDao;

       }

 

       public Member get(String mail) {

             return memberDao.get(mail);

       }

}



2. 녹화 -> 재생 -> 검증
MemberService를 구현할 때 사용한 MemberDao의 get() 메소드가 어떻게 동작할지 녹화를 해야합니다.
그리고 녹화 한 상태를 재생(replay) 시킨 다음 MemberService를 동작 시키고 마지막으로 녹화한 대로 잘 동작하였는지 검증(verify)하면 됩니다.

       @Test

       public void testGetMemberByNull() {

             mail = null;

             expect(mockMemberDao.get(mail)).andReturn(null);

             replay(mockMemberDao);

             member = memberService.get(mail);

             assertNull(member);

             verify(mockMemberDao);

       }

 

       @Test

       public void testGetMemberByWrongMail() {

             mail = "nonExistMail@mail.com";

             expect(mockMemberDao.get(mail)).andReturn(null);

             replay(mockMemberDao);

             member = memberService.get(mail);

             assertNull(member);

             verify(mockMemberDao);

       }

 

       @Test

       public void testGetMemberByCorrectMail() {

             mail = "existMail@mail.com";

             Member correctMember = new Member(mail);

             expect(mockMemberDao.get(mail)).andReturn(correctMember);

             replay(mockMemberDao);

             member = memberService.get(mail);

             assertNotNull(member);

             assertEquals(mail, member.getMail());

             verify(mockMemberDao);

       }


3. 테스트가 모두 통과하여 MemberService의 get() 메소드 구현이 끝났습니다.
사용자 삽입 이미지
top


테스트 코드 작성은 언제 끝나는가?



참조 : Spring MVC 288페이지

1. 경계(Edge case)를 테스트 하라.
- 메소드가 예상하고 있지 않은 값들을 인자로 넘겨주는 것을 테스트 해야한다. 0, null값, 음수나 조건들과 같은 Edge 들은 비즈니스 로직을 통해 정의 된다.

2. 일반적인 경우(Common case)를 테스트 하라.
- 가장 자주 발생 하는 정상적인 시나리오 대로 메소드를 사용해본다. 보톤 경계 테스트를 빼먹는 경우가 많아서 경계 테스트가 더 중요하지만 이 것도 빠트리면 안된다.
- 분기문과 반복문의 모든 경우를 테스트 해야한다.
    - if 문에 true일 경우와 false일 경우
    - switch문의 모든 case
    - 예외가 발생할 경우

3. 가능한 모든 경우를 테스트 했는지 확인하는 툴을 사용한다.
- code coverage 툴을 사용(Clover, EMMA)
- test coverage가 100% 나와도 테스트 코드가 완벽하다고 할 수는 없다. 위 도구들이 해당 테스트가 경계 테스트를 했는지는 확인할 수 없기 때문이다.

결론
경계 테스트와 코드 커버리지 툴을 사용하면 완벽한 테스트 코드를 가졌다는 느낌을 받을 수 있을 것이다.

top


Unit Tests



참조 : Spring MVC 10장

단위 테스트란?
The basic definition of a unit test is “a discrete test condition to check correctness of an isolated software module.”
진짜 단위 테스트라고 하려면 다음과 같은 특징을 가져야 합니다.
1. 빠르게 실행되어야 합니다.
- DB 커넥션이나 외부 자원등을 읽느라 테스트가 느려지면 해당 테스트의 유용성을 급격히 떨어집니다. 즉각적인 응답을 받을 수 있어야 합니다.

2. 외부 설정이 필요없습니다.
- 테스트 코드를 빠르게 실행을 하고 외부와 종속성을 제거하기 위함입니다.

3. 다른 테스트들과 독립적이어야 합니다.
- 완전히 독립적인 테스트여야 합니다. 다른 테스트를 먼저 또는 나중에 실행해야 하는 관계는 없어야 합니다.

4. 외부 자원이 필요없습니다.
- DB 커넥션이나 웹서비스에 종속성을 가지면 안됩니다.

5. 흔적을 남기지 않습니다.
- 단위 테스트를 계속해서 반복적으로 수행하기 위해서는 흔적이 남으면 안됩니다. 외부 자원에 종속되지 않는 것이 도움이 됩니다.

6. 가능한한 작은 단위로 테스트 합니다.
- OO에서는 보통 메소드 단위로 작성 합니다.

진정한 단위 테스트라면 Spring의 ApplicationContext나 BeanFactory에서 bean을 가져와서 테스트를 하면 안 되는 거군요. 외부 자원과 설정에 의존하게 되는 거니깐 그냥 TestCase만 가져다가 테스트를 만들면 되겠습니다. 다른 테스트들에 독립적인 테스트 메소드들을 구현해야겠습니다.


top


Spring MVC Validation Testing



참조 :
Spring MVC 9장
springmodules의 valang을 활용한 Validation 테스트
Where is ErrorsVerifier class mentioned in Expert Spring MVC?

본문에서 Validator를 테스트 하는 방법을 소개하고 있습니다.
public class PersonValidatorTests extends TestCase {
    public void testEmptyPersonValidation() {
        Person person = new Person();
        Validator validator = new PersonValidator();
        BindException errors = new BindException(person, "target");
        validator.validate(person, errors);
        new ErrorsVerifier(errors) {
            {
            forProperty("firstName").hasErrorCode("person.firstName.required")
            .forProperty("lastName").hasErrorCode("person.lastName.required")
            .otherwise().noErrors();
            }
        }
    }
}

위의 소스 코드는 Spring MVC 281페이지 그대로 입니다. 하지만 위의 코드는 선뜻 이해가 가지 않았습니다.
언뜻 봤을 때 person 객체를 두번이나 사용한 것부터 뭔가 이상하게 느껴졌으며(좀 더 보고 있으니까 바인딩 할 때 한번 검증할 때 한 번 사용하는 의도 인것 같습니다.)
ErrorsVerifier라는 익명 클레스의 사용이 낯설었습니다.

ErrorsVerifier를 사용한 것을 보면 fluent interface 개념으로 구현하여 매우 재밌고 직관적으로 작명된 메소드의 이름들을 확인할 수 있습니다. 하지만 안타깝게도 책에 나온 설명과 달리 저 클레스(?)는 spring 프레임웤에 존재하지 않습니다.

이 클레스를 찾느라 여러명이 고생한 흔적을 구글에서 찾을 수 있었는데 그 곳에서 보다 더 값진 것을 발견했습니다. 바로 뛰어난 선배님재미난 참여를 살펴 볼 수 있었습니다.

위 링크에 가시면 Spring에는 전혀 없던 ErrorsVerifier 코드를 만들어 낸 과정이 들어있습니다.

ErrorsValidator 다운

ErrorsValidator를 사용한 예제 코드
public class MemberInfoValidatorTest extends TestCase{

    public void testEmptyMemberInfo() {
        MemberInfo memberInfo = new MemberInfo();
        Validator validator = new MemberInfoValidator();
        BindException errors = new BindException(memberInfo, "target");
        validator.validate(memberInfo, errors);
        ErrorsVerifier errorsVerifier = new ErrorsVerifier(errors);
        errorsVerifier.forProperty("name").hasErrorCode("required")
            .forProperty("password").hasErrorCode("required")
            .forProperty("confirmPassword").hasErrorCode("required")
            .forProperty("email").hasErrorCode("required");
    }
}

음~ 좋네요. 다시 보니까 Spring MVC 에 나온 코드는 컴파일 에러가 나지 않을까 싶네요;; 클레스파일이 없기도 없거니와 인너 클래스의 사용이 이상해요. 중괄호 연속 두 개씩.. 대체 무슨 의미인건지;;; 어떻게 하라는건지~ㅋㅋ

top


Spring MVC Validation 정리



Programmatic
- Vlidator - ValidationUtils 사용하기
- Vlidator - Property 파일 사용하기

Declarative
- Declarative Validators - Valang 사용하기
    - Valang - syntex
    - Valang - CustomFunction
    - ValangValidatorFactoryBean -> ValangValidator

참조
- Fail-fast vs. complete validation
- Really easy field validation 사용하기 (4)
- Spring MVC 9장
- Spring Modules


top


ValangValidatorFactoryBean -> ValangValidator



링크 : Spring Modules Wiki :: Using Valang validator

Spring Modules 사이트는 https 라 그런가 약간 느리고 답답합니다. Spring Modules API를 보던 중에 Spring MVC 9장에서 예제로 사용하는 ValangValidatorFactoryBean이 보이질 않았습니다. 이게 대체 무슨일인가... 하고 구글링을 했더니 Spring Modules 0.4 이전에 사용하던 클레스인데 0.5 부터는 ValangValidator로 대체 된 것(deprecated 된 후 삭제) 같습니다.
Prior to release 0.4, the org.springmodules.validation.ValangValidatorFactoryBean could be used as an alternative to org.springmodules.validation.ValangValidator. Since both classes served the same purpose, the former was deprecated and is planned to be removed in 0.5.
이름이 짧아져서 좋습니다. :)

둘의 차이점으로는 ValangValidatorFactoryBean에서는 syntax라는 property로 valang을 등록했는데 ValangValidator에서는 valang이라는 property에 설정한다는 것입니다. 보며 좋은 이름을 선택했다고 볼 수 있습니다.

그 외에 customFuction을 등록할 수 있는 것은 같습니다.

top


Valang - CustomFunction



Valang이 제공하는 여러 Validation로직이 있지만 사용자가 직접 특정 로직을 구현하여 끼워넣을 수 있습니다.

AbstractFunction 을 상속하여 구현합니다.

제약사항으로 세 개의 인자(Functions[] functions, int line, int column)를 가지는 생성자를 만들어서 AbstractFunction 클레스를 생성할 때 사용할 수 있도록 super를 호출해야 합니다.

Spring MVC 9장에 나와있는 예제로 그다지 쓸만해 보이지는 않지만 입력받은 값의 case를 뒤바꿔서 검증을 하는 예제가 나옵니다.

import org.apache.commons.lang.WordUtils;

import org.springmodules.validation.valang.functions.AbstractFunction;

import org.springmodules.validation.valang.functions.Function;

 

public class AlterCaseFunction extends AbstractFunction {

       public AlterCaseFunction(Function[] functions, int line, int column) {

             super(functions, line, column);

             definedMinNumberOfArguments(1);

             definedMaxNumberOfArguments(1);

       }

 

       protected Object doGetResult(Object target) {

             String value = getArguments()[0].getResult(target).toString();

             return WordUtils.swapCase(value);

       }

}

이렇게 작성한 Function을 ValangValidatorFactoryBean에 등록하여 syntax에서 사용할 수 있습니다.

<bean id="caseSwappingValidator"

       class="org.springmodules.validation.ValangValidatorFactoryBean">

       <property name="syntax">

             <value>

                    <![CDATA[

{ name : alterCase(?) = 'sTEVEN' : 'Name must be Steven' }

]]>

             </value>

       </property>

       <property name="customFunctions">

             <map>

                    <entry key="alterCase"

       value="com.apress.expertspringmvc.validation.AlterCaseFunction" />

             </map>

       </property>

</bean>

흠... 그런데..API에서 ValangValidatorFactoryBean 이녀석이 사라졌습니다. 어디로 갔을런지;;

top


Valang - syntex



기본 적인 syntex 예제 입니다.

{ location : ? is not blank : 'Location must be specified' : 'addressLocationEmpty' : ? }

1. validation syntex는 {와 } 사이에 들어가야 합니다.

2. 인자는 총 다섯 개가 올 수 있으며 : 로 구분합니다. 마지막 인자는 생략 가능합니다.
- 첫 번째 인자 : 검사할 객체의 프로퍼티
- 두 번째 인자 : 검사하는 로직
- 세 번째 인자 : default message
- 네 번째 인자 : 프로퍼티 파일의 에러코드
- 다섯 번째 인자 : 아규먼트

3. 프로퍼티를 가져오는 방법
- name
- address.location
- customers[0].name
- salesParameters[seasonStartDate]
위와 같이 다양한 벙법으로 가져 올 수 있으며 기본적으로 String 타입으로 가져옵니다. 이 때 []를 사용하면 그 안의 값은 Date 타입으로 인식합니다. 현재 시각을 나타내는 T 를 사용할 수 있습니다.
예) 입력한 시각이 현재 시각보다 앞서지 않도록 검증
{ order.timestamp : ? < [T] : '' : 'order.timestamp.in_past' }
예) 입력한 시각이 오늘이라는 시간대에 포함되는지 검증
{ order.timestamp : ? between [T<d] and [T>d] : '' : 'order.timestamp.not_today' : ? }

4. 로직을 작성할 때 사용할 수 있는 오퍼레이터들
- String 관련 오퍼레이터
사용자 삽입 이미지
사용자 삽입 이미지
- Date 관련 오퍼레이터
사용자 삽입 이미지
- Date Incremental operator
사용자 삽입 이미지
- Date Regural Expression
사용자 삽입 이미지
top

TAG Syntax, Valang

Declarative Validators - Valang 사용하기



XML 설정파일로 Validation을 할 수 있습니다.


Jakarta Commons Validator또는 Valang(Validation language)을 사용하여 선언적으로 Validation을 할 수 있습니다. 여기서는 SpringMVC 9장에서 소개하고 있는 Valang을 살펴봅니다.

1. 필요한 jar파일
- Spring Modules

2. 사용법
- org.springmodules.validation.ValangValidatorFactoryBean 사용하여 validation bean만들기
- syntax 등록
- custom function 등록
- Validation사용할 수 있는 Controller에 Setter Injection 시키면 끝!

3. 예제코드

<bean id="caseSwappingValidator"

       class="org.springmodules.validation.ValangValidatorFactoryBean">

       <property name="syntax">

             <value>

                    <![CDATA[

{ name : alterCase(?) = 'sTEVEN' : 'Name must be Steven' }

]]>

             </value>

       </property>

       <property name="customFunctions">

             <map>

                    <entry key="alterCase"

       value="com.apress.expertspringmvc.validation.AlterCaseFunction" />

             </map>

       </property>

</bean>


top


Vlidator - Property 파일 사용하기



이전 글에서 사용한 방법으로는 입력 필드에 값이 비어있는지만 확인할 수 있습니다. 아마도 Errors 객체를 사용할 때 값이 비어있는지 검사하려면 if 문이 자주 사용되니까 그 코드를 줄여주기 위해 만든것 같습니다.

따라서 빈 값인지 확인할 때는 유용하게 사용할 수 있지만 그 이외의 경우에는 Errors 인터페이스를 사용해야 한다.
사용자 삽입 이미지
Errors의 인터페이스 중에 에러 메시지를 기록할 수 있는 메소드들입니다.

인자의 종류를 살펴보면 다음과 같습니다.
field :: 에러 메시지를 남길 대상이 되는 필드
errorCode :: 프로퍼티 파일에 있는 에러 메시지의 이름(키)
errorAgs :: 프로퍼티 파일에 있는 에러 메시지의 내용(값)을 출력할 때 특정 문자열을 넘겨 줄 수 있습니다. 그 때 이 아규먼트를 사용합니다.
defaultMessage :: 프로퍼티 파일에서 errorCode에 해당하는 메시지 키를 못찾으면 여기에 입력한 값을 출력합니다.


프로퍼티 파일을 사용하는 방법은 간단합니다.
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="message" />
    </bean>

위와 같이 messageSource를 등록합니다. 이 때 프로퍼티 파일의 이름을 basename에 입력해 줍니다. 만약 프로퍼티 파일이 여러개라면 besenames 속성을 사용하여 다음과 같이 list로 넘겨줍니다.

<beans>
  <bean id="messageSource"
        class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
      <list>
        <value>format</value>
        <value>exceptions</value>
        <value>windows</value>
      </list>
    </property>
  </bean>
</beans>

그 다음 프로퍼티 파일을 작성합니다. 위 설정에서 message 라는 이름을 입력했으니까 프로퍼티 파일은 message.properties 또는 message_ko_KR.properties 이런식으로 입력값 뒤에 지역코드가 붙은 프로퍼티 파일도 자동으로 읽히게 됩니다.

required=Input {0} Properties
passwordTooShort=Enter password at least 6 characters.
notSamePassword=Enter the same vlaue with password


프로퍼티에 저렇게 내용을 채우고 이제는 Validator를 만듭니다. 저번에 만든 코드에서 살짝 내용을 추가/수정했습니다.

public class MemberInfoValidator implements Validator{

    public boolean supports(Class clazz) {
        return MemberInfo.class.isAssignableFrom(clazz);
    }

    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required", new Object [] {"email"}, "Enter your email");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required", new Object [] {"password"}, "Enter your password");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "required", new Object [] {"confirmPassword"}, "Enter the same password for confirmation");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmMember", "required", new Object [] {"confirmMember"}, "Enter ajn member code");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", new Object [] {"name"}, "Enter your name");

        MemberInfo memberInfo = (MemberInfo)object;
        if(memberInfo.getPassword().length() < 6)
            errors.rejectValue("password", "passwordTooShort");

        if(!memberInfo.getPassword().equals(memberInfo.getConfirmPassword()))
            errors.rejectValue("confirmPassword", "notSamePassword");
    }

}

이제 실행해보면 다음과 같이 에러메시지들이 출력됩니다.
사용자 삽입 이미지


top


Vlidator - ValidationUtils 사용하기



Spring에서 Validator를 구현하는 방법은 두 가지가 있습니다.
1. Programmatic
2. Declarative

그 중에서 첫 번째 Programmatic 방법을 사용하여 구현할 때 ValidationUtils를 사용하면 매우 간단하게 구현할 수 있습니다.
사용자 삽입 이미지
인터페이스 중에 인자가 네개인 녀석을 사용하여 defaultMessage를 주면 프로퍼티 파일을 만들지 않아도 메시지를 출력할 수 있습니다.

1. Validator 만들기
public class MemberInfoValidator implements Validator{

    public boolean supports(Class clazz) {
        return MemberInfo.class.isAssignableFrom(clazz);
    }

    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required", "Enter your email");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required", "Enter your password");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "required", "Enter the same password for confirmation");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmMember", "required", "Enter ajn member code");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "Enter your name");
    }

}

Validator 인터페이스를 구현하고 ValidationUtils를 사용하여 간단하게 구현할 수 있습니다.

2. Controller에 등록하기
  public CreateMemberInfoController() {
        setCommandClass(MemberInfo.class);
        setCommandName("memberInfo");
        setFormView("createMemberInfo");
        setSuccessView("viewMemberList");
        setValidator(new MemberInfoValidator());
    }

setValidator 메소드를 사용하여 위에서 만든 Validator를 등록해 줍니다.

3. 화면에 보여주기
<form:errors path="속성 이름" />

이런식으로 속성 이름을 적어주면 그 이름에 해당하는 에러 메시지를 고자리에 출력해 줍니다.

사용자 삽입 이미지

<form:errors path="*" />

이렇게 써주면 저 태그가 들어간 위치에 모든 에러 메시지를 모아서 출력할 수 있습니다. 좋군요~
사용자 삽입 이미지

top


SimpleFormController's onSubmit()



오버로딩을 사용해서 같은 이름의 메소드가 세 개 있었습니다.

보통은 onSubmit(Object command) 이 녀석만 사용했었는데요. 세션에 객체 하나를 담고 싶어서 아래와 같은 코드가 onSubmit() 메소드 안에 추가 되어야 했습니다.

request.getSession().setAttribute("user", member);

그러나... request 인자가 onSubmit() 메소드에 없는 것입니다. 그래서 이를 어쩌나..했는데 찬욱군이 onSubmit() 메소드가 세 개가 있는데 그중에 request 객체를 받아오는 녀석이 있다고 알려줘서 찾아봤습니다.

Spring MVC 155쪽에 나와있는 그림을 보니 어떤 메소드 들이 있고 어떻게 동작하는지 명확히 알 수 있었습니다.
사용자 삽입 이미지
인자가 제일 많은 녀석 부터 시작해서 하나 인 녀석 순으로 내부에서 호출하게 되는 것 같습니다. 이 걸 코드로 표현하면 아래 처럼 될 것입니다.

protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
    //할 일
    onSubmit(command, errors);
}

protected ModelAndView onSubmit(Object command, BindException errors) throws Exception {
    //할 일
    onSubmit(command);
}

protected ModelAndView onSubmit(Object command) throws Exception {
    //할 일
    doSubmitAction(formBean);
}

이 중에서 필요한 인자에 따라 필요한 녀석을 오버라이딩해서 사용하면 될 것 같습니다. 사용자(개발자) 편의를 위한 계층화라고 생각되네요.


'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


SimpleFormController



참조 : org.springframework.web.portlet.mvc.SimpleFormController

주로 새로운 정보를 입력 또는 수정할 때 사용하는 컨트롤러라고 합니다. 그런데 저는 간단한 검색을 할 때 사용해 봤던 적이 있지요. ㅎㅎㅎ;; 사용하면 안되는건 아니지만 권총으로 맞출 과녁을 대포로 쏴서 맞춘격에 비유할 수 있는 것 같습니다.

이녀석을 사용할 때 설정 해 줄 것이 몇 개 있는데요. form에서 정보 받아올 command 객체(이름과 class)랑 form이 있는 view, 그리고 에러 없이 command 객체를 받아서 전해 줄 successView 이름을 줄 수 있습니다. form이 있는 view는 생략이 가능합니다. 그리고 부가적으로 command 객체에 정보를 검증할 수 있는 validation을 만들어서 사용할 수 있습니다.

사용자 삽입 이미지
이런식으로 동작하게 됩니다. sequence diagram보다 이게 더 보기 좋군요.
Controller와의 관계를 보기 위해 클래스 다이어그램을 보겠습니다.
사용자 삽입 이미지
와.. 기네요~ 이렇게 계층화가 잘 되어 있기 때문에 확장성이 좋다고 하는 것 같습니다. 계층화가 잘 되어 있으면 원하는 지점에서 상속 받아서 사용하면 되기 때문이겠죠?

'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


Controller



참조 : org.springframework.web.servlet.mvc.Controller

MVC 모델에서 C에 해당하는 녀석으로 주로 Servlet이 이 역할을 담당하고 있었고 Struts에서는 Action이라는 것이 역할을 하고 있었는데 Spring MVC를 사용하면 Servlet = Controller or Action = Controller 라고 외우지 않아도 "Controller는 Controller다." 라고 그냥 받아 들이면 되서 편하네요.

Controller API를 보면 책임이 딱 한 개 있는 것이 보입니다.
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception
HttpServletRequest와 HttpServletResponse 객체를 받아서 ModelAndView 객체를 반환합니다.
사용자 삽입 이미지
이렇게 그리면 되는건지.. 흠..시퀀스 다이어그램은 익숙치가 않아서 많이 보고 그려봐야겠네요.

'Spring MVC > 6장 Controller' 카테고리의 다른 글

AbstractController  (0) 2007.06.21
Non-String DataBinding 테스트하기  (0) 2007.06.21
간단한 DataBinding 테스트하기  (0) 2007.06.20
MultiactionController  (0) 2007.06.19
SimpleFormController's onSubmit()  (0) 2007.04.11
SimpleFormController  (0) 2007.02.28
Controller  (0) 2007.02.28
top


Service Layer



특징
- clinet에게 : clinet가 사용하기 쉽도록 시스템의 기능을 까칠하게(coarse grained interace) 표현해준다.
- system에게 : Service Layer의 메소드는 작업의 처리 가능한 단위(Transactional Unit)를 표현한다.
- Stateless : 실행시에 이 계층의 객체들이 여러 쓰레드를 다룰수 있다. 이때 Stateless 해야 어떤 쓰레드가 다른 쓰레드를 폐끼치는 것을 피할 수 있다.
- 하나의 Use Case를 Transactional Unit of work
- 이 계층을 사용함으로써 system과 client 간의 결합도를 줄인다.

종속성
- Domain Model, Persistence Layer
- View나 Web Layer에 종속성을 가지면 안된다.
- 프레임웍에 종속적인 코드가 필요없다.
사용자 삽입 이미지

Spring's Support
- 거의없다.
- ApplicationContext는 Web Layer에 객체를 주입(Injection)한다.
- Transactional management, Performance monitoring, pooling을 원한다면 지원해 줄 수 있다.


'Spring MVC > 3장 Spring MVC' 카테고리의 다른 글

Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext  (6) 2008.07.03
Service Layer  (2) 2006.12.13
Web Layer  (0) 2006.12.12
User Interface Layer  (0) 2006.10.09
Layers of Abstractions  (0) 2006.10.08
top


Web Layer



Web Layer

이 계층에서 다루는 주요 기능 두가지
- Navigation Logic 담당
- Domain model 과 Service Layer의 중계자 역할

특징
- servlet 으로 간단하게 구현될 수 있다. 이런 servlet은 request 파라미터를 객체로 바꾸고 service 인터페이스의 메소드를 호출한다.
- 유저들을 위해서 비즈니스 exception들을 적당한 에러 메시지로 바꿔야 하는 책임도 있다.

Spring MVC가 하는 일
- Spring MVC는 request 파라미터를 비즈니스 로직이 직접 작동할 수 있는 POJO로 맵핑하는 등의 request 파라미터와 비즈니스 로직 계층간의 복잡한 메카니즘을 제공한다.
- Spring은 request들을 처리하고 확장이 편하도록 복잡한 업무 흐름을 구현하고 있다.

종속성
-Service Layer(web에서 받은 정보를 service layer에 전해 줄 domain 객체로 전환)
-Domain Layer
사용자 삽입 이미지
Spring MVC Web Layer API
- org.springframework.web.servlet.mvc.Controller 인터페이스
- HttpServletRequest와 HttpServletResponse를 받을 때 필요하다.
- client에게 정보를 돌려줄 때 ModelAndView 객체를 만든다.


'Spring MVC > 3장 Spring MVC' 카테고리의 다른 글

Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext  (6) 2008.07.03
Service Layer  (2) 2006.12.13
Web Layer  (0) 2006.12.12
User Interface Layer  (0) 2006.10.09
Layers of Abstractions  (0) 2006.10.08
top


User Interface Layer



Layers in a Spring MVC Application

User Interface Layer

The user interface layer is responsible for presenting the application to the end user. 이 계층은 web layer에서 생성된 응답을 유저가 요청한 양식으로 변환합니다. 예를 들어 휴대폰 사용자들을 위해서는 WML 또는 최소한 특화된 XHTML을 필요로 할 것입니다.

웹 개발자들에게 user interface layer 추상단계는 매우 중요합니다. user interface layer를 weblayer 의 sublayer로 생각하면 쉬울 것입니다. 이 책은 웹 에플리케이션에 초점을 맞췄기 때문에 이 계층을 따로 분리를 했으며 이 계층 만의 관심사와 특징들이 존재한다.


User Interface Layer는 최상위 계층입니다. 개념적으로 이 말은 유저(Client)에게 데이터를 보내기 전 마지막 계층 이라는 것입니다. 이 계층에 다다르기 전에 이미 비즈니스 로직과 트랜잭션이 처리가 되고 모든 자원들이 반환되었었을 것입니다.

이 계층이 마지막에 있는 것은 좋은 것입니다. 이 계층은 유저들에게 보내질 데이터를 원하는 양식으로 바꿔(rendering)줍니다. UI 계층은 다른 계층들과 분리 되어 있기 때문에 시스템은 계속해서 다른 요청들을 처리할 수 있습니다. 유저에게 보낼 응답을 랜더링 하는 것은 응답을 모아오는 행위(DB 커넥션 유지, 비즈니스 로직 처리 등등을 마말하는 거겠죠?? 긴가 민가 하네요. 응답을 처리하는 행위가 더 나르려나..)와 분리 되어 있다는 것입니다.

UI 계층을 분리 시키는 것은 현실적입니다. 여러가지 UI 랜더링을 위한 툴들이 존재하기 때문입니다. JSP, Velocity, Freemarker등에 종속되지 않고 UI 인터페이스를 사용하여 특정 랜더링 기술을 사용하여 UI를 변경하더라도 다른 계층들에 영향을 주지 않을 수 있습니다.

Spring MVC's User Interface Layer


Spring MVC UI 관심사를 몇몇 주요 인터페이스들로 분리시킵니다. org.springframework.web.servlet.View 인터페이스는 에플리케이션의 view 또는 page 를 나타냅니다. 유저가 요청한 작업의 결과를 고객이 볼 수 있는 폼으로 전환하는 역할을 합니다.


Model은 객체들의 이름의 집합입니다. 어떤 객체든지 Model 안에 있는 View로 랜더링 될 수 있습니다. Model은 일반적인 목적으로 설계되었기 때문에 어떠한 랜더링 기술과도 작업이 가능합니다. View 랜더링 툴킷은 Model안에 있는 각각의 객체들을 랜더링 합니다.


View 인터페이스는 완전히 일반적이고 특정한 뷰 랜더링에 종속되어 있지 않습니다. 각각의 뷰 기술들은 이 인터페이스를 구현한 클래스로 제공됩니다.  Spring MVC는 기본적으로 JSP, FreeMarker, Velocity, JasperReports, Excel, PDF를 지원합니다.

org.springframework.web.servlet.ViewResolver는 인디렉션을 하는 유용한 인터페이스를 제공합니다. ViewResolever는 뷰 개체들과 그들의 논리적인 이름간의 맵핑하는 기능을 제공합니다. 예를 들어 /WEB-INF/jsp/onSuccess.jsp 파일을 success라는 이름으로 참조 할 수 있도록 합니다. 이것은 실제 View 객체와 코드 내에서 그것을 참조하는 것을 decoupling 시킵니다. ViewResolver를 사용하여 보다 유연한 설정을 할 수 있습니다.



Dependencies

View 계층은 일반적으로 domain 계층에 종속됩니다. 항상 그런 것은 아니지만, 보통 도메인 모델에 직접 접근하여 랜더링 하는 것이 편리합니다. Spring MVC를 사용하는 대부분의 편리함은 view가 도메인 객체에 직접 작업을 한다는 것으로부터 생깁니다.



Summary

User interface 계층(View 계층이라고도 합니다.)은 유저를 위한 결과물의 랜더링을 책입집니다. Spring MVC는 다양한 view 랜더링 기술을 지원합니다. 중요한 인터페이스로는 org.springframework.web.servlet.View와 org.springframework.web.servlet.ViewResolver입니다. view기술들에 대해서는 7장과 8장에서 자세히 다룰것입니다.



'Spring MVC > 3장 Spring MVC' 카테고리의 다른 글

Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext  (6) 2008.07.03
Service Layer  (2) 2006.12.13
Web Layer  (0) 2006.12.12
User Interface Layer  (0) 2006.10.09
Layers of Abstractions  (0) 2006.10.08
top


Layers of Abstractions



Layers of Abstractions

Spring MVC 에플리케이션들은 여러 계층으로 나누어져있다. layer is a discrete, othogonal area of concern within an application. 여러 계층들은 에플리케이션의 추상화에 해당하며 인터페이스는 계층들간 상호작용의 규약을 제공한다. 어떤 계층들은 몇몇 다른 Layer들과 상호작용을 하지만 매우 중요한 계층은 모든 계층과 상호작용을 한다.
계층은 개념적인 경계선이고 물리적으로 떨어져 있을 필요는 없다.

A Layer is a logical abstraction within an application. A tier is best thought of as a physical deployment of the layers.


계층 구조로 생각하는 것이 에플리케이션의 전체 흐름을 개념화하는데 도움이 된다. 에플리케이션 계층들을 케잌처럼 쌓아 놓은 모습으로 보는 것이 가장 흔하고 편리한 방법이다.
Spring MVC 에플리케이션은 최소 다섯 개의 추상화된 계층을 가지고 있으며 각각은 다음과 같다.
  • user interface
  • web
  • service
  • domain object model
  • persistence

위 그림을 보면 Domain Model이 왼쪽에 세로로 서있는 것을 볼 수 있습니다. 이것은 다른 모든 계층들이 Domain Model 계층에 종속하기 때문입니다.
매우 중요한 계층인듯 합니다. 저런 경우에 AOP를 사용한다고 본 것 같은데 지금 저 계층들에 없는 tracjaction management, security같은 것은 AOP를 사용하여 뺏다고 하는데 Domain Model은 그럴만한 성격이 아닌가 보네요.

Layer Isolation


계층을 분리시키는 것은 에플리케이션을 보다 유연하고 테스트가 용이하도록 만듭니다. 이러한 분리는 계층 사이의 종속성을 최소화 함으로써 달성할 수 있습니다. 종속성 남발을 피할 수 있는 최소한 두가지 방법이 있습니다. 만약에 어떤 계층이 다른 여러 계층들에 종속하기 시작하면 다른 계층간의 상호작용을 추상화하는 새로운 계층을 만드는 것을 고려해 봅니다. 또 하나의 계층이 여러 계층들에서 사용된다면 Spring의 AOP를 사용하는 것을 어떨지 생각해 봅니다.
여기서 기억하야 할 것은 에플리케이션을 여러 계층으로 나누는 것은 decouple 된 설계를 만들어 낸다는 것입니다. 그렇게 함으로써 여러분의 에플리케이션이 보다 유연하고(확장이나 변동이 쉽고), 테스트가하기 편해집니다.

Java Interface As Layer Contract


에플리케이션의 계층을 나누는데 Java 인터페이스가 중요한 역할을 합니다. 인터페이스는 계층간의 규약이며 그들의 구현과 세부사항을 숨긴 채 계층 간의 작동이 유지될 수 있도록 합니다. 인터페이스에 의해 제공되는 low coupling의 이점에 대해서는 잘 알 고있을 것입니다. 그러나 여태까지 그러한 장점은 여태까지 인터페이스가 아닌 실제 구현된 클래스를 사용할 수 밖에 없는 상황이였기 때문에 잘 살리지 못했습니다. 하지만 Spring과 다른 DI 프레임웤을 사용한다면 가능합니다. Spring이 직접 에플리케이션의 코드 대신 객체의 생성에 대한 일을 해주기 때문입니다.
계층간의 규약을 인터페이스로 정의해두면 개발 시간을 단축시킵니다. 개발자들이 인터페이스에 맞춰 개발을 하기 때문에 그것을 구현한 코드가 작성되고 바뀌고 테스트되더라도 개발이 가능하기 떄문입니다.
계층간의 상호작용을 인터페이스로 정의해 두면 단위 테스트가 간편합니다. EasyMock과 같은 프레임웤을 사용하여 쉽게 인터페이스의 구현체를 만들어서 테스트할 수 있기 때문입니다.
인터페이스를 사용하여 계층에 접근하는 것은 컴파일 시간을 단축시키고 보다 모듈화된 개발을 가능케 합니다. Client code가 이미 사용하고 있는 것들을 재컴파일하지 않아도 새로운 클래스를 추가하거나 변경이 가능하기 때문입니다.
인터페이스를 사용하면 또한 시스템이 매우 유연해 집니다. 시스템이 시작할 때 또는 이미 실행 중일 때도 특정 구현체로 변동이 가능합니다. Client code가 인터페이스에 맞춰 컴파일 되었기 때문에 실행중에도 구현된 클래스를 바꿀수가 있습니다. 매우 동적인 시스템을 만들수가 있습니다.

여기까지 매우 간단하게 요약하자면 인터페이스를 사용하여 각 계층간의 규약을 정의한다. 인터페이스는 계층의 추상화를 제공하고 계층을 구현한 것이 다른 계층에 영향을 주지 않고 쉽게 변할 수 있도록 허용한다.

'Spring MVC > 3장 Spring MVC' 카테고리의 다른 글

Spring MVC에서 사용하는 ApplicationContext와 WebApplicationContext  (6) 2008.07.03
Service Layer  (2) 2006.12.13
Web Layer  (0) 2006.12.12
User Interface Layer  (0) 2006.10.09
Layers of Abstractions  (0) 2006.10.08
top


Spring ApplicationContexts



Spring ApplicationContexts

Spring 에플리케이션의 심장과 정신에 해당하는 ApplicationContext 안에서 실제 DI를 수행합니다.

ApplicationContext는 BeanFactory를 특화시킨 것입니다.(ApplcationContext가 BeanFacotory를 상속받았죠.) BeanFactory는 Spring에서 사용되는 객체들의 레지스트리입니다. BeanFactory는 bean의 생성과 그들 간의 종속성 주입, 그리고 bean lookup(찾아 주기)를 담당합니다.

ApplicationContext는 이러한 BeanFactory의 기능에 부가적인 기능을 추가시킨 것입니다. 보통 BeanFactory대신에  이것을 사용한답니다. 웹 에플리케이션의 경우 웹에 특화된 WebApplicationContext를 사용합니다. ApplicationContext는 BeanFactoryPostProcessors를 가지고 초기화를 거친 후에 BeanFactory를 자동으로 처리할 수 있다고 합니다.(뭐가 어떻게 돌아간다는 건지는 잘 모르겠네요--;) 이밖에도 메시지를 internationalication(국제화)하는 기능, loosely coupled(약하게 결합된) 생산자와 소비자를 위한 event-routing mechanism, ApplicationContextAware같은 생명주기 인터페이스를 지원합니다.

ApplicationContext는 주로 XML 파일을 사용하여 bean들을 설정합니다. 매우 간단한 applicationContext.xml파일을 보겠습니다.



위 설정 파일에서 두 개의 bean을 등록한 것을 볼 수 있습니다. 이 bean들을 생성하고 관리하는 것이 ApplicationContext가 할일 입니다. 저 위의 <property> 속성을 보시면 Dependency Injection을 정의해 둔 것을 볼 수 있습니다. cashRegister bean에서 priceMatrix 레퍼런스가 priceMatrixBean을 사용하도록 정의한 것입니다.

위 설정 파일 없이도 그러한 것은 다음 순수 java code로도 가능합니다.

The Return of the POJO


이전 글과 이번 글에 걸쳐 DI와 ApplicationContext에 대해 배웠습니다. 이 두 개념은 Spring의 핵심 개념입니다. 이 것들을 에플리케이션(심지어 웹 에플리케이션 마저)을 Plain Old Java Object(POJO)로 개발 하기 위한 도구 입니다. 이것이 가능하기 때문에 강력한 Object Oriented 개념을 웹 에플리케이션 개발에 사용할 수 있습니다. 프레임웤에 특화된 코드를 작성하기 보다 비즈니스 로직에만 집중하여 개발하기 때문입니다. 이 책에서는 앞으로 도메인 객체를 먼저 작성하고 그런 다음 프레임웤을 사용하여 그것들을 웹 에플리케이션으로 향상시도록 할 것입니다.

여기까지 1장을 모두 요약하자면
  • Dependency Injection은 Inversion Of Control의 한 종류이다. 이것은 코드에 종속성을 주입하는 원리이다. 이것을 사용하면 객체 생성 또는 그것을 위치시키는 일을 프레임웍에게 전가 시키기 때문에 코드 자체의 테스트가 용이해진다.
  • ApplicationContext는 Spring의 주요 객체 레지스트리와 통합 포인트다. 주로 XML파일을 통해서 bean과 그들의 종속성을 설정한다. 많은 기능이 있지만 그 중에 객체 생성과 DI가 핵심 기능이다.
  • 마지막으로 POJO 스타일로 개발이 가능하다는 것이다. 따라서 객체 지향 개발 기술의 사용이 가능하며 POJO로 개발 된 시스템은 테스트가 용이하고 유연하다. 또한 개발자들이 프레임웤을 어떻게 다루는지 보다는 문제 영역과 비즈니스 로직에 집중하여 개발이 가능해진다.

'Spring MVC > 2장 Spring 기본요소' 카테고리의 다른 글

Spring ApplicationContexts  (0) 2006.10.06
Inversion of Control  (4) 2006.10.06
top


Inversion of Control



Inversion of Control

Dependency Injection(의존성 주입)과 혼용되어 사용되는 것을 종종 보았는데 이 글을 읽어보니 어느정도 명확해 지네요. IOC가 보다 광범위한 의미이고 이 것을 표현하는 여러가지 방법이 있습니다. DI도 그중에 하나라고 하네요. 이번 장에서 말하고 있기로는 AOP와 DI가 IOC의 일부라고 합니다.

먼저 간략히 정의를 살펴보면 IOC는 braod range of techniques that allow an object to become a passive participant in the system. IOC를 사용하면 객체의 몇몇 특성이나 시점(aspect)에 대한 제어권을 프레임웍이나 환경에게 넘기게 됩니다. 그러한 제어권에는 객체의 생성이나 의존하는 객체들의 임명 등이 있습니다.

AOP

먼저 AOP를 사용하게 되는 경우를 살펴보겠습니다.

여기 보시면 주황색 부분의 코드(인증 관련 코드)가 매번 중복되어 나타나는 것을 볼 수 있습니다.
사실 인증은 메소드 본래의 작업이 아닌 부가적인 작업에 불과함에도 본래 코드보다 많은 부분을 차지 할 뿐만 아니라 계속해서 반복되어 나타나고 있습니다.

이 코드를 시스템 뒤딴으로 빼려면 Spring의 AOP(Aspect-Oriented Programming)를 사용하서 간단히 해결할 수 있습니다. Aspects are concerns of the application that apply themselves across the entire system. Aspect란 전체 시스템에서 통용되어 사용되는 에플리케이션과의 관계라고 할 수 있나요...흠; 어렵네요. 위 예제에서는 SecurityManager가 시스템에서 통용되는 aspect의 하나의 예라고 할 수 있겠네요. 그리고 통용된다는 증거로 모든 BankAccount의 모든 메소드에서 hasPermission 메소드를 호출하고 있습니다. 이런 비슷한 aspect로는 logging, auditing, transaction management가 있습니다. 이 중에 auditing은 뭔지 잘 모르겠네요.


Spring AoP는 이러한 aspect들을 runtime 때 또는 compile 때 도메인 모델(여기선 BankAccount)에 끼워넣습니다.(보통 weaving 한다고 합니다.) 이 말은 BankAccount에서 위와 같이 주황색 부분의 코드를 전부 지워도 AOP 프레임웤이 이 전과 똑같이 동작하도록 도와준다는 것입니다.

Spring은 기본적으로 proxy-based AOP(프록시 기반 AOP)를 사용합니다. 이 말은 원래 목표가 되는 객체(targer object)를 - 여기서는 BankAccount가 되겠죠- Aspect를 적용하기 위해서 Proxy로 감싸서 목표 객체 대신에 사용한다는 것입니다.  다음의 그림을 보시면 이해가 될 것입니다.

여기서 잠깐 Spring은 프록시가 아닌 컴파일 할 때 끼워넣는 AspectJ를 지원한다는 점. 그리고 이게 단순한 프록시를 사용하는 솔루션 보다 더 많은 기능을 가지고 있다고 합니다.


BankTeller가 colseOut을 호출 하면 이것은 BankAccount의 메소드를 호출하는 것이 아니라 BankAccount의 프록시를 호출하게 되고 이 프록시는 weaving된 aspect에 따라 3번 작업을 하고 그리고 본래의 작업인 4번을 BankAccount를 이용하여 처리합니다.
여기까지 정리
  • IOC는 제어권을 프레임웤에 넘기는 포괄적인 개념이다.
  • 여기서 제워권이란 새로운 객체의 생성, 트랜잭션, 보안에 대한 제어들을 이야기 한다.
  • AOP는 IOC를 실현하는 하나의 기술이다.
  • DI또한 IOC를 실현하는 하나의 기술이다.
Dependency Injection

DI는 Spring 프레임웤의 핵심 기술입니다. DI is a technique that framework use to wire together an application. 프레임웤은 에플리케이션 간의 종속성을 연결하는 작업을 하여 에플리케이션에 있는 객체 생성을 하는 코드를 제거하는 일을 합니다.

DI와 Service Locator pattern을 비교할 수 있는데.. 그 둘을 비교하기 위해 간단한 예제를 보겠습니다. 이 예제는 쇼핑카트에 있는 물품들을 계산을 하는데 DB로 부터 그 물품들의 가격을 가져와서 합산하는 프로그램입니다. 따라서 먼저 간단한 인터페이스를 하나 작성을 합니다. 이 인터페이스는 CashRegister로써 쇼핑 카트를 매개변수로 받아서 그 안에 있는 물품을 계산하는 calculateTotalPrice 메소드가 있습니다. 그리고 PriceMatrix 인터페이스에는 lookup 메소드가 있어서 원하는 물품의 가격을 반환해주는 일을 합니다.


그리고 CashRegister를 구현한 CashRegisterImpl을 봅시다.


어쩌면 이렇게 한 것이 매우 자연스럽지만 다음과 같은 세가지 중요한 문제가 있습니다.
먼저 가장 중요한 것으로 인터페이스가 아닌 특정 구현에 의존한다는 것입니다. 이 말은 H.F. Degisn Pattern 1장에 나오는 디자인 원칙이며 위 코드는 그것을 어겼습니다. 특정 구현에 의존하게 되면 왜 안좋은지에 대한 것은 위에서 확인하시기 바랍니다.
둘째 PriceMatrixImpl 객체가 CashRegisterImpl을 생성 할 때 마다 매번 생성된다는 것입니다. 이것은 문제가 있지요. 가격표는 하나만 있으면 되는데 매번 계산할 때 마다 새로운 가격표를 생성한다는 것은 자원을 낭비하는 일입니다.
마지막으로 첫번째 발생한 일에 딸려 오는 문제로써 테스트하기가 힘들다는 것입니다. CashRegister를 테스트하기 위해서는 지금 PriceMatrixImpl까지 제대로 구현으르 해야 테스트가 가능합니다. 그리고 단위 테스트는 아예 할 수가 없는 상황이네요.

그럼 PriceMatrix없이 테스트가 가능하기나 하냔 말인가가 궁금한데요. 저도 잘 모르는 부분인데 Mock이라는 기술을 사용하면 단위 테스트가 가능하다고 합니다.

Service Locator를 사용해 봅시다.

Service Locator Pattern은 객체를 생성하여 그 레퍼런스를 얻어내는 과정을 숨기는 것을 말합니다. 클라이언트를 객체가 언제, 어디서 어떻게 생성되는지 모르도록 하는 패턴입니다. 클라이언트를 보호하고 코드 중복을 줄이기 위해서 이 패턴이 만들어졌다고 합니다. 보통 static 메소드를 사용하여 요구된 객체에 대한 하나의 객체를 반환해 줍니다. (싱글턴 패턴하고 다른게 뭐죠? 첨에는 static 팩토리인가.. 생각했다가 오직 하나의 클래스에 대한 하나의 객체만 리턴 해주면 이거 싱글턴 아닌가.. 하는 생각이 들었습니다.)


이렇게 함으로써 첫번째 문제(특정 구현에 의존하던 문제)와 두번째 문제(매번 새로운 객체를 생성하던 문제)가 해결되었습니다. 하지만 세번째 단위 테스트를 하려면 Mock 객체를 사용해야 하는데 Mock객체를 끼워넣을 방법이 없습니다. 왜내면 저 위의 주황색 부분이 test할 때는 Mock 객체를 넘겨주고 실제 사용할 때는 PriceMatrix객체를 주도록 바꾸기가 어렵기 때문입니다. 이런것을 효율적으로 하려면 클라이언트 코드에서 자원을 가져오거나 생성하는 활동에 전혀 참여하면 안됩니다. 그냥 자원이 클라이언트에 주어져야 합니다.

Dependency Injection을 사용해 봅시다.

Service Locator를 사용하는 대신에 프레임웤이 PriceMatrix type 객체의 레퍼런스를 CashRegisterImpl에 제공해 줍니다.  객체 생성과 객체 연결(location)에 대한 책임이 클래스에서 프레임웤으로 뒤집어졌습니다. 이러한 것을 DI라고 하며 두 가지 방법이 흔히 사용됩니다.

Spring AOP의 도움을 받는 method-based injection이라고 불리는 세번째 방법도 있으나 복잡하고 잘 쓰이질 않아서 여기서 다루진 않는답니다.

처음으로 볼 DI의 한 종류로는 constructor-based injection이 있습니다. 이 것은 객체가 생성될 때 종속성을 주입하는 방법입니다.


이렇게 하면 된거죠. 하지만 이 클래스는 Hollywood Principle을 따르고 있는데요. 할리우드 원칙이란 "내가 전화할테니 전화하지 마!"라는 원칙입니다. 다시 이 경우에 대입해 본다면 "내가 줄테니 자원을 달라고 요청하지마!" 라고 할 수 있겠습니다.

다음은 Setter-based injection입니다.


PriceMatrix type의 객체 생성 시기가 좀더 유연해 졌습니다. 생성자 기반과 세터 기반 중에 어떤 것을 사용할 지는 사용자의 선택입니다. 물론 어떤 경우를 선택하느냐에 따라 실제 종속성을 주입할 때 따르게 되는 지침에 해당하는 XML 파일의 내용이 약간 바뀔 것입니다.
이 것은 생성자 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부이며
이 것은 세터 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부입니다.

첫번째 문제-특정 구현에 의존했었던-는 해결된 듯 합니다.
두번째 문제-매번 새로운 객체를 생성했던-도 역시 bean이 기본적으로 싱클턴 객체로 생성되기 때문에 해결 된 듯합니다.
그럼 남은것은 세번째 문제-단위 테스트를 할 수 없었던-것인데요.
이렇게 하면 Service Locator에서 해결 못했던 Mock을 사용한 단위 test를 어떻게 할 수 있을까요?

처음 본거라 저도 잘 모르겠지만 다음과 같이 Mock을 사용한다고 합니다.

여기까지 정리하면..
  • DI는 종속성을 필요로 하는 코드 없이 여러 클래스들을 묶는데 사용하는 기술입니다.
  • 클라이언트는 프레임웤에게 종속성의 생명주기 관리를 넘겼습니다.
  • 이렇게 함으로써 클라이언트는 테스트하기 용이해 집니다.

'Spring MVC > 2장 Spring 기본요소' 카테고리의 다른 글

Spring ApplicationContexts  (0) 2006.10.06
Inversion of Control  (4) 2006.10.06
top