'Programming/Spring Boot'에 해당하는 글 6건

Spring MVC 에서는 기본 error 페이지를 제공하지 않아 SimpleMappingExceptionResolver 를 사용해야 하지만, Spring Boot 는 기본적으로 BasicErrorController 를 설정하여 모든 error 를 처리하는 /error 매핑을 제공하며, 서블릿 컨테이너에 전역 error 페이지로 등록된다. 예로, Thymeleaf 를 사용할 경우 /error 와 매핑되는 페이지는 src/main/resources/templates/error.html 가 될 것이다.


시스템 클라이언트의 경우, HTTP 상태, error, 메시지를 담은 JSON 형식으로 응답한다. 브라우저 클라이언트의 경우, 위와 동일한 데이터를 HTML 형식으로 렌더링한 /error 페이지로 응답할 것이며, 존재하지 않을 경우 Spring boot 에서 제공하는 "whitelabel" error 페이지로 응답한다. whitelabel 에는 exception 정보가 명시되므로 다른 error 페이지로 대체하는 것이 좋다. (properties 파일에서 설정이 가능하다. server.error.whitelabel.enabled=false)


error 를 처리할 뷰를 추가하여 커스터마이징 할 수 있는데, @ExceptionHandler 와 @ControllerAdvice 같은 일반적인 Spring MVC 기능을 사용할 수 있다.

@ExceptionHandler 는 단일 컨트롤러 등에서, @ControllerAdvice 는 전역에서 exception 처리를 한다.



@ResponseStatus 과 HTTP 상태 코드 사용


일반적으로 웹 요청을 Spring boot 에서 처리할 때 정의되지 않은 exception 이 발생하면 서버는 HTTP 500 응답을 반환한다. @ResponseStatus 어노테이션을 메소드나 exception 클래스에 사용하면 지정한 상태 코드와 메시지를 반환할 수 있다. 아래는 404 exception 클래스를 만들고, 컨트롤러에 전달된 주문번호(id)가 존재하지 않으면 해당 exception 처리를 하는 예이다.


@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
public class OrderNotFoundException extends RuntimeException {
    // Process Exception
}
 
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id"long id, Model model) {
    Order order = orderRepository.findOrderById(id);
 
    // Call Exception
    if (order == nullthrow new OrderNotFoundException(id);
 
    model.addAttribute(order);
    return "orderDetail";
}
cs


이렇게 반환되는 에러 정보는 콘솔이나 /error 에 매핑되는 페이지에서 확인할 수 있으며, 없다면 whitelabel 페이지에서 확인할 수 있다.

RESTful 요청의 경우 다음과 같이 error 정보를 json 형식의 응답으로 보여준다.


{
    "timestamp""2018-11-08T05:22:05.440+0000",
    "status"404,
    "error""Not Found",
    "message""No such page",
    "path""/orders"
}
cs



@ExceptionHandler 사용


@RequestMapping 어노테이션을 가진 메소드에서 exception 을 처리하기 위해 동일한 컨트롤러에 @ExceptionHandler 메소드를 추가하여 사용할 수 있다. @ExceptionHandler 을 사용하면, @ResponseStatus 어노테이션이 없는 exception 도 다룰 수 있고, 별로도 작성한 error 페이지로 사용자를 리디렉션 시킬 수 있다. @ExceptionHandler 는 HttpServletRequest, HttpSession, WebRequest, Locale, InputStream, OutputStream, Empty Model, ... 등의 여러가지 파라미터 type 을 가질 수 있다. 또한 ModelAndView, Model, Map, View, String, @ResponseBody, HttpEntiry<?>, ResponseEntity<?>, void, ... 등의 type 을 반환할 수 있다.


다음은 DataIntegrityViolationException 라는 미리 작성한 exception 클래스를 호출시켜 핸들러에서 처리하는 예제이다.


@Controller
public class ExceptionHandlingController {
    ...
    @RequestMapping("/dataIntegrityViolation")
    String throwDataIntegrityViolationException() throws SQLException {
        throw new DataIntegrityViolationException("Duplicate id");
    }
 
    @ResponseStatus(value = HttpStatus.CONFLICT, reason = "Data integrity violation"// 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void conflict() {
        // Nothing to do
    }
}
cs



@ControllerAdvice 사용


@ControllerAdvice 를 사용하면 개별 컨트롤러뿐만 아니라 전체 어플리케이션에서 exception 처리를 할 수 있다.

모든 exception 의 기본 핸들러의 코드는 다음과 같다.


@ControllerAdvice
class GlobalDefaultExceptionHandler {
    public static final String DEFAULT_ERROR_VIEW = "error";
 
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // exception 에 @ResponseStatus 를 가진 경우 프레임워크가 처리하도록 한다.
        if (AnnotationUtils.findAnnotation (e.getClass(), ResponseStatus.class!= null)
            throw e;
 
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

@Value 대신에 속성들의 타입을 명시하여 아래와 같이 @ConfigurationProperties 를 선언할 수 있다.

아래는 @ConfigurationProperties 로부터 사용자 메타데이터 파일을 쉽게 생성할 수 있도록 spring-boot-configuration-processor 종속성을 가져와서 properties 작성, getter/setter 정의, getter 를 사용하는 순으로 properties 의 속성값을 가져오는 예이다.


- properties

acme.enabled=false
acme.remote-address=192.168.1.234
...
acme.security.username=abc
acme.security.password=efg
...
cs


- build.gradle

dependencies {
   compile "org.springframework.boot:spring-boot-configuration-processor"
}
cs


- AcmeProperties.java

package com.example;
 
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@Component
@ConfigurationProperties(prefix="acme")
public class AcmeProperties {
 
    private boolean enabled;
    private InetAddress remoteAddress;
    private final Security security = new Security();
    public boolean isEnabled() { ... }
    public void setEnabled(boolean enabled) { ... }
    public InetAddress getRemoteAddress() { ... }
    public void setRemoteAddress(InetAddress remoteAddress) { ... }
    public Security getSecurity() { ... }
 
    public static class Security {
        private String username;
        private String password;
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        public String getUsername() { ... }
        public void setUsername(String username) { ... }
        public String getPassword() { ... }
        public void setPassword(String password) { ... }
        public List<String> getRoles() { ... }
        public void setRoles(List<String> roles) { ... }
    }
}
cs


- MyService

@Service
public class MyService {
 
    private final AcmeProperties properties;
 
    @Autowired
    public MyService(AcmeProperties properties) {
        this.properties = properties;
    }
 
     //...
 
    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }
}
cs


위 예에서 getter와 setter는 Spring MVC 와 마찬가지로 표준 Java Beans 프로퍼티 디스크립터를 통해 바인딩되기 때문에 일반적으로 필수이지만 setter 는 생략될 수도 있다. Project Lombok 을 사용하여 getter 와 setter 를 자동으로 추가하기도 한다.


Spring Boot 는 properties 정보를 @ConfigurationProperties Bean 에 바인딩할 때 다소 완화된 규칙을 사용하므로 properties 속성 이름과 Bean 속성 이름이 정확히 일치하지 않아도 된다. 일반적으로 properties 속성 이름의 단어 사이에 camel/Kebab case 를 사용하거나 언더바(_)로 이어주곤 하는데 이러한 이름들을 모두 동일하게 바인딩 시켜준다. @Value 는 엄격한 규칙 때문에 속성 이름이 일치하지 않으면 값을 불러올 수 없다.


  • acme.my-project.person.first-name (Kebab case)
  • acme.myProject.person.firstName (camel case)
  • acme.my_project.person.first_name (underscore)



@Validated


@ConfigurationProperties 클래스에 @Validated 어노테이션을 사용하여 유효성 검사를 시도할 수 있다.

해당 Properties 를 사용할 @Bean 메서드에 @Validated 를 사용하여 검증을 트리거 할 수도 있다.


@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
 
    @NotNull
    private InetAddress remoteAddress;
 
    @Valid
    private final Security security = new Security();
 
    // ... getters and setters
 
    public static class Security {
 
        @NotEmpty
        public String username;
 
        // ... getters and setters
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

Spring Boot 사용시 특정 속성들을 별도로 만들어(외부화) 다른 환경에서도 동일하게 코딩을 할 수 있다.

일반적으로 공통적으로 사용되는 속성들을 정의하거나, 개발 환경(prod/dev) 등을 구분하고자 할 때 주로 사용된다.

그 방법으로 properties 파일이나, YAML 파일, 환경변수 및 명령행 인수 등을 사용할 수 있으며, 

워낙 다양한 방법들이 있다보니 중복되거나 하는 속성 값들에는 PropertySource 우선 순위가 작용한다.


  1. ~/.spring-boot-devtools.properties (devtools가 활성화시)
  2. @TestPropertySource (테스트시)
  3. @SpringBootTest (테스트시)
  4. Command line 파라미터
  5. SPRING_APPLICATION_JSON 속성 (환경 변수 또는 시스템 속성에 포함 된 인라인 JSON).
  6. ServletConfig 초기화 파라미터
  7. ServletContext 초기화 파라미터
  8. java:comp/env JNDI 속성
  9. System.getProperties() (Java System 등록 정보)
  10. OS 환경 변수
  11. RandomValuePropertySource (random.* 속성으로 랜덤값을 삽입할 때 유용)
  12. jar 외부의 프로필 별 속성(application-{profile}.properties 및 YAML 변형)
  13. jar 내부의 프로필 별 속성(application-{profile}.properties 및 YAML 변형)
  14. jar 외부의 속성 ( application.properties 및 YAML 변형)
  15. jar 내부의 속성 ( application.properties 및 YAML 변형)
  16. @Configuration 클래스의 @PropertySource
  17. SpringApplication.setDefaultProperties()


많기도 하여라. 나름이겠지만 사실상 사용하는건 @SpringBootTestapplication-{profile}.properties, @PropertySource 정도가 아닐까.

일반적으로 다음과 같은 방식으로 properties 파일을 작성하여 속성을 정의한다.


spring.profiles.active=dev,hsqldb

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

app.name=MyApp
app.description=${app.name} is a Spring Boot application
cs


Spring Profiles 는 어플리케이션 구성의 일부를 분리하여 특정 환경에서만 사용할 수 있게 한다. 

@Component 나 @Configuration 은 @Profile 으로 표시되어 로드될 상황을 분리할 수 있다.


@Configuration
@Profile("production")
public class ProductionConfiguration {
    // ...
}
cs


나는 주로 YAML 보다는 properties 를 사용하므로 YAML 에 관련된 내용은 생략하겠다. 

properties 와 동일한 정의가 가능하지만, @PropertySource 로 값을 가져올 수 없다는 차이점 정도?


이렇게 설정된 속성들은 @Value("${property}") 어노테이션을 사용하여 값을 가져올 수 있지만, 여러 속성을 작성하거나 계층적으로 그룹화 된 여러 property 들을 관리할 때는 @Value("${property}") 보다는 @ConfigurationProperties 로 설정하여 바인딩 하는 것이 좋다.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret

devtools 는 이름처럼 어플리케이션 개발시 유용한 기능들을 포함하고 있는 Spring Boot 모듈이다.

개발 도중 결과를 확인하기 위해 build 를 수동으로 실행하고 브라우저를 새로고침 하여 결과를 확인하는 동작을 자동화하는 것이 특히 유용하다.

gradle 에서 devtools 를 사용하려면 dependency 에 아래처럼 한 줄만 추가한다.


configurations {     developmentOnly     runtimeClasspath {         extendsFrom developmentOnly } }

dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}
cs


그리고 IDE 마다 설정이 약간씩 틀린데 어플리케이션 실행 중 자동 빌드에 관련한 옵션을 설정한다. (아래: Intellij)


  • File -> Settings [Ctrl-Alt-S] -> 검색 Compiler -> Build project automatically 체크
  • [Ctrl-Shift-A] -> Registry... -> compiler.automake.allow.when.app.running 체크
  • Run - Edit Configurations... - On 'Update' action / On frame deactivation: 적절히 선택 (Update classes and resources 등...)


이제 mainClass 를 실행(run) 시키고 코드를 수정하면 수초 뒤 변경 사항을 브라우저를 새로고침 하여 확인할 수 있다. 패키지(archive) 된 어플리케이션을 실행하면 production 모드로 간주하고 devtools 를 비활성화 하기 때문에 java -jar 나 bootRun 등을 이용하여 어플리케이션을 실행하면 devtools 는 동작하지 않는다. devtools 는 클래스 패스에 존재하는 코드 변경만 주시하므로, 특정 디렉토리의 코드만 자동 빌드가 되지 않는다면 클래스 패스에 추가하는 등의 방법으로 해결해야 한다.



Live Reload


spring-boot-devtools 모듈에는 리소스가 변경 될 때 브라우저가 새로 고침을 할 수 있게 하는 LiveReload 서버가 포함되어 기본으로 활성화 되어 있다.

서버브라우저에 liveReload 확장 프로그램을 설치하면, 코드 수정 후 사용자가 브라우저를 새로고침 하지 않아도 자동으로 새로고침 되어 보여진다.



Cache disabled


Spring Boot 의 몇가지 라이브러리는 성능 향상을 위해 캐시를 사용하는데, devtools 를 사용 중에는 사용자가 자동 빌드를 즉시 확인할 수 있도록, 알려진 템플릿 관련 캐시를 모두 비활성화 하는 것을 기본값으로 한다. application.properties 에 명시하여 수정도 가능하다. (예: spring.thymeleaf.cache=false)


아래는 devtools 의 자동화에 관련된 기본값이다. application.properties 파일에서 수정할 수 있다.


spring.devtools.livereload.enabled=true

spring.devtools.restart.enabled=true




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  4개가 달렸습니다.
  1. developmentOnly
    runtimeClasspath {
    extendsFrom developmentOnly
    }
    configurations{}에 위에 것은 왜 추가 안 하나요? 예제보면 저렇게 하라고 나와있던데? 하고 안 하고 차이가 없나요?
    • 수정했습니다. developmentOnly 를 위와 같이 수정한 경우 프로젝트에서 사용되는 다른 모듈에 적용되는 것을 방지할 수 있습니다.
  2. 개발자 2019.10.08 01:37
    안녕하세요, livereload 적용 했는데 새로고침까지 4~5초 정도가 걸립니다ㅠㅠ 소스 수정시 바로 반영되게 할 수 없을까요??
    • 어떤 IDE 를 사용하시는지, 컴파일이 필요한 수정을 하신건지 모르겠어서 답변이 어렵네요; 해당 IDE 설정으로 검색을 해보시는게 좋겠어요 ^^;
secret

일반적으로 프로젝트의 Main 클래스(main 메소드를 정의하는 클래스) 는 다른 클래스보다 상위에있는 루트 패키지(아래 예에서 com/example/myapplication) 에 배치하는 것이 좋다. 

@SpringBootApplication 어노테이션은 주로 Main 클래스에 배치되며 특정 항목에 대한 기본 "패키지 검색" 을 암시적으로 정의한다. 

예를 들어 JPA 프로젝트를 작성하는 경우 @SpringBootApplication 어노테이션이 정의된 클래스의 패키지를 사용하여 @Entity 항목을 검색한다. 

루트 패키지를 사용하면 component scan 을 프로젝트에만 적용할 수도 있다.

@SpringBootApplication 은 @EnableAutoConfiguration / @ComponentScan / @Configuration 어노테이션을 기본 설정으로 사용한 것과 같다.

@ComponentScan 어노테이션은 어플리케이션의 모든 구성요소(@Component , @Service , @Repository , @Controller 등) 를 자동으로 Spring Bean 에 등록한다.


com

 +- example

     +- myapplication

         |

         +- Application.java

         |

         +- customer

         |   +- Customer.java

         |   +- CustomerController.java

         |   +- CustomerService.java

         |   +- CustomerRepository.java

         |

         +- order

             +- Order.java

             +- OrderController.java

             +- OrderService.java

             +- OrderRepository.java



Main 클래스인 Application.java 파일은 @SpringBootApplication 과 함께 main 메소드를 다음과 같이 선언한다.


package com.example.myapplication;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
cs




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

트랙백  0 , 댓글  0개가 달렸습니다.
secret