Environment 추상화와 프로퍼티
- 지금까지 자동구성에 대한 과정을 살펴봤다.
- 자동 구성에 의해 만들어지는 빈 오브젝트에는 보통 기본값이 들어가있다. ex) port: 8080
- cf) Environment 인터페이스
- 스프링 애플리케이션의 환경 설정과 관련된 정보를 제공한다.
- 프로퍼티 값을 가져오거나 환경 변수 값을 조회하는 메서드를 제공한다. getProperty()
- 스프링은 단일화된 방식으로 접근할 수 있도록 설계되어있다.
- 1) standardEnviroment
- System propertyies
- System Enviroment Variables
- 2) standardServletEnvironment: 웹 환경일 때 사용 가능
- ServletConfig Parameters
- servletcontext Parameters;
- JNDI
- @PropertySource: 커스텀해서 새로운 property를 추가할 수 있다.
- 3) SpringBoot
- application.properties, xml, yml
- Environment.getProperty("property.name")
- 스프링 부트에서 위와 같이 사용하면 아래 네 가지의 변형들을 모두 확인한다.
- property.name
- property_name
- PROPERTY.NAME
- PROPERTY_NAME
자동 구성에 Environment 프로퍼티 적용
- ApplicationRunner 인터페이스를 구현한 빈을 등록하면?
- 스프링 부트의 모든 초기화 작업 이후, run() 메서드를 통해 해당 빈을 실행한다.
- 기존에 만들어둔 톰캣 웹 서버를 삭제하고 시작한다.
- 다음과 같이 bean을 추가한다.
- Environment: 환경 정보를 추상화한 오브젝트
<hide/>
@MySpringBootApplication
public class HellobootApplication {
@Bean
ApplicationRunner applicationRunner(Environment env) {
return args -> {
String name = env.getProperty("my.name");
System.out.println("my.name: " + name);
};
}
public static void main(String[] args) {
SpringApplication.run(HellobootApplication.class, args);
}
}
Note) 실행 결과
- 설정 정보가 없기 때문에 null 출력된다.
- 설정 정보를 만들고 나면 다음 값이 출력된다.
- properties 파일에 다음과 같이 저장한다.
my.name=egg
- application.properties 보다 우선하는 것은 환경 변수이다.
- 환경 변수명은 일반적으로 대문자로 시작하는 게 관례이다.
- 실행 디버그 구성에 다음과 같이 설정해주면 아래의 설정이 우선시된다.
Note) 실행 결과
- 시스템 변수는 방금 살펴본 환경 변수보다도 우선 순위에 있다.
- 옵션 수정의 vm 옵션 추가 버튼을 누르고다음과 같이 시스템 변수를 설정해준다.
- "-D" + properties 파일에 넣었던 내용을 넣는다. (자바 표준 방식)
Note) 실행 결과
- 다음과같이 properties 를 설정한다.
contextPath=/app
- test 코드도 다음과 같이 바꿔줘야한다.
<hide/> @Test
void helloApi() {
TestRestTemplate rest = new TestRestTemplate();
ResponseEntity<String> response = rest.getForEntity(
"http://localhost:8080/app/hello?name={name}", String.class, "Spring");
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).startsWith(
MediaType.TEXT_PLAIN_VALUE);
assertThat(response.getBody()).isEqualTo("*Hello! Spring*");
}
@Test
void failHelloApi() {
TestRestTemplate rest = new TestRestTemplate();
ResponseEntity<String> response = rest.getForEntity(
"http://localhost:8080/app/hello?name=", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
}
@Value와 PropertySourcesPlaceholderConfigurer
- @Value를 이용해서 설정 값을 컴포넌트 내에서 이용하려고 한다.
- 다음과 같이 가져올 수 있다.
<hide/>
@Value("${contextPath}")
String contextPath;
@Bean("TomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(Environment env) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(this.contextPath);
return factory;
}
Note) 실행 결과
- 위와 같이 실행하면 contextPath 에 값이 주입되지 않는다. PropertyPlaceholderConfig 를 설정하려고 한다.
- PropertySourcesPlaceholderConfigurer
- 빈 정보를 우선적으로 모은 다음에 후처리기(PostProcessor)를 돌린다.
- 다음 설정 정보를 resources. 안의 META-INF.spring.tobyspring.config.MyAutoConfig 파일의 안에 넣어준다.
- (오류) 로 인해 selectImport()의 반환 값에 추가해줬다.
tobyspring.config.autoconfig.PropertyPlaceholderConfig
Note) 실행 결과
- 그러면 스프링 부트가 잘 돌아간다.
프로퍼티 클래스의 분리
- 지난 시간에는 프로퍼티를 클래스 내에 필드로 선언했는데 이는 번거롭다는 단점이있다.
- 따라서, 프로퍼티를 따로 추출해서 재사용성을 높이려고 한다.
- 필드로 설정하는 경우에 만약 설정 정보가 없다면?
- 이런 경우를 위해 default 값을 정할 수 있다.
- 필드에 직접 넣는 방법
- port 뒤에 8080을 넣어주면 8080을 default 값으로 설정 가능
<hide/>
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
@Value("${contextPath:}")
String contextPath;
@Value("${port:8080}")
int port;
@Bean("TomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(this.contextPath);
factory.setPort(port);
return factory;
}
}
- properties
contextPath=/app
port=9090
- 기존 테스트 코드의 port도 9090으로 바꾼다.
- 아래와 같이 설정하면
- 프로퍼티 소스의 프로퍼티명과 클래스의 프로퍼티명이 일치하는 값을 찾아서 자동으로 바인딩한다.
- bind(name, 어떤 클래스로 바인딩할 것인지)
- 새로운 프러퍼티가 추가될 때, 서버 프로퍼티즈 라는 클래스에 필드를 추가해주고 그 프라퍼티를 사용한다.
<hide/>
@MyAutoConfiguration
public class ServerPropertiesConfig {
@Bean
public ServerProperties serverProperties(Environment environment){
return Binder.get(environment).bind("", ServerProperties.class).get();
}
}
프로퍼티 빈의 후처리기 도입
- 기존에 만든 ServerPropertiesConfig 를 우선 제거한다.
- ServerProperties 클래스에 컴포넌트를 적용한다.
- 톰캣 클래스에 @Import 애너테이션을 붙인다.
- 빈으로 등록한다.
<hide/>
@MyAutoConfiguration
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
@Import(ServerProperties.class)
public class TomcatWebServerConfig {
@Bean("TomcatWebServerFactory")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(ServerProperties properties) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setContextPath(properties.getContextPath());
factory.setPort(properties.getPort());
return factory;
}
}
- MyConfigurationProperties 에너테이션을 생성한다.
<hide/>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyConfigurationProperties {
}
- ServerProperties 인스턴스가 만들어지면 필드를 바인딩하려고한다.
- bindOrCreate() 바인딩을 시도했다가 없으면 새로 만든다.
<hide/>
@MyAutoConfiguration
public class PropertyPostProcessorConfig {
@Bean
BeanPostProcessor propertyPostProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) {
return null;
}
return Binder.get(env).bindOrCreate("", bean.getClass());
}
};
}
}
- MyConfigurationProperties
- prefix 적용
<hide/>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
public @interface MyConfigurationProperties {
String prefix();
}
- properties
<hide/>
server.contextPath=/app
server.port=9090
- ServerProperties
- prefix를 적용한다.
<hide/>
@MyConfigurationProperties(prefix="server")
public class ServerProperties {
private String contextPath;
private int port;
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
- PropertyPostProcessConfig
- 아래와 같이 만들면 프로퍼티를 바인딩할 때, prefix를 붙여서 뒤에 나오는 이름과 prefix의 이름과 일치하는지를 체크한다.
<hide/>
@MyAutoConfiguration
public class PropertyPostProcessorConfig {
@Bean
BeanPostProcessor propertyPostProcessor(Environment env) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), MyConfigurationProperties.class);
if (annotation == null) {
return null;
}
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(annotation);
String prefix = (String) attrs.get("prefix");
return Binder.get(env).bindOrCreate(prefix, bean.getClass());
}
};
}
}
Note)실행 결과 - 서버가 잘 뜬다.
- 톰캣에 붙인 @Import를 @EnableMyConfigurationProeprties로 바꾸려고한다.
- 애너테이션 앞에 "Enable"이 붙는 경우는 메타 애너테이션
<hide/>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyConfigurationPropertiesImportSelector.class)
public @interface EnableMyConfigurationProperties {
}
- MyConfigurationPropertiesImportSelector 클래스 생성
title
- content
- content
title
- content
- content
title
- content
- content
출처 - 인프런 토비의 스프링
'Spring Framework > 토비의 스프링' 카테고리의 다른 글
Chapter 07. 조건부 자동 구성 (0) | 2023.07.24 |
---|---|
Chapter 06. 메타 애너테이션과 합성 애너테이션 (2) | 2023.07.14 |
Chapter 05. DI와 테스트, 디자인 패턴 (0) | 2023.07.04 |
Chapter 04. 독립 실행형 스프링 애플리케이션 (0) | 2023.06.24 |
Chapter 03. 독립 실행형 서블릿 애플리케이션 (0) | 2023.06.20 |