|
: JSR-303 어노테이션을 사용하면 Validatioin을 조금 더 간결하게 설정할 수 있다. 스프링 3부터 JSR-303 어노테이션을 이용한 Validation을 지원한다. 그럼 사용법을 간단히 알아보자.
먼저 JSR-303 어노테이션을 사용하기위해서는 아래의 의존 라이브러리를 pom.xml에 추가한다.
==== pom.xml ====
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Beta1</version>
</dependency>
--> Hibernate Validator는 JSR-303 Validator 구현체이다.
다음으로 스프링 설정파일에 아래 설정을 추가한다.
==== dispatcher-servlet.xml ====
<mvc:annotation-driven />
--> 위와 같이 정의된 경우 클래스패스로부터 Hibernate Validator와 같은 JSR-303 Validator 구현체가 자동으로 검색되어 모든 @Controller에 적용된다.
다음으로 스프링 설정 파일에 아래 설정을 추가한다.
==== context-common.xml ====
<!-- JSR-303 Validation 설정 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
--> LocalValidatiorFactoryBean은 JSR-303의 검증기능을 스프링의 Validator처럼 사용할 수 있게 해주는 일종의 어댑터다.
다시 말해서 Hibernate Validator와 같은 JSR-303 Validator 구현체를 실행시킬 수 있도록 하기 위해 LocalValidatorFactoryBean 을 빈으로 등록하는것이다.
LocalValidatiorFactoryBean을 빈으로 등록하면 컨트롤러에서 Validator타입으로 DI 받아서 @InitBinder에서 WebDataBinder에 설정하거나 코드에서 직접 Validator처럼 사용할 수 있다.
다음으로 컨트롤러에서 Validation 수행을 위해 추가해야될 코드를 살펴보자.
1. 컨트롤러 핸들러 메서드에서 validator를 직접 호출하는 방법과
2. @Valid 어노테이션과 @InitBinder 어노테이션을 이용해서 validator를 직접적으로 호출하지않고, 스프링 프레임워크에서 호출하는 두가지 방법을 살펴보도록한다.
둘중에 어느것을 사용해도 무방하지만 내 경우에는 2번 방법으로 일관성있게 사용하려고 한다.
먼저, 1번 방법을 알아보자.
==== GameController.java ====
package egovframework.rte.controller.game;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.validation.Valid;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import egovframework.rte.controller.BaseController;
import egovframework.rte.dto.DictationDto;
import egovframework.rte.dto.GameDto;
import egovframework.rte.service.util.paging.PaginationInfo;
import egovframework.rte.validator.GameValidator;
/**
* 게임과 관련된 요청을 처리하는 컨트롤러
*
* @author 황정식
* @since 2012.03.14
* @version 1.0
*/
@Controller
@RequestMapping("/onnuri/game")
@SessionAttributes("game")
public class GameController extends BaseController
{
<!-- (1) --->
@Resource(name="validator")
protected Validator validator;
@RequestMapping(value="gameMain", method=RequestMethod.GET)
public String gameMain()
{
return "game/gameMain";
}
/**
* 게임 등록 폼.
*
* @return
*/
@RequestMapping(value="gameReg", method=RequestMethod.GET)
public String gameDictRegForm(Model model)
{
GameDto gameDto = gameDtoProvider.get();
// 로그인한 사용자 이메일을 폼에 사전 등록. 게임타입 값 설정.
gameDto.setUserEmail(SecurityContextHolder.getContext().getAuthentication().getName());
gameDto.setGameType("1");
model.addAttribute("game", gameDto);
return "game/gameRegForm";
}
/**
* 게임 등록 처리
*
*/
@RequestMapping(value="gameReg", method=RequestMethod.POST)
public String gameDictReg(@ModelAttribute("game") GameDto gameDto, BindingResult result) throws Exception
{
logger.info(gameDto.toString());
<!-- (2) -->
validator.validate(gameDto, result);
if(result.hasErrors())
{
return "game/gameRegForm";
}
gameService.insertDataGame(gameDto);
return "redirect:/onnuri/game/questionRegForm?gameSeq=" + gameDto.getGameSeq() + "&gameType=" + gameDto.getGameType();
}
--> (1)에서 DI받는 validator 빈은 스프링 설정파일에 등록해둔 LocalValidatorFactoryBean 빈이다.
--> (2) DI 받은 validator 빈으로 validate() 메서드를 호출한다. 이전 포스팅에서 살펴봤었던 GameValidator를 직접 구현했을때에는 GameValidator 클래스의 validate() 메서드를 호출했지만 여기서 호출하는것은 JSR-303 어노테이션 기반의 validate() 메서드를 호출하는것이므로 Validator를 직접적으로 구현하지 않는다. 다만, 아래에서 도메인 클래스에 어노테이션으로 밸리데이션을 설정하는부분은 따로 살펴보도록 한다.
다음으로, @Valid 어노테이션과 @InitBinder 어노테이션을 사용해서 핸들러 메서드에서 validate() 메서드를 직접적으로 호출하지 않고, 스프링 프레임워크에서 호출하는 방법으로 컨트롤러를 설정해보자.
==== GameController.java ====
package egovframework.rte.controller.game;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.validation.Valid;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import egovframework.rte.controller.BaseController;
import egovframework.rte.dto.DictationDto;
import egovframework.rte.dto.GameDto;
import egovframework.rte.service.util.paging.PaginationInfo;
import egovframework.rte.validator.GameValidator;
/**
* 게임과 관련된 요청을 처리하는 컨트롤러
*
* @author 황정식
* @since 2012.03.14
* @version 1.0
*/
@Controller
@RequestMapping("/onnuri/game")
@SessionAttributes("game")
public class GameController extends BaseController
{
<!-- (1) --->
@Resource(name="validator")
protected Validator validator;
@RequestMapping(value="gameMain", method=RequestMethod.GET)
public String gameMain()
{
return "game/gameMain";
}
/**
* 게임 등록 폼.
*
* @return
*/
@RequestMapping(value="gameReg", method=RequestMethod.GET)
public String gameDictRegForm(Model model)
{
GameDto gameDto = gameDtoProvider.get();
// 로그인한 사용자 이메일을 폼에 사전 등록. 게임타입 값 설정.
gameDto.setUserEmail(SecurityContextHolder.getContext().getAuthentication().getName());
gameDto.setGameType("1");
model.addAttribute("game", gameDto);
return "game/gameRegForm";
}
/**
* 게임 등록 처리
*
*/
@RequestMapping(value="gameReg", method=RequestMethod.POST) <!-- (2) -->
public String gameDictReg(@ModelAttribute("game") @Valid GameDto gameDto, BindingResult result) throws Exception {
logger.info(gameDto.toString());
if(result.hasErrors())
{
return "game/gameRegForm";
}
gameService.insertDataGame(gameDto);
return "redirect:/onnuri/game/questionRegForm?gameSeq=" + gameDto.getGameSeq() + "&gameType=" + gameDto.getGameType();
}
@InitBinder
public void initBinder(WebDataBinder dataBinder)
{
dataBinder.setValidator(this.validator); <!-- (3) -->
}
--> (1) validator 빈을 DI 받는 부분은 똑같다.
--> (2) @Valid 어노테이션을 커맨드 클래스의 객체 앞에 붙이면 핸들러 메서드 호출전에 InitBinder 어노테이션이 붙은 메서드를 먼저 호출해서 밸리데이션을 수행한다.
--> (3) WebDataBinder의 setValidator() 메서드에 validator를 지정하면서 밸리데이션이 이루어진다.
이제 마지막으로 커맨드 클래스 즉, 도메인 클래스로 사용되는 클래스의 프로퍼티에 유효성 검증을 지정하는 어노테이션을 지정해보자.
==== GameDto.java ====
package egovframework.rte.dto;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import egovframework.rte.service.util.CodeService;
import egovframework.rte.service.util.Time;
@Component
@Scope("prototype")
public class GameDto extends CommonInfoDto
{
private int gameSeq;
private String gameStatus;
private String userEmail;
private String openFlag;
private String passNo;
private String gameType;
@NotNull
@Size(min=1, max=30, message="이름을 입력하세요.")
private String gameTitle;
private String gameDesc;
private String basicInfo;
private String writeTime;
private String modifyTime;
private String delFlag;
private String gameTypeText;
private List<DictationDto> dictationDtoList;
public GameDto()
{
}
public GameDto(int gameSeq, String gameType)
{
this.gameSeq = gameSeq;
this.gameType = gameType;
}
public GameDto(int gameSeq, String gameStatus, String userEmail,
String openFlag, String passNo, String gameType, String gameTitle,
String gameDesc, String basicInfo, String writeTime,
String modifyTime)
{
super();
this.gameSeq = gameSeq;
this.gameStatus = gameStatus;
this.userEmail = userEmail;
this.openFlag = openFlag;
this.passNo = passNo;
this.gameType = gameType;
this.gameTitle = gameTitle;
this.gameDesc = gameDesc;
this.basicInfo = basicInfo;
this.writeTime = writeTime;
this.modifyTime = modifyTime;
}
public int getGameSeq()
{
return gameSeq;
}
public void setGameSeq(int gameSeq)
{
this.gameSeq = gameSeq;
}
public String getGameStatus()
{
return gameStatus;
}
public void setGameStatus(String gameStatus)
{
this.gameStatus = gameStatus;
}
public String getUserEmail()
{
return userEmail;
}
public void setUserEmail(String userEmail)
{
this.userEmail = userEmail;
}
public String getOpenFlag()
{
return openFlag;
}
public void setOpenFlag(String openFlag)
{
this.openFlag = openFlag;
}
public String getPassNo()
{
return passNo;
}
public void setPassNo(String passNo)
{
this.passNo = passNo;
}
public String getGameType()
{
return gameType;
}
public void setGameType(String gameType)
{
this.gameType = gameType;
}
public String getGameTitle()
{
return gameTitle;
}
public void setGameTitle(String gameTitle)
{
this.gameTitle = gameTitle;
}
public String getGameDesc()
{
return gameDesc;
}
public void setGameDesc(String gameDesc)
{
this.gameDesc = gameDesc;
}
public String getBasicInfo()
{
return basicInfo;
}
public void setBasicInfo(String basicInfo)
{
this.basicInfo = basicInfo;
}
public String getWriteTime()
{
return writeTime;
}
public void setWriteTime(String writeTime)
{
this.writeTime = writeTime;
}
public String getModifyTime()
{
return modifyTime;
}
public void setModifyTime(String modifyTime)
{
this.modifyTime = modifyTime;
}
public List<DictationDto> getDictationDtoList()
{
return dictationDtoList;
}
public void setDictationDtoList(List<DictationDto> dictationDtoList)
{
this.dictationDtoList = dictationDtoList;
}
/**
* 게임 유형코드를 스트링 값으로 변환.
* "\n"을 "<br/>"로 변환. 등의 Dto객체의 데코레이터 작업.
*
* @param gameDtoResult
*/
public static void setDecorateGameDto(GameDto gameDtoResult)
{
int gameType = Integer.parseInt(gameDtoResult.getGameType());
String gameTypeValue = CodeService.KeyIntValueString.getValue(gameType);
String writeTime = Time.getFromDateTimeSerialToDateTime(gameDtoResult.getWriteTime());
gameDtoResult.setGameTypeText(gameTypeValue);
gameDtoResult.setGameDesc(gameDtoResult.getGameDesc().replace("\n", "<br/>"));
gameDtoResult.setBasicInfo(gameDtoResult.getBasicInfo().replace("\n", "<br/>"));
gameDtoResult.setWriteTime(writeTime);
}
public String getDelFlag()
{
return delFlag;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
public String getGameTypeText()
{
return gameTypeText;
}
public void setGameTypeText(String gameTypeText)
{
this.gameTypeText = gameTypeText;
}
public String toString()
{
String DtoDescription = "\n============================================== Dto 상세 ================================================\n";
DtoDescription += " gameSeq : " + this.gameSeq + "\n";
DtoDescription += " gameStatus : " + this.gameStatus + "\n";
DtoDescription += " userEmail : " + this.userEmail + "\n";
DtoDescription += " openFlag : " + this.openFlag + "\n";
DtoDescription += " passNo : " + this.passNo + "\n";
DtoDescription += " gameType : " + this.gameType + "\n";
DtoDescription += " gameDesc : " + this.gameDesc + "\n";
DtoDescription += " basicInfo : " + this.basicInfo + "\n";
DtoDescription += " writeTime : " + this.writeTime + "\n";
DtoDescription += " modifyTime : " + this.modifyTime + "\n";
DtoDescription +="=========================================================================================================";
return DtoDescription;
}
}
--> 위에서 gameTitle 프로퍼티에 JSR-303 어노테이션을 지정했다. 위와 같이 지정한경우, 해당 프로퍼티에 대한 유효성 검증을 수행하고, 유효성 검증에 실패할 경우, 폼 페이지에서 스프링 폼 태그를 이용해서 에러 메시지를 출력하게 된다.
에러 메시지를 출력하는 부분은 이전 포스팅에서 설명을 한걸로 아는데 패스하도록 한다.
JSR-303 어노테이션의 더 많은 기능들을 알아보기 위해서 아래 사이트를 참고하면 되겠다.
http://dev.anyframejava.org/docs/anyframe/plugin/foundation/4.6.1/reference/html/ch13.html
|