ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot API - Validation, Internationalization(다국어 처리), Filtering
    카테고리 없음 2024. 2. 20. 13:05

    Validation

    dependency 추가

     implementation group: 'org.springframework.boot',
                name: 'spring-boot-starter-validation', version: '3.2.1'

     

    spring boot에서 validation기능은 JDK에 포함된 validation API 혹은 하이버네트 라이브러리에 포함되어 있는 하이버네이트 validation을 사용할 수 있다.

    @PostMapping("/users")
        public void createUser(@Valid @RequestBody User user){
            User savedUser = service.save(user);
        }

     

    @Data
    @AllArgsConstructor
    public class User {
        private Integer id;
    
       @Size(min = 2)
        private String name;
    
        @Past
        private Date joinDate;
    }

     

     

    ResponseEntityExceptionHandler → handleMethodArgumentNotValid

     @Override
        protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                      HttpHeaders headers,
                                                                      HttpStatusCode status,
                                                                      WebRequest request) {
    
            ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),
                    ex.getMessage(),ex.getBindingResult().toString());
    
            return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
        }

     

    ex.getBindResult()는 error 객체에 들어 있는 정확한 에러에 대한 메시지를 담고 있다.


    Internationalization(다국어 처리)

    하나의 출력 값을 여러가지 언어로 표시해주는 것이다. 이것은 클라이언트 브라우저에서 언어값이 무엇으로 설정되어 있냐에 따라 request가 달라진다.  물론 자동으로 번역되는 기능을 의미하는 것이 아니라 제공하려고 하는 언어별로 문자에 대한 데이터를 미리 가지고 있다가  지역코드, 언어설정에 따라서 적절한 언어가 표시되는 것을 의미한다. 해당하는 지역코드가 존재하지 않으면 기본값으로 설정되어 있는 값이 보여진다.

     

    다국어 처리가 단순하게 특정 컨트롤러에서만 처리 되는 것이 아니라 프로젝트 전반에서 적용시켜야 한다. 다국어 처리에 필요한 빈들을 스프링 부트 클래스에 등록한다. 스프링 부트가 초기화 될때 메모리에 등록해서 사용할 수 있도록 한다. 

    @GetMapping(path = "hello-world-internationalization")
        public String helloworldInternationalized(){
            return "Good morning";
        }

     

    • @Configuration 등록
      •  LocaleResolver
      • Default Locale
        • Locale.US or Locale.KOREA
      • ResourceBundleMessageSource

     

    • Usage
      1. generate message budle files
        • 언어별로 되어 있는 파일의 묶음
      2. @Autowired MessageSource
      3. @RequestHeader(value="Accept-Language", required = false) Local local
      4. messageSource.getMessage("greeting.message",null,local)

    1.

    @GetMapping(path = "hello-world-internationalized")
        public String helloworldInternationalized(
        @RequestHeader(name="Accept-Languge",required = false) Locale locale){
            return "Good morning";
        }

     

    사용자가 보낸 request header에 Accept - Languge 속성을 사용해서 Locale 값을 받아 온다. 

     

    2.

    @SpringBootApplication
    public class MonsterApplication {
    
        public static void main(String[] args) {
             SpringApplication.run(MonsterApplication.class, args);
        }
    
        @Bean
        public LocaleResolver localeResolver(){
            SessionLocaleResolver localeResolver = new SessionLocaleResolver();
            localeResolver.setDefaultLocale(Locale.US);
            return localeResolver;
        }
    
    }

     

    1번의 메서드인 helloworldInternationalized메서드에서 messageSauce를 인스턴스를 받아서 사용하기 위해 메인 어플리케이션 클래스에 LocalResolver 빈을 등록한다. 사용자의 요청에 속성값에 따라 반환값이 달라지는 것이기 때문에 SessionLocaleResolver를 사용한다.  이때 기본 값은 Local.US로 해준다. 


    Filtering

    @JsonIgnore

    @Data
    @AllArgsConstructor
    public class User {
        private Integer id;
    
        @Size(min = 2 , message = "Name은 2글자 이상 입력해 주세요")
        private String name;
    
        @Past(message = "등록일은 미래 날짜를 입력하실 수 없습니다.")
        private Date joinDate;
    
        @JsonIgnore
        private String password;
        @JsonIgnore
        private String ssn;
    }

     

    위 와 같은 User 객체에서 클라이언트에 응답을 보낼때 노출하고 싶지 않은 정보가 있을 경우 해당 정보를 특수문자처리 하는  작업을 filtering으로 할 수 있다. 

     

    또 한 외부에 노출시키고 싶지 않은 정보가 있을 경우에는 해당 필드위에 @JsonIgnore 애노테이션을 사용해준다. 그럼 자동으로 response에는 해당 값이 제외된 User객체가 응답된다. 

     

    @JsonIgnoreProperties

    @Data
    @AllArgsConstructor
    @JsonIgnoreProperties(value ={"password","ssn"})
    public class User {
        private Integer id;
    
        @Size(min = 2 , message = "Name은 2글자 이상 입력해 주세요")
        private String name;
    
        @Past(message = "등록일은 미래 날짜를 입력하실 수 없습니다.")
        private Date joinDate;
    
        //@JsonIgnore
        private String password;
        //@JsonIgnore
        private String ssn;
    }

     

    @JsonIngnoreProperties 애노테이션을 활용하면 필드마다 @JsonIgnore를 지정하지 않고 class스 차원에서 한번에 지정할 수 있다.

     


    Dynamic Filtering

    Retrieve user info for admin

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonFilter("UserInfo")
    public class AdminUser {
        private Integer id;
    
        @Size(min = 2 , message = "Name은 2글자 이상 입력해 주세요")
        private String name;
    
        @Past(message = "등록일은 미래 날짜를 입력하실 수 없습니다.")
        private Date joinDate;
    
        //@JsonIgnore
        private String password;
        //@JsonIgnore
        private String ssn;
    }

     

    @JsonFilter("UserInfo")추가 했다. 

     

    @GetMapping("/users/{id}")
        public MappingJacksonValue retrieveUser(@PathVariable int id){
            User user = service.findOne(id);
    
            //1. 단순하게 검색되 데이터를 반환해주는 것이 아니다.
            AdminUser adminUser = new AdminUser();
            if(user == null){
                throw new UserNotFoundException(String.format("ID[%s] not found",id));
            }else{
                BeanUtils.copyProperties(user,adminUser);
            }
    
            //2. SimpleBeanPropertyFilter를 사용
            SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                    .filterOutAllExcept("id","name","joinDate","ssn");
    
            FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo",filter);
    
            MappingJacksonValue mapping =  new MappingJacksonValue(adminUser);
            mapping.setFilters(filters);
    
            return mapping;
        }

     

    AdminUser 클래스에 위에 사용한 @JsonFilter("UserInfo")와 같은 필터를 사용하기 위해서는 SimpleBeanPropertyFilter와 같은 객체가 필요하다.  해당 객체 인스턴스를 SimpleBeanPropertyFilter.filterOutAllException()메서드로 생성해준뒤 해당 메서드의 인자로 response로 반환할 User객체의 속성을 String 형태로 넘겨준다. 즉 필터가 할 일에 대한 내용을 해당 객체 인스턴스로 생성하는 것이다. 

     

    그리고 FilterProvider 객체 인스턴스를 생성해 "UserInfor"라는 필터 이름에 필터링 정보인 SimpleBeanPropertyFilter 의 인스턴스 filter필터를 연결해준다. 이것은 다시 FilterProvider 타입의 filters라는 인스턴스에 담긴다. 

     

    마지막으로 MappingJacksonValue 의 인스턴스를 생성해 인자로 adminUser 인스턴스를 넣어주고  MappingJacksonValue타입은 인스턴스 mapping에 setFilters메서드를 이용해 adminUser인스턴스와 filters를 연결해준다. 

     

    전제 사용자 가져올때 Dynamic Filter추가

      @GetMapping("/users")
        public MappingJacksonValue retrieveAllUser(){
            List<User> users = service.findAll();
    
            List<AdminUser> adminUsers = new ArrayList<>();
            AdminUser adminUser = null;
    
            for(User user: users){
                adminUser = new AdminUser();
                BeanUtils.copyProperties(user,adminUser);
                adminUsers.add(adminUser);
            }
    
            //2. SimpleBeanPropertyFilter를 사용
            SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                    .filterOutAllExcept("id","name","joinDate","ssn");
    
            FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo",filter);
    
            MappingJacksonValue mapping =  new MappingJacksonValue(adminUsers);
            mapping.setFilters(filters);
    
            return mapping;
        }

     

     

    댓글

Designed by Tistory.