Valid vs Validated

목표 @Valid와 @Validated의 차이점을 이해한다. @Valid와 @Validated의 사용법을 이해한다. 공통점 컨트롤러 파라미터에 @Valid, @Validated 를 붙이면, 글로벌 Validator로 검증이 된다. Spring Boot는 JSR-380의 기본 구현인 Hibernate Validator를 글로벌 Validator로 사용한다. 컨트롤러에서 @Valid vs @Validated @Valid는JSR-303의 어노테이션으로 메서드 수준의 유효성 검사에 사용한다. @Validated는 Spring에서 @Valid를 확장하기 위해 만든 어노테이션이다. @Validated는 validation group이라는 기능을 제공한다. 컨트롤러에서는 validation group 기능 유무 차이가 존재한다. 컨트롤러에서 @ReqeustBody, @ModelAttribute, @RequestPart가 붙어있는 파라미터는 ArgumentResolver에서 처리되며, @Valid나 @Validated가 있을 때 검증 실패시 MethodArgumentNotValidException 예외가 발생한다....

2025-02-25 · 2 min · 423 words

Spring에서 로깅 시 각 요청마다 식별자 만들기

배경 로그 모니터링할 때 각 로그가 서로 연관이 있는지, 한 요청에 대해서 어떤 순서로 로그가 남았는지 확인이 필요한 상황이 있었다. 로그를 남길 때 요청마다 고유한 식별자를 만들어서 로그에 같이 남도록 구현한다. 현재 프로젝트는 Logback을 사용해 로그를 남기고 있다. MDC MDC는 log4j나 logback이 로그를 남길 때 Appedner가 접근할 수 있는 데이터로 Map과 같은 구조로 작성할 수 있다. MDC의 고주논 ThreadLocal을 사용하여 실행 중인 스레드에 내부적으로 연결된다. 아래 사진과 같이 MDC 클래스 내에는 MDCAdapter 라는 인터페이스의 static 필드를 가지고 있다....

2025-02-20 · 1 min · 184 words

Spring Framework에서 존재하지 않는 URL로 요청 시 처리 과정

목표 Spring Boot에서 존재하지 않는 URL로 요청 시 404 응답한다. 어떤 내부 동작으로 404 응답을 하게 되는지 이해한다. ResourceHttpRequestHandler Spring Boot 기본 설정 기준으로 모든 요청에 대해서, 처리할 다른 핸들러가 없으면 ResourceHttpRequestHandler로 정적 리소스를 찾아서 반환하려고 시도한다. 해당 핸들러는 spring.web.resources.add-mappings를 false로 설정하는 등의 방법으로 사용하지 않게 설정하여, 정적 리소스를 응답하지 않도록 설정할 수 있다. 해당 핸들러 사용 유무에 따라 404 응답을 처리하는 위치가 달라진다. ResourceHttpRequestHandler를 사용하는 경우 처리 과정 Spring Framework 6....

2024-10-16 · 1 min · 190 words

특정 Endpoint만 Interceptor를 거치도록 구현

아래와 같이 인터셉터를 추가할 때 addPathPatterns 를 통해 일부 경로에 대해서만 인터셉터를 처리할 수 있다. @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AdminAccessInterceptor(administratorService)) .addPathPatterns("/api/games/**", "/api/tags/**", "/api/sliders/**", "/api/admins/**"); } } 하지만 WAS가 HTTP를 받았을 때 항상 IP가 웹 서버(NGINX)의 IP로 인식되는 문제가 있었다. 문제 해결 WAS는 중간에 있는 리버스 프록시의 IP를 인식하는 문제가 있었다. 보통 요청을 보낸 Client의 IP 를 확인하기 위해서 X-Forwarded-For 헤더를 사용한다고 한다....

2024-09-15 · 1 min · 186 words

쿼리 파라미터가 여러 개인 경우 어떻게 처리할까

일반적으로 두 가지 방법을 사용한다. http://localhost:8080/api/foos?id=1,2,3 http://localhost:8080/api/foos?id=1&id=2 위의 두 예시모두 Spring MVC에서 자동으로 매핑시켜주기 때문에 걱정없이 사용할 수 있다. @GetMapping("/api/foos") @ResponseBody public String getFoos(@RequestParam List<String> id) { return "IDs are " + id; } 참고 자료 https://stackoverflow.com/questions/2602043/rest-api-best-practice-how-to-accept-list-of-parameter-values-as-input

2024-09-15 · 1 min · 37 words

요청에 파일 포함 시키기

Spring에서 request body로 파일이 오면 어떻게 처리할 수 있는지 방법이 필요했다. HTTP에서는 한 개의 요청에 여러 개의 body를 담을 수 있는 multipart라는 content-type을 사용한다. 이전에 책으로 읽은 경험이 있는데 잊고 있었다…(링크) Spring에서 Multipart로 받은 파일은 다음과 같이 다룰 수 있다. // Controller @PostMapping("/") public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { storageService.store(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/"; } 참고 자료 https://spring.io/guides/gs/uploading-files/ https://www.baeldung.com/spring-file-upload

2024-09-15 · 1 min · 68 words

서비스 클래스가 다른 서비스 클래스를 의존해도 되는가

서비스가 다른 서비스를 참조하도록 설계를 하는게 좋을지, 아니면 서비스는 다른 서비스를 의존하지 않고, 여러 개의 DAO를 가지고 오도록 하면 좋을지에 대한 고민이였다. 서비스가 다른 서비스를 가지는 경우의 장단점은 명확했다. 장점: 유효성 검사 등 비즈니스 로직의 중복 코드를 제거할 수 있다. 단점: 서비스 간의 순환 참조가 발생할 수도 있다. → 내가 내린 결론 중복 코드 제거의 장점을 가져가기 위해 서비스가 다른 서비스를 가질 수 있는 방법을 선택 순환 참조가 발생하지 않게, 서비스간의 계층을 잘 설계해야된다....

2024-09-15 · 1 min · 74 words

WebMvcConfigurer

WebMvcConfigurer 를 구현하면, WebMvc의 설정을 할 수 있다. addViewControllers 를 통해 정적인 웹을 url에 매핑 시킬 수 있다. @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("home"); } } addInterceptors 를 통해 오는 요청에 인터셉트를 할 수 있다. @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LocaleChangeInterceptor()); registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**"); registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*"); } } addArgumentResolvers를 통해 커스텀 어노테이션을 인자로 매핑시킬 수 있다....

2024-09-15 · 1 min · 134 words

Spring으로 WebSocket 시작하기

Spring으로 WebSocket 시작하기(STOMP) Maven 의존성 추가 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>5.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>5.2.2.RELEASE</version> </dependency> 최신 버전은 여기서 또한 JSON 사용하여 메시지를 전달 하려면 Jackson 의존을 추가해야된다. Spring Boot의 경우에는 추가할 필요 없을 것으루 추정된다. <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.2</version> </dependency> Spring에서 WebSocket 활성화 AbstractWebSocketMessageBrokerConfigurer 클래스를 상속하고 @EnableWebSocketMessageBroker 어노테이션을 붙여준다. @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config....

2024-09-15 · 3 min · 476 words

Spring Validation과 lombok 예외 처리

티 spring validation의 @NotNull과 lombok의 @NonNull의 차이를 알아본다. @NotNull Spring MVC Spring boot에서 컨트롤러에 @Valid와 함께쓰면 검증을 하게된다. 만약 조건을 만족하지 못하면 MethodArgumentNotValidException 이 발생한다. 하지만 예외 메시지가 직접 담기는 것이 아니라 아래 코드와 같이 메시지를 뽑아내야된다. private List<ExceptionDto> extractErrorMessages(final MethodArgumentNotValidException exception) { return exception.getBindingResult() .getAllErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .map(ExceptionDto::new) .collect(Collectors.toList()); } JPA JPA에서도 @NotNull을 사용할 수 있다. @Entity 클래스의 필드에 @NotNull이 있으면 동작을한다. 만약 조건을 만족하지 않으면 ConstraintViolationException 이 발생한다. 하지만 예외 메시지가 직접 담기는 것이 아니라 아래 코드와 같이 메시지를 뽑아내야된다....

2024-09-15 · 1 min · 114 words

Spring MVC로 RSS 피드 API 만들기

의존 추가 Spring에서 RSS 지원은 ROME이라는 프레임워크를 기반으로 한다. <dependency> <groupId>com.rometools</groupId> <artifactId>rome</artifactId> <version>1.10.0</version> </dependency> 피드 구현 AbstractRssFeedView 를 구현해야된다. 2가지 메서드를 오버라이드해야된다. buildFeedMetadata는 해당 피드에 대한 정보를 반환하고, buildFeedItems는 피드에 있는 아이템들을 반환한다. public class PostRssView extends AbstractRssFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { feed.setTitle("Baeldung RSS Feed"); feed.setDescription("Learn how to program in Java"); feed.setLink("http://www.baeldung.com"); } @Override protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) { Item entryOne = new Item(); entryOne....

2024-09-15 · 1 min · 115 words

Spring MVC 동작 과정

스프링 @MVC 스프링은 DispatcherServlet과 7가지 전략을 기반으로 한 MVC 프레임워크를 제공한다. 애노테이션을 중심으로 한 새로운 MVC의 확장 기능은 @MVC라는 별칭으로도 불린다. 스프링 웹 기술과 스프링 MVC 엔터프라이즈 애플리케이션의 가장 앞단에서 클라이언트 시스템과 연동하는 책임을 맡고 있는 것이 웹 프레젠테이션 계층이다. Java의 웹 프레젠테이션 계층의 기술과 프레임워크는 매우 다양하여 선택의 폭이 넓다. 스프링은 기본적으로 웹 계층과 다른 계층을 깔끔하게 분리해서 스프링 애플리케이션이지만 웹 계층을 다른 기술로 대체하더라도 아무런 문제가 없게 만들 수 있다....

2024-09-15 · 5 min · 856 words

Request Parameter 기본값 지정하기

@RequestParam 어노테이션을 이용해서 지정할 수 있다. defaultValue라는 속성에 자신이 원하는 기본값을 지정해주면 된다. @GetMapping("/api/foos") @ResponseBody public String getFoos(@RequestParam(defaultValue = "test") String id) { return "ID: " + id; } 참고 자료 https://www.baeldung.com/spring-request-param

2024-09-15 · 1 min · 32 words

request body로 enum 값 받기

목표 사용자가 request body로 string 값을 보냈을 때, enum으로 자동으로 매핑시켜서 컨트롤러 파라미터로 넘어오기를 원한다. Spring 기본 처리 Spring MVC에서는 StringToEnumConverterFactory 클래스가 String을 Enum 객체로 변환하려고 시도한다. 이 때, Enum.valueOf() 메소드를 통해서 변환을 시도한다. 이 때 일치하는 Enum 타입이 없다면, ConversionFailedException이 발생한다. Custom Converter 사용하기 Converter 인터페이스를 이용해서 변환을 커스텀 할 수도 있다. public class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> { private final Class<T> type; public StringToEnumConverter(Class<T> type) { this....

2024-09-15 · 1 min · 88 words

Pagination 커스터마이징

@Configuration public class PageableConfig implements WebMvcConfigurer { private static final int MAX_PAGE_SIZE = 16; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(); resolver.setOneIndexedParameters(true); resolver.setMaxPageSize(MAX_PAGE_SIZE); argumentResolvers.add(resolver); } } setOneIndexedParameters(true): 페이징 인덱스가 1부터 시작하도록 설정. 이 경우에도 PageRequest.of(1, 16) 을 호출하면 두 번째 페이지가 가져와지니 주의 해야된다. Pageable을 직접 커스터마이징 하는 것이 아니라, 컨트롤러에서 Pageable을 파라미터로 받을 때 1이 자동으로 0으로 바뀌는 것으로 추측 된다. setMaxPageSize(MAX_MAGE_SIZE): 한 페이지로 불러 올 수 있는 최대 크기를 지정한다....

2024-09-15 · 1 min · 95 words

Pagination 1부터 시작하도록 구현

2024-09-15 · 0 min · 0 words

Locale 처리 방식

목표 locale이 무엇인지 이해한다. Spring에서 locale을 어떻게 처리하는지 이해한다. Locale 지리적, 문화적, 정치적 지역을 나타내는 역할을 가지는 클래스 언어, 국가 등을 표현한다. Spring에서 Locale 처리 방법 Spring 애플리케이션에서 요청자의 지역에 따라 다른 언어를 표시하기 위해서는 현재 Locale을 결정하는 방법이 있어야 된다. DispatcherServlet은 locale resolver를 찾고, 찾은 locale resolver를 통해서 locale을 지정하려고 시도한다. RequestContext.getLocale() 메서드 호출을 통해 locale resolver가 설정한 locale을 확인해 볼 수 있다. LocaleResolver LocaleResolver: locale을 선택하기 위한 전략을 나타내는 전략 인터페이스 대표적인 기본 구현체 AcceptHeaderLocaleResolver: HTTP 요청의 Accept-Language 헤더를 통해 locale을 선택한다....

2024-09-15 · 1 min · 185 words

Interceptor

특정 IP만 접근 허용하기 @Override public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) { String clientIp = request.getRemoteAddr(); administratorService.validateIp(clientIp); } getRemoteAddr() 를 통해 현재 요청을 보낸 client의 IP를 확인할 수 있다. 특정 HTTP Method만 허용하기 @Override public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) { if (!"GET".equals(request.getMethod())) { throw new IllegalArugmentException(); } } getMethod() 메소드를 통해 현재 요청을 보낸 HTTP 메소드를 알 수 있다.

2024-09-15 · 1 min · 70 words

HTTP Client 인터페이스

목표 Spring 6의 HTTP Client 인터페이스가 무엇인지 이해한다. Spring 6의 HTTP Client 인터페이스의 사용법을 이해한다. HTTP Client 인터페이스 Spring Framework 6와 Spring Boot 3부터 사용 가능한 기능 Feign과 같은 클라이언트 라이브러리와 같이 인터페이스를 기반으로 HTTP 요청을 보내고 응답을 받을 수 있는 기능을 제공한다. 사용법 인터페이스 정의 인터페이스 메소드에 어노테이션을 붙이면 된다. 사용 가능한 파라미터 URI: 요청에 대한 URL을 동적으로 설정한다. HttpMethod: 요청에 대한 HTTP 메서드를 동적으로 설정한다. @RequestHeader: 요청 헤더 @RequestBody: 요청 바디 @RequestParam: 요청 매개변수 @CookieValue: 쿠키 이름과 값 사용 가능한 반환 타입 void, Mono<Void> HttpHeaders, Mono<HttpHeaders> <T>, Mono<T> <T>, Flux<T> ResponseEntity<T>, Mono<ResponseEntity<T>> Mono<ResponseEntity<Flux<T>> interface BooksService { @GetExchange("/books") List<Book> getBooks(); @GetExchange("/books/{id}") Book getBook(@PathVariable long id); @PostExchange("/books") Book saveBook(@RequestBody Book book); @DeleteExchange("/books/{id}") ResponseEntity<Void> deleteBook(@PathVariable long id); } 클라이언트 생성 HttpServiceProxyFactory 를 통해서 클라이언트를 생성한다....

2024-09-15 · 1 min · 172 words

Hibernate Validator로 단위 테스트하기

목표 Spring MVC에서 컨트롤러의 파라미터로 사용되는 dto들의 어노테이션 기반 validation을 단위 테스트한다. @SpringBootTest 등을 사용해서 테스트가 느려지게 하지 않는다. 방법 dto를 검증하기위해서는 Validator가 필요하다. Validator는 ValidatorFacotry를 통해서 생성할 수 있다. val validatorFactory = Validation.buildDefaultValidatorFactory() val validator = validatorFactory.validator 아래와 같은 User 클래스가 있다면, validator를 통해서 검증을 통과했는지 테스트 해볼 수 있다. import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Email; public class User { @NotNull(message = "Name cannot be null") private String name; @AssertTrue private boolean working; @Size(min = 10, max = 200, message = "About Me must be between 10 and 200 characters") private String aboutMe; @Min(value = 18, message = "Age should not be less than 18") @Max(value = 150, message = "Age should not be greater than 150") private int age; @Email(message = "Email should be valid") private String email; // standard setters and getters } // test code Set<ConstraintViolation<User>> violations = validator....

2024-09-15 · 1 min · 162 words