Table of Contents

개요#

@MVC = 애노테이션 기반의 MVC

  • Controller 타입 기반의 클래스를 대부분 대체함

4.1. @RequestMapping 핸들러 매핑#

  1. Controller 타입의 클래스를 사용할 경우 URL당 하나의 컨트롤러를 사용함. 즉 URL하나에 클래스 하나
  2. @MVC는 메소드 레벨로 세분화됨
  3. @MVC의 핸들러 매핑을 위해서는 DefaultAnnotationHandlerMapping 이 필요함

4.1.1 클래스/메소드 결합 매핑정보#

  1. DefaultAnnotationHandlerMapping 의 핵심은 매핑정보로 @RequestMapping 애노테이션을 활용

@RequestMapping 애노테이션#

  • String value(): URL 패턴
@RequestMapping("/hello")
@RequestMapping("/main*")
@RequestMapping("/view.*")
@RequestMapping("/admin/**/user")
@RequestMapping ("/user/{userid}")
@RequestMapping({"/hello", "/hi"})
@RequestMapping("/hello") // 디폴트 접미어 패턴, @RequestMapping( {" /hello" , "/hello/" , "/hello. *"} )
  • RequestMethod method(): HTTP 요청 메소드
    1. RequestMethod 이늄은 GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE 값을 가짐
@RequestMapping(value="/user/add", method쾌equestMethod.GET)
@RequestMapping(value="/user/add", method=RequestMethod.POST)
    1. 정해진 요청메소드 이외에 요청일 경우 클라이언트는 HTTP 405 - Method Not Allowed 응답을 받음
  • String params: 요청 파라미터
@RequestMapping(value="/user/edit", params="type=admin")
@RequestMapping(value="/user/edit", params="type=member")
@RequestMapping(value="/user/edit"’ params="!type")
  • String headers(): HTTP 헤더
@RequestMapping(value="/view", headers="content-type=text!*")

타입 레벨 매핑과 메소드 레벨 매핑#

@RequestMapping("/user")
public class UserController {
	@RequestMapping("/add") public String add(...) { }
	@RequestMapping("/edit") public String edi t(...) { }
	@RequestMapping("/delete") public String delete(...) { }
}

@RequestMapping("/user")
public class UserController {
	@RequestMapping(methods=RequestMethod ,GET) public String form(...) { }
	@RequestMapping(methods=RequestMethod ,POST) public String submit (...) { }
}

4.1.2. 타입 상속과 매핑#

  1. @RequestMapping 정보는 상속된다.
  2. 서브클래스에서 @RequestMapping을 재정의하면 상위클래스 정보를 무시된다.

매핑정보 상속의 종류#

  • 상위 타입과 메소드의 @RequestMapping 상속
  • 상위 타입과 @RequestMapping과 하위 타입 메소드의 @RequestMapping 결합
  • 상위 타입 메소드의 @RequestMapping과 하위 타입의 @RequestMapping결합
  • 하위 타입과 메소드의 @RequestMapping 재정의
  • 서브클래스 메소드의 URL패턴없는 @RequestMapping 재정의

제네릭스와 매핑정보 상속을 이용한 컨트롤러 작성#

설명은 하지 않을 예정. 근데 보면 좋을듯

4.2. @Controller#

DefaultAnnotationHandlerMapping은 @RequestMapping정보를 활용해서 컨트롤러 빈의 메소드에 매핑해준다. AnnotationMethodHandlerAdapter는 매핑된 메소드를 실제로 호출하는 역할을 담당한다.

@Controller 애노테이션이면서 애노테이션을 이용해 컨트롤러를 개발하는 방법 자체를 지칭한다. 스프링 3.x에서부터는 3장에서 본 Controller를 직접 사용하지 않도록 권장한다. 심지어는 @Deprecated 처리 되어 있다. 그래서 @Controller 개발방식을 사용해야 한다.

@RequestMapping("/complex")
public String complex(@RequestParam( "name" ) String name,
@CookieValue("auth") String auth , ModelMap model) (
	model.put("info", name + "/" + auth );
	return "myview";
}

4.2.1. 메소드 파라미터의 종류#

  1. HttpServletRequest, HttpServletResponse
  2. HttpSession
  3. WebRequest, NativeWebRequest
  4. Locale
  5. InputStream, Reader
  6. OutputStream, Writer
  7. @PathVariable
  8. @RequestParam
  9. @CookieValue
  10. @RequestHeader
  11. Map, Model, ModelMap
  12. @ModelAttribute
  13. Errors, BindingResult
  14. SessionStatus
  15. @RequestBody
  16. @Value
  17. @Valid

4.2.2. 리턴타입의 종류#

자동 추가 모델 오브젝트와 자동생성 뷰 이름#

  1. @ModelAttribute 모델 오브젝트 또는 커맨드 오브젝트
  2. Map, Model ModelMap 파라미터
  3. @ModelAttribute 메소드
  4. BindingResult

ModelAndView#

  1. 컨트롤러가 리턴해야 하는 정보를 담고 있는 가장 대표적인 타입
  2. 다른 방법도 많아서 실제로 많이 사용하지는 않는다.
  • 2.5나 그 이전버전에서의 처리 형태
public class HelloController implements Controller (
	public ModelAndView handleRequest(HttpServletRequest request,
		HttpServletResponse response) throws Exception (
		String name = request.getParameter("hello");
		return new ModelAndView( "hello.jsp").addObject( "name", name);	
	}
}
  • @Controller 스타일로 수정
@Controller
public class HelloController (
	@RequestMapping("/hello")
	public ModelAndView hello(@RequestParam String name) (
		return new ModelAndView("hello").addObject("name", name);
	}
}
  • Model 파라미터 사용
@Controller
public class HelloController (
	@RequestMapping("/hello")
	public ModelAndView hello(@RequestParam String name, Model model) (
		model.addAttribute("name", name);
		return new ModelAndView("hello");
	}
}

String#

  1. 메소드의 리턴타입이 String이면 뷰이름을 나타낸다.
  2. 모델 정보는 메소드 파라미터로 지정해서 추가해주는 방법을 사용해야 한다.
  3. 가장 깔끔한 방법이라 많이 사용하는 형태이다.
@RequestMapping("/hello")
public String hello(@RequestParam String name, Model model) {
	model.addAttribute("name", name);
	return "hello";
}

void#

  1. 리턴타입이 없는 경우로 RequestToViewNameResolver전략을 통해 뷰이름을 자동으로 생성한다.
  2. URL과 뷰이름이 일관된다면 적극 고려해볼 수 있다.
@RequestMapping( "/hello")
public void hello(@RequestParam String name, Model model) {
	model.addAttribute("name", name);
}
-- 뷰이름 : /WEB-INF/view/hello.jsp

모델 오브젝트#

  1. 뷰이름은 RequestToViewNameResolver전략을 기반으로 자동생성된다.
  2. 리턴한 모델객체는 모델에 추가된다.
@RequestMapping("/view")
public User view(@RequestParam int id) (
	return userService,getUser(id);
}

Map / Model / ModelMap#

  1. 컨트롤러 자체에서 맵을 그대로 리턴하기 보다는 다음처럼 처리하는게 좋다.
@Request Mapping( "/view")
public void view(@RequestParam int id, Model model) {
	model.addAttribute("userMap", userService.getUserMap(id));
}

View#

public class UserController (
	@Autowired MarshallingView userXmlView;
	@RequestMapping("/user/xml") 
	public View userXml(@RequestParam int id) (
		return this.userXmlView;
	}
}

@ResponseBody#

  1. 메소드 레벨에서 뷰여되면 뷰를 통해 결과를 만드는 대신 메시지 컨버터를 통해 바로 HTTP응답의 메시지 본문으로 전환된다.
@RequestMapping("/hello")
@ResponseBody
public String hello() {
	return "<html><body>Hello Spring</body></html>";
}

4.2.3. @SessionAttributes와 SessionStatus (이 내용을 뺄지도 고민이 필요할듯)#

도메인 중심 프로그래밍 모델과 상태 유지를 위한 세션 도입의 필요성#

@SessionAttributes#

  1. 컨트롤러 메소드가 생성하는 모델정보중에서 @SessionAttributes에 지정한 이름과 동일한 것이 있다면 이를 세션에 저장해준다.
  2. @SessionAttributes가 해주는 일은 @ModelAttributes가 지정한 파라미터가 있을 때 이 파라미터에 전달해줄 오브젝트를 세션에서 가져온다.

SessionStatus#

등록폼을 위한 @SessionAttributes 사용#

4.3. 모델 바인딩과 검증#

  • 컨트롤러 메소드에 @ModelAttribute 가 지정된 파라미터를 @Controller 메소드에 추가하면 크게 세가지 작업이 자동으로 진행된다.
    1. 파라미터 타입의 오브젝트를 만든다. 이때 해당 객체에는 디폴트 생성자가 반드시 필요하다.
    2. 준비된 모델 오브젝트의 프로퍼티에 웹 파라미터를 바인딩해준다.
    3. 모델이 값을 검증한다.

4.3.1. PropertyEditor#

  1. 스프링이 기본적으로 제공하는 바인딩용 타입 변환 API는 PropertyEditor 이다.
    • 자바빈 표준에 정의된 인터페이스

디폴트 프로퍼티 에디터#

커스텀 프로퍼티 에디터#

public class LevelPropertyEditor extends PropertyEditorSupport (
	public String getAsText() (
		return String.valueOf(((Level)this.getValue()).intValue());
	}

	public void setAsText(String text) throws IllegalArgumentException (
		this.setValue(Level.valueOf(Integer.parselnt(text.trim())));
	}
}

@InitBinder#

  1. 프로퍼티 에디터를 통해 문자열과 오브젝트 사이의 타입변환을 처리할 수 있다.
  2. @Controller 메소드를 호출해 줄 책임이 있는 AnnotationMethodHandlerAdapter는 @RequestParamol나 @ModelAttribute, @PathVariable 등처럼 HTTP 요청을 파라미터 변수에 바인딩해주는 작업이 필요한 애노태이션을 만나변 먼저 WebDataBinder를 만든다.
  3. WebDataBinder 의 바인딩 적용 대상은 @RequestParam 파라미터, @CookieValue 파라미터, @RequestHeader 파라미터, @PathVariable 파라미터, @ModelAttribute 파라미터의 프로퍼티다.
  • @InitBinder애노테이션을 사용해서 WebDataBinder에 커스텀 프로퍼티 에디터를 적용하는 코드
@InitBinder
public void initBinder(WebDataBinder dataBinder) (
	dataBinder.registerCustomEditor(Level.class , new LevelPropertyEditor());
}

@InitBinder
public void initBinder(WebDataBinder dataBinder) (
	dataBinder.registerCustomEditor(int.class, "age", new MinMaxPropertyEditor(0, 200));
}

WebBinderInitializer#

  1. @InitBinder는 컨트롤러 클래스마다 메서드내 파라미터를 바인딩할때마다 각각 넣어줘야 한다.
  2. 모든 컨트롤러에 공통으로 적용할 바인딩은 WebBinderInitializer를 사용한다.
public class MyWebBindinglnitializer implements WebBindinglnitializer {
	public void initBinder(WebDataBinder binder, WebRequest request) {
		binder.registerCustomEditor(Level.c1ass, new LevelPropertyEditor());
	}
}
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
	<property name="webBindinglnitializer">
		<bean class="springbook ... MyWebBindinglnitializer" />
	</property)
</bean>

프로토타입 빈 프로퍼티 에디터(내용이 많음.. 해야 할지 말아야 할지.. )#

  1. 프로퍼티 에디터는 싱글톤 빈을 등록할 수 없다.
    • 두개의 메소드가 선언되어 있고 각각의 값이 두 메서드를 왔다갔다해서 짧지만 상태를 가지기 때문이다.

4.3.2. Converter와 Formatter#

  1. 스프링 3.0 이후
  2. Converter 인터페이스 : PropertyEditor를 대신할 수 있는 새로운 타입 변환 API
  3. 변환과정에서 메소드를 한번만 호출해서 싱글톤으로 등록할 수 있다.

Converter#

  1. PropertyEditor는 문자열과 오브젝트 사이의 양방향 변환기능을 제공
  2. Converter 는 소스타입에서 타켓타입으로 단방향 변환만 지원
public interface Converter<S , T> {
	T convert(S source);
}
public class LevelToStringConverter implements Converter(Level, String> {
	public String convert(Level level) (
		return String.valueOf(level.intValue());
	}
}
public class StringToLevelConverter implements Converter<String , Level> {
	public Level convert(String text) (
		return Level.valueOf(Integer.parselnt(text));
	}
}

ConversionService#

  1. Converter는 PropertyEditor처럼 개별로 추가하는 방식이 아닌 ConversionService를 통해 WebDataBinder에 설정
    • 대개는 GenericConversionService 를 사용한다.
    • Converter는 GenericConverter와 ConverterFactory를 이용해서 만든다.
  • 첫번째 방법. @InitBinder 를 통한 수동등록
<bean class="org.springframework.context.support.ConversionServiceFactoryBean">
	<property name=" converters ">
		<set>
			<bean class="springbook...LevelToStringConverter" />
			<bean class="springbook...StringToLevelConverter" />
		</set>
	</property>
</bean>
@Controller 
public static class SearchController (
	@Autowired 
	ConversionService conversionService;

	@InitBinder
	public void initBinder(WebDataBinder dataBinder) (
		dataBinder.setConversionService(this.conversionService);
	}
}
  • 두번째 방법. ConfigurableWebBindingInitializer 를 이용한 일괄등록
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
	<property name="webBindinglnitializer" ref="webBindinglnitializer" />
</bean>

<bean class="org.springframework.web.bind.support.ConfigurableWebBindinglnitializer">
	<property name="conversionService" ref="conversionService" />
</bean>

<bean id="conversionService" class="org.springframework.context  support.ConversionServiceFactoryBean ">
	<property name="converters">
	...<! -- 적용할 컨버터 빈 목록 >
	</property>
</bean>

Formatter와 FormattingConversionService#

  1. Formatter는 그 자체로 Converter와 같이 스프링이 기본적으로 지원하는 범용적인 타입 변환 API가 아니다.
    • Formatter를 구현해서 만든 타입변환 오브젝트를 GenericconversionService등에 직접 등록할 수가 없다.
    • FormattingConversionService를 통해서만 적용가능
  2. 다국어 서비스를 위한 로케일 정보를 설정할 수 있다.
  3. 직접 만든 포매터를 적용하려면 FormattingConversionServiceFactoryBean 을 상속해서 Formatter의 초기화 기능을 담당하는 installFormatters()메소드를 오버라이드하는 방법을 사용
    • 등록방법이 쉽지 않아서 일단은 기본으로 제공하는 애노테이션 기반의 포맷터만 사용하는게 좋을것 같다.
  • @NumberFormat
class Product {
	@NumberFormat("$###,##0.00")
	BigDecimal price;
}
<bean class="org.springframework.web.bind.support.ConfigurableWebBindinglnitializer">
	<property name="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
</bean>
  • @DateTimeFormat (Joda Time 을 이용한 애노테이션 기반의 포맷터)
@DateTimeFormat(style="F-")
Calendar birthday;
@DateTimeFormat(pattern="yyyy/MM/dd")
Date orderDate;

바인딩 기술의 적용 우선순위와 활용 전략#

  1. 사용자 정의 타입의 바인딩을 위한 일괄적용 : Converter
  2. 필드와 메소드 파라미터, 애노테이션 등의 메타정보를 활용하는 조건부 변환 기능 : ConditionalGenericConverter
  3. 애노테이션 정보를 활용한 HTTP요청과 모델 필드 바인딩 : AnnotationFormatterFactory와 Formatter
  4. 특정 필드에만 적용되는 변환기능 : PropertyEditor

4.3.3. WebDataBinder 설정 항목#

allowedFields, disallowedFields#

  1. 도메인 오브젝트 방식을 사용하는 경우 @ModelAttribute로 HTTP요청을 전달받을때는 보안문제를 신경써야 한다.
    • 폼 서브릿을 할때 URL을 추측해서 파라미터를 변경할 수 있다.
  2. WebDataBinder안에 바인딩을 허용할 필드 목록을 넣을 수 있다.
    • allowedFields : 바인딩할 목록을 정해주고 나머지는 다 막는다.
    • disallowedFields : 기본적으로는 다 허용하지만 설정한 필드만 막아준다.
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
	dataBinder.setAllowedFields("name", "email", "*tel*");

requiredFields#

  1. 필수파라미터 체크
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
	dataBinder.setRequiredFields("name", "email");

fieldMarkerPrefix#

  1. 폼의 체크박스의 경우 폼에 필드를 넣었음에도 HTTP파라미터가 제공되지 않는 경우가 발생한다.
  2. 체크하지 않았을 경우 URL에 값이 설정되지 않아 결과적으로 @ModelAttribute 에 반영되지 않는 현상을 해결하기 위해 사용함
  3. setFieldMarkerPrefix() 메소드를 사용한다.

fieldDefaultPrefix#

  1. WebDataBinder의 프로퍼티인 fieldDefaultPrefix의 디폴트값은 느낌표(!)이다.
  2. fieldDefaultPrefix를 변경할때는 setFieldDefaultPrefix() 메소드를 사용한다.

4.3.4.Validator와 BindingResult, Errors#

  • @ModelAttribute로 지정된 모델 오브젝트의 바인딩 작업이 실패하는 경우
    1. 타입변환이 불가능한 경우
    2. 검증기(Validator)를 이용한 검사를 통과하지 못하는 경우

Validator#

  • 스프링에서 범용적으로 사용할 수 있는 오브젝트 검증기를 정의하는 API
    • supports() 메소드는 검증할 수 있는 오브젝트 타입인지 체크
    • validate() 메소드를 통해 검증한다. 검증오류는 Errors인터페이스를 통해 내용을 등록하면 BindingResult에 담겨서 컨트롤러에 전달된다.
package org.springframework.validation;
public interface Validator {
	boolean supports(Class<?> clazz);
	void validate(Object target, Errors errors);
}
  • 컨트롤러 메소드 내의 코드
@Controller
public class UserController (
	@Autowired UserValidator validator;

	@RequestMapping("/add")
	public void add(@ModelAttribute User user, BindingResult result) (
		this.validator.validate(user, result);
		if (result.hasErrors()) {
			// 오류가 발견된 경우의 작업
		} else (
			// 오류가 없을 때의 작업
		}
	}
	...
  • @Valid 를 이용한 자동검증
    • JSR-303의 @javax.validation.Valid 애노테이션을 사용
@Controller
public class UserController (
	@Autowired UserValidator validator;

	@InitBinder
	public void initBinder(WebDataBinder dataBinder) (
		dataBinder.setValidator(this.validator);
	}

	@RequestMapping("/add ")
	public void add(@ModelAttribute @Valid User user , BindingResult result) (
	}
	...
  • 서비스 계층 오브젝트에서의 검증

JSR-303 빈 검증 기능#

  • 최근 표준스펙으로 인증받은 JSR-303 빈 검증 방식도 스프링에서 사용가능
  • 스프링에서는 LocalValidatorFactoryBean 을 이용
  • 애노테이션을 이용해서 검증
  • 별도로 검증용 애노테이션을 만드는 것도 가능(ConstraintValidator 를 구현한 클래스 생성)
// <bean id="localValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> 를 등록해줘야 한다. 
public class User 
	...
	@NotNull
	String name;

	@Min(0)
	lnt age;
	...

BindingResult와 MessageCodeResolver#

  • BindingResult객체에 담긴 오류정보에 대한 코드를 사용해서 다국어를 지원해서 메시지를 노출할 수 있다.

4.3.5. 모델의 일생#

  • 모델은 MVC아키텍처에서 정보를 담당하는 컴포넌트

HTTP 요청으로부터 컨트롤러 메소드까지#

  • (그림) HTTP 요청으로붙터 컨트롤러 메소드까지의 과정
    • @ModelAttribute 메소드 파라미터
    • @SessionAttribute세션 저장 대상 모델 이름
    • WebDataBinder 에 등록된 프로퍼티 에디터, 컨버전 서비스
    • WebDataBinder에 등록된 검증기
    • ModelAndView의 모델맵
    • 컨트롤러 메소드와 BindingResult파라미터

  • 컨트롤러 메소드로부터 뷰까지
    • ModelAndView의 모델 맵
    • WebDataBinder에 기본적으로 등록된 MessageCodeResolver
    • 빈으로 등록된 MessageSource와 LocaleResolver
    • @SessionAttribute세션 저장 대상 모델 이름
    • 뷰의 EL과 스프링 태그 또는 매크로

4.4. JSP뷰와 form태그#

  • 폼을 처리할 경우 단순히 모델 정보를 출력하는것에 더해 입력된 값이나 바인딩과 검증 작업의 오류 메시지도 함께 출력해줘야 한다.
  • 폼에 값을 입력하는 과정에서 오류가 나면 에러메시지도 표시하고 에러가 발생한 값도 다시 보여줘야 한다.
  • 경우에 따라 에러가 발생한 영역의 UI도 달리 보여줘야 한다.

4.4.1. EL과 spring태그 라이브러리를 이용한 모델 출력#

JSP EL#

// model. addAttribute( "name" "Spring");
(div)이름 : ${name}(/div)
// User객체의 프로퍼티를 가져온다면.
${user. age}

스프링 SpEL#

  • JSP EL보다 유연하고 강력한 표현식을 지원
<%@ taglib prefix="spri ng" uri="http://www.springframework.org/tags" %>

<spring:eval expression="user.name" />
<spring:eval expression="user.toStringO" />
<spring:eval express ion="new java.text.DecimalFormat( "###,##0.00").format(user.point)" />

지역화 메시지 출력#

  • message.properties 파일을 만들어두고 지역정보에 따라 메시지를 가져와서 출력
    • 지역정보가 KOREAN이면 messages_ko.properties 파일에서 메시지를 검색
// 단순메시지 출력
(spring:message code="greeting" />

// 파마미터 치환이 필요하다면
greeting=Hello {0}!
(spring:message code="greeting" arguments=" ${user .name}" text=" Hi"/>

4.4.2. spring 태그 라이브러리를 이용한 폼 작성#

단일 폼 모델#

<spring:bind> 와 BindingStatus#

<input type="text" id="name" name="name" value="${user.name} " />
  • jsp EL를 사용할 경우
    • 바인딩 오류가 있을때 에러 메시지를 출력하는 기능이 없다.
    • 바인딩 작업중 타입변환에서 오류가 날 경우 잘못 입력한 값을 출력할 수 없다.
// user.name 프로퍼티에 대한 정보를 담은 BindStatus타입의 status변수를 사용할 수 있다. 
public class User (
	...
	@Size(min=2, max=10)
	String name;


<spring:bind path="user.name">
	<label for="name">Name</label>
	<input type="text" id="${status.expression}" name="${status.expression}" value="${status.value}" />
	<span class="errorMessage">
		<c :forEach var="errorMessage" items="${status.errorMessages}">
			${errorMessage}
		</c: forEach>
	</span>
</spring:bind>

4.4.3. form 태그 라이브러리#

<form:label path="name" cssErrorClass="errorMessage">Name</form:label>
<form:input path="name" size="30"/>
<form:errors path="name" cssClass="errorMessage "/>
  • HTML 태그를 직접 사용할때는 <spring:bind> 를 사용하면 된다.
  • 스프링 form태그를 사용하면 HTML 태그를 사용할수는 없으나 form태그 자체가 HTML태그의 각각의 속성을 대부분 그대로 제공한다.
〈%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<form:form>#

  • commandName, modelAttribute : 폼에 적용할 모델의 이름, 디폴트 값은 command
  • method : <form> 태그의 method속성으로 디폴트는 post이다.
  • action : <form> 태그의 action, 대개의 경우 생략한다. 대개 같은 URL을 사용해서 폼 화면을 띄울때는 get, 폼 화면을 전송할때는 post로 해서 같은 URL을 사용하는 방법을 이용한다.
<form:form commandName="user"> ... </form:form>
// URL이 /usr/add 라면.
<form id="user" action="/user/add" method="post">

<form:input>#

  • <input type="text"> 태그를 만든다. 필수 속성은 path뿐이다.
  • path : id를 지정하지 않으면 <input>태그의 id, name에 할당된다. value속성에 지정할 모델의 프로퍼티 이름으로 사용
  • cssClass, cssErrorClass
<form:input path="name" />
// 모델 오브젝트의 name프로퍼티 값이 Spring이라면
<input type="text" id="name" name="name" value="Spring" />

<form:label>#

  • 폼의 레이블 출력에 사용되는 <label> 태그를 만든다. 필수 속성은 path뿐이다.
<form:label path="name" cssClass="label" cssErrorClass="errors">Name
</form : label>
// 바인딩 오류가 없다면.
<label for="name" class="label">Name</label>
// 바인딩 오류가 있다면.
<label for="name" class="errors">Name</label>

<form:errors>#

  • path : 지정한 프로퍼티의 에러만 출력한다. 생략하면 모드 에러메시지를 출력한다.
  • delimiter : 여러개의 에러메시지가 있을때 각각의 에러메시지를 구분하는 구분자
  • cssClass

<form:hidden>#

  • <input type="hidden"> 태그를 작성한다.
<form:hidden path="loginCount" />
// loginCount 프로퍼티의 값이 10이라면 
<input id="loginCount" name="loginCount" type="hidden" value="10" />

<form:password>, <form:textarea>#

  • 각각 <input type="text">와 <textarea> 태그를 생성한다.
  • 사용법은 <form:input>과 동일하다.

<form:checkbox>, <form:checkboxes>#

  • <input type="checkbox"> 태그를 만들어준다.
<form:checkbox path="registered" label="Registered" />
// 모델의 registered값이 true이면
<input id="registeredl " name="registered" type="checkbox" value="true" checked="checked" />
<label for="registeredl">Registered</label>
<input type="hidden" name="_registered" value="on" /><br/>
@ModelAttribute("interests")
public Map<String, String> interests() {
	Map<String, String> interests = new HashMap<String, String>();
	interests.put("A", "Java");
	interests.put("B", "C#");
	interests.put("C", "Ruby");
	interests.put("D", "Python");
	return lnterests;

<form:checkboxes path="interests" items="${interests)" />

<form:radiobutton>, <form:radiobuttons>#

  • <input type="radio"> 태그를 생성한다.

<form:select>, <form:option>, <form:options>#

  • <select>와 <option> 를 생성해준다.
<form:select path="type">
	<form:option value="l ">관리자</form:option>
	<form:option value="2">회원</form:option>
	<form:option value="3">손님</form:option>
</form:select>

<form:select path="type">
	<form:options items="${types}" itemlabel="name" itemValue="id" />
</form:select>

커스텀 UI태그 만들기#

4.5. 메시지 컨버터와 AJAX#

  • 메시지 컨버터는 XML이나 JSON을 이용한 AJAX기능이나 웹 서비스를 개발할때 사용할 수 있다.
  • HTTP요청 프로퍼티를 모델 오브젝트에 바인딩하는 대신 HTTP요청 메시지 본문과 HTTP응답 메시지 본문을 통째로 메시지로 다루는 방식
  • @RequestBody, @ResponseBody 애노테이션을 사용
  • 메시지 방식의 컨트롤러를 사용하는 두가지 방법
    • GET : 요청정보가 URL과 쿼리 스트링을 제한되므로 @RequestBody를 사용하는 대신 @RequestParam이나 @ModelAttribute로 요청 파라미터를 전달
    • POST : HTTP 요청 본문이 제공되므로 @RequestBody를 사용

4.5.1. 메시지 컨버터의 종류#

  • 메시지 컨버터는 AnnotationMethodHandlerAdapter를 통해 등록
    • ByteArrayHttpMessageConverter : 지원하는 오브젝트 타입은 byte 이다.
    • StringHttpMessageConverter : 지원하는 오브젝트 타입은 String이다.
    • FormHttpMessageConverter : 미디어 타입이 application/x-www-form-urlencoded로 정의된 폼 데이터를 주고 받을수 있다.
    • SourceHttpMessageConverter : 미디어 타입은 application/xml, application/*+xml, text/xml 세 가지를 지원한다. 오브젝트 타입은 javax.xml.transform.Source 타입인 DOMSource, SAXSource, StreamSource 세 가지를 지원한다.
    • Jaxb2RootElementHttpMessageConverter : JAXB2 의 @XmlRoot El ement와 @Xm lType 이 붙은 클래스를 이용해서 XML과 오브젝트 사이의 메시지 변환을 지원한다. 기본적으로 SourcehttpMessageConverter와 통일한 XML 미디어 타입을지원한다. 오브젝트는두가지 애노테이션중하나가 적용됐다면 어떤 타입이든사용할수 있다.
    • MarshallingHttpMessageConverter : 스프링 OXM 추상화의 Marshaller와 Unmarshaller를 이용해서 XML 문서와 자바 오브젝트 사이의 변환을 지원해주는 컨버터다.
    • MappingJacksonHttpMessageConverter : Jackson ObjectMapper를 이용해서 자바오브젝트와 JSON 문서를 지동변환해주는 메시지 컨버터다. 지원 미디어 타입은 application/json 이다. 자바오브젝트 타입에 제한은 없지만 프로퍼티를 가진 자바빈 스타일이거나 HashMap을 이용해야 정확한 변환결과를 얻을수 있다.
<bean class="org.springframewor k.web.serv let.mvc.annotation.AnnotationMethodHandlerAdapter">
	<property name="messageConverters">
		<list>
			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
			<bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" />
		</list>
	</property>
</bean>
  • JSON기반의 AJAX를 지원하려면 컨트롤러는 결과를 JSON포맷으로 만들어서 리턴하는 방법
    • JSON지원뷰를 사용 : 모델을 JSON으로 변환하는 MappingJacksonJsonView를 사용하거나 다른 포맷과 함께 사용한다면 ContentNegotiatingViewResolver를 사용한다.
    • @ResponseBody 를 사용하는 방법
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
	<property name=깨essageConverters“〉
		<list>
			<bean class="org .springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
		</list>
	</property>
</bean>

public class Result {
	boolean duplicated:
	String availableld:
	// 수정자/접근자 생략
}

// {"duplicated" : true , "availableld" : ’‘ ceoahn930"}
@RequestMapping(value="checkloginid/{loginld }", method=RequestMethod.GET)
@ResponseBody
public Result checklogin(@PathVariable String loginld) {

4.6. MVC 네임스페이스#

  • @MVC관련 기능인 바인딩기능이나 JSR-303의 빈 검증기능, 메시지 컨버터등을 제대로 활용하려면 AnnotationMethodHandlerAdapter를 빈으로 등록하고 여러가지 프로퍼티 설정을 해줄 필요가 있다.
  • 최신 @MVC기능을 손쉽게 등록할수 있게 해주는 mvc스키마의 전용 태그를 제공한다.

<mvc:annotation-driven> 이 자동으로 등록하는 빈정보#

  • DefaultAnnotationHandlerMapping : @RequestMapping을 이용한 핸들러 매핑 전략을 등록
  • AnnotationMethodHandlerAdapter : DispatcherServlet 을 자동으로 등록, <mvc:annotation-driven>은 기본적으로 이 AnnotationMethodHandlerAdapter 를 빈으로 등록한다. 따라서 <mvc:annotation-driven>을 사용했을 때는 직접 AnnotationMethodHandlerAdapter 빈을 등록해서는 안 된다.
  • ConfigurableWebBindinglnitializer : 모든 컨트롤러 메소드에 자동으로 적용되는 WebDataBinder 초기화용 빈을 등록하고 AnnotationMethodHandlerAdapter의 프로퍼티로 연결해준다. 기본적으로 컨버전 서비스는 @NumberFormat과 같은 애노테이션 방식의 포맷터를 지원하는 FormattingConversionServiceFactoryBean을 등록한다. 글로벌 검증기는 LocalValidatorFactoryBean으로 설정된다.
  • 메시지 컨버터 : AnnotationMethodHandlerAdapter 의 messageConverters 프로퍼티로 메시지 컨버터들이 등록된다. 네 개의 디 폴트 메시지 컨버터와 함께 Jaxb2RootElementHttpMessageConverter와 MappingJacksonHttpMessageConverter가 추가로 등록된다
  • <spring:eval>을 위한 컨버전 서비스 노출용 인터셉터

<mvc:annotation-driven> 에서 커스텀 검증기, 컨버전 서비스등을 적용할때 사용할 속성#

  • validator : 대개는 디폴트로 추가되는 JSR-303 방식의 LocalValidatorFactoryBean이면 충분하다
<mvc:annotation-driven validator="myValidator" />
<bean id=“myValidator" class="MyLocalValidatorFactoryBean">
	// propery 설정
</bean>
  • converslon-servlce : 대개는 디폴트로 등록되는 FormattingConversionServiceFactoryBean으로 충분
<mvc:annotation-driven conversion-service="myConversionService" />
<bean id="myConversionService" class="FormattingConversionServiceFactoryBean">
	<property name="converters">
	...
	</property>
</bean>

<mvc:interceptors>#

  • HandlerInterceptor 적용방법
    • 핸들러 매핑 빈의 interceptors프로퍼티를 이용해서 등록 : 핸들러 매핑 빈을 명시적으로 빈으로 선언해줘야 하고 핸들러 매핑이 여러개라면 인터셉터를 핸들러 매핑마다 인터셉터를 반복으로 설정해줘야 하는 단점이 있다.
    • <mvc:interceptors> 를 이용해서 등록 : 모든 핸들러 매핑에 일괄적용되는 인터셉터를 한번에 설정할 수 있다. URL패턴을 통해 지정할 수 있다.
<mvc:interceptors>
	<mvc:interceptor>
		<mvc:mapping path=“ /admin/*"/>
		<bean class=“...Adminlnterceptor" />
	</mvc:interceptor>
</mvc:interceptors>

<mvc:view-controller>#

  • 컨트롤러의 로직이 없고 모델도 없이 뷰만 존재할 경우. 즉 요청이 들어왔을때 바로 대상 jsp로 이동싴킬때 사용한다.
<mvc:view-controller path="/“ view-name="/index" />

4.7. @MVC 확장포인트#

  • 애노테이션 방식의 컨트롤러를 만들때 적용되는 기능을 확장하는 방법

4.7.1. AnnotationMethodHandlerAdapter#

SessionAttributeStore#

WebArgumentResolver#

  • 3.1에서는 HandlerMethodArgumentResolver로 변경
<mvc:annotation-driven conversion-service="conversionService">
	<mvc:argument-resolvers>
		<bean class="com.nhncorp.lucy.spring.core.web.helper.PagerInfoArgumentResolver"/>
		<bean class="com.nhncorp.lucy.spring.mvc.attribute.RequestAttributeArgumentResolver"/>
	</mvc:argument-resolvers>
</mvc:annotation-driven>

public class SnsUserArgumentResolver implements WebArgumentResolver {
	private static final Logger LOGGER = LoggerFactory.getLogger(SnsUserArgumentResolver.class);

	public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
		HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
		Class<?> parameterType = methodParameter.getParameterType();
		if (parameterType.equals(SnsUser.class)) {
			return new SnsUser();
		}
		return UNRESOLVED;
	}
Spring 3.0, 3.1, 3.2에서 WebArgumentResolver 관련 동작의 차이점 : http://devcafe.nhncorp.com/Lucy/forum/1714622

ModelAndViewResolver#

4.8. URL과 리소스 관리#

  • 여기서 설명하는 내용은 스프링 3.0.4 또는 그 이후 버전에서만 지원된다.

4.8.1. <mvc:default-servlet-handler />를 이용한 URL관리 #

<mvc:default-servlet-handler />#

  • 웹애플리케이션에서의 리소스(정적, 동적)는 URL을 기준으로 한다.
  • 예전에는 확장자를 통한 리소스 타입을 구분했으나 Restful 스타일을 사용하면서 구분이 없어졌다.
  • 즉 정적 리소스와 동적 리소스를 구분하는 방법이 없어진 상황이다.
  • <mvc:default-servlet-handler /> 를 선언하면 @RequestMapping에 선언된 URL이 있으면 DispatcherServlet이 처리하고 @RequestMapping 에서 찾지 못하면 디폴트 서블릿이 처리하도록 한다.
<servlet-mapping>
	<servlet-name>spring</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

4.8.2. <url:resource/>를 이용한 리소스 관리 #

  • 리소스 위치를 정할때는 classpath:, file:, http: 등을 지정할 수 없다. 이런 접두어가 없다면 루트 컨텍스트가 기준이 된다.
<mvc:resources mapping="/ui/**" location="classpath:/META-INF/webresources/" />
Spring MVC에서 <mvc:resources> 선언을 활용할 경우 디렉토리 순회 취약점 : http://devcafe.nhncorp.com/Lucy/forum/2053406

4.9. 스프링 3.1의 @MVC#

4.9.1. 새로운 RequestMapping전략#

DispatcherServlet전략스프링3.0스프링3.1
HandlerMappingDefaultAnnotationHandlerMappingRequestMappingHandlerMapping
HandlerAdapterAnnotationMethodHandlerAdapterRequestMappingHandlerAdapter
HandlerExceptionResolverAnnotationMethodHandlerExceptionResolverExceptionHandlerExceptionResolver
  • @RequestMapping메소드와 핸들러 매핑 전략의 불일치
    1. 전통적인 핸들러 오브젝트 구조를 요청마다 독립적인 오브젝트 하나씩 대응하는 구조로 되어 있다
    2. 최근에는 웹 애플리케이션 요구사항이 커지면서 엡 요청에 개수가 급격히 증가했다.
    3. 요청정보를 메소드레벨로 처리하고 파라미터나 리턴 데이터를 자유롭게 해주는 @RequestMapping은 결국에 리플렉션과 같은 API를 활용해서 처리가 가능하다.
    4. 스프링 3.0의 DefaultAnnotatonHandlerMapping 매핑결과는 기본적으로 대상 클래스를 찾고 실행할 메소드를 찾는 작업을 추가로 처리했다.
      • 매핑 대상이 클래스여서 메소드에 대한 세부적인 메타정보를 가져오지 못하는 문제가 있다.
    5. 스프링 3.1에서는 매핑대상을 클래스가 아닌 메소드로 처리해서 개선했다.

4.9.2. @RequestMapping 핸들러 매핑 : RequestMappingHandlerMapping#

  • 요청조건의 결합방식
    • URL패턴 : PatternsRequestCondition
    • HTTP요청방법 : RequestMethodsRequestCondition
    • 파라미터 : ParamsRequestCondition
    • 헤더 : HeadersRequestCondition
    • Content-Type헤더 : ConsumesRequestCondition
    • Accept헤더 : ProducesRequestCondition

4.9.3. @RequestMapping 핸들러 어댑터#

  • RequestMappingHandlerAdapter에는 스프링 3.0에서 지원하던 메소드 파라미터와 리턴 타입에 몇가지 타입과 애노테이션이 추가됐다.

파라미터 타입#

  • @Validated/@Valid
    • @Valid애노테이션은 JSR-303 빈 검증기 애노테이션이고 JSR-303은 검증그룹을 지정할 수 있다.
    • @Validated애노테이션은 검증그룹을 지정할때 사용한다.
// 검증그룹을 나타내는 인터페이스를 정의
public interface User {}
public interface Admin {}

// JSR-303 빈 검증용 애노테이션에서 그룹을 지정한다. 
@NotEmpty(groups={User.class, Admin.class})
String name;
@NotEmpty(groups=User.class)
String userId;
@NotEmpty(groups=Admin.class)
String adminId;

// @RequestMapping(...)
public String saveUser(@ModelAttribute("user") @Validated(User.class) User user) {

}
  • @Valid와 @RequestBody
    • 스프링 3.1의 @Valid는 메시지 컨버터를 이용하는 @RequestBody파라미터에도 사용할 수 있다.
  • UriComponentsBuilder
    • URI/URL 정보를 추상화한 빌더클래스로 문자열 하나에 여러 요소가 조합된 URI를 안전하고 편리하게 다룰수 있도록 해준다.
  • RedirectAttributes와 리다이렉트 뷰
    • 핸들러 메소드에서 리다이렉트를 하려면 redirect: 접두어가 붙은 뷰이름을 리턴하거나 RedirectView뷰를 사용한다.
    • 리다이렉트뷰를 사용할 경우 모델에 값을 넣어주면 자동으로 리다이렉트 URL에 파라미터로 추가된다.

redirect:와 RedirectView의 차이 : http://devcafe.nhncorp.com/Lucy/forum/881591

  • RedirectAttributes와 플래시 애트리뷰트

4.9.4. @EnableWebMvc와 WebMvcConfigurationSupport를 이용한 @MVC 설정#

  • 스프링 3.1의 최신 @MVC 전략을 사용하려면 XML설정에 <mvc:annotation-driven />을 넣는다.
  • 확장하기 위해서는 @EnableWebMvc와 WebMvcConfigurer 를 사용해서 확장한다.

정리#

  • Lucy 1.7.8 릴리즈 공지 : http://devcafe.nhncorp.com/Lucy/forum/2063873

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-19) was last changed on 16-Mar-2015 21:41 by DongGukLee