keytool.exe -genkey -alias juheon -keyalg RSA -keypass 1234qwer -storepass 1234qwer -keystore juheon.jks keytool.exe -export -alias juheon -storepass 1234qwer -file juheon.cer -keystore juheon.jks keytool.exe -importkeystore -srckeystore juheon.jks -destkeystore juheon.key.p12 -deststoretype PKCS12 D:\Java\32bit\jdk1.5.0_22\jre\lib\security\cacert D:\Java\32bit\jdk1.5.0_22\bin\keytool.exe -import -alias tsasample -keystore D:\Java\32bit\jdk1.5.0_22\jre\lib\security\cacert -file D:\workspace\Servers\tsa-server-web-config\tomcat.cer keytool -import -alias somansa -keystore C:\Program Files\Java\jdk1.8.0_102\jre\lib\security\cacert -file c:\somansa.cer #인증서 생성 keytool -genkey -alias keyAlias -keyalg RSA -keypass changeit -storepass changeit –validity 9999 -keystore keystore.jks keytool -export -alias keyAlias -storepass changeit -file server.cer -keystore keystore.jks keytool -import -v -trustcacerts -alias keyAlias -file server.cer ...
Exception
예외 처리는 중요하지만 활용을 잘하지 않는다. 제일 많이 쓰는데가 자원 해제하는 곳 정도?
Spring boot에서 exception 처리 예제
Exception 생성
조회 결과가 없을 때 처리를 위한 exception을 만든다. @ResponseStatus(HttpStatus.NOT_FOUND) 어노테이션을 붙이면 response status code가 해당 코드로 response를 내린다.
@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8130371070858589559L;
public RecordNotFoundException(String exception) {
super(exception);
}
}
컨트롤러에서 결과가 없을 때 생성한 "RecordNotFoundException" 예외를 던진다.
@GetMapping("/{id}")
public ResponseEntity getEmployeeById(@PathVariable("id") Long id) {
Employee entity = employeeService.getEmployeeById(id);
logger.info("Result : {}", entity);
if(entity == null) {
throw new RecordNotFoundException("Invalid employee id: " + id);
}
return new ResponseEntity(entity, new HttpHeaders(), HttpStatus.OK);
}
공통 예외 처리
위에서 생성해서 던지는 예외를 해당 클래스에서 처리해도 되지만 예외는 다양한 곳에서 사용되는게 대부분으므로 하나또는 여러개의 클래스에서 처리하는것이 일반적이다.Custom 예외를 처리할 클래스를 생성한다. 클래스에 @ControllerAdvice 어노테이션을 붙이면 컨트롤러에서 던진 exception을 공통으로 처리할 수 있다.
ResponseEntityExceptionHandler 클래스를 상속 받으면 Spring에서 기본적으로 처리하는 exception들을 상속받아 재정의 할 수 있다. (현재 Spring boot 2.2.2에서 테스트 결과 ResponseEntityExceptionHandler 상속 받는 경우 REST API 형태의 에러를 Response를 못받고 있다. 좀더 확인해봐야함.)
함수를 생성하고 RecordNotFoundException 예외를 처리할 수 있게 @ExceptionHandler(RecordNotFoundException.class) 어노테이션을 추가한다.
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Server Error", details);
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(RecordNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Record Not Found", details);
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
예외 처리하면서 결과를 리턴할 클래스를 만든다.
@XmlRootElement(name = "error")
public class ErrorResponse {
public ErrorResponse(String message, List details) {
super();
this.message = message;
this.details = details;
}
private String message;
private List details;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List getDetails() {
return details;
}
public void setDetails(List details) {
this.details = details;
}
}
위 코드까지 작성하고 존재하지 않는 아이디를 조회하는 API를 호출하면 ErrorResponse에 정의된 형식으로 Json response가 내려온다.
{
"message": "Record Not Found",
"details": [
"Invalid employee id: 10909"
]
}
데이터 입력 시 사용자로 부터 받는 데이터 Valid 기능을 사용해보자. EmployeeDto 클래스를 만든다.
@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeDto {
private Long id;
@NotEmpty(message = "first name must not be empty")
private String firstName;
@NotEmpty(message = "last name must not be empty")
private String lastName;
@NotEmpty(message = "email must not be empty")
@Email(message = "email should be a valid email")
private String email;
//getter, setter
}
EmployeeController 클래스의 createOrUpdateEmployee 함수 input parameter를 수정한다.
@PostMapping
public ResponseEntity createOrUpdateEmployee(@Valid EmployeeDto employee) {
Employee updated = employeeService.createOrUpdateEmployee(employee);
return new ResponseEntity(updated, new HttpHeaders(), HttpStatus.OK);
}
CustomExceptionHandler 클래스에 handleMethodArgumentNotValid 함수를 오버라이딩한다.
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for (ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
여기까지 하면 사용자 데이터 입력 시 필수값이 없으면 필수값 없다고 respnse가 내려온다고 대부분의 사이트에 적혀있었다. 하지만 실상 response는 내려오지 않고 console에 아래와 같은 로그만 찍힌다.
01:58:26.964 [http-nio-80-exec-1] WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors
Field error in object 'employeeDto' on field 'firstName': rejected value [null]; codes [NotEmpty.employeeDto.firstName,NotEmpty.firstName,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employeeDto.firstName,firstName]; arguments []; default message [firstName]]; default message [first name must not be empty]
Field error in object 'employeeDto' on field 'lastName': rejected value [null]; codes [NotEmpty.employeeDto.lastName,NotEmpty.lastName,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employeeDto.lastName,lastName]; arguments []; default message [lastName]]; default message [last name must not be empty]]
MethodArgumentNotValidException 이 발생한게 아니라 BindException가 발생했다. 그리고 그에 대한 response도 화면으로 내리지 않았다. ResponseEntityExceptionHandler 클래스를 상속 받지 않으면 에러 메시지는 response로 내린다. (이부분은 내부 소스코드 확인필요)
따라서 위와 같은 문제가 발생할 경우 handleMethodArgumentNotValid 함수가 아니라 handleBindException 함수를 재정의 해야한다.
@Override
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> details = new ArrayList<>();
for (ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
Spring Exception 관련 잘 정리된 사이트 https://supawer0728.github.io/2019/04/04/spring-error-handling/
댓글
댓글 쓰기