ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring jsr305
    카테고리 없음 2023. 4. 23. 14:05
    오늘은 Spring5 부터 지원하는 jsr305 어노테이션에 대해서 알아보자. 많은 이야기는 아니지만 Spring 에서 이 어노테이션을 몇가지 기능을 지원해 주고 있다. Spring에서 사용하는 Nullable, NonNull, NonNullApi 어노테이션은 jsr305의 메타 어노테이션을 사용한다. 실제 간단히 코드를 보자면 다음과 같다.
    //...
    import javax.annotation.Nonnull;
    import javax.annotation.meta.TypeQualifierDefault;
    
    //...
    @Nonnull
    @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
    public @interface NonNullApi {
    }
    
    jsr305 어노테이션은 그냥 메타 어노테이션으로만 사용하고 있다. 하지만 Spring 에서는 몇가지 기능을 지원해주고 있으니 알아보도록 하자.

    Controller

    Spring web에서 흔히 파라미터로 받을 경우 사용할 수 있다. @RequestParam 어노테이션을 사용할 경우 required 속성의 기본값은 true이다. 그래서 name이라는 파라미터를 보내지 않을 경우 에러가 발생한다.
    @GetMapping("/")
    public String hello(@RequestParam String name) {
      return "hello " + name + "!";
    }
    
    만약 위와 같이 @RequestParam의 required 속성을 false로 하지 않을 경우 아래와 같이 에러가 발생한다.
    http :8080
    
    HTTP/1.1 400
    Connection: close
    Content-Type: application/json;charset=UTF-8
    Date: Mon, 07 May 2018 12:38:23 GMT
    Transfer-Encoding: chunked
    
    {
        "error": "Bad Request",
        "message": "Required String parameter name is not present",
        "path": "/",
        "status": 400,
        "timestamp": "2018-05-07T12:38:23.120+0000"
    }
    
    물론 required 속성을 false로 해도 되지만 Spring5 부터는 @Nullable 어노테이션을 사용해서 null을 허용해도 된다.
    @GetMapping("/")
    public String hello(@RequestParam @Nullable String name) {
      return "hello " + name + "!";
    }
    
    위와 같이 @Nullable 어노테이션을 사용했을 경우에는 아래와 같이 에러가 발생하지 않는다.
    http :8080
    
    HTTP/1.1 200
    Content-Length: 11
    Content-Type: text/plain;charset=UTF-8
    Date: Mon, 07 May 2018 12:41:14 GMT
    
    hello null!
    

    Endpoint

    위와 비슷한 동일한 맥략이다. 커스텀한 Endpoint를 만들 경우에 @Nullable 어노테이션을 사용할 수 있다.
    @Endpoint(id = "hello")
    @Component
    public class HelloEndpoint {
    
      @ReadOperation
      public String hello(String name) {
        return "hello " + name + "!";
      }
    }
    
    
    만약 위와 같은 코드를 작성했을 경우 name이라는 파라미터를 보내지 않으면 위와 동일하게 에러가 발생한다.
    http :8080/actuator/hello
    
    HTTP/1.1 400
    Connection: close
    Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
    Date: Mon, 07 May 2018 12:44:03 GMT
    Transfer-Encoding: chunked
    
    {
        "error": "Bad Request",
        "message": "Missing parameters: name",
        "path": "/actuator/hello",
        "status": 400,
        "timestamp": "2018-05-07T12:44:03.471+0000"
    }
    
    하지만 여기에서도 @Nullable 어노테이션을 작성하여 null을 허용할 수 있다.
    @Endpoint(id = "hello")
    @Component
    public class HelloEndpoint {
    
      @ReadOperation
      public String hello(@Nullable String name) {
        return "hello " + name + "!";
      }
    }
    
    다음과 같이 작성할 경우에는 파라미터를 보내지 않아도 에러가 발생하지 않는다.
    http :8080/actuator/hello
    HTTP/1.1 200
    Content-Length: 11
    Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
    Date: Mon, 07 May 2018 12:45:17 GMT
    
    hello null!
    

    Null injection

    Spring5 에서는 null을 허용하는 주입을 @Nullable 어노테이션을 사용하여 주입하면 된다. 예전에는 @Autowired 어노테이션의 required 속성을 false로 하면 주입하는 Object가 null 이어도 에러가 발생하지 않고 null 그대로 주입한다. 또한 필자는 요즘에 주입받는 Object에 @Autowired를 잘 작성하지 않는다. Spring 4.3 부터는 생성자 혹은 빈의 디펜더시 받는 Object에 @Autowired 가 존재 하지 않아도 자동으로 Spring이 주입을 해주고 있어서 좋은 기능 같다.
    public class PersonService {
      //nothing
    }
    
    @Bean
    ApplicationRunner applicationRunner(@Nullable PersonService personService) {
      return args -> {
      };
    }
    
    //생성자
    public Some(@Nullable PersonService personService) {
      this.personService = personService;
    }
    
    
    위와 같이 PersonService는 빈으로 등록되지 않은 Object이다. 그래서 만약 이 상태로 주입받으려 하면 PersonService 라는 빈이 존재하지 않아 에러가 발생한다. 하지만 이제부터는 @Nullable 어노테이션을 사용해서 null을 허용하면 null이 주입된다. 만약 위와 같이 사용한다면 null check 는 꼭 해줘야 할 것 같다.

    Spring data

    Spring data 프로젝트에서도 jsr305 어노테이션을 지원한다. Spring data에서 query method를 사용할 경우 파라미터와 리턴값에 위와 같은 어노테이션을 작성할 수 있다. 여기서 주의할 점은 해당 패키지안에 package-info.java 를 작성해줘야 한다. 이때 사용하는 어노테이션은 @NonNullApi 어노테이션이다.
    @NonNullApi
    package ml.wonwoo.springjsr305.domain;
    
    import org.springframework.lang.NonNullApi;
    
    위와 같이 작성한다면 기본적으로 파라미터 와 리턴값은 null이 아니어야 한다.
    public interface PersonRepository extends JpaRepository<Person, Long> {
    
      Person findByName(String name);
    }
    
    만약 NonNullApi 사용하고 위와 같이 사용한다면 name에는 null이 될수 없고 반환값도 null이 될 수 없다. 만약 null을 넣거나 null을 리턴한다면 IllegalArgumentException exception이 발생한다. 한번 controller로 테스트를 해보자.
    @GetMapping("/hello")
    public Person helloName(String name) {
      return personRepository.findByName(name);
    }
    
    http :8080/hello
    
    HTTP/1.1 500
    Connection: close
    Content-Type: application/json;charset=UTF-8
    Date: Mon, 07 May 2018 13:09:16 GMT
    Transfer-Encoding: chunked
    
    {
        "error": "Internal Server Error",
        "message": "Parameter of type String at index 0 in PersonRepository.findByName must not be null!",
        "path": "/hello",
        "status": 500,
        "timestamp": "2018-05-07T13:09:16.024+0000"
    }
    
    위와 같이 호출할 경우 파라미터가 null이라고 에러가 발생한다. 이번에는 리턴값이 null인 것으로 호출해보자.
    http :8080/hello name==foo
    
    HTTP/1.1 500
    Connection: close
    Content-Type: application/json;charset=UTF-8
    Date: Mon, 07 May 2018 13:11:29 GMT
    Transfer-Encoding: chunked
    
    {
        "error": "Internal Server Error",
        "message": "Result must not be null!",
        "path": "/hello",
        "status": 500,
        "timestamp": "2018-05-07T13:11:29.367+0000"
    }
    
    이번에는 리턴값이 null이라고 에러가 발생한다. 한번 제대로 된 값으로 호출해보자.
    http :8080/hello name==wonwoo
    
    HTTP/1.1 200
    Content-Type: application/json;charset=UTF-8
    Date: Mon, 07 May 2018 13:12:59 GMT
    Transfer-Encoding: chunked
    
    {
        "id": 1,
        "name": "wonwoo"
    }
    
    정상적으로 호출이된다. 만약 위와 다르게 null을 허용하고 싶다면 이때까지 알아본 @Nullable 어노테이션을 사용하면 된다. 이것은 파라미터와 메서드위에 작성할 수 있다.
    public interface PersonRepository extends JpaRepository<Person, Long> {
    
      Person findByNameIsLike(@Nullable String name);
    
      @Nullable
      Person findByName(String name);
    }
    
    
    위와 같이 한다면 findByNameIsLike 파라미터는 null을 허용하고 즉 findByNameIsLike(null) 같이 메소드를 호출해도 된다. (하지만 해당 메서드는 null을 넣으면 에러가 발생한다. jsr305 때문이 아니라 쿼리 자체가 null을 허용하지 않는 듯 하다.)
    또한 findByName 메서드는 파라미터에는 null을 작성하면 안되고 리턴값 자체를 null값 이어도 상관 없다. 오늘 이렇게 Spring 에서 지원해주는 jsr305 어노테이션에 대해서 알아봤다. 물론 더 많은 기능이 있을지는 모른다. 필자가 알아본 부분은 여기까지이다. 더 많은 정보가 있다면 댓글로.. 이상!

    댓글

Designed by Tistory.