Study Web Development

3월 2일

이전으로

Spring

4. 스프링 MVC

4-1 스프링 MVC 설정

  1. 새 패키지 kr.spring.ch05.vo 생성 후 새 클래스 SearchVO 생성
package kr.spring.ch05.vo;

public class SearchVO {
	private String type;
	private String query;
	
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getQuery() {
		return query;
	}
	public void setQuery(String query) {
		this.query = query;
	}
	
	@Override
	public String toString() {
		return "SearchVO [type=" + type + ", query=" + query + "]";
	}
}
  1. 새 패키지 kr.spring.ch05.service 생성 후 새 클래스 SearchService 생성
package kr.spring.ch05.service;

import kr.spring.ch05.vo.SearchVO;

public class SearchService {
	public String search(SearchVO vo) {
		System.out.println(vo);
		return "검색 완료!";
	}
}
  1. 새 패키지 kr.spring.ch05.controller 생성 후 새 클래스 GameSearchController 생성
package kr.spring.ch05.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import kr.spring.ch05.service.SearchService;
import kr.spring.ch05.vo.SearchVO;

@Controller
public class GameSearchController {
	@Autowired
	private SearchService searchService; // @Autowired 어노테이션에 의해 setter 메서드는 자동으로 생성됨
	
	// 폼 호출
	@RequestMapping("/search/main.do")
	public String main() {
		return "search/main";
	}
	
	// 폼에서 전송된 데이터를 처리
	@RequestMapping("/search/game.do")
	public ModelAndView search(@ModelAttribute("vo") SearchVO vo) { // 인자로 받은 SearchVO는 @ModelAttribute 어노테이션에 의해 request에 저장됨
		// Service 호출
		String result = searchService.search(vo);
		
		ModelAndView mav = new ModelAndView();
		mav.setViewName("search/game");
		mav.addObject("searchResult", result);
		
		return mav;
	}
}
  1. search 폴더에 새 JSP 파일 main.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게임 검색 메인</title>
</head>
<body>
<form action="game.do" method="get">
	<select name="type">
		<option value="1">전체</option>
		<option value="2">아이템</option>
		<option value="3">캐릭터</option>
	</select>
	<input type="text" name="query">
	<input type="submit" value="검색">
</form>
</body>
</html>
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 폼에서 전송받은 데이터 처리 -->
	<beans:bean class="kr.spring.ch05.controller.GameSearchController"/> <!-- 요청 URL에 의해 매핑되므로(@RequestMapping), bean 식별자 불필요 -->
	<beans:bean class="kr.spring.ch05.service.SearchService"/> <!-- 클래스명(=자료형)에 의해 매핑되므로(@Autowired), bean 식별자 불필요 -->
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/search/main.do">GameSearchController</a><br>
  1. search 폴더에 새 JSP 파일 game.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게임 검색 결과</title>
</head>
<body>
검색 결과 : ${searchResult}<br>
전송된 데이터 : ${vo.type}, ${vo.query}
</body>
</html>

4-4 메시지 처리

순위 메시지 코드
1 에러 코드.커맨드 객체명.필드명 required.loginVO.userId
2 에러 코드.필드명 required.userId
3 에러 코드.필드 타입 required.java.lang.String
4 에러 코드 required
순위 메시지 코드
1 에러 코드.커맨드 객체명 required.loginVO
2 에러 코드 required
  1. src/main/resources 오른쪽 클릭하고 새 폴더 messages 생성 후 새 파일 validation.properties 생성
# 에러 코드=에러 메시지
required=필수 항목
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 리소스 번들 지정 -->
	<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<beans:property name="basenames">
			<beans:list> <!-- properties 파일을 여러 개 등록 가능 -->
				<beans:value>messages.validation</beans:value> <!-- 리스트의 값들은 전부 properties 파일로 인식하므로 확장자는 생략하며, src/main/resources 하위의 폴더 경로는 포함 -->
			</beans:list>
		</beans:property>
	</beans:bean>

4-3 Validator 인터페이스를 이용한 유효성 검증

  1. 새 패키지 kr.spring.ch06.vo 생성 후 새 클래스 MemberVO 생성
package kr.spring.ch06.vo;

public class MemberVO {
	private String id;
	private String name;
	private String zipcode;
	private String address1;
	private String address2;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getZipcode() {
		return zipcode;
	}
	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}
	public String getAddress1() {
		return address1;
	}
	public void setAddress1(String address1) {
		this.address1 = address1;
	}
	public String getAddress2() {
		return address2;
	}
	public void setAddress2(String address2) {
		this.address2 = address2;
	}
	
	@Override
	public String toString() {
		return "MemberVO [id=" + id + ", name=" + name + ", zipcode=" + zipcode + ", address1=" + address1
				+ ", address2=" + address2 + "]";
	}
}
  1. 새 패키지 kr.spring.ch06.validator 생성 후 새 클래스 MemberValidator 생성
package kr.spring.ch06.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import kr.spring.ch06.vo.MemberVO;

public class MemberValidator implements Validator {
	// Validator가 검증할 수 있는 타입인지(=자바빈 구조인지)를 검사
	@Override
	public boolean supports(Class<?> clazz) {
		return MemberVO.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) { // Object는 자바빈의 부모 클래스, Errors는 BindingResult의 부모 인터페이스
		MemberVO memberVO = (MemberVO)target;
		if(memberVO.getId()==null || memberVO.getId().trim().isEmpty()) {
			errors.rejectValue("id", "required"); // 에러가 발생한 자바빈의 프로퍼티명과 에러 코드를 인자로 전달
		}
		if(memberVO.getName()==null || memberVO.getName().trim().isEmpty()) {
			errors.rejectValue("name", "required");
		}
		if(memberVO.getZipcode()==null || memberVO.getZipcode().trim().isEmpty()) {
			errors.rejectValue("zipcode", "required");
		}
		if(memberVO.getAddress1()==null || memberVO.getAddress1().trim().isEmpty()) {
			errors.rejectValue("address1", "required");
		}
		if(memberVO.getAddress2()==null || memberVO.getAddress2().trim().isEmpty()) {
			errors.rejectValue("address2", "required");
		}
	}
}
  1. 새 패키지 kr.spring.ch06.controller 생성 후 새 클래스 CreateAccountController 생성
package kr.spring.ch06.controller;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import kr.spring.ch06.validator.MemberValidator;
import kr.spring.ch06.vo.MemberVO;

@Controller
public class CreateAccountController {
	// 자바빈 초기화; @ModelAttribute 어노테이션을 사용하면 해당 메서드에 의해 생성된 자바빈 객체를 request 영역에 저장
	@ModelAttribute("vo")
	public MemberVO initCommand() {
		return new MemberVO();
	}

	// 폼 호출
	@GetMapping("/account/create.do")
	public String form() {
		return "account/creationForm";
	}

	// 폼에서 전송된 데이터 처리
	@PostMapping("/account/create.do")
	public String submit(@ModelAttribute("vo") MemberVO memberVO, BindingResult result) { // @ModelAttribute에서 지정한 속성명이 account/created.jsp에 적용됨
		System.out.println("전송된 데이터 : " + memberVO);
		
		// 전송된 데이터 유효성 검증
		new MemberValidator().validate(memberVO, result);
		// BindingResult에 유효성 검증 결과 에러에 대한 내용이 저장되어 있으면 폼을 다시 호출
		if(result.hasErrors()) { // hasErrors() 메서드는 하나라도 에러 정보가 있으면 true
			return "account/creationForm";
		}
		
		return "account/created";
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 유효성 체크 -->
	<beans:bean class="kr.spring.ch06.controller.CreateAccountController"/>

4-2 주요 <form> 관련 커스텀 태그

  1. views 폴더에 하위 폴더로 account 생성하고 새 JSP 파일 creationForm.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>계정 생성</title>
</head>
<body>
<form:form method="post" modelAttribute="vo">
	<ul>
		<li>
			<form:label path="id">아이디</form:label>
			<form:input path="id"/>
			<form:errors path="id"/>
		</li>
		<li>
			<form:label path="name">이름</form:label>
			<form:input path="name"/>
			<form:errors path="name"/>
		</li>
		<li>
			<form:label path="zipcode">우편번호</form:label>
			<form:input path="zipcode"/>
			<form:errors path="zipcode"/>
		</li>
		<li>
			<form:label path="address1">주소</form:label>
			<form:input path="address1"/>
			<form:errors path="address1"/>
		</li>
		<li>
			<form:label path="address2">나머지 주소</form:label>
			<form:input path="address2"/>
			<form:errors path="address2"/>
		</li>
	</ul>
	<div>
		<form:button>전송</form:button>
	</div>
</form:form>
</body>
</html>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/account/create.do">CreateAccountController</a><br>
  1. account 폴더에 새 JSP 파일 created.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>계정 생성</title>
</head>
<body>
계정이 생성되었습니다.
<ul>
	<li>아이디 : ${vo.id}</li>
	<li>이름 : ${vo.name}</li>
	<li>우편번호 : ${vo.zipcode}</li>
	<li>주소 : ${vo.address1}</li>
	<li>나머지 주소 : ${vo.address2}</li>
</ul>
</body>
</html>

4-5 파일 업로드 처리

  1. pom.xml에서 <dependencies> 태그 사이에 다음의 내용을 추가
		<!-- 라이브러리 추가 시작 -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!-- 라이브러리 추가 끝 -->
  1. src/main/webapp 오른쪽 클릭하고 하위 폴더로 upload 생성
  2. src/main/resources 오른쪽 클릭하고 하위 폴더로 config 생성 후 새 파일 file.properties 생성
# 경로 구분자는 \\ 또는 /를 사용
file_path=C:/javaWork/workspace_spring/ch04MVC/src/main/webapp/upload
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- multipartResolver 설정 -->
	<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="maxUploadSize" value="52428800"/> <!-- 50MB -->
		<beans:property name="defaultEncoding" value="UTF-8"/>
	</beans:bean>
	
	<!-- 업로드 폴더 등록 -->
	<context:property-placeholder location="classpath:config/file.properties"/> <!-- classpath가 src/main/resources를 가리킴 -->
	
	<!-- 파일 업로드 처리 -->
	<beans:bean class="kr.spring.ch07.controller.SubmitReportController"/>
  1. 새 패키지 kr.spring.ch07.vo 생성하고 새 클래스 SubmitReportVO 생성
package kr.spring.ch07.vo;

import org.springframework.web.multipart.MultipartFile;

public class SubmitReportVO {
	private String subject;
	private MultipartFile reportFile;
	
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public MultipartFile getReportFile() {
		return reportFile;
	}
	public void setReportFile(MultipartFile reportFile) {
		this.reportFile = reportFile;
	}
}
  1. 새 패키지 kr.spring.ch07.validator 생성하고 새 클래스 SubmitReportValidator 생성
package kr.spring.ch07.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import kr.spring.ch07.vo.SubmitReportVO;

public class SubmitReportValidator implements Validator {
	@Override
	public boolean supports(Class<?> clazz) {
		return SubmitReportVO.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		SubmitReportVO vo = (SubmitReportVO)target;
		if(vo.getSubject()==null || vo.getSubject().trim().isEmpty()) {
			errors.rejectValue("subject", "required");
		}
		if(vo.getReportFile()==null || vo.getReportFile().isEmpty()) {
			errors.rejectValue("reportFile", "required");
		}
	}
}
  1. 새 패키지 kr.spring.ch07.controller 생성하고 새 클래스 SubmitReportController 생성
package kr.spring.ch07.controller;

import java.io.File;
import java.io.IOException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import kr.spring.ch07.validator.SubmitReportValidator;
import kr.spring.ch07.vo.SubmitReportVO;

@Controller
public class SubmitReportController {
	// 파일 업로드 경로 읽기
	@Value("${file_path}")
	private String path;
	
	// 자바빈 초기화
	@ModelAttribute("report")
	public SubmitReportVO initCommand() {
		return new SubmitReportVO();
	}
	
	// 폼 호출
	@GetMapping("/report/submitReport.do")
	public String form() {
		return "report/submitReportForm";
	}
	
	// 폼에서 전송된 데이터 처리
	@PostMapping("/report/submitReport.do")
	public String submit(@ModelAttribute("report") SubmitReportVO vo, BindingResult result) throws IllegalStateException, IOException {
		// 전송된 데이터 유효성 검증
		new SubmitReportValidator().validate(vo, result);
		// 전송된 데이터 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return form();
		}
		
		// 전송된 파일을 업로드 경로에 저장
		File file = new File(path + "/" + vo.getReportFile().getOriginalFilename()); // 업로드할 경로 정보를 담은 File 객체 생성
		vo.getReportFile().transferTo(file); // File 객체가 지시하는 경로에 전송된 파일을 저장; transferTo() 메서드는 에러가 발생할 수 있어 transferTo()가 실행되는 메서드의 선언부에 throws를 추가하거나 또는 transferTo()를 try~catch문으로 감싸야 함
		
		System.out.println("주제 : " + vo.getSubject());
		System.out.println("업로드한 파일 : " + vo.getReportFile().getOriginalFilename());
		System.out.println("파일 크기 : " + vo.getReportFile().getSize());
		
		return "report/submittedReport";
	}
}
  1. views 폴더에 하위 폴더로 report 생성하고 새 JSP 파일 submitReportForm.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>리포트 등록 폼</title>
</head>
<body>
<form:form action="submitReport.do" method="post" enctype="multipart/form-data" modelAttribute="report">
	<ul>
		<li>
			<form:label path="subject">제목</form:label>
			<form:input path="subject"/>
			<form:errors path="subject"/>
		</li>
		<li>
			<form:label path="reportFile">리포트 파일</form:label>
			<input type="file" name="reportFile" id="reportFile"/>
			<form:errors path="reportFile"/>
		</li>
	</ul>
	<div>
		<form:button>리포트 전송</form:button>
	</div>
</form:form>
</body>
</html>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/report/submitReport.do">SubmitReportController</a><br>
  1. report 폴더에 새 JSP 파일 submittedReport.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>리포트 등록 완료</title>
</head>
<body>
리포트 <b>${report.subject}</b>를 등록했습니다.<br>
업로드한 파일 : ${report.reportFile.originalFilename}
</body>
</html>

로그인 처리

  1. 새 패키지 kr.spring.ch08.vo 생성 후 새 클래스 LoginVO 생성
package kr.spring.ch08.vo;

public class LoginVO {
	private String userId;
	private String password;
	
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	@Override
	public String toString() {
		return "LoginVO [userId=" + userId + ", password=" + password + "]";
	}
}
  1. 새 패키지 kr.spring.ch08.validator 생성 후 새 클래스 LoginValidator 생성
package kr.spring.ch08.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import kr.spring.ch08.vo.LoginVO;

public class LoginValidator implements Validator {
	@Override
	public boolean supports(Class<?> clazz) {
		return LoginVO.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		LoginVO vo = (LoginVO)target;
		if(vo.getUserId()==null || vo.getUserId().trim().isEmpty()) {
			errors.rejectValue("userId", "required");
		}
		if(vo.getPassword()==null || vo.getPassword().trim().isEmpty()) {
			errors.rejectValue("password", "required");
		}
	}
}
  1. 새 패키지 kr.spring.ch08.controller 생성 후 새 클래스 LoginController 생성
package kr.spring.ch08.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import kr.spring.ch08.service.AuthCheckException;
import kr.spring.ch08.service.LoginService;
import kr.spring.ch08.validator.LoginValidator;
import kr.spring.ch08.vo.LoginVO;

@Controller
public class LoginController {
	// 의존 관계 주입
	@Autowired
	private LoginService loginService;
	
	// 자바빈 초기화
	@ModelAttribute
	public LoginVO initCommand() {
		return new LoginVO();
	}
	
	// 폼 호출
	@GetMapping("/login/login.do")
	public String form() {
		return "login/form";
	}
	
	// 폼에서 전송된 데이터 처리
	@PostMapping("/login/login.do")
	public String submit(LoginVO vo, BindingResult result) { // @ModelAttribute 어노테이션 사용하지 않으면 클래스명 첫 글자를 소문자로 변경한 속성명으로 request 영역에 저장
		System.out.println(vo);
		
		// 전송된 데이터 유효성 검증
		new LoginValidator().validate(vo, result);
		// 전송된 데이터 유효성 검증 결과 오류가 있으면 폼 호출
		if(result.hasErrors()) {
			return form();
		}
		
		try {
			loginService.checkLogin(vo);
			// 아이디와 비밀번호가 일치하는 경우 리다이렉트 처리
			return "redirect:/index.jsp";
		}
		catch (AuthCheckException e) {
			// 필드가 없는 에러 메시지 처리
			result.reject("invalidIdOrPassword");
			// 아이디 또는 비밀번호가 불일치하는 경우 폼 호출
			return form();
		}
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 로그인 처리 -->
	<beans:bean class="kr.spring.ch08.controller.LoginController"/>
	<beans:bean class="kr.spring.ch08.service.LoginService"/>
  1. views 폴더에 하위 폴더로 login 생성하고 새 JSP 파일 form.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 폼</title>
</head>
<body>
<form:form modelAttribute="loginVO">
	<form:errors element="div"/>
	<ul>
		<li>
			<form:label path="userId">아이디</form:label>
			<form:input path="userId"/>
			<form:errors path="userId"/>
		</li>
		<li>
			<form:label path="password">비밀번호</form:label>
			<form:password path="password"/>
			<form:errors path="password"/>
		</li>
	</ul>
	<div>
		<form:button>전송</form:button>
	</div>
</form:form>
</body>
</html>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/login/login.do">LoginController</a><br>
  1. validation.properties 하단에 다음의 내용을 추가
required.userId=사용자 아이디는 필수 항목
required.password=비밀번호는 필수 항목
invalidIdOrPassword=아이디 또는 비밀번호 불일치
  1. 새 패키지 kr.spring.ch08.service 생성 후 새 클래스 AuthCheckException 생성
package kr.spring.ch08.service;

public class AuthCheckException extends Exception { // 사용자 정의 예외 클래스

}
  1. 새 클래스 LoginService 생성
package kr.spring.ch08.service;

import kr.spring.ch08.vo.LoginVO;

public class LoginService {
	public void checkLogin(LoginVO loginVO) throws AuthCheckException {
		// 테스트 목적으로 userId와 password가 일치하면 로그인된 것으로 처리
		if(!loginVO.getUserId().equals(loginVO.getPassword())) {
			System.out.println("인증 에러 : " +  loginVO.getUserId());
			throw new AuthCheckException(); // 메서드 선언부에 throws를 추가하거나 try~catch문으로 감싸야 함
		}
	}
}

국제화 처리

  1. messages 폴더에 새 파일 label.properties 생성
login.form.title=로그인 입력 폼
login.form.userId=로그인 ID
login.form.password=로그인 비밀번호
login.form.submit=전송
  1. servlet-context.xml<beans:beans> 태그 사이의 내용을 다음처럼 수정
	<!-- 리소스 번들 지정 -->
	<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<beans:property name="basenames">
			<beans:list> <!-- properties 파일을 여러 개 등록 가능 -->
				<beans:value>messages.validation</beans:value> <!-- 리스트의 값들은 전부 properties 파일로 인식하므로 확장자는 생략하며, src/main/resources 하위의 폴더 경로는 포함 -->
				<beans:value>messages.label</beans:value>
			</beans:list>
		</beans:property>
	</beans:bean>
  1. login 폴더의 form.jsp의 내용을 다음처럼 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><spring:message code="login.form.title"/></title>
</head>
<body>
<form:form modelAttribute="loginVO">
	<form:errors element="div"/>
	<ul>
		<li>
			<form:label path="userId"><spring:message code="login.form.userId"/></form:label>
			<form:input path="userId"/>
			<form:errors path="userId"/>
		</li>
		<li>
			<form:label path="password"><spring:message code="login.form.password"/></form:label>
			<form:password path="password"/>
			<form:errors path="password"/>
		</li>
	</ul>
	<div>
		<form:button><spring:message code="login.form.submit"/></form:button>
	</div>
</form:form>
</body>
</html>
  1. messages 폴더에 새 파일 label_en.properties 생성
login.form.title=Login Form
login.form.userId=ID
login.form.password=Password
login.form.submit=Login
  1. messages 폴더에 새 파일 validation_en.properties 생성
required=required
required.userId=ID is required
required.password=Password is required
invalidIdOrPassword=Login ID or Password does not match
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- localeResolver 지정 -->
	<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
	
	<!-- 로케일 변경하기 -->
	<beans:bean class="kr.spring.ch09.controller.LocaleChangeController"/>
  1. 새 패키지 kr.spring.ch09.controller 생성 후 새 클래스 LocaleChangeController 생성
package kr.spring.ch09.controller;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.LocaleResolver;

@Controller
public class LocaleChangeController {
	@Autowired
	private LocaleResolver localeResolver;
	
	@RequestMapping("/changeLanguage.do")
	public String change(@RequestParam("lang") String language, HttpServletRequest request, HttpServletResponse response) {
		Locale locale = new Locale(language);
		// 원하는 Locale 정보를 세팅; 브라우저가 열려 있는(=세션이 유지되는) 동안 세팅한 로케일이 유지됨
		localeResolver.setLocale(request, response, locale);
		return "redirect:/index.jsp";
	}
}
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
----------------------------<br>
===로케일 변경하기===<br>
<a href="${pageContext.request.contextPath}/changeLanguage.do?lang=ko">/changeLanguage.do?lang=ko</a><br>
<a href="${pageContext.request.contextPath}/changeLanguage.do?lang=en">/changeLanguage.do?lang=en</a><br>
로케일 변경하기를 클릭한 후 로케일 변경을 확인하기 위해 /login/login.do를 클릭하세요.<br>
----------------------------<br>

다음으로