Spring 2.5 MVC 요약

토비님 발표를 들으며 요약한 내용입니다. 많은 걸 적을 순 없었습니다. 어차피 PPT가 KSUG 사이트에 올라올테니 그걸 기다려봅니다.

@Controller

@RequestMapping
    method level에서 narrowing.
        value, method, params
    파라미터와 리턴타입의 자동인식

    리턴값
    파라미터

@RequestParam
    이름이 다를 경우에만 사용
    URL 파라미터 받아오기

@ModelAttribute : 커맨드 객체, 레퍼런스 데이터

@SessionAttribute : 세션폼
    SessionStatus.setComplete();

@InitBinder : 커스텀 프로퍼티 에디터
    WebDataBinder

POJO 컨트롤러 테스트 가능
FlowContext를 이용한 flow control 가능
@PathElement REST 스타일… good! 빨리 나와주길 바래.

실습해볼 것
Spring MVC 예제 만들기(실습 과정을 동영상으로 찍어볼까~ 흠..)
    1. 컨트롤러 하나로 MultiAction + SimpleForm 역할하기
    2. 프로퍼티 에디터 사용하기
    3. 파라미터에서 값 가져오기
    4. 세션 객체 사용하기

이전에 공부했던 내용
2007/11/28 – [Spring/Chapter 13] – Annotation-based controller configuration

Annotation-based controller configuration

0. 컴포넌트 스캐너 등록하기

우선, 컴포넌트 스캔 기능을 사용해서 @Controller 애노테이션이 붙어있는 클래스들을 bean으로 인식하도록 해야합니다. 따라서 context:component-scan 엘리먼트로 컨트롤러들이 위치한 패키지를 명시해 줍니다.

<context:component-scan base-package=”org.springframework.samples.petclinic.web” />

1. 컨트롤러 작성하기

완전 POJO로 컨트롤러를 작성할 수 있습니다. 획기적이네요. 일단 컨트롤러로 사용할 클래스는 이제 더이상 아무런 클래스도 상속받지 않아도 됩니다. 정말 그야말로 POJO입니다. 이 POJO에다가 @Controller 애노테이션을 붙여주면 컨트롤러가 됩니다.

2. Request Mapping 하기

원래는 Handler Mapper가 하던 일인데, 이제는 이것도 애노테이션이 해줍니다. @RequestMapping 애노테이션으로 해당 클래스 또는 메소드가 처리할 요청을 명시해주면 됩니다.

3. Form Controller로 사용하기

클래스 선언부에 @RequestMapping 애노테이션으로 폼을 요청할 Request를 설정해줍니다. 이 애노테이션이 붙어있는 클래스 안의 메소드 위에 같은 애노테이션을 사용하여 Request의 method에 따라 호출될 메소드를 설정할 수 있습니다.

@SessionAttributes 엘리먼트는 Session에 담을 attribute 를 나타냅니다. 주로 여러 폼에 걸쳐서 보여줄 데이터를 명시합니다.

@ModelAttribute는 두 가지 경우에 사용할 수 있는데, 메소드 위에 사용하면 폼에서 참조할 객체(Reference Data)를 나타낼 때 사용합니다. 아래의 예제에서 populatePetTypes() 메소드가 그 예에 해당합니다. 또 다른 경우는 메소드의 매개변수 앞에 이 애노테이션을 사용할 경우 인데, 이 때는 폼에 입력된 정보를 해당 애노테이션이 붙어있는 객체로 맵핑해 줍니다. processSubmit() 메소드의 인자를 보시면 됩니다.

@RequestParam은 Request의 특정 파라미터의 값을 가져옵니다. 물론 자동으로 해당 값을 이 애노테이션이 붙어있는 타입으로 변환해 줍니다. 기본타입만 가능하겠죠.

@Controller
@RequestMapping(“/editPet.do”)
@SessionAttributes(“pet”)
public class EditPetForm {

    private final Clinic clinic;

    @Autowired
    public EditPetForm(Clinic clinic) {
        this.clinic = clinic;
    }

    @ModelAttribute(“types”)
    public Collection<PetType> populatePetTypes() {
        return this.clinic.getPetTypes();
    }

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam(“petId”) int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute(“pet”, pet);
        return “petForm”;
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processSubmit(@ModelAttribute(“pet”) Pet pet, BindingResult result,
            SessionStatus status) {
        new PetValidator().validate(pet, result);
        if (result.hasErrors()) {
            return “petForm”;
        }
        else {
            this.clinic.storePet(pet);
            status.setComplete();
            return “redirect:owner.do?ownerId=” + pet.getOwner().getId();
        }
    }

}

4. MultiActionController로 사용하기

@RequestMapping 애노테이션으로 처리할 URL을 메소드 위에 표기해 줍니다. 물론 class위에는 적을 필요가없겠죠. 메소드 단위니까요.

뷰는 CoC에 따라 요청 URL을 보고 판단합니다.
뷰를 명시할 수 있는 다른 방법이 있는지 궁금해지네요. 이 부분은 살펴봐야겠습니다.
ModelAndView 객체를 리턴타입으로 사용할 수 있다면 간단해 지겠지만 말이죠.

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping(“/welcome.do”)
    public void welcomeHandler() {
    }

    @RequestMapping(“/vets.do”)
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

    @RequestMapping(“/owner.do”)
    public ModelMap ownerHandler(@RequestParam(“ownerId”) int ownerId) {
        return new ModelMap(this.clinic.loadOwner(ownerId));
    }

}

5. 커스텀 프로퍼티 에디터 등록하기.

다음과 같이 @InitBinder를 사용하여 바인딩 하는 녀석을 등록할 수 있습니다.

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd”);
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // …
}

해당 바인더는 커맨드나 폼 객체 그리고 그에 따른 에러 객체를 제외한 @RequestMapping이 지원하는 모든 아규먼트에 적용됩니다. 흠.. 폼에서 사용자가 입력한 값은 이걸로 바인딩 하지 않는다는 건가?? -_-?? 이상하네.. 이 부분도 공부나 추가 정보가 필요함.

커스텀 프로퍼티 에디터 등록을 xml에서 하기

WebBindingInitializer 인터페이스 구현체에 프로퍼티 에디터를 등록해서 다음과 같이 설정해주는 듯 합니다. 이 것 역시 어떻게 구현하는지 공부가 필요함.

<bean class=”org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter”>
    <property name=”cacheSeconds” value=”0″ />
    <property name=”webBindingInitializer”>
        <bean class=”org.springframework.samples.petclinic.web.ClinicBindingInitializer” />
    </property>
</bean>

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를 못 보여 주는 것이 단점인 것 같은데 이 걸 어떻게 해결 할 수 있을지…

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=”*” />

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

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);
}

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