기본 콘텐츠로 건너뛰기

keytool 사용법

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 -keystore cacerts.jks -keypass changeit

Spring boot hello world(5)

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/

댓글

이 블로그의 인기 게시물

Tomcat : context.xml 파일 사용

context.xml 파일을 사용하여 JNDI 등록 사용하는 방법 1. server.xml 파일에 JNDI 등록  server.xml 파일의 GlobalNamingResources노드에 Resource를 등록하고 Host 노드 하위 Context 노드에 ResourceLink노드를 등록하여 많이 사용했다. tomcat 5.5 이전 방식이다. server.xml ... <globalnamingresources>     <!-- Editable user database that can also be used by          UserDatabaseRealm to authenticate users     -->     <resource auth="Container" driverclassname="oracle.jdbc.OracleDriver" maxidle="4" maxtotal="8" name="global.db" password="password" type="javax.sql.DataSource" url="jdbc:oracle:thin:@localhost:1521:xe" username="user">   </resource> </globalnamingresources> ... 2. context.xml 파일 분리 위 방식처럼 server.xml 파일에 모든 context의 정보를 추가하는 방식은 서버에 대한 제어권이 없으면 정보를 변경하기 힘들다. Tomcat 5.5? 이후부터는 context.xml 파일을 분리하여 webapplication에서 별도로 자원을 등록 사용할 수있도록 제공한다. META-INF/context.xml 파일을 위치시키면 Tomcat은 구동시 META-INF 폴더의 context.xml 파일을

Jeus was jsp 컴파일된 파일 경로

WAS 구동시 jsp 에러가 났을때 jsp가 컴파일된 파일을 확인하여 디버깅한다. jeus was jsp 컴파일된 파일 경로 %WAS_HOME%/WEBAPP/WEB-INF/.warjspwork/jeus_jspwork/ jsp 에러의 예) javax.servlet.ServletException: String index out of range: -13 at javax.servlet.http.HttpServlet.service(Unknown Source) at jeus.servlet.jsp.JspServletWrapper.executeServlet(JspServletWrapper.java(Compiled Code)) at jeus.servlet.engine.RequestDispatcherImpl.processCommonRequestDispatcher(RequestDispatcherImpl.java(Compiled Code)) at jeus.servlet.engine.RequestDispatcherImpl.doForward(RequestDispatcherImpl.java(Compiled Code)) at jeus.servlet.engine.RequestDispatcherImpl.forward(RequestDispatcherImpl.java(Compiled Code)) at com.tomato.jef.servlet.BaseServlet.doDispatch(BaseServlet.java(Inlined Compiled Code)) at com.tomato.jef.bizframe.servlet.XecureHTMLMainServlet.doProcess(XecureHTMLMainServlet.java(Compiled Code)) at com.tomato.jef.bizframe.servlet.XecureHTMLMainServlet.doSessionProcess(Xec

Vmware Fusion Window가 정상 종료가 되지 않을때

* 1번 방법   1. VMWare가 실행된 그 상태에서 Option키를 누르고 위에 메뉴바에서 Virtual Machine 메뉴를 클릭합니다.  2. 종료 메뉴가 Force 메뉴로 바뀌어 있습니다. Force shut down 이나 Force restart 합니다.  * 2번 방법   1. 유틸리티 - 활성 상태 보기로 들어가서 vmware 단어가 포함된 모든 프로세스를 강제종료 합니다.    2. 가상머신의 파일을 찾아갑니다. (eg. Documents/Virtual Machines/ 안에 있습니다.) 그리고 가상머신 파일의 오른쪽을 눌러 "패키지 내용 보기"로 들어갑니다.  3. ".vmem" 확장자로 된 모든 파일을 쓰레기통에 버리고 난 후 모두 비웁니다.  4. 다시 VMWare을 실행합니다. 어떤 오류가 나면 그냥 Disgard 하면됩니다.