욱'S 노트

Spring MVC - Controllers 본문

Programming/Spring MVC

Spring MVC - Controllers

devsun 2015. 6. 17. 18:04

컨트롤러는 어플리케이션 레이어와의 연결을 제공한다. 서비스 인터페이스를 통해서 일반적으로 정의된다. 컨트롤러는 유저 입력을 인터프리터하고 모델로 변환한다. 모델은 뷰로서 유저에게 표현된다. 스프링은 추상화된 방법으로 컨트롤러를 구현한다. 컨트롤러는 매우 다양한 방법으로 생성될 수 있다.


스프링 2.5에서 MVC 컨트롤러를 위한 어노테이션 기반 프로그래밍 방법이 소개되었다. 어노테이션은 Spring MVC와 Protlet MVC에서 모두 이용가능하며 특정한 베이스 클래스나 특정한 인터페이스를 구현할 필요가 없다. 더욱이 Servlet이나 Porlet API에 직접적인 디펜던시를 가지지 않아서 쉽게 서블릿이나 Portlet 기능에 접근할 수 있다.


@Controller

public class HelloWorldController {

    @RequestMapping("/helloWorld")

public String helloWorld(Model model) { 

model.addAttribute("message", "Hello World!"); 

return "helloWorld";

}


보시다시피 @Controller와 @RequestMapping 어노테이션은 유연한 메소드명과 시그니쳐를 지원한다. 예제의 메소드는 Model을 받아서 String으로된 view의 이름을 반환한다. 그러나 다양한 다른 메소드 파라미터와 리턴값이 사용가능하다. @Controller와 @RequestMapping은 Spring MVC 구현을 위한 기본이 되는 어노테이션 형식이다.


Defining a controller with @Controller


@Controller 어노테이션이 지정되었다는 것은 컨트롤로 역할을 수행하는 특별한 클래스라는 것을 의미한다. 스프링은 어떤 베이스 클래스를 확장하거나 Servlet API를 참조할 것을 요구하지 않는다. 하지만 만약 필요하다면 서블릿 기능들을 참조할 수 있다.


@Controller 어노테이션은 명신된 클래스의 스테레오타입처럼 행동한다. 디스패처는 어노테이션이 명시된 클래스들을 찾아서 메소드와 @RequestMapping을 연결한다.


디스패처의 컨텍스트에 어노테이션된 표준 스프링 빈 설정을 통해 클래스를 명시적으로 정의할 수 있다. 또한  @Controller 스테레오 타입은 autodetection을 허용한다.


이러한 어노테이션을 가진 컨트롤러들을 autodectection을 가능하게 하기 위해서 설정에 컴포넌트 스캐닝을 추가하면 된다. 예제는 다음과 같다.


<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context.xsd">

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

<!-- ... -->

</beans>


Mapping Requests With @RequestMapping


@RequestMapping 어노테이션을 사용하여 URL과 전체 클래스 혹은 특정한 핸들러 메소드를 매핑할 수 있다. 일반적으로 클래스레벨 어노테이션은 특정한 리퀘스트 패스를 컨트롤러에 매핑하고 특정한 메소드 타입 혹은 요청 파라미터 조건을 메소드레벨 어노테이션에 지정한다.


@Controller

@RequestMapping("/appointments")

public class AppointmentsController {

private final AppointmentBook appointmentBook;


@Autowired

public AppointmentsController(AppointmentBook appointmentBook) { 

this.appointmentBook = appointmentBook;

}

        @RequestMapping(method = RequestMethod.GET)

public Map<String, Appointment> get() {

return appointmentBook.getAppointmentsForToday();

}

    

        @RequestMapping(value="/{day}", method = RequestMethod.GET)

public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {

return appointmentBook.getAppointmentsForDay(day); 

}

        @RequestMapping(value="/new", method = RequestMethod.GET)

public AppointmentForm getNewForm() { 

return new AppointmentForm();

}

    

        @RequestMapping(method = RequestMethod.POST)

public String add(@Valid AppointmentForm appointment, BindingResult result) { 

if (result.hasErrors()) {

return "appointments/new"; 

}

        

                appointmentBook.addAppointment(appointment);

return "redirect:/appointments"; 

}

}


예제를 보면 @RequestMapping은 많은 부분에 사용된 것을 알 수 있다. 첫번째 사용처는 타입 레벨인데 이것은 모든 핸들링 메서드들이 /appointments 패스에 연결되어 있다는 것을 지정한다. get() 메소드는  /appointments/{day} 패스로 요청된 HTTP GET 요청을 처리한다.


클래스 레벨의 @RequestMapping은 필수적인 것은 아니다. 모든 패스를 단순하게 절대적으로 정의할 수도 있다.


@Controller's and AOP Proxying


어떤 경우에는 컨트롤러는 런타임시 AOP 프록시로 데코레이션될 필요가 있다. 하나의 예로 컨트롤러에 @Transactional 어노테이션을 직접 지정하고 싶을 경우이다. 이런 경우 특별히 컨트롤러는 클래스 기반 프록싱을 사용한다. 이것은 컨트롤러에 대해서는 일반적인 선택이다. 하지만 만약 컨트롤러가 스프링 컨텍스트 콜백(InitailizingBean, *Aware)이 아닌 인터페이스를 구현한다면 명식적으로 클래스 기반 프록싱을 설정해 주어야 한다. 예를 들어 <tx:annotaion-driven/>은 <tx:annotaion-drvien proxy-target-class="true"/>


New Support Classes for @RequestMapping methods in Spring MVC 3.1


Spring 3.1 부터는 @RequestMapping 메소드를 위한 새로운 지원 클래스들이 소개 되었다. 새로운 지원 클래스들은 MVC 네임스페이스와 MVC 자바 Config을 통해서 활성하되지만 만약 사용하기를 원한다면 명시적으로 설정하는 것이 좋다.


Spring 3.1 이전에는 타입 및 메소드 레벨 리퀘스트 매핑은 두 단계로 구성되어진다. 첫번째 DefautlAnnotationHandlerMapping에 의해 컨트롤러가 선택되어지고, 두번째 AnnotaionMethodHandlerAdpater에 의해 실제 실행할 메소드가 선택된다.


RequestMappingHandlerMapping은 Spring 3.1 버전부터 DefaultAnnotationHandlerMapping을 대체하여 제공되는 Class로, @RequestMapping annotation을 기반으로 구현된 Controller와 요청 URL을 매핑시켜주는 HandlerMapping 구현클래스이다. Java 5 이상인 경우 DispatcherServlet이 디폴트로 등록해준다. 따라서 사용자가 명시적으로 정의할 필요가 없지만, RequestMappingHandlerMapping 클래스가 가진 디폴트 속성들을 변경하고자 할때는 action-servlet.xml에 명시적으로 정의할 수 있다. 다음은 사용자가 변경할 수 있는 RequestMappingHandlerMapping의 속성들이다.


interceptors - 사용할 interceptor들의 목록

defaultHandler  - 요청을 처리할 Controller를 찾지 못했을 때 디폴트로 사용할 Controller

order - 여러가지 Handler mapping을 사용할 경우 order 속성에 정의된 값을 기반으로 순서대로 동작

alwaysUseFullPath - 이 속성의 값이 true인 경우, servlet context 하위의 전체 URL path를 가지고 요청을 처리할 Controller를 찾고, 디폴트 값인 false인 경우DispatcherServlet과 mapping한 URL path 하위의 path로 요청을 처리할 Controller를 찾는다. 예를 들어, 현재 DispatcherServlet이 '/rest/*'와 매핑되어있고, 이 속성값이 true로 셋팅되어 있다면, '/rest/welcome.jsp' 전체가 사용되고, false인 경우 'welcome.jsp'만 사용될 것이다.


다음은 interceptors 속성을 오버라이드하여 RequestMappingHandlerMapping을 정의한 예이다.


<beans>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">

        <property name="interceptors">

            <bean class="example.MyInterceptor"/>

        </property>

    </bean>

<beans>


URI Template Patterns


URI templates을 사용하면 편리하게 @RequestMapping 메소드에서 URL의 특정부분을 추출할 수 있다. 


URI 템플릿은 URI과 유사한 문자열로 하나이상의 변수명을 포함하고 있다. 이러한 변수에 값을 대체했을 때 비로소 탬플릿은 URI가 된다. URI 템플릿에 제안된 RFC은 어떻게 URI를 파라미터화하는지 정의하고 있다. 예를 들어 URI 템플릿 http://www.example.com/users/{userId}는 변수 userId를 포함하고 있다. 이 변수에 fred 값을 할당하면 http://www.example.com/users/fred가 된다.


스프링 MVC에서 URI 템플릿 변수의 값에 바인딩하려고 메서드 인자에 @PathVariable 어노테이션을 사용할 수 있다.


@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)

public String findOwner(@PathVariable String ownerId, Model model) {

  Owner owner = ownerService.findOwner(ownerId);

  model.addAttribute("owner", owner);

  return "displayOwner";

}


URI 템플릿 "/owners/{ownerId}"는 ownerId라는 변수명을 지정한다. 컨트롤러가 이 요청을 처리할 때 URI의 적합한 부분에서 찾아낸 값을 ownerId의 값에 설정한다. 예를 들어 /owners/fred로 들어오는 요청에서 ownerId의 값은 fred가 된다.


Tip

@PathVariable 어노테이션을 처리하려면 스프링 MVC가 이름과 일치하는 URI 템플릿 변수를 찾아야 한다. 어노테이션에서 이를 지정할 수 있다.


@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)

public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {

  // 구현부는 생략한다

}


또는 URI 템플릿 변수 이름이 메서드 인자의 이름과 일치한다면 자세한 내용은 생략할 수 있다. 코드를 디버깅 정보없이 컴파일하지 않는 한 스프링 MVC가 메서드 인자 이름와 일치하는 URI 템플릿 변수 이름를 찾아낼 것이다.


@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)

public String findOwner(@PathVariable String ownerId, Model model) {

  // 구현부는 생략한다

}



메서드는 다수의 @PathVariable 어노테이션을 가질 수 있다.


@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)

public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {

  Owner owner = ownerService.findOwner(ownerId);

  Pet pet = owner.getPet(petId);

  model.addAttribute("pet", pet);

  return "displayPet";

}


URI 템플릿은 타입과 경로의 @RequestMapping 어노테이션으로 만들 수 있다. 그래서 findPet() 메서드는 /owners/42/pets/21같은 URL로 실행할 수 있다.


@Controller

@RequestMapping("/owners/{ownerId}")

public class RelativePathUriTemplateController {

 

  @RequestMapping("/pets/{petId}")

  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {

    // 구현부는 생략한다

  }

}



URI Template Patterns with Regular Expressions


때때로 URI 템플릿 변수를 더 정교하고 만들 필요가 있다. 


@RequestMapping 어노테이션은 URI 템플릿 변수에 정규 표현식을 사용하는 것을 지원한다. 문법은 {변수명:정규표현식} 이다.



@RequestMapping("/spring-web/{symbolicName:[a-z-]}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]}") public void handle(@PathVariable String version, @PathVariable String extension) {

// ...

} }


Path Patterns


추가적으로 URI 템플릿에는 Ant 스타일의 패스 패턴을 또한 지원한다. 예를들면 /mypath/*.do과 같은 형식이며 변수와 혼용해서 사용할 수 있다. (/owners/*/pets/{petId})


Consumable Media Types 


소비가능한 미디어 타입의 목록을 지정해서 주요한 매핑을 제한할 수 있다. Content-Type 요청 헤더가 지정한 미디어타입과 일치할 때만 요청이 매칭할 것이다. 예를 들면 다음과 같다.


@Controller

@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")

public void addPet(@RequestBody Pet pet, Model model) {

  // 구현부는 생략한다

}


소비가능한 미디어 타입 표현식에서 text/plain의 Content-Type을 제외한 모든 요청과 일치하도록 !text/plain처럼 부정문을 사용할 수도 있다.


Tip

consumes 조건은 타입수준과 메서드 수준에서 지원한다. 대부분의 다른 조건과는 달리 타입수준에서 사용했을 때 메서드 수준의 소비가능한 타입이 타입 수준의 소비가능한 타입을 확장하는 게 아니라 오버라이드한다.


Producible Media Types 


생산가능한 미디어 타입의 목록을 지정해서 주요 매핑을 제한할 수 있다. Accept 요청헤더가 이러한 값 중 하나와 일치할 때만 요청이 매칭될 것이다. 게다가 produces 상태를 사용하면 produces 조건에 지정한 미디어 타입과 관련된 응답을 생성하는데 사용한 실제 컨텐트 타입을 보장한다. 예를 들면 다음과 같다.


@Controller

@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")

@ResponseBody

public Pet getPet(@PathVariable String petId, Model model) {

  // 구현부는 생략한다

}


consumes에서처럼 생산가능한 미디어타입 표현식에는 text/plain의 Accept 헤더값을 가진 요청을 제외한 모든 요청에 매칭되도록 !text/plain처럼 부정문을 사용할 수 있다.


Tip

produces 조건은 타입수준과 메서드 수준에서 지원한다. 다른 대부분의 조건과는 달리 타입수준에서 사용했을 때 메서드수준의 producible 타입은 타입수준의 producible 타입을 확장하는 게 아니라 오버라이드한다.


Request Parameters and Header Values


"myParam", "!myParam", "myParam=myValue"같은 요청 파라미터 조건으로 요청에 대한 매칭을 제한할 수 있다. 앞에 두가지는 요청파라미터의 존재/부재 여부를 확인하고 세번째는 특정 파라미터 값을 확인한다. 다음은 요청 파라미터 값 조건에 대한 예제이다.


@Controller

@RequestMapping("/owners/{ownerId}")

public class RelativePathUriTemplateController {

 

  @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")

  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {

    // 구현부는 생략한다

  }

}


요청 헤더의 존재/부재 여부에 대한 검사나 지정한 요청 헤더값에 기반한 매칭도 동일하게 이뤄질 수 있다.


@Controller

@RequestMapping("/owners/{ownerId}")

public class RelativePathUriTemplateController {

 

@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")

  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {

    // 구현부는 생략한다

  }

}


Tip

미디어타입 와일드카드를 사용해서 Content-Type와 Accept 헤더값을 일치하도록 할 수 있기는 하지만 대신에 consumes와 produces 조건을 각각 사용하는 것을 권장한다. 이 둘은 해당 목적에 맞게 만들어진 것이다.


 



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

Spring MVC - DispatcherServlet  (0) 2015.06.16
Spring MVC - Introduction to Spring Web MVC framework  (0) 2015.06.16
Comments