728x90

https://hahagogo.tistory.com/154

 

데이터 바인딩 - PropertyEditor, Converter, Formatter

📝 데이터 바인딩 : 사용자가 입력한 문자열 값을 프로퍼티 타입에 맞춰 변환하여 할당하는 것 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/propertyeditors/p..

hahagogo.tistory.com


Validator 인터페이스를 지원하여 어플리케이션에서 사용하는 객체를 검증할 수 있는 기능을 제공
boolean supports(Class clazz) : 이 객체의 클래스가 이 Validator가 검증할 수 있는 클래스인 지를 판단하는 매서드
void validate(Object target, Errors error) : 실제 검증 로직이 이루어지는 메서드


수동 검증


컨트롤러 메서드 내에서 검증하던것을

if( id == null || "".equals(id.trim() ){  
   model.addAttribute("msg" , "id는 필수 입력 입니다."); 
   return "redirect:/register/add; 
}

컨트롤러 메서드 와 별도로 분리 하여 검증

@PostMapping("/register/add")
public String save(Model m , @Valid User user, BindingResult result ){

    UserValidator userValidator = new UserValidator(); 
    userValidator.validate(user , result); //검증

    if( result.hasErrors() ){ 
       return "registForm"; 
    }
}

Validator

public class UserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
    
    	User user = (User)target;
        String id = user.getId();
        
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id", "required" , "id는 필수값입니다.");//  하드코딩
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pwd", "required");
        if(id == null || id.length() < 5 || id.length() > 12){
        	error.rejectValue("id","invalidLength");
        }
   }
}

Bean Validation API > pom.xml 추가
https://mvnrepository.com/artifact/javax.validation/validation-api

자동 검증( Controller 안에서만 동작 ) (  binder.setValidator )

@InitBinder 
public void toDate( WebDataBinder binder ){ 
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 
    binder.registerCustomEditor(Date.class , new CustomDateEditor(df, false) );
    // =============================================================================
    binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor("#") ); 
    binder.registerCustomEditor(String[].class , "hobby" , new StringArrayPropertyEditor("#") );  //hobby에만
    // =============================================================================    
    binder.setValidator( new UserValidator() );   
}

@PostMapping("/register/add")
public String save(Model m , @Valid User user, BindingResult result ){
    if( result.hasErrors() ){
    	return "registForm";
    }
}

 

자동 검증( Global )

servlet-context.xml

<annotation-driven validator = "globalValidator" />
<beans:bean id="globalValidator" class="com.fastkk.ch2.GlobalValidator" />


GlobalValidator

public class GlobalValidator implements Validator {
	@Override
	public boolean supports(Class<?> clazz) {
//		return User.class.equals(clazz); // 검증하려는 객체가 User타입인지 확인
		return User.class.isAssignableFrom(clazz); // clazz가 User 또는 그 자손인지 확인
	}

	@Override
	public void validate(Object target, Errors errors) { 
		System.out.println("GlobalValidator.validate() is called");

		User user = (User)target;
		
		String id = user.getId();
		
//		if(id==null || "".equals(id.trim())) {
//			errors.rejectValue("id", "required");
//		}
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id",  "required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pwd", "required");
		
		if(id==null || id.length() <  5 || id.length() > 12) {
			errors.rejectValue("id", "invalidLength", new String[]{"5", "12"}, null);
		}
	}
}

 

Global + Controller 안에서만 동작( 주의 binder.addValidators )

@InitBinder 
public void toDate( WebDataBinder binder ){ 
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 
    binder.registerCustomEditor(Date.class , new CustomDateEditor(df, false) );
    // =============================================================================
    binder.registerCustomEditor(String[].class , new StringArrayPropertyEditor("#") ); 
    binder.registerCustomEditor(String[].class , "hobby" , new StringArrayPropertyEditor("#") );  //hobby에만
    // =============================================================================    
    binder.addValidators( new UserValidator() );   
}

@PostMapping("/register/add")
public String save(Model m , @Valid User user, BindingResult result ){
    if( result.hasErrors() ){
    	return "registForm";
    }
}

 

MessageResource

다양한 리소스 에서 메시지를 읽기위한 MessageSource 인터페이스 보기..

public interface MessageSource {
    String getMessage ( Strong code , Object[] args , String defaultMessage, Locale locale );
    String getMessage ( String code , Object[] args , Locale locale ) throws NoSuchMessageException;
    String getMessage ( MessageSourceResolvable, Locale locale ) throws NoSuchMessageException;
}

 

프로퍼티 파일을 메시지로 하는 ResourceBundleMessageSource를 servlet-context.xml 에 등록해야함.

<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <beans:property name="basenames">
        <beans:list>
            <beans:value>error_message</beans:value> <!-- /src/main/resources/error_message.properties -->
        </beans:list>
    </beans:property>
    <beans:property name="defaultEncoding" value="UTF-8"/>
</beans:bean>

/src/main/resources > error_message.properties 에는

required = 필수 항목입니다.
required.user.pw = 비밀번호 필수항목임
invalidLength.id = 아이디 길이는 {0} ~ {1} 입니다.

찾는 순서 ex) id
required.user.id > required.id > required.java.lang.String > required > defaultMessage

검증 메시지의 출력

<%@ taglib uri="http://www.springframework.org/gags/form" prefix="form" %>
기존 <form action="<c:url value="/register/save"/>" method="post" onsubmit="return formCheck(this)"></form>
변경 <form:form modelAttribute="user" ></form:form>
기존
<div id="msg" class="msg">
    <c:if test="${not empty param.msg}">
        <i class="fa fa-exclamation-circle"> ${URLDecoder.decode(param.msg)}</i>            
    </c:if>
</div>

변경
<div id="msg" class="msg">
    <c:if test="${not empty param.msg}">
        <i class="fa fa-exclamation-circle">
        	<form:errors path="id"/>
        </i>            
    </c:if>
</div>

사용 예시

UserDao.java

public interface UserDao {

    int deleteUser(String id);

    User selectUser(String id);

    // 사용자 정보를 user_info테이블에 저장하는 메서드
    int insertUser(User user);

    // 매개변수로 받은 사용자 정보로 user_info테이블을 update하는 메서드
    int updateUser(User user);

    void deleteAll() throws Exception;
}

 

UserDaoImpl.java

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired DataSource ds;
    final int FAIL = 0;

    @Override
    public int deleteUser(String id) {
        int rowCnt = FAIL; //  insert, delete, update

        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "delete from user_info where id= ? ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, id);
//        int rowCnt = pstmt.executeUpdate(); //  insert, delete, update
//        return rowCnt;
            return pstmt.executeUpdate(); //  insert, delete, update
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        } finally {
            // close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
//            try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
//            try { if(conn!=null)  conn.close();  } catch (SQLException e) { e.printStackTrace();}
            close(pstmt, conn);  //     private void close(AutoCloseable... acs) {
        }
    }

    @Override
    public User selectUser(String id) {
        User user = null;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        String sql = "select * from user_info where id= ? ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
            pstmt.setString(1, id);

            rs = pstmt.executeQuery(); //  select

            if (rs.next()) {
                user = new User();
                user.setId(rs.getString(1));
                user.setPwd(rs.getString(2));
                user.setName(rs.getString(3));
                user.setEmail(rs.getString(4));
                user.setBirth(new Date(rs.getDate(5).getTime()));
                user.setSns(rs.getString(6));
                user.setReq_date(new Date(rs.getTimestamp(7).getTime()));
            }
        } catch (SQLException e) {
            return null;
        } finally {
            // close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
            // close()의 호출순서는 생성된 순서의 역순
//            try { if(rs!=null)    rs.close();    } catch (SQLException e) { e.printStackTrace();}
//            try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
//            try { if(conn!=null)  conn.close();  } catch (SQLException e) { e.printStackTrace();}
            close(rs, pstmt, conn);  //     private void close(AutoCloseable... acs) {
        }

        return user;
    }

    // 사용자 정보를 user_info테이블에 저장하는 메서드
    @Override
    public int insertUser(User user) {
        int rowCnt = FAIL;

        Connection conn = null;
        PreparedStatement pstmt = null;

//        insert into user_info (id, pwd, name, email, birth, sns, reg_date)
//        values ('asdf22', '1234', 'smith', 'aaa@aaa.com', '2022-01-01', 'facebook', now());
        String sql = "insert into user_info values (?, ?, ?, ?,?,?, now()) ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
            pstmt.setString(1, user.getId());
            pstmt.setString(2, user.getPwd());
            pstmt.setString(3, user.getName());
            pstmt.setString(4, user.getEmail());
            pstmt.setDate(5, new java.sql.Date(user.getBirth().getTime()));
            pstmt.setString(6, user.getSns());

            return pstmt.executeUpdate(); //  insert, delete, update;
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        } finally {
            close(pstmt, conn); //     private void close(AutoCloseable... acs) {
        }
    }

    // 매개변수로 받은 사용자 정보로 user_info테이블을 update하는 메서드
    @Override
    public int updateUser(User user) {
        int rowCnt = FAIL; //  insert, delete, update

//        Connection conn = null;
//        PreparedStatement pstmt = null;

        String sql = "update user_info " +
                "set pwd = ?, name=?, email=?, birth =?, sns=?, reg_date=? " +
                "where id = ? ";

        // try-with-resources - since jdk7
        try (
                Connection conn = ds.getConnection();
                PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
        ){
            pstmt.setString(1, user.getPwd());
            pstmt.setString(2, user.getName());
            pstmt.setString(3, user.getEmail());
            pstmt.setDate(4, new java.sql.Date(user.getBirth().getTime()));
            pstmt.setString(5, user.getSns());
            pstmt.setTimestamp(6, new java.sql.Timestamp(user.getReq_date().getTime()));
            pstmt.setString(7, user.getId());

            rowCnt = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        }

        return rowCnt;
    }

    public void deleteAll() throws Exception {
        Connection conn = ds.getConnection();

        String sql = "delete from user_info ";

        PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
        pstmt.executeUpdate(); //  insert, delete, update
    }

    private void close(AutoCloseable... acs) {
        for(AutoCloseable ac :acs)
            try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
    }
}

 

Pom.xml

<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

 

web.xml

<!-- 한글 변환 필터 시작 -->
<filter>
   <filter-name>encodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
   <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
   </init-param>
   <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
   </init-param>
</filter>

<filter-mapping>
   <filter-name>encodingFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 한글 변환 필터 끝 -->

 

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
      http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

   <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
   
   <!-- Enables the Spring MVC @Controller programming model -->
   <annotation-driven />

   <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
   <resources mapping="/**" location="/resources/" />

   <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
   <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <beans:property name="prefix" value="/WEB-INF/views/" />
      <beans:property name="suffix" value=".jsp" />
   </beans:bean>

   <!-- com ( component-scan ) -->
   <!-- 패키지 안에 있는 클래스 중에서 @Component 붙은것을 찾아서 Bean 으로 등록 -->
   <context:component-scan base-package="com.test.ch3" >
      <context:exclude-filter type="regex" expression="com.test.ch3.dpCopy*.*"/>
   </context:component-scan>

   <view-controller path="/" view-name="index"/>

   <beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
      <beans:property name="basenames">
         <beans:list>
            <beans:value>error_message</beans:value> <!-- /src/main/resources/error_message.properties -->
         </beans:list>
      </beans:property>
      <beans:property name="defaultEncoding" value="UTF-8"/>
   </beans:bean>

</beans:beans>

 

UserValidator.java

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
//		return User.class.equals(clazz); // 검증하려는 객체가 User타입인지 확인
        return User.class.isAssignableFrom(clazz); // clazz가 User 또는 그 자손인지 확인
    }

    @Override
    public void validate(Object target, Errors errors) {
        System.out.println("UserValidator.validate() is called");

        User user = (User)target;
        String id = user.getId();

//		if(id==null || "".equals(id.trim())) {
//			errors.rejectValue("id", "required");
//		}
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id",  "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pwd", "required");

        if(id==null || id.length() <  5 || id.length() > 12) {
            errors.rejectValue("id", "invalidLength", new String[]{"5","12"}, null);
        }
    }
}

 

LoginController.java 

@Controller
@RequestMapping("/login")
public class LoginController {

    @Autowired UserDao userDao;

    @GetMapping("/login")
    public String loginForm() {
        return "loginForm";
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        // 1. 세션을 종료
        session.invalidate();
        // 2. 홈으로 이동
        return "redirect:/";
    }

    @PostMapping("/login")
    public String login(String id, String pwd, String toURL, boolean rememberId,
                        HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 1. id와 pwd를 확인
        if(!loginCheck(id, pwd)) {
            // 2-1   일치하지 않으면, loginForm으로 이동
            String msg = URLEncoder.encode("id 또는 pwd가 일치하지 않습니다.", "utf-8");
            return "redirect:/login/login?msg="+msg;
        }

        // 2-2. id와 pwd가 일치하면,
        //  세션 객체를 얻어오기
        HttpSession session = request.getSession();
        //  세션 객체에 id를 저장
        session.setAttribute("id", id);

        if(rememberId) {
            //     1. 쿠키를 생성
            Cookie cookie = new Cookie("id", id); // ctrl+shift+o 자동 import
//           2. 응답에 저장
            response.addCookie(cookie);
        } else {
            // 1. 쿠키를 삭제
            Cookie cookie = new Cookie("id", id); // ctrl+shift+o 자동 import
            cookie.setMaxAge(0); // 쿠키를 삭제
//           2. 응답에 저장
            response.addCookie(cookie);
        }
//           3. 홈으로 이동
        toURL = toURL==null || toURL.equals("") ? "/" : toURL;

        return "redirect:"+toURL;
    }

    private boolean loginCheck(String id, String pwd) {
        User user = userDao.selectUser(id);
        if(user == null) return false;
        return user.getPwd().equals(pwd);
        //return "asdf".equals(id) && "1234".equals(pwd);
    }
}

 

RegisterController.java ( @Valid User user, BindingResult result )

@Controller // ctrl+shift+o 자동 import
@RequestMapping("/register")
public class RegistController {

    @Autowired UserDao userDao;

    final int FAIL = 0;

    @InitBinder
    public void toDate(WebDataBinder binder) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(df, false));
        binder.setValidator(new UserValidator()); // UserValidator를 WebDataBinder의 로컬 validator로 등록
        // List<Validator> validatorList = binder.getValidators();
        // System.out.println("validatorList="+validatorList);
    }

    @GetMapping("/add")
    public String register() {
        return "registerForm"; // WEB-INF/views/registerForm.jsp
    }

    @PostMapping("/add")
    public String save(@Valid User user, BindingResult result, Model m) throws Exception {
        System.out.println("result="+result);
        System.out.println("user="+user);

        // User객체를 검증한 결과 에러가 있으면, registerForm을 이용해서 에러를 보여줘야 함.
        if(result.hasErrors()) {
            // 2. DB에 신규회원 정보를 저장
            int rowCount = userDao.insertUser(user);
            if(rowCount != FAIL){ return "registerForm"; }
        }

        return "registerForm";

    }

    private boolean isValid(User user) {
        return true;
    }
}

 

error_message.properties

required = 필수 항목입니다.
required.user.pw = 비밀번호 필수항목임
invalidLength.id = 아이디 길이는 {0} ~ {1} 입니다.
728x90

+ Recent posts