@Profile 이란?
@Conditional 이란? - Spring Core(AOP)할때 학습
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
/**
* All {@link Condition}s that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class? extends Condition[] value();
}
Condition.Java -> matches 메소드의 반환값이 true이면 동작
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
AutoConfiguration
@EnableAutoConfiguration
AutoConfiguration에서 제외 - @SpringBootAutoConfiguration에서 exclude 해주면 됨!
@SpringBootApplication을 사용한 경우도 동일하다
@SpringBootApplication(exclude= RedisAutoConfiguration.class)
public class StudentApplication {
public static void main(String[] args) {
SpringApplication.run(StudentApplication.class, args);
}
}
Auto Configuration의 예시
@AutoConfiguration
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
// 생략
}
@ConditionalOnXXX - SpringBoot가 제공하는 @Conditional의 확장
*ConditionalOnBean, ConditionalOnMissingBean -> AutoConfig에서만 쓸 수 있음
@ConditionalOnBean
@Configuration
public class MyAutoConfiguration {
@ConditionalOnBean(Value=Datasource.class) // 이런식으로 많이 씀
@Bean
public MyService myService() {
...
}
}
@ConditionalOnMissingBean
@Configuration
public class MyAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public MyService myService() {
...
}
}
@Externalized Configuration
예시) 서버 포트를 변경하는 세가지 방법
1) application.properties : server.port=8888
2) 환경변수 : SERVER_PORT=8888 java -jar target/foo.jar
3) 실행 명령어(Command Line Argument) 인자 : java -jar target/student.jar --server.port=8888
@ConfigurationProperties 바인딩으로 server.port << 시 동작함(ServerProperties라는 클래스에서 값 객체로 받음)
application.properties / application.yaml
com.foo.example=test
com:
foo:
example: test
@Value 바인딩: 속성값을 @Value 에너테이션으로 바인딩하여 쓴다
@Component
public class MyBean {
@Value("${nhn.student.name}")
private String name;
// ...
}
@ConfigurationProperties 바인딩
@ConfigurationProperties(”nhn.student")
public class StudentProperties {
private String name;
// getters / setters...
}
Externalized Configuration 자동완성
@ConfigurationProperties의 Relaxed Binding
예시)
@ConfigurationProperties(”nhn-academy.student")
public class StudentProperties {
private String firstName;
// getters / setters...
}
@ConfigurationProperties 활성화 -> 예시로 실습
이게 달린 클래스는 Bean이 된다.
또한 application.properties에 값을 작성하면 무난하게 값이 들어옴을 확인할 수 있다.
알아야 할 것은
1) application.properties / application-prod.properties에서 active profile을 줄때, 전자가 기본으로 동작하고 후자가 덮어쓰는 방식이다.
2) properties에서 값을 읽을 때 String으로 값을 읽는데, 이를 @ConstructorBinding을 적용하고 if문으로 검사한 다음 값을 넣어주면, 생성자 내부에서 혹시 값이 없으면 에러를 찍어준다. 또한 타입도 변형해서 가져온다.
Spring Profile
Externalized Configuration 우선순위 : spring-boot 는 다음 순서로 설정을 읽어 들인다. 중복되는 경우, 덮어쓰게 된다
결국 덮어쓰는 순서로 최우선이 된다.
Application Properties 우선순위
실행 인자로 제공하는 spring.profiles.active 설정으로 application-{profile}.properties 를 사용할 것인지 결정한다.
중복되는 경우, 덮어쓰게 된다(override)
또한. 위치를 찾아가는 순서에 따라 최종 순서도 정할 수 있다.
SpringBoot 개발자 도구
비활성화 방법?
개발자 도구와 캐시 - 개발자 도구는 뷰 템플릿, 정적 리소스 캐시를 자동 비활성화 함
개발자 도구 - 자동 재시작
Spring Boot Actuator : 상용화 준비(Production-Ready) 기능을 위한 Spring Boot 모듈
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Spring Boot Actuator - Endpoint(/Get/foo <<<)
Spring Boot Actuator - EndPoint 활성화
기본적으로 shutdown 빼고 전부 활성화(누가 끄면 안됨)
management.endpoint.{id}.enabled 속성으로 활성화/비활성화 (id는 위 사진에 있는 id)
shutdown endpoint 활성화
management.endpoint.shutdown.enabled=true (properties)
management: (yaml)
endpoint:
shutdown:
enabled: true
shutdown endpoint Opt-in 설정
management.endpoints.enabled-by-default=false ## 모두 비활성화
management.endpoint.info.enabled=true ## info만 활성화
management:
endpoints:
enabled-by-default: false ## 모두 비활성화
endpoint:
info:
enabled: true ## info만 활성화
Endpoint 노출방식(JMX,Web) 설정
JMX는 모든 Endpoint를 노출하고, Web은 health만 노출하는것이 기본 설정이니...
include, exclude property로 노출방식을 활성화 할 수 있다!
단. Exclude 설정이 include 설정보다 우선된다.
ex) health, info 만 jmx에서노출할 경우
management.endpoints.jmx.exposure.include=health,info
ex) env,bean을 제외한모든 Endpoint를 Web에서 노출
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,bean
EndPoint 사용자 정의
@Endpoint : Endpoint로 노출할 Bean에 선언
* WebEndpoint : HTTP Endpoint로만 노출
@ReadOperation, @WriteOperation, @DeleteOperation - HTTP의 GET,POST,DELETE 메소드
@EndpointWebExtension, @EndpointJmxExtension - 이미 존재하는 EndPoint에 기술 전용 operation을 추가할 때 사용
EndPoint 사용자 정의 예시
@Component
@Endpoint(id = "counter") // id 이름이 겹치면 안됨(actuator id와)
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();
}
}
Health Endpoint : 애플리케이션의 정상동작 정보를 제공한다.
- L4 layer에서 서버의 동작을 확인하고 서버가 죽어있으면 다른 서버로 request 전송
- 서버 재 배포 후 다시 연결
Spring Boot의 기본 HealthIndicators - AutoConfiguration에 의해 동작여부 결정
커스텀 HealthIndicator 작성하기 -> 기본 actuator로 받아오는 정보에 추가 정보를 넣을 수 있음
ex)
@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(); // 요기서 Health.down()을 넣으면 status 확인 시 down으로 나옴
}
private int check() {
// perform some specific health check
return ...
}
}
info Endpoint : info.* 형식의 모든 환경변수 정보 제공(2.6 이후 기본 비활성화)
application.properties에 설정을 추가해야 함
management.info.env.enabled=true
info.edu.springboot.version=10.1.1 // 내가 넣는 값
info.edu.springboot.instructor=foo // 내가 넣는 값
info Endpoint - GitInfoContributor
info Endpoint - InfoContributor 사용자 정의- 인터페이스 구현체 개발하여 빈으로 등록
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example", Map.of("key", "value"));
}
}
Endpoint 경로변경
Actuator Endpoint 경로변경
management.endpoints.web.base-path=/actuator2 # SpringBoot 2.x
management.context-path=/actuator2 # SpringBoot 1.x : Set /actuator
Sync : Rest-Api에 호출했을때 응답을 기다림
Async : Rest-Api에 호출했을때 응답을 기다리지 않고 내 코드 진행
RestTemplate 이란?
RestTemplate 빈 설정
@Configuration
public class WebConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setReadTimeout(Duration.ofSeconds(5L)) // 5sec
.setConnectTimeout(Duration.ofSeconds(3L)) // 3sec
.build();
}
}
GET method를 호출한다면?
@Override
public List<Student> getStudents() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON); // GET일 경우는 안써도 된다.
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>>() {
}); // Student는 Get,Set으로 Mapping 가능, List는 Generic으로 객체를 받음. 따라서 List로는 Field를 알 수 가 없음. 따라서 new Para~~~을 통해 List<Student>의 필드를 알게 해준다.
return exchange.getBody();
}
POST method를 호출한다면?
@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();
}