$ java –jar target/student.jar --spring.profiles.active=prod
application.properties (프로파일에 따라 순위 변경) > OS 환경 변수 > SPRING_APPLICATION_JSON (json 형식의 환경 변수) > 실행 명령어와 함께 전달된 인자 (java -jar student.jar --server.port=9999) > @TestPropertiesSource (테스트 코드에 포함된 애너테이션)
외부 설정은 실행 명령어 인자가 가장 우선순위가 높고 그 다음은 환경변수이고 그 다음으로 application.properties이다.
application.properties (inside jar) > application-{profile}.properties (inside jar) > application.properties (outside jar) > application-{profile}.properties (outside jar)
Classpath root > Classpath의 /config 패키지 > 실행 디렉토리 > 실행 디렉토리의 config 디렉토리
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
dependencies {
developmentOnly("org.springframework.boot:spring-boot-devtools")
}
keyMap에서 Build Project를 찾아서 사용하면 된다.
개발자 도구 - 라이브 리로드
spring.devtools.restart.trigger-file=.reloadtrigger
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>false</excludeDevtools>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
dependencies {
compile("org.springframework.boot:spring-boot-starter-actuator")
}
| ID | 설명 |
|---|---|
| autitevent | 응용시스템의 모든 감사 이벤트 목록을 제공, AuditEventRepository 빈 필요 |
| beans | 애플리케이션의 모든 빈의 목록을 제공 |
| caches | 가능한 캐시를 노출 |
| conditions | 설정 및 자동설정 클래스를 평가한 조건의 목록과 조건의 부합 여부에 대한 이유를 제공 |
| configprops | 값이 설정된 모든 @ConfigurationProperties의 목록을 제공 |
| env | 스프링의 ConfigurableEnvironment의 속성을 제공 |
| health | 애플리케이션의 health 정보를 제공 |
| httptrace | http의 요청, 응답 내용을 표시, (기본 설정으로 100개 까지만 제공, HttpTraceRepository 빈 필요) |
| info | 애플리케이션의 정보 제공 |
| shutdown | 애플리케이션의 셧다운 명령 |
| startup | startup 단계 데이터를 제공 (SpringApplication을 BufferingApplicationStartup으로 설정 필요) |
| threaddump | 쓰레드 덤프를 실행 |
threaddump를 사용하면 어디서 스레드가 돌고 있는지 알 수 있다.
management.endpoints.enabled-by-default=false
health endpoint의 동작은 HealthContributor의 타입의 빈을 활용해서 전부 확인 후 하나라도 DOWN이면 DOWN이 리턴된다.
| Property | 기본 설정 |
|---|---|
| management.endpoints.jmx.exposure.exclude | * |
| management.endpoints.jmx.exposure.include | * |
| management.endpoints.web.exposure.exclude | * |
| management.endpoints.web.exposure.include | health |
management.endpoints.jmx.exposure.include=health,info
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,bean
@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint());
http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@Component
@Endpoint(id = "counter")
public class CounterEndpoint {
private final AtomicLong counter = new AtomicLong();
// curl -XGET http://localhost:8080/actuator/counter
@ReadOperation
public Long read() {
return counter.get();
}
// curl –X POST -H"Content-Type: application/json" -d'{"delta":100}' http://localhost:8080/actuator/counter
@WriteOperation
public Long increment(@Nullable Long delta) {
if (delta == null) {
return counter.incrementAndGet();
}
return counter.addAndGet(delta );
}
// curl –X DELETE http://localhost:8080/actuator/counter
@DeleteOperation
public Long reset() {
counter.set(0);
return counter.get();
}
}
@EndpointWebExtension(endpoint = CounterEndpoint.class)
@Component
public class CounterWebEndPoint {
private final CounterEndpoint target;
public CounterWebEndPoint(CounterEndpoint target) {
this.target = target;
}
@WriteOperation
public WebEndpointResponse<Long> increment(@Nullable Long delta) {
return new WebEndpointResponse<>(target.increment(delta));
}
}
$ http://localhost:8080/actuator/health
| Key | Name | Description |
|---|---|---|
| livenessstate | LivenessStateHealthIndicator | "Liveness" 상태 |
| readinessstate | ReadinessStateHealthIndicator | "Readiness" 상태 |
Liveness 상태 : 프로세스가 살아있는 상태
Readiness 상태 : 요청을 처리할 준비가 되어있는 상태


@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
private int check() {
// perform some specific health check
return ...
}
}
http://localhost:8080/actuator/info
management.info.env.enabled=true
info.edu.springboot.version=10.1.1
info.edu.springboot.instructor=sangsang
{
"edu": {
"springboot": {
"version": "10.1.1",
"instructor": "manty"
}
},
"app": {
"java": {
"source": "11"
}
}
}
git init을 한 후 commit이 남겨져 있어야 확인이 가능하다.
<build>
<plugins>
...
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</plugin>
</plugins>
</build>
plugins {
id "com.gorylenko.gradle-git-properties" version "1.5.1"
}
{
"git": {
"branch": "master",
"commit": {
"id": "077a397",
"time": "2022-02-01T05:12:05Z"
}
}
}
<plugin>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-maven-plugin</artifactid>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
springBoot {
buildInfo()
}
{
"build": {
"artifact": "student",
"name": "student",
"time": "2022-02-01T07:07:41.030Z",
"version": "0.0.1-SNAPSHOT",
"group": "com.nhn.edu.springboot"
}
}
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example", Map.of("key", "value"));
}
}
{
"example": {
"key": "value"
}
}
management.endpoints.web.base-path=/actuator2 # 2.x
management.context-path=/actuator2 #1.x : Set /actuator
management.server.port=8888
Endpoint 경로변경과 Port 변경 중 완전히 숨길 수 있는 방법은 Port 변경이다.
Port 변경은 웹 서버 두개를 띄운다.
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
설치하고 싶은 위치로 가서
docker run \
--platform=linux/arm64 \
-d \
--name prometheus \
-p 9090:9090 \
-v $(pwd)/config:/etc/prometheus \
-v $(pwd)/data:/prometheus:rw \
prom/prometheus:v2.33.4
위 코드를 실행
mac이 아닌 경우에는 --platform=linux/arm64 제거
설치를 하고 나면 config 폴더가 생기는데 그 안에 prometheus.yml을 생성하고
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9090']
- job_name: 'student'
metrics_path: actuator/prometheus
static_configs:
- targets: ['${pc ipaddress}:8080']
위 코드를 집어넣고 맨 밑에 targets 안에 pc ipaddress를 내 pc ipaddress로 넣어주면됨
그리고 prometheus 폴더로 나가서 data 디렉토리를 rwxrwxrwx권한으로 만들어주고 start.sh와 stop.sh을 만들어준다.
#!/bin/bash
docker run \
--platform=linux/arm64 \
-d \
--name prometheus \
-p 9090:9090 \
-v $(pwd)/config:/etc/prometheus \
-v $(pwd)/data:/prometheus:rw \
prom/prometheus:v2.33.4
#!/bin/bash
docker stop prometheus
docker rm prometheus
이렇게 만들어 놓은 뒤 start.sh와 stop.sh은 아직 실행 파일이 아니기 때문에 chmod로 +x를 사용해서 실행 권한을 추가해준다. 그리고 실행하면 정상적으로 돌아가는 것을 볼 수 있다.
@Configuration
public class WebConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setReadTimeout(Duration.ofSeconds(5L))
.setConnectTimeout(Duration.ofSeconds(3L))
.build();
}
}
@Override
public List<Student> getStudents() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<String> requestEntity = new HttpEntity<>(httpHeaders);
ResponseEntity<List<Student>> exchange = restTemplate.exchange("http://localhost:8080/students",
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<List<Student>>() {
});
return exchange.getBody();
}
@Override
public Student createStudent(Student student) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<Student> requestEntity = new HttpEntity<>(student, httpHeaders);
ResponseEntity<Student> exchange = restTemplate.exchange("http://localhost:8080/students",
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<>() {
});
return exchange.getBody();
}
@Bean
WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.responseTimeout(Duration.ofSeconds(3L))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
dependencies {
testCompile("org.springframework.boot:spring-boot-starter-test")
}
| 구분 | 설명 |
|---|---|
| JUnit5 | Java 애플리케이션 단위 테이스의 산업계 표준(de-facto standard) |
| Spring Test & Spring Boot Test | Spring Boot 애플리케이션 테스트 지원용 유틸리티와 통합테스트 지원 도구 |
| AssertJ | Assertion 라이브러리 |
| Hamcrest | Matcher 객체용 라이브러리 |
| Mockito | Mocking framework |
| JSONassert | JSON Assertion 용 |
assertThat은 AssertJ와 Hamcrest 둘 다 있지만 AssertJ를 사용하는 것이 더 좋고 Matcher를 사용할 때는 Hamcrest를 사용한다.
| 구분 | 설명 | 비고 |
|---|---|---|
| MOCK | MockMvc로 테스트 가능 | 기본 |
| RANDOM_PORT | Embedded WAS 실행, 임의의 포트로 실행, (rollback 동작하지 않음) | @LocalServerPort로 주입 |
| DEFINED_PORT | Embedded WAS 실행, 설정한 포트로 실행, (rollback 동작하지 않음) | server.port 속성으로 결정 |
| NONE | WEB이 아닌 일반 서비스 테스트용 | - |