Open Pyohwan opened 4 years ago
SpringApplication
는 main 메소드에서 스프링 애플리케이션을 부트스트랩하는 편리한 기능 제공
public static void main(String[] args) {
SpringApplication.run(MySpringConfiguration.class, args);
}
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: v2.2.2.RELEASE
2019-04-31 13:09:54.117 INFO 56603 --- [ main] o.s.b.s.app.SampleApplication : Starting SampleApplication v0.1.0 on mycomputer with PID 56603 (/apps/myapp.jar started by pwebb) 2019-04-31 13:09:54.166 INFO 56603 --- [ main] ationConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e5a8246: startup date [Wed Jul 31 00:08:16 PDT 2013]; root of context hierarchy 2019-04-01 13:09:56.912 INFO 41370 --- [ main] .t.TomcatServletWebServerFactory : Server initialized with port: 8080 2019-04-01 13:09:57.501 INFO 41370 --- [ main] o.s.b.s.app.SampleApplication : Started SampleApplication in 2.992 seconds (JVM running for 3.658)
* 기본적으로 INFO 로깅 메시지
* `spring.main.log-startup-info` 로 startup 정보 로깅을 끌 수 있음
> SpringApplication 에서 `logStartupInfo(boolean)` 로도 설정 가능
## 1.1. Startup Failure
* FailureAnalyzers 에 등록된 오류 메시지와 해결을 위한 조치를 제공 함
* 8080 포트가 이미 사용중이라면?
APPLICATION FAILED TO START
Description:
Embedded servlet container failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
> Spring Boot 는 수많은 `FailureAnalyzer` 구현체가 있고, 직접 추가도 가능 함
* failure analyzers 가 처리 하지 못하는 exception 이 있을 수 있는데, DEBUG 로깅으로 바꿔서 상세하게 봐라
* `java -jar` 할때 DEBUG 로깅을 하고 싶다면
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
## 1.2. Lazy Initialization
* lazy initialization 를 활성화 하면, Bean 이 필요할때 생성된다. (애플리케이션 startup 이 아니라)
* 애플리케이션 시작 시간을 단축 시킬 수 있다
* HTTP 요청 수신전까지 웹 관련 Bean 이 초기화 되지 않는다
* 하지만 단점은, 만약 잘못된 Bean 이 있을 경우 startup 시 발견 되지 않는다
* 또한 startup 시에 충분한 메모리 공간을 알수가 없다
spring.main.lazy-initialization=true
> 특정 Bean 만 Lazy 를 사용하지 않으려면 `@Lazy(false)` 를 붙혀라
## 1.3. Customizing the Banner
* classpath 에 `banner.txt` 파일을 추가하거나, `spring.banner.location` 로 file 위치 설정 가능
* 파일이 UTF-8 로 인코딩 되어 있다면, `spring.banner.charset` 로 설정 가능
* banner.gif, banner.jpg, or banner.png 등의 이미지 파일로 추가 가능
* `spring.banner.image.location` 로 파일 위치 설정 가능
* 이미지는 ASCII 로 표현 됨
* `banner.txt` 에서 사용할 수 있는 placeholders. [이 링크](https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/spring-boot-features.html#boot-features-banner)의 표 참고
> 프로그래밍 적으로는 `SpringApplication.setBanner(…)` method 로 설정 가능
* `spring.main.banner-mode` 로 `System.out (console)` 로 찍을지, 로그로 찍을지 설정 가능 함
* 프린트된 배너는 `springBootBanner` Bean 으로 등록 됨
## 1.4. Customizing SpringApplication
* `SpringApplication` 기본값이 마음에 안들면, 로컬 인스턴스를 작성하고 사용자 정의 가능하다.
* 예를 들어 밴어를 끄려면
public static void main(String[] args) { SpringApplication app = new SpringApplication(MySpringConfiguration.class); app.setBannerMode(Banner.Mode.OFF); app.run(args); }
> SpringApplication 로 전달되는 생성자 아규먼트는 Spring Beans 의 구성 소스임. 대부분은 @Configuration 클래스에 대한 참조이지만, XML 구성에 대한 참조일 수도 있다
* application.properties 로도 SpringApplication 구성할수 있는데, Externalized Configuration 장에서 봐라
## 1.5. Fluent Builder API
* ApplicationContext 를 여러개 계층적으로 만들어야 하거나, “fluent” API 를 선호하면 SpringApplicationBuilder 를 사용해라
* SpringApplicationBuilder를 사용하면 여러 메소드 호출을 체인할 수 있고, 계층(child) 구조 작성 가능하다
new SpringApplicationBuilder() .sources(Parent.class) .child(Application.class) .bannerMode(Banner.Mode.OFF) .run(args);
> ApplicationContext 를 계층적으로 만들때에는 제약사항이 있다. 웹 컴포넌트는 child context 에 포함되어야 하고, 상하위의 컨텍스트 모두 동일한 환경이어야 한다.
## 1.6. Application Events and Listeners
* `ContextRefreshedEvent` 와 같은 일반적인 이벤트 외에도, 추가적인 이벤트를 보낸다
> 특정 이벤트는 실제로 `ApplicationContext` 가 생성되기 전에 트리거되개 때문에, `@Bean` 으로 등록할 수 없다. `SpringApplication.addListeners(…)` 메소드로 등록해라
> 리스너 자동 등록을 하고 싶다면 `META-INF/spring.factories` 파일에 리스너를 추가해라
org.springframework.context.ApplicationListener=com.example.project.MyListener
* 애플리케이션 이벤트는 다움의 순서로 전송 된다
1. `ApplicationStartingEvent` 는 run 시작시 에 발송 (리스너 및 초기화 등록 제외하고)
1. `ApplicationEnvironmentPreparedEvent` 는 컨텍스트 생선전에 사용될때 `Environment` 전송
1. `ApplicationContextInitializedEvent` 는 `ApplicationContext` 가 준비되고, `ApplicationContextInitializers` 가 호출되고 Bean 이 생성전에 전송
1. `ApplicationPreparedEvent` 는 refresh 시작 전, bean 정의가 로드된 후 전송
1. `ApplicationStartedEvent` 는 컨텍스트가 refresh 되었고, command-line runners 호출 전에 전송
1. `ApplicationReadyEvent` 는 애플리케이션 및 command-line runners 가 호출 된 후 전송. 애플리케이션이 요청을 처리할 준비가 되었다는 의미
1. `An ApplicationFailedEven` 는 startup 시 exception 있을때 전송
* 이외에 ApplicationStartedEvent 와 ApplicationPreparedEvent 사이에 발생하는 이벤트
1. `ContextRefreshedEvent` 는 ApplicationContext 가 refresh 되고 나서 전송
1. `WebServerInitializedEvent` 는 웹서버가 준비된 후 전송
## 1.7. Web Environment
* SpringApplication 는 사용자를 대신하여 올바르게 ApplicationContext 를 만들려고 함
* Spring MVC 가 있다면 AnnotationConfigServletWebServerApplicationContext 적용
* Spring MVC 가 없고, Spring WebFlux 가 있다면 AnnotationConfigReactiveWebServerApplicationContext 적용
* 모두 그렇지 않다면 AnnotationConfigApplicationContext 적용
* setWebApplicationType(WebApplicationType) 로 설정 가능
> JUnit test 할때에는 `setWebApplicationType(WebApplicationType.NONE)` 로 써라
## 1.8. Accessing Application Arguments
* `SpringApplication.run(…)` 에서 아규먼트를 접근해야 하는 경우 `org.springframework.boot.ApplicationArguments` bean 을 삽입 가능
import org.springframework.boot.; import org.springframework.beans.factory.annotation.; import org.springframework.stereotype.*;
@Component public class MyBean {
@Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
}
}
## 1.9. Using the ApplicationRunner or CommandLineRunner
* SpringApplication 가 시작되고 나서, 특정 코드를 한번 실행해야 한다면, ApplicationRunner or CommandLineRunner 인터페이스를 구현해라
import org.springframework.boot.; import org.springframework.stereotype.;
@Component public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
* 순서대로 호출해야 하면 `org.springframework.core.Ordered` interface or `org.springframework.core.annotation.Order` 애너테이션을 써라
## 1.10. Application Exit
* 각 SpringApplication 종료시, ApplicationContext 가 close 되도록 JVM에 종료 후크를 등록 한다.
* DisposableBean 인터페이스 or `@PreDestroy` 를 사용 하라
* 또한 Bean 이 SpringApplication.exit() 가 호출되고, 특정 종료 코드를 응답하고자 한다면 org.springframework.boot.ExitCodeGenerator 인터페이스를 구현하라
@SpringBootApplication public class ExitCodeApplication {
@Bean
public ExitCodeGenerator exitCodeGenerator() {
return () -> 42;
}
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(ExitCodeApplication.class, args)));
}
}
## 1.11. Admin Features
* spring.application.admin.enabled 로 관리자 기능 사용 가능
* 플랫폼 MBeanServer 에 SpringApplicationAdminMXBean 를 노출 시킨다
* 이를 통해 Spring Boot application 을 원격으로 관리할 수 있다
스프링부트를 사용하면 configuration 을 externalize 하여, 다른 환경에서도 동일한 애플리케이션 코드로 작업할 수 있다
@Value
애너테이션으로 특정 값을 Bean 에 주입 하거나, 스프링 Environment
추상화 접근하거나, @ConfigurationProperties
로 구조화된 오브젝트 바인드가 가능하다.
스프링부트는 현명하게 값을 재정의하도록 설계된 PropertySource
가 있고, 적용 순서가 있음
$HOME/.config/spring-boot
에 Devtools 글로벌 세팅 properties@TestPropertySource
properties
애뜨리뷰트. @SpringBootTest
와 각종 테스트 애너테이션 들 (`@Test)SPRING_APPLICATION_JSON
의 properties (환경변수 또는 시스템 프로퍼티에 임베디드 된 inline JSONServletConfig
init 파라미터ServletContext
init 파라미터@PropertySource
애너테이션이 있는 @Configuration
클래스. 이런 property sources 는 애플리케이션 컨텍스트가 refresh 되기전까지 Environment
에 추가 되지 않음구체적인 예제를 위해 다음과 같은 name
property 를 사용한 @Component
가 있다.
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;
@Component public class MyBean {
@Value("${name}")
private String name;
// ...
}
* 애플리케이션 classpath 에 `application.properties` 파일이 있을 수 있고, jar 외부에서 `name` 을 overrides 하여 제공 가능하다.
* 일회성 테스트로 한번만 커맨드라인 수행 가능
* `java -jar app.jar --name="Spring"`
> `SPRING_APPLICATION_JSON` properties 는 커맨드라인에서 환경변수와 함께 제공 가능하다.
> `$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
> System property 로도 제공 가능
> `$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar`
> 커맨드라인 아규먼트로도 가능
> `$ java -jar myapp.jar --spring.application.json='{"name":"test"}'`
> JNDI 변수로도 가능 `java:comp/env/spring.application.json`
## 2.1. Configuring Random Values
* `RandomValuePropertySource` 는 랜덤값을 주입하는데 유용함 (for example, into secrets or test cases)
* integers, longs, uuids, or strings 생산 가능
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]}
* `random.int*` 문법은 `OPEN value (,max) CLOSE` 인데 `OPEN,CLOSE` 는 모든 캐릭터이고 `value,max` 는 integers 다
* `max` 가 있다면 `value` 는 최소값이고, `max` 는 최대값이다 (exclusive)
## 2.2. Accessing Command Line Properties
* 기본적으로 `SpringApplication` 는 커맨드라인 옵션 아규먼트(that is, arguments starting with --, such as --server.port=9000)를 `property` 로 변환하고, Spring `Environment` 에 추가 함
* 앞에서 봤듯이 커먼드라인 properties 는 다른 property sources 보다 우선임
* 커맨드라인 properties 를 `Environment` 에 추가를 원하지 않으면, `SpringApplication.setAddCommandLineProperties(false)` 게 해라
## 2.3. Application Property Files
* `SpringApplication` 아래에 있는 위치에서 `application.properties` 파일을 읽어들여 Spring `Environment` 에 추가한다
1. 현재 디렉터리의 `/config` 서브 디렉터리
1. 현재 디렉터리
1. 클래스패스 `/config/ 패키지
1. 클래스패스 root
* 위 목록은 우선순위 별로 나타낸것임
* 만일 `application.properties` 란 파일 이름을 원하지 않는다면, `spring.config.name` 환경 property 로 수정 가능하다.
* `spring.config.location` 환경 property (콤마로 구분된) 로 명시적인 로케이션을 참조토록 할 수 있다.
* 예제임
$ java -jar myproject.jar --spring.config.name=myproject
* 두가지 로케이션을 지정하는 예제
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
> spring.config.name and spring.config.location 은 로드할 파일을 결정하기 위해 일찍 사용 된다. 환경 property 로만 정의 해야 한다 (OS 환경 변수, 시스템 프로퍼티, 커맨드라인 아규먼트)
* `spring.config.location` 가 디렉터리를 포함하고 있다면, `/` 로 끝나야 한다. (profile-specific 파일들과 함께 로드 되기 전에, 런타임에 `spring.config.name` 가 생성되어야 한다)
* `spring.config.location` 에 지정된 파일은 그대로 사용, profile-specific 변형은 지원되지 않고, profile-specific properties 로 재정의 된다
* Config location 은 역순으로 검색된다.
* 기본적으로 classpath:/,classpath:/config/,file:./,file:./config/
1. file:./config/
1. file:./
1. classpath:/config/
1. classpath:/
* `spring.config.location` 로 커스텀 config location 을 사용하면, default location 이 바뀐다. 예를 들어, `spring.config.location` 이 `classpath:/custom-config/,file:./custom-config/` 로 되어 있다면, 다음 순으로 될것이다.
1. file:./custom-config/
1. classpath:custom-config/
* 또는, `spring.config.additional-location` 으로 커스텀 config location 을 사용하면, default locations 이 외에 추가 된다.
* 예를 들어, `classpath:/custom-config/,file:./custom-config/` 로 되어 있다면, 다음 순으로 될것이다.
1. file:./custom-config/
1. classpath:custom-config/
1. file:./config/
1. file:./
1. classpath:/config/
1. classpath:/
* 이 검색 순서를 이용하면 하나의 configuration 파일에서 기본값과 과 선택적 값을 재정의 할 수 있다.
* `application.properties` 에서 기본값 제공 가능
* 런타임 시에 다른 file location 주입 가능
> 시스템 프로퍼티 대신 환경 변수을 사용하는 경우, period-separated (마침표) 키 네임을 허용 하지 않지만, 대신 밑줄 사용 가능하다. (spring.config.name 대신에 SPRING_CONFIG_NAME)
> 컨테이너에서 애플리케이션을 실행중일때 JNDI 프로퍼티 나 서블릿 초기화 파라미터를 환경 변수 또는 시스템 프로퍼티 대신에 사용할 수 있다.
## 2.4. Profile-specific Properties
* `application-{profile}.properties` 처럼 profile-specific properties 정의 가능함
* `Environment` 에 명시적으로 profile 을 활성화 하지 않으면, 기본 프로파일을 `[default]` 을 사용함
* `application-default.properties` 가 사용 됨
* Profile-specific properties 은 `application.properties` 과 동일한 location 에서 읽는다.
* packaged jar 안이든 밖이든 profile-specific 파일이 있다면, profile-specific 파일이 non-specific 을 항상 오버로딩 한다.
* 여러개의 profiles 이 주어지면, last-wins strategy 이 적용됨
* 예를 들어 `spring.profiles.active` property 로 추가된 profile 은 `SpringApplication` API 로 구성된거 보다 뒤에 추가되므로, 뒤에 추가된것이 적용 됨
> `spring.config.location` 이 지정되어 있다면, profile-specific 변형이 고려 되지 않음
## 2.5. Placeholders in Properties
* `application.properties` 의 값은 `Environment` 에서 필터링 되고, 이전에 정의한 값들을 다시 사용 가능 함
app.name=MyApp app.description=${app.name} is a Spring Boot application
## 2.6. Encrypting Properties
* 기본적으로 property 암호화는 안되는데, `Environment` 의 값을 수정하는 후크 포인트를 제공함
* Spring Cloud Vault 참고
## 2.7. Using YAML Instead of Properties
* SnakeYAML 라이브러리가 있어서 YAML 지원 함
### 2.7.1. Loading YAML
* YAML 문서를 로드하는 두가지 방법이 있음
* `YamlPropertiesFactoryBean` 은 YAML 을 `Properties` 로 읽음
* `YamlMapFactoryBean` 은 YAML 을 `Map` 로 읽음
environments: dev: url: https://dev.example.com name: Developer Setup prod: url: https://another.example.com name: My Cool App
* 위 YAML 은 다음의 properties 로 변환 됨
environments.dev.url=https://dev.example.com environments.dev.name=Developer Setup environments.prod.url=https://another.example.com environments.prod.name=My Cool App
* YAML 목록은 `[index]` dereferencers 를 가진 property key 로 표현 됨
my: servers:
another.example.com
* 위 YAML 은 다음의 properties 로 변환 됨
my.servers[0]=dev.example.com my.servers[1]=another.example.com
* `@ConfigurationProperties` 와 같은 `Binder` 유틸리티에서 사용하려면, java.util.List (or Set) 타입과 세터 or initialize 가 필요하다
@ConfigurationProperties(prefix="my") public class Config {
private List
public List
YamlPropertySourceLoader
클래스는 Environment
에서 PropertySource
를 YAML 로 expose 하는데 사용할 수 있다.
@Value
와 placeholders 문법으로 YAML 에 접근 가능하다spring.profiles
key 를 사용하여 multiple profile-specific YAML documents 지정 가능
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production & eu-central
server:
address: 192.168.1.120
development
이면 server.address
는 127.0.0.1
production
and eu-central
이면 server.address
는 192.168.1.120
192.168.1.100
production & (eu-central | eu-west)
이렇게 표현 가능
spring.security.user.password
이 사용 됨
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
server:
port: 8000
spring:
security:
user:
password: weak
!
문자로 사용 가능하다.
@PropertySource
로 YAML 파일 일기 불가능.
server:
port: 8000
---
spring:
profiles: "!test"
security:
user:
password: "secret"
--spring.profiles.active=dev
로 하면 security.user.password
가 "secret" 일것 같지만 그렇지 않다
application-dev.yml
에서 중첩된 문서는 필터링 된다. 이미 profile-specific 된것으로 간주 되어 중첩 문서는 무시 됨
mix profile-specific YAML files and multiple YAML documents 을 혼합하지 마라. 그중 하나만 써라
@Value("${property}")
로 configuration properties 를 주입하는건 때론 성가시다
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;
@ConfigurationProperties("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) { ... }
}
}
* 위 POJO 는 다음의 properties 를 정의한다
* acme.enabled, with a value of false by default.
* `acme.remote-address` 는 `String` 으로 강제
* `acme.security.username` 은 중첩된 "security" object 의 property 이고, 리턴 타입이 없다(?)
* `acme.security.roles` 은 기본적으로 `USER` 스프링 배열로 제공
> `@ConfigurationProperties` 는 properties files, YAML files, environment variables etc. 등 으로 구성되는 public API 이지만, 클래스의 accessors (getters/setters) 는 직접적으로 사용 되진 않음. 다음의 경우 세터는 생략 가능
> 배열은 empty 생성자에 의존하고, 일반적인 Java Bean 과 마찬가지로, 게터 세터 로 바인딩하므로 필수다
> * 초기화된 Map 은 게터는 필요하지만 세터는 바인더에 의해 변경 가능하므로 필요 없다
> * 콜렉션과 배열은 index (typically with YAML) or single comma-separated value (properties) 를 통해 접근 가능하다. 후자는 세터가 필수다. 하지만 그냥 세터 추가하는걸 추천한다. 초기화한 콜렉션이라면 not immutable 인지 확인 해라
> * 초기화된 중첩 POJO properties(like the Security field) 라면 세터는 필수아님. 하지만 기본 생성자로 객체를 만들면 세터 필요함
> 누구는 Lombok 으로 게터 세터를 자동화 하는데, 컨테이너에서 객체를 초기화 해주는데, 이런타입에 대해 Lombok 은 부분적인 생성자를 생성 하지 않는다
> * 표준 자바 빈 프로퍼티만 고려하기 때문에 static properties 를 지원 안함
### 2.8.2. Constructor binding
* 이전 세션에서 한것을 immutable 방식으로 재작성 가능
```java
package com.example;
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.DefaultValue;
@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public boolean isEnabled() { ... }
public InetAddress getRemoteAddress() { ... }
public Security getSecurity() { ... }
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password,
@DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
public String getUsername() { ... }
public String getPassword() { ... }
public List<String> getRoles() { ... }
}
}
@ConstructorBinding
는 생성자 바인딩을 사용함을 의미
Security
처럼 중첩된 멤버도 @ConstructorBinding
를 통해 생성자로 바인딩 됨@DefaultValue
로 기본값을 설정 가능하고, String
값을 타켓 타입의 missing property 를 강제로 적용 함생성자 바인딩을 사용하려면
@EnableConfigurationProperties
나 configuration property scanning 를 사용하여 클래스를 활성화 해야 한다. 일반적인 스프링 메카니즘(e.g. @Component beans, beans created via @Bean methods or beans loaded using @Import) 에서는 안된다하나 이상의 생성자가 있다면
@ConstructorBinding
로 직접 생성자를 바인딩해라
@ConfigurationProperties
유형을 바인딩 하고 빈일 등록하는 인프라를 제공함
때때로 @ConfigurationProperties
는 자동구성을 개발하거나 조건부로 활성화를 할때에는 적합하지 않다.
@EnableConfigurationProperties
로 직접 지정해라
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AcmeProperties.class)
public class MyConfiguration {
}
configuration property 스캐닝을 하려면 @ConfigurationPropertiesScan
를 추가해라
@SpringBootApplication
붙은 애플리케이션에 추가 되지만, @Configuration
클래스에도 추가할 수 있다.@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" })
public class MyApplication {
}
@ConfigurationProperties
빈이@EnableConfigurationProperties
로 등록되면, 빈의 이름은<prefix>-<fqn>
이다.
<prefix>
는@ConfigurationProperties
에 있는 environment key prefix<fqn>
는 빈의 전체 이름- prefix 를 제공 안하면 전체이름만 사용 된다. 위의 경우 이름은
acme-com.example.AcmeProperties
이다
@ConfigurationProperties
는 environment 만 다루고, 컨텍스트에는 다른 빈을 주입 하지 않는것이 좋다
SpringApplication
와 외부 YAML configuration 와 잘 동작 한다
# application.yml
acme: remote-address: 192.168.1.1 security: username: admin roles:
* `@ConfigurationProperties` 빈을 사용하려면, 다음 예제처럼 다른 빈 주입 하듯이 사용 함
```java
@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());
// ...
}
}
@Bean
메소드 사용 가능
@ConfigurationProperties(prefix = "another")
@Bean
public AnotherComponent anotherComponent() {
...
}
### 2.8.6. Relaxed Binding
Environment
프로퍼티와 빈 프로퍼티 이름을 정확하게 match 안해도 된다. (relaxed ruls)
@ConfigurationProperties(prefix="acme.my-project.person")
public class OwnerProperties {
private String firstName;
public String getFirstName() { return this.firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
}
* 위 코드는 다음과 같은 properties names 사용 가능
#### Table 2. relaxed binding
Property | Note
-- | --
acme.my-project.person.first-name | Kebab case, which is recommended for use in .properties and .yml files.
acme.myProject.person.firstName | Standard camel case syntax.
acme.my_project.person.first_name | Underscore notation, which is an alternative format for use in .properties and .yml files.
ACME_MYPROJECT_PERSON_FIRSTNAME | Upper case format, which is recommended when using system environment variables.
> `prefix` 값은 무조건 kebab 케이스
#### Table 3. relaxed binding rules per property source
Property Source | Simple | List
-- | -- | --
Properties Files | Camel case, kebab case, or underscore notation | Standard list syntax using [ ] or comma-separated values
YAML Files | Camel case, kebab case, or underscore notation | Standard YAML list syntax or comma-separated values
Environment Variables | Upper case format with underscore as the delimiter. _ should not be used within a property name | Numeric values surrounded by underscores, such as MY_ACME_1_OTHER = my.acme[1].other
System properties | Camel case, kebab case, or underscore notation | Standard list syntax using [ ] or comma-separated values
> 가능하면 properties 는 케밥 포맷 추천
* `Map` 바인딩
```yaml
acme:
map:
"[/key1]": value1
"[/key2]": value2
/key3: value3
괄호는 파씽하기 위해선
"
를 붙혀라
예를 들어 MyPojo
객체에 name
과 description
애트리뷰트가 있는데 기본적으로 null
이라고 가정
@ConfigurationProperties("acme")
public class AcmeProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
* configuration
```yaml
acme:
list:
- name: my name
description: my description
---
spring:
profiles: dev
acme:
list:
- name: my another name
dev
profile 이 활성화 안되면 AcmeProperties.list
는 MyPojo
한개 포함한다dev
profile 이 활성화 되면, 여전히 list
는 한개만 포함 됨.
MyPojo
객체를 리스트에 추가 하지 않고 머지 하기 때문acme:
list:
- name: my name
description: my description
- name: another name
description: another description
---
spring:
profiles: dev
acme:
list:
- name: my another name
dev
profile 이 활성화 되면 AcmeProperties.list
는 MyPojo
한개 포함한다 (with a name of my another name
and a description of null)Map 은 여러 소스에서 우선순위가 높은 property 가 사용 됨
@ConfigurationProperties("acme")
public class AcmeProperties {
private final Map<String, MyPojo> map = new HashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
*configuration
acme: map: key1: name: my name 1 description: my description 1 ---yaml spring: profiles: dev acme: map: key1: name: dev name 1 key2: name: dev name 2 description: dev description 2
* `dev` profile 이 활성화 안되면 `AcmeProperties.map` 는 `key1` 한개 포함한다
* `dev` profile 이 활성화 되면, 2개의 key 를 포함한다
> 머지 룰은 YAML 뿐만 아니라 모든 property 소스에 적용 된다
### 2.8.8. Properties Conversion
* `@ConfigurationProperties` 빈 바인딩 할때 올바른 유형으로 강제하길 시도한다
* 커스텀 타입 conversion 이 필요하면 `ConversionService` `CustomEditorConfigurer` `@ConfigurationPropertiesBinding` 참고
> 애플리케이션 lifecycle 중에 매우 일찍 요청하므로 `ConversionService` 사용에 제약이 있다. 일반적으로 필요한 디펜던시가 아직 생성전일 수 있다. `@ConfigurationPropertiesBinding` 로 커스텀 `ConversionService` 리네임 가능
#### Converting durations
* `java.time.Duration` 프로퍼티 사용
* 표준 `long` 표현 (`@DurationUnit` 를 지정 안하면 기본 milliseconds 사용)준
* The standard ISO-8601 format used by java.time.Duration
* value 와 unit 보다 읽기 쉬운 포맷 (e.g. 10s means 10 seconds)
```java
@ConfigurationProperties("app.system")
public class AppSystemProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public void setSessionTimeout(Duration sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
}
@DurationUnit
로 재정의 가능
단순히
Long
으로 duration 을 표현하던것에서 업그레이드 하려면,@DurationUnit
로 다시 정의 해야 함.
바이트 사이즈를 표현하는 DataSize
long
표현 (@DataSizeUnit
를 지정 안하면 기본 bytes 사용)
@ConfigurationProperties("app.io")
public class AppIoProperties {
@DataSizeUnit(DataUnit.MEGABYTES) private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
public DataSize getBufferSize() { return this.bufferSize; }
public void setBufferSize(DataSize bufferSize) { this.bufferSize = bufferSize; }
public DataSize getSizeThreshold() { return this.sizeThreshold; }
public void setSizeThreshold(DataSize sizeThreshold) { this.sizeThreshold = sizeThreshold; }
}
* 버퍼사이즈를 10메가로 하려면 10 and 10MB 해라
* threshold 256 bytes 는 256 or 256B 해라
* 다음은 units 지원
* B for bytes
* KB for kilobytes
* MB for megabytes
* GB for gigabytes
* TB for terabytes
### 2.8.9. @ConfigurationProperties Validation
* `@ConfigurationProperties` 클래스에 `@Validated` 붙히면 유효성 검사 함
* JSR-303 javax.validation constraint annotation 도 사용 가능하다
```java
@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
@Bean
메소드에도@Validated
달아 유효성 트리거 가능
중첩된 properties 검사하려면 @Valid
붙혀라
@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
}
}
* `configurationPropertiesValidator` 를 정의하면 커스텀 `Validator` 추가 가능
* ` @Bean` 메소드는 `static` 이어야 한다
* configuration properties validator 는 애플리케이션 lifecycle 에서 일찍 생성되기 때문에, @Bean method as static 해야 생성 가능
### 2.8.10. @ConfigurationProperties vs. @Value
* `@Value` 는 코어 컨테이너 피쳐인데, type-safe configuration 는 제공 안함
Feature | @ConfigurationProperties | @Value
-- | -- | --
Relaxed binding | Yes | No
Meta-data support | Yes | No
SpEL evaluation | No | Yes
* 자체 콤퍼넌트의 configuration keys 를 정의할때에는 @ConfigurationProperties 와 POJO 가 좋음
* `@Value` 는 relaxed binding 을 지원안해서, environment variables 통해 제공하는 것은 적합하지 않다
* `@Value` 는 `SpEL` 사용이 가능하긴 한데 application property files 에서는 처리 안됨
특정 환경에서만 사용 가능한 @Profile
제공
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
* `application.properties` 에서 `spring.profiles.active` 로 설정 가능
spring.profiles.active=dev,hsqldb
* 커맨드 라인에서 `--spring.profiles.active=dev,hsqldb` 로 지정 가능
## 3.1. Adding Active Profiles
* `PropertySource` 의 적용 우선순위가 있음
* `spring.profiles.include` 로 무조건 active 가능 함
* `--spring.profiles.active=prod` 하면 proddb and prodmq active 됨
spring.profiles: prod spring.profiles.include:
SpringApplication.setAdditionalProfiles(…)
로 프로그래밍적으로 active 가능ConfigurableEnvironment
로도 active 가능자바에는 수많은 로깅 프레임워크들이 있는데, 스프링부트가 제공하는 기본 로깅들로도 잘 동작하니 바꾸지 마라
2019-03-05 10:57:51.112 INFO 45469 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms
2019-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2019-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
Logback does not have a FATAL level. It is mapped to ERROR.
$ java -jar myapp.jar --debug
DEBUG
레벨로 나오는건 아님--trace
로 "trace" 모드 사용 가능하다. (이게 뭘까?)spring.output.ansi.enabled
로 조절 가능%clr
이런식으로 씀
%clr(%5p)
Level | Color |
---|---|
FATAL | Red |
ERROR | Red |
WARN | Yellow |
INFO | Green |
DEBUG | Green |
TRACE | Green |
%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}
logging.file.name
or logging.file.path
propertylogging.file.name | logging.file.path | Example | Description |
---|---|---|---|
(none) | (none) | Console only logging. | |
Specific file | (none) | my.log | Writes to the specified log file. Names can be an exact location or relative to the current directory. |
(none) | Specific directory | /var/log | Writes spring.log to the specified directory. Names can be an exact location or relative to the current directory. |
logging.file.max-size
로 조절logging.file.max-history
없으면 기본 7일간 로그 파일 rotatelogging.file.total-size-cap
로 아카이브 토탈 사이즈 정의 가능
logging.file.clean-history-on-start
로깅 설정은 실제 로깅 infrastructure 와 독립적이다. 예를 들어
logback.configurationFile
은 스프링부트가 관리 안함
logging.level.<logger-name>=<level>
로 레벨 설정 가능
logging.level.root
로 root
로거 구성 함
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG
은 org.springframework.web
를 DEBUG
로 함logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
logging.level.tomcat=TRACE
Name | Loggers |
---|---|
web | org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql | org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener |
logging.config
로 configuration file 지정할 수 있음Logging System | Customization |
---|---|
Logback | logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
-spring
형식으로 만들어 달라. (logback.xml 보단 logback-spring.xml)"executable jar" 에서 Java Util Logging 가 문제 발생하니, 이 상황에선 쓰지 마라
Spring Environment | System Property | Comments |
---|---|---|
logging.exception-conversion-word | LOG_EXCEPTION_CONVERSION_WORD | The conversion word used when logging exceptions. |
logging.file.clean-history-on-start | LOG_FILE_CLEAN_HISTORY_ON_START | Whether to clean the archive log files on startup (if LOG_FILE enabled). (Only supported with the default Logback setup.) |
logging.file.name | LOG_FILE | If defined, it is used in the default log configuration. |
logging.file.max-size | LOG_FILE_MAX_SIZE | Maximum log file size (if LOG_FILE enabled). (Only supported with the default Logback setup.) |
logging.file.max-history | LOG_FILE_MAX_HISTORY | Maximum number of archive log files to keep (if LOG_FILE enabled). (Only supported with the default Logback setup.) |
logging.file.path | LOG_PATH | If defined, it is used in the default log configuration. |
logging.file.total-size-cap | LOG_FILE_TOTAL_SIZE_CAP | Total size of log backups to be kept (if LOG_FILE enabled). (Only supported with the default Logback setup.) |
logging.pattern.console | CONSOLE_LOG_PATTERN | The log pattern to use on the console (stdout). (Only supported with the default Logback setup.) |
logging.pattern.dateformat | LOG_DATEFORMAT_PATTERN | Appender pattern for log date format. (Only supported with the default Logback setup.) |
logging.pattern.file | FILE_LOG_PATTERN | The log pattern to use in a file (if LOG_FILE is enabled). (Only supported with the default Logback setup.) |
logging.pattern.level | LOG_LEVEL_PATTERN | The format to use when rendering the log level (default %5p). (Only supported with the default Logback setup.) |
logging.pattern.rolling-file-name | ROLLING_FILE_NAME_PATTERN | Pattern for rolled-over log file names (default ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz). (Only supported with the default Logback setup.) |
PID | PID | The current process ID (discovered if possible and when not already defined as an OS environment variable). |
logback-spring.xml
로 확장 기능을 제공 한다
표준
logback.xml
너무 일찍 로드하기 때문에, 확장 기능을 쓸 수 없다. logback-spring.xml or logging.config 써라
<springProfile>
로 profile 지정 가능
production & (eu-central | eu-west)
이런것도 가능
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
### 4.7.2. Environment Properties
* `<springProperty>` 로 Spring `Environment` 에서 properties 가져올 수 있음
```xml
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
<remoteHost>${fluentHost}</remoteHost>
...
</appender>
source
는 케밥 케이스 여야 함. (my.property-name)
messages
리소스 번들을 찾는다spring.messages
namespace 를 사용하여 리소스 번들의 basename 과 여러 다른 속성 사용 가능
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
spring-boot-starter-json
에 포함되었음
ObjectMapper
빈이 자동 구성 됨spring.gson.*
로 설정 가능
@RequestMapping 으로 메소드 매핑
@RestController
@RequestMapping(value="/users")
public class MyRestController {
@RequestMapping(value="/{user}", method=RequestMethod.GET)
public User getUser(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
List<Customer> getUserCustomers(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}", method=RequestMethod.DELETE)
public User deleteUser(@PathVariable Long user) {
// ...
}
}
### 7.1.1. Spring MVC Auto-configuration
* 자동구성으로 설정되는 기본 기능들
* Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
* WebJars 포함 장적 리소스 서빙
* Automatic registration of Converter, GenericConverter, and Formatter beans.
* Support for HttpMessageConverters
* Automatic registration of MessageCodesResolver
* Static index.html support.
* Custom Favicon support
* Automatic use of a ConfigurableWebBindingInitializer bean
* 커스텀할 수 있는데 `@EnableWebMvc` 대신 `WebMvcConfigurer` 클래스 넣고 작업 함
### 7.1.2. HttpMessageConverters
* 기본적으로 `HttpMessageConverter` 로 HTTP 요청 응답을 변환 함
* JSON 은 Jackson lib 로, XML 은 Jackson XML extension 으로
* 기본적으로 스트링은 `UTF-8` 로 인코딩
* 커스텀 컨버터를 추가하고 싶다면, `HttpMessageConverters` 를 사용해라
```java
mport org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}
}
JsonSerializer
and JsonDeserializer
classes 작성 가능@JsonComponent
붙히면 쉽게 빈으로 등록 해줌
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;
@JsonComponent public class Example {
public static class Serializer extends JsonSerializer<SomeObject> {
// ...
}
public static class Deserializer extends JsonDeserializer<SomeObject> {
// ...
}
}
### 7.1.4. MessageCodesResolver
* `MessageCodesResolver` 바인딩 에러로부터 에러 코드를 생성하는 전략이 있음
* `spring.mvc.message-codes-resolver-format` 로 PREFIX_ERROR_CODE or POSTFIX_ERROR_CODE 설정 가능
### 7.1.5. Static Content
* 기본적으로 정적 컨텐츠를 서빙 하는 path
* /static (or /public or /resources or /META-INF/resources)
* 기본적으로 리소스는 `/**` 로 매핑 되지만, `spring.mvc.static-path-pattern` 로 수정 가능
* 모든 리소스를 `/resources/**` 로 relocating 하고 싶다면
spring.mvc.static-path-pattern=/resources/**
* 정적 리소스 locations 도 기본값에서 대체할 수 있음
* `spring.resources.static-locations`
* Webjars 컨텐츠는 jar 로 패키징 되어 있고, path 는 `/webjars/**` 임
> 애플리케이션이 jar 로 패키지 되어 있다면, `src/main/webapp` 디렉토리는 사용하지 마라. 이는 war 패키징에서만 동작하고, jar 에선 무시 됨
* cache-busting 이나 version agnostic URLs 와 같은 추가 기능도 있음
* version agnostic URLs 예제
* `/webjars/jquery/jquery.min.js` 추가 하면 `/webjars/jquery/x.y.z/jquery.min.js` 에서 접근 가능. `x.y.z` 는 Webjar 버전
* cacue busting(무효화) 쓰는 법, content hash 를 사용하는 것이다. 예제 `<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`
spring.resources.chain.strategy.content.enabled=true spring.resources.chain.strategy.content.paths=/**
> Thymeleaf and FreeMarker 는 `ResourceUrlEncodingFilter` 자동구성으로 런타임시에 리소스 링크가 재작성 됨. JSP 는 수동으로 해줘야 함.
* 자바스크립트 처럼 리소스를 동적으로 로딩할때, 파일 renaming 은 옵션이 아님.
* "fixed" 전략은 파일이름을 변경하지 않고 URL에 정적 문자열을 추가 한다
spring.resources.chain.strategy.content.enabled=true spring.resources.chain.strategy.content.paths=/** spring.resources.chain.strategy.fixed.enabled=true spring.resources.chain.strategy.fixed.paths=/js/lib/ spring.resources.chain.strategy.fixed.version=v12
* 위의 경우 `"js/lib/"` 는 고정 버전 전략을 하고 `"/v12/js/lib/mymodule.js"` 다른 리소스는 content 를 사용 `<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`
### 7.1.6. Welcome Page
* static 과 templated welcome 페이지를 지원 함
* `index.html` 를 먼저 찾고, 없다면 `index` template 를 찾음.
### 7.1.7. Custom Favicon
* `favicon.ico` 를 찾음
### 7.1.8. Path Matching and Content Negotiation
* Spring MVC 는 요청 path 를 보고, 정의된 매핑(@GetMapping annotations on Controller methods) 으로 핸들러 가능
* 스프링부트는 기본적으로 suffix 패턴은 disable 다
* `"GET /projects/spring-boot.json"` 는 `@GetMapping("/projects/spring-boot")` 와 매치 안됨 : [best practice for Spring MVC applications](https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/web.html#mvc-ann-requestmapping-suffix-pattern-match)
* 옛날에 "Accept" 요청 헤더를 보낼수 없을때 유용했음
* "Accept" 욫어 헤더 못보내는 HTTP clients 를 위한 다른 방법이 있음
* 쿼리 파라미터를 넣어라. `"GET /projects/spring-boot?format=json"` 는 `@GetMapping("/projects/spring-boot")` 와 매핑 됨
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
* 굳이 suffix 패턴 매칭 하려면
spring.mvc.contentnegotiation.favor-path-extension=true spring.mvc.pathmatch.use-suffix-pattern=true
### 7.1.9. ConfigurableWebBindingInitializer
* 생략
### 7.1.10. Template Engines
* FreeMarker
* Groovy
* Thymeleaf
* Mustache
> JSP 는 가능하면 쓰지 마라
* `src/main/resources/templates` 에 있는 템플릿이 자동으로 선택된다
### 7.1.11. Error Handling
* 기본적으로 모든 에러는 `/error` 매핑되고, 몇가지 상세 정보가 포함된 JSON 응답이 나옴
* 브라우저에서는 "whitelabel" 에러 뷰가 있음
* 기본 동작을 바꾸려면 `ErrorController` 구현
* `@ControllerAdvice` 로 컨트롤러 / 예외 타입 / JSON 내용 등을 커스텀 가능
```java
@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
AcmeController
에서 YourException
가 발생하면, JSON 방식으로 POJO CustomErrorType
에러가 나감/error
폴더에 static HTML 을 넣으면 됨
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
5xx
에러를 넣으려면
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
더 복잡하게 하고 싶으면 ErrorViewResolver
구현 해라
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
#### Mapping Error Pages outside of Spring MVC
* 생략
### 7.1.12. Spring HATEOAS
* `@EnableHypermediaSupport` 붙혀서 사용
### 7.1.13. CORS Support
* Cross-origin resource sharing (CORS) is a W3C specification 으로 [대부분의 브라우저](https://caniuse.com/#feat=cors)에서 구현 됨
* `@CrossOrigin` 으로 사용 가능
* `addCorsMappings(CorsRegistry)` 로 글로벌 설정 가능
```java
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
functional and annotation-based 의 두가지 형태로 제공
@RestController
@RequestMapping("/users")
public class MyRestController {
@GetMapping("/{user}")
public Mono<User> getUser(@PathVariable Long user) {
// ...
}
@GetMapping("/{user}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long user) {
// ...
}
@DeleteMapping("/{user}")
public Mono<User> deleteUser(@PathVariable Long user) {
// ...
}
}
* functional "WebFlux.fn" 에서는 라우팅 구성을 실제 요청 핸들링과 구분한다.
```java
@Configuration(proxyBeanMethods = false)
public class RoutingConfiguration {
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
}
}
@Component
public class UserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
// ...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
// ...
}
}
spring.codec.*
로 설정
spring.jackson.*
CodecCustomizer
로 커스텀 예제
import org.springframework.boot.web.codec.CodecCustomizer;
@Configuration(proxyBeanMethods = false) public class MyConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return codecConfigurer -> {
// ...
};
}
}
### 7.2.3. Static Content
* 기본적으로 /static (or /public or /resources or /META-INF/resources 에서 정적 컨텐츠 제공
* `ResourceWebHandler` 로 수정 가능
* 기본적으로 `/**` 로 매핑 되는데 바꿀 수 있음
spring.webflux.static-path-pattern=/resources/**
* `spring.resources.static-locations` 로 정적 리소스 location 수정 가능
* Webjars 컨텐츠도 제공 함
> Spring WebFlux 은 서블릿 API 에 의존하지 않아 `src/main/webapp` 디렉터리를 사용하지 않음
### 7.2.4. Template Engines
* 동적 컨텐츠를 위한 템플릿 엔진
* FreeMarker
* Thymeleaf
* Mustache
### 7.2.5. Error Handling
* `WebExceptionHandler` 로 모든 에러 핸들 가능.
* 머신 클라이언트에서는 JSON 응답과 세부 에러 내용이, 브라우저 클라이언트에서는 "whitelabel" 에러로 표시 됨
* 커스텀 하는 첫번째 단계는 `ErrorAttributes` 빈을 추가하는 것
* 오류 처리 동작을 변경하기 위해서는 `ErrorWebExceptionHandler` 를 등록해라
* 근데 `WebExceptionHandler` 는 매우 low-level 임
* 따라서 `AbstractErrorWebExceptionHandler` 로 편리하게 할 수 있음
```java
public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
// Define constructor here
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions
.route(aPredicate, aHandler)
.andRoute(anotherPredicate, anotherHandler);
}
}
/error
폴더에 status code 에 대한 HTML 에러 페이지 넣음
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
5xx
에러
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
WebFilter
인터페이스 제공 함
Ordered
구현하거나, @Order
붙힘Web Filter | Order |
---|---|
MetricsWebFilter | Ordered.HIGHEST_PRECEDENCE + 1 |
WebFilterChainProxy (Spring Security) | -100 |
HttpTraceWebFilter | Ordered.LOWEST_PRECEDENCE - 10 |
spring-boot-starter-jersey
디펜던시 추가하고 ResourceConfig
Bean 추가 필요
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Endpoint.class);
}
}
> Jersey 는 executable archives 지원에 제한적
* ` @Components` 로 HTTP 엔드포인트 등록 해줘야 함
```java
@Component
@Path("/hello")
public class Endpoint {
@GET
public String message() {
return "Hello";
}
}
Servlet
, Filter
, *Listnener
객체 등록 가능@Order
를 지정하지 않는게 안전하다. 꼭 필요할때만 써라
javax.servlet.ServletContainerInitializer
나 org.springframework.web.WebApplicationInitializer
인터페이스를 직접 실행하지 않음
org.springframework.boot.web.servlet.ServletContextInitializer
를 구현해라
@ServletComponentScan
으로 자동으로 등록 됨
@ServletComponentScan
은 standalone 컨테이너엔 영향 안줌
server.port
, server.address
server.servlet.session.persistent
, server.servlet.session.timeout
, server.servlet.session.store-dir
, server.servlet.session.cookie.*
server.error.path
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
#### Customizing ConfigurableServletWebServerFactory Directly
* 위 코드의 사용이 제한적이면, Tomcat, Jetty, UndertowServletWebServerFactory 를 직접 등록할 수 있음
```java
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(9000);
factory.setSessionTimeout(10, TimeUnit.MINUTES);
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
return factory;
}
java -jar
로 구동하면 표준 컨테이너로 배포 가능하다. 하지만 JSPs 는 executable jar 를 지원하지 않음error.jsp
페이지가 적용 안됨WebClient.Builder
로 자동구성 됨spring-messaging
모듈은 서버와 클라이언트에서 RSocket 요청 과 응답을 지원 함RSocketStrategies
빈을 제공하고, 다음의 순서대로 구성하려고 함
spring-boot-starter-rsocket
이 위 2개를 모두 제공 함spring.rsocket.server.mapping-path=/rsocket # a mapping path is defined
spring.rsocket.server.transport=websocket # websocket is chosen as a transport
#spring.rsocket.server.port= # no port is defined
RSocket 을 웹서버로 연결하는것은 Reactor Netty 에서만 지원한다.
또는, RSocket TCP or websocket 서버가 독립적으로 내장 서버로 시작한다.
spring.rsocket.server.port=9898 # the only required configuration
spring.rsocket.server.transport=tcp # you're free to configure other properties
RSocket
채널이 한번 확립되면, 모든 파티가 상대방과 요청을 주고 받을 수 있다RSocketRequester.Builder
객체는 주입 시점에 새 객체가 생성 됨
@Service
public class MyService {
private final Mono<RSocketRequester> rsocketRequester;
public MyService(RSocketRequester.Builder rsocketRequesterBuilder) {
this.rsocketRequester = rsocketRequesterBuilder
.connectTcp("example.org", 9898).cache();
}
public Mono<User> someRSocketCall(String name) {
return this.rsocketRequester.flatMap(req ->
req.route("user").data(name).retrieveMono(User.class));
}
}
12. Caching
@EnableCaching
하면 자동 구성 해줌@Component public class MathService {
}
@Bean public CacheManagerCustomizer cacheManagerCustomizer() {
return new CacheManagerCustomizer() {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setAllowNullValues(false);
}
};
}
Only necessary if more than one provider is present
spring.cache.jcache.provider=com.acme.MyCachingProvider spring.cache.jcache.config=classpath:acme.xml
spring.cache.ehcache.config=classpath:config/another-config.xml
spring.cache.infinispan.config=infinispan.xml
spring.cache.cache-names=cache1,cache2
@Configuration(proxyBeanMethods = false) public class CouchbaseCacheConfiguration {
}
spring.cache.cache-names=cache1,cache2 spring.cache.redis.time-to-live=600000
spring.cache.cache-names=cache1,cache2 spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
spring.cache.cache-names=cache1,cache2
spring.cache.type=none