Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 프로그래머스
- Kafka
- Intellij
- DevOps
- rabbitmq
- Github Actions
- CI/CD
- Redis
- aop
- AWS
- querydls
- 아키텍처
- testcode
- 테스트 코드
- Til
- 유효성 검사
- trouble shooting
- docker
- 객체지향원칙
- EC2
- JPA
- 멀티 모듈
- MSA
- 어노테이션
- spring boot
- algorithm
- Java
- springboot
- JWT
- swagger
Archives
- Today
- Total
개발노트
25.02.21 Java 커스텀 어노테이션 본문
개요
controller에서 스웨거 @Opration 설정을 하고있었는데 관련 설정이 너무 길어 controller의 코드가 지저분한게 상당히
마음에 걸렸다. 그래서 이문제를 해결하고자 커스텀 어노테이션을 만들어 설정을 다른 파일에서 관리하고 컨트롤러에서 깔끔하게 적용하게 변경했다. 커스텀 어노테이션 작성에 대해 정리해본다.
어노테이션이란?
메타데이터를 제공하는 Java의 기능으로, 코드에 추가 정보를 첨부하여 컴파일러나 프레임워크가 이를 활용할 수있도록 제공한다. 어노테이션은 클래스,메섣,필드 등에 부착할 수있으며, 런타임 또는 컴파일 타임에 특정한 동작을 수행한다.
커스텀 어노테이션 만드는 방법
- @interface 키워드를 사용하여 어노테이션을 생성한다.
- @Retention 어노테이션을 사용하여 어노테이션의 유지 정책을 결정한다.
- @Target 어노테이션을 사용하여 적용할 수 있는 대상을 설정한다.
Swagger 문서화 설정을 제공하는 커스텀어노테이션 생성
package com.sparta.delivery.domain.user.swagger;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import java.lang.annotation.*;
@Target({ElementType.METHOD}) // 메서드에만 적용할 수 있음
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지됨
@Documented // javadoc 과 같은 문서에 포함되도록 지정
public @interface UserSwaggerDocs {
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원가입", description = "회원가입 요청을 처리합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "회원 가입 성공"),
@ApiResponse(responseCode = "400", description = "유효성 검증 실패"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface SignUp {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "로그인", description = "사용자 로그인 후 JWT를 발급합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 성공, JWT 토큰 발급"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"),
@ApiResponse(responseCode = "409", description = "이미 로그인되어 있거나 비정상 로그아웃됨"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface SignIn {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "로그아웃", description = "사용자의 RefreshToken을 제거하고 로그아웃 처리합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그아웃 성공"),
@ApiResponse(responseCode = "400", description = "쿠키에서 RefreshToken을 찾을 수 없음"),
@ApiResponse(responseCode = "401", description = "잘못된 토큰이 전달됨"),
@ApiResponse(responseCode = "409", description = "이미 로그아웃된 상태"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface Logout {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원 정보 조회", description = "유저 ID를 통해 회원 정보를 조회합니다.")
@Parameters({
@Parameter(name = "id", description = "조회할 회원의 UUID", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479")
})
@ApiResponses({
@ApiResponse(responseCode = "404" , description = "리소스를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface GetUser {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원 목록 검색", description = "검색 조건을 기반으로 회원 목록을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "404" , description = "리소스를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface SearchUsers {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원 정보 수정", description = "회원의 정보를 수정합니다.")
@Parameters({
@Parameter(name = "id", description = "수정할 회원의 UUID", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479")
})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "회원 정보 수정 성공"),
@ApiResponse(responseCode = "403", description = "수정 권한 없음"),
@ApiResponse(responseCode = "404", description = "해당 회원 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface UpdateUser {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원 권한 수정", description = "회원의 권한을 변경합니다.")
@Parameters({
@Parameter(name = "id", description = "권한을 수정할 회원의 UUID", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479")
})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "회원 정보 수정 성공"),
@ApiResponse(responseCode = "403", description = "수정 권한 없음"),
@ApiResponse(responseCode = "404", description = "해당 회원 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface UpdateRole {}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Operation(summary = "회원 삭제", description = "회원 정보를 논리적으로 삭제합니다.")
@Parameters({
@Parameter(name = "id", description = "삭제할 회원의 UUID", example = "f47ac10b-58cc-4372-a567-0e02b2c3d479")
})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "회원 삭제 성공"),
@ApiResponse(responseCode = "403", description = "삭제 권한 없음"),
@ApiResponse(responseCode = "404", description = "회원 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@interface DeleteUser {}
}
Controller에 적용
package com.sparta.delivery.domain.user.controller;
import com.sparta.delivery.config.auth.PrincipalDetails;
import com.sparta.delivery.domain.user.dto.*;
import com.sparta.delivery.domain.user.service.UserService;
import com.sparta.delivery.domain.user.swagger.UserSwaggerDocs;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Tag(name ="User API", description = "회원 관련 API")
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@UserSwaggerDocs.SignUp
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody @Valid SignupReqDto signupReqDto, BindingResult bindingResult){
if (bindingResult.hasErrors()){
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ValidationErrorResponse(bindingResult));
}
return ResponseEntity.status(HttpStatus.OK)
.body(userService.signup(signupReqDto));
}
@UserSwaggerDocs.SignIn
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser (@RequestBody @Valid LoginRequestDto loginRequestDto,
BindingResult bindingResult,
HttpServletResponse response){
if (bindingResult.hasErrors()){
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ValidationErrorResponse(bindingResult));
}
JwtResponseDto jwtResponseDto = userService.authenticateUser(loginRequestDto);
String accessToken = jwtResponseDto.getAccessToken();
String refreshToken = jwtResponseDto.getRefreshToken();
response.addCookie(createCookie("refresh", refreshToken));
return ResponseEntity.status(HttpStatus.OK)
.header(HttpHeaders.AUTHORIZATION, accessToken)
.build();
}
@UserSwaggerDocs.Logout
@PostMapping("/logout")
public ResponseEntity<?> removeRefreshToken(@CookieValue(name = "refresh", defaultValue = "")String refreshToken){
if (refreshToken.isEmpty()){
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Refresh token not found in cookies");
}
userService.removeRefreshToken(refreshToken);
return ResponseEntity.status(HttpStatus.OK)
.build();
}
@UserSwaggerDocs.GetUser
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable("id")UUID id){
return ResponseEntity.status(HttpStatus.OK)
.body(userService.getUserById(id));
}
@UserSwaggerDocs.SearchUsers
@GetMapping
public ResponseEntity<?> getUsers(@RequestBody UserSearchReqDto userSearchReqDto){
return ResponseEntity.status(HttpStatus.OK)
.body(userService.getUsers(userSearchReqDto));
}
@UserSwaggerDocs.UpdateUser
@PatchMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable("id") UUID id,
@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestBody @Valid UserUpdateReqDto userUpdateReqDto,
BindingResult bindingResult){
if (bindingResult.hasErrors()){
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ValidationErrorResponse(bindingResult));
}
return ResponseEntity.status(HttpStatus.OK)
.body(userService.updateUser(id, principalDetails, userUpdateReqDto));
}
@UserSwaggerDocs.UpdateRole
@PatchMapping("/{id}/role")
public ResponseEntity<?> updateRole(@PathVariable("id") UUID id,
@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestBody @Valid UserRoleUpdateReqDto userRoleUpdateReqDto,
BindingResult bindingResult){
if (bindingResult.hasErrors()){
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ValidationErrorResponse(bindingResult));
}
return ResponseEntity.status(HttpStatus.OK)
.body(userService.updateRole(id, principalDetails, userRoleUpdateReqDto));
}
@UserSwaggerDocs.DeleteUser
@PatchMapping("/{id}/delete")
public ResponseEntity<?> deleteUser(@PathVariable("id") UUID id,
@AuthenticationPrincipal PrincipalDetails principalDetails) {
userService.deleteUser(id, principalDetails);
return ResponseEntity.status(HttpStatus.OK)
.build();
}
private Map<String, Object> ValidationErrorResponse(BindingResult bindingResult) {
List<Map<String, String>> errors = bindingResult.getFieldErrors().stream()
.map(fieldError -> Map.of(
"field", fieldError.getField(),
"message", fieldError.getDefaultMessage(),
"rejectedValue", String.valueOf(fieldError.getRejectedValue()) // 입력된 값도 포함
))
.toList();
return Map.of(
"status", 400,
"error", "Validation Field",
"message", errors
);
}
// 쿠키 생성 메소드
private Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value); // 쿠키 객체 생성
cookie.setMaxAge(24 * 60 * 60); // 쿠키의 유효 기간 설정 (60시간)
// cookie.setSecure(true); // https 환경에서만 쿠키 사용
cookie.setPath("/"); // 모든 경로에서 쿠키 사용 가능
cookie.setHttpOnly(true); // 자바스크립트에서 쿠키 접근 불가
return cookie; // 생성한 쿠키 반환
}
}
'Java' 카테고리의 다른 글
25.02.26 객체 지향 설계의 5가치 원칙 S.O.L.I.D (0) | 2025.02.26 |
---|