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>