Spring Boot 2 - 콩이 초기화되기 전에 조치를 취합니다.
문제 설명
빈이 초기화되기 전에 클래스 경로 또는 외부 위치의 속성 파일에서 속성을 로드하려고 합니다.이러한 속성은 Bean 초기화의 일부이기도 합니다.Spring의 표준 application.properties 또는 사용자 지정에서 속성을 자동으로 연결할 수 없습니다. 동일한 속성 파일은 여러 배포 가능해야 하기 때문입니다.
내가 시도한 것
Spring Application Events에 대해 알고 있습니다. 사실 Spring Context가 초기화된 후 일부 작업을 수행하기 위해 ContextRefreshedEvent를 이미 후킹하고 있습니다(이 단계에서는 Beans도 초기화됨).
, 에서 SpringDocs의 설명으로 볼 수 .ApplicationEnvironmentPreparedEvent
희망적으로 보였지만 후크는 작동하지 않았습니다.
@SpringBootApplication
public class App {
public static void main(String[] args) throws IOException {
SpringApplication.run(App.class, args);
}
@EventListener
public void onStartUp(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEvent"); // WORKS
}
@EventListener
public void onShutDown(ContextClosedEvent event) {
System.out.println("ContextClosedEvent"); // WORKS
}
@EventListener
public void onEvent6(ApplicationStartedEvent event) {
System.out.println("ApplicationStartedEvent"); // WORKS BUT AFTER ContextRefreshedEvent
}
@EventListener
public void onEvent3(ApplicationReadyEvent event) {
System.out.println("ApplicationReadyEvent"); // WORKS WORKS BUT AFTER ContextRefreshedEvent
}
public void onEvent1(ApplicationEnvironmentPreparedEvent event) {
System.out.println("ApplicationEnvironmentPreparedEvent"); // DOESN'T WORK
}
@EventListener
public void onEvent2(ApplicationContextInitializedEvent event) {
System.out.println("ApplicationContextInitializedEvent"); // DOESN'T WORK
}
@EventListener
public void onEvent4(ApplicationContextInitializedEvent event) {
System.out.println("ApplicationContextInitializedEvent");
}
@EventListener
public void onEvent5(ContextStartedEvent event) {
System.out.println("ContextStartedEvent");
}
}
갱신하다
M이 제안한 바와 같이.댓글에 Deinum, 나는 아래와 같은 애플리케이션 컨텍스트 이니셜라이저를 추가하려고 했습니다.그것도 효과가 없는 것 같습니다.
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(App.class)
.initializers(applicationContext -> {
System.out.println("INSIDE CUSTOM APPLICATION INITIALIZER");
})
.run(args);
}
업데이트 #2
제 문제는 속성 로드에 관한 것이지만, 제 질문/호기심은 클래스가 콩으로 초기화되어 Spring IoC 컨테이너에 들어가기 전에 코드를 실행하는 방법에 관한 것입니다.이제 이러한 콩은 초기화 중에 몇 가지 속성 값이 필요하며 다음과 같은 이유로 자동 배선할 수 없습니다.
의견 및 답변에 명시된 바와 같이 Spring Boot의 외부화된 구성 및 프로필을 사용하여 동일한 작업을 수행할 수 있습니다.그러나 응용 프로그램 속성과 도메인 관련 속성을 별도로 관리해야 합니다.기본 도메인 속성은 100개 이상의 속성을 가져야 하며, 그 수는 시간이 지남에 따라 증가합니다.응용 프로그램 속성과 도메인 관련 속성 모두 서로 다른 환경(개발, SIT, UAT, 운영)에 대한 속성 파일을 가지고 있습니다.속성 파일이 하나 이상의 기본 속성을 재정의합니다.8개의 속성 파일입니다.이제 동일한 앱을 여러 지역에 배포해야 합니다.을 상이니다입.8 * n
: 속파위치n
는 지역의 수입니다.모든 속성 파일을 공통 모듈에 저장하여 서로 다른 배포 가능한 파일에 액세스하기를 원합니다.런타임에 환경과 지리를 시스템 속성이라고 합니다.
스프링 프로필과 우선 순위를 사용하여 이러한 작업을 수행할 수 있지만, 프로그래밍 방식으로 제어하고 싶습니다(나는 나만의 자산 저장소도 유지할 것입니다).예를 들어, 저는 다음과 같은 편리한 유틸리티를 작성할 것입니다.MyPropUtil
다음과 같이 액세스할 수 있습니다.
public class MyPropUtil {
private static Map<String, Properties> repository;
public static initialize(..) {
....
}
public static String getDomainProperty(String key) {
return repository.get("domain").getProperty(key);
}
public static String getAppProperty(String key) {
return repository.get("app").getProperty(key);
}
public static String getAndAddBasePathToAppPropertyValue(String key) {
...
}
}
@Configuration
public class MyComponent {
@Bean
public SomeClass getSomeClassBean() {
SomeClass obj = new SomeClass();
obj.someProp1(MyPropUtil.getDomainProperty('domainkey1'));
obj.someProp2(MyPropUtil.getAppProperty('appkey1'));
// For some properties
obj.someProp2(MyPropUtil.getAndAddBasePathToAppPropertyValue('some.relative.path.value'));
....
return obj;
}
}
서류를 보니, 마치ApplicationEvents
그리고.ApplicationInitializers
제 요구에 부합하지만, 저는 제 문제 진술을 위해 그들이 일하도록 할 수 없습니다.
파티에 조금 늦었지만 당신의 업데이트된 문제 진술에 대한 해결책을 제시할 수 있기를 바랍니다.
여기에서는 클래스가 콩으로 초기화되고 Spring IoC 컨테이너에 들어가기 전에 일부 코드를 실행하는 방법에 초점을 맞춥니다.
한 가지 문제는 @EventListener 주석을 통해 응용 프로그램 이벤트를 정의하고 있다는 것입니다.
이러한 주석은 컨텍스트가 준비되었을 때만 트리거되는 EventListenerMethodProcessor에 의해 처리되므로 모든 빈이 시작된 후에만 호출됩니다(Singletons 뒤의 SmartInitializingSingleton# 참조).인스턴스화됨)
컨텍스트가 준비되기 전에 발생하는 일부 이벤트(예: ContextStartedEvent, ApplicationContextInitializedEvent)는 수신기에 도달하지 못합니다.
대신 이러한 이벤트에 대한 인터페이스를 직접 확장할 수 있습니다.
@Slf4j
public class AllEvent implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info("I am a {}", event.getClass().getSimpleName());
}
누락된 @Component를 기록합니다.이러한 이벤트 중 일부가 발생한 후에는 인스턴스화도 발생할 수 있습니다.@Component를 사용하면 다음 로그가 표시됩니다.
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent
주석 수신기보다 더 좋고 더 즉각적이지만 초기화 이벤트를 수신하지 않습니다.이를 위해 필요한 것은 여기에 나와 있는 지침을 참조하십시오.
요약하자면,
- 디렉토리 리소스/META-INF 생성
- 파일 스프링.팩토리 생성
- org.스프링프레임워크.스프링프레임워크.스냅샷ApplicationListener=full.path.to.my .class.모든 이벤트
결과:-
I am a ApplicationContextInitializedEvent
I am a ApplicationPreparedEvent
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent
특히 ApplicationContextInitializedEvent를 사용하면 필요한 인스턴스별 작업을 수행할 수 있습니다.
속성 저장소가 될 콩을 만들고 속성이 필요한 다른 콩에 주입합니다.
정적 을 제에서 방법대 신적정에 두는 에,MyPropUtil
인스턴스 메서드를 사용하여 클래스 자체를 been으로 만듭니다. 화기Map<String, Properties> repository
에 시대에initialize
린방법으로 을 단 방법@PostConstruct
.
@Component
public class MyPropUtil {
private static final String DOMAIN_KEY = "domain";
private static final String APP_KEY = "app";
private Map<String, Properties> repository;
@PostConstruct
public void init() {
Properties domainProps = new Properties();
//domainProps.load();
repository.put(DOMAIN_KEY, domainProps);
Properties appProps = new Properties();
//appProps.load();
repository.put(APP_KEY, appProps);
}
public String getDomainProperty(String key) {
return repository.get(DOMAIN_KEY).getProperty(key);
}
public String getAppProperty(String key) {
return repository.get(APP_KEY).getProperty(key);
}
public String getAndAddBasePathToAppPropertyValue(String key) {
//...
}
}
그리고.
@Configuration
public class MyComponent {
@Autowired
private MyPropUtil myPropUtil;
@Bean
public SomeClass getSomeClassBean() {
SomeClass obj = new SomeClass();
obj.someProp1(myPropUtil.getDomainProperty("domainkey1"));
obj.someProp2(myPropUtil.getAppProperty("appkey1"));
// For some properties
obj.someProp2(myPropUtil.getAndAddBasePathToAppPropertyValue("some.relative.path.value"));
//...
return obj;
}
}
아니면 주사를 놓을 수도 있습니다.MyPropUtil
京都까지 SomeClass
:
@Component
public class SomeClass {
private final String someProp1;
private final String someProp2;
@Autowired
public SomeClass(MyPropUtil myPropUtil) {
this.someProp1 = myPropUtil.getDomainProperty("domainkey1");
this.someProp2 = myPropUtil.getAppProperty("appkey1");
}
//...
}
Spring Cloud Config는 귀사의 문제 진술을 위한 완벽한 솔루션이라고 생각합니다.자세한 설명서는 여기를 참조하십시오.
Spring Cloud Config는 분산 시스템의 외부 구성에 대한 서버 측 및 클라이언트 측 지원을 제공합니다.
따라서 모든 인스턴스가 동일한 구성을 사용할 뿐만 아니라 앱 외부의 구성을 쉽게 관리할 수 있습니다.
애플리케이션 속성과 도메인 관련 속성을 별도로 관리해야 하는 것이 주요 문제인 것 같습니다.봄의 관점에서 보면, 모든 속성 파일이 메모리에 로드된 후에 함께 병합되기 때문에 그다지 중요하지 않습니다.예를 들어 다음과 같은 속성이 포함된 두 개의 파일이 있습니다.
application.related=property1 # this is in application.properties
domain.related=property2 # this is in domain-specific.properties
큰을 하나 수 . , 은 것들이적후에하, 당신속포함는, 다약제틀않니그, 만가은것면다지았리입입니다.org.springframework.core.env.ConfigurableEnvironment
사례.
그러면 당신이 해야 할 일은 단지 당신이 필요로 하는 속성을 주입하는 것입니다.@Value
.
하려면 spring▁▁forspring.config.name
속성(환경 변수, 명령줄 또는 프로그래밍 방식을 통해)을 확인할 수 있습니다.예에 , 은 위예에따르면, 은다같습다니음과그것이 되어야 .spring.config.name=application,domain-specific
.
또한 프로그래밍 제어를 원한다면 사용자 정의를 추가할 수 있습니다.EnvironmentPostProcessor
그것은 그것을 드러냅니다.ConfigurableEnvironment
사례.
이 게시물에서 설명한 대로 다음과 같은 외부 속성 파일을 추가할 수 있습니다.
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
properties.setLocation(new FileSystemResource("/Users/home/conf.properties"));
properties.setIgnoreResourceNotFound(false);
return properties;
}
하고 싶지 , 을 읽고 을 이약것을사고싶않지다면용하만요세, 함속파설정하같다이음과읽속성을일고로 하세요.System.setProperty("key","value")
에 시대에main
봄이 시작되기 전의 방법.
이것도 사용하고 싶지 않다면 방법을 살펴보세요.봄까지 초기화된 bean 속성 이전에 실행됩니다.
"콩 초기화"가 정확히 무엇을 의미하는지 놓칠 수도 있습니다. 아마도 질문에서 그러한 콩의 예가 도움이 될 수 있습니다.
속성 읽기 부분과 bean initialization을 구분해야 할 것 같습니다.빈을 초기화할 때쯤이면 속성이 이미 읽혀지고 사용할 수 있습니다.원한다면, 그것은 봄 마법의 일부입니다.
이러한 이유로 다음 코드가 작동합니다.
@Component
public class MySampleBean {
public MySampleBean(@Value("${some.prop}" String someProp) {...}
}
이러한 속성이 어디서 오는지는 중요하지 않습니다(스프링 부트는 이러한 위치의 여러 가지 다른 방법을 정의합니다). 콩의 초기화가 발생하기 전에 발생할 것입니다.
이제 원래 질문으로 돌아가 보겠습니다.
클래스 경로 또는 외부 위치(빈이 초기화되기 전에 - 관련 없음)의 속성 파일에서 속성을 로드하려고 합니다.
spring / 수 .application-foo.properties
yaml)및 ( yaml)과 함께 로드할 때--spring.profiles.active=foo
파일에는 이 에 정의된 됩니다.application-foo.properties
의 통보에 .application.properties
"classpath에서 싶은 을 "classpath에서 로드"하고 싶은 을 "classpath"에 넣을 수 있습니다.application-local.properties
(local이라는 단어는 예시만을 위한 것입니다) 그리고 응용 프로그램을 시작합니다.--spring.profiles.active=local
스크립트, 기타 형식Docker 파일", "Docker 파일")
경로 하려면 다음을할 수 .--spring.config.location=<Full-path-file>
하십시오.application.properties
그리고 여전히 사용합니다.--spring.config.location
동일한 키-값 쌍을 사용하면 클래스 경로의 속성보다 우선합니다.
또다음사수있다니습할용만는▁only다있만 사용할 수 있습니다.--sring.profiles.active=local or remote
구성 위치를 전혀 사용하지 않습니다.
명령줄에서 직접 외부 위치를 구성할 수 있습니다.
java -jar app.jar --spring.config.location=file:///Users/home/config/external.properties
웹 응용 프로그램을 사용할 수 있습니다.클래스가 빈으로 초기화되기 전에 코드를 실행하는 이니셜라이저
public class MyWebInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { var ctx = new AnnotationConfigWebApplicationContext(); ctx.register(WebConfig.class); ctx.setServletContext(servletContext);
AnnotationConfigWebApplicationContext를 만들고 register()에 웹 구성 파일을 등록합니다.
다음의 경우 확인할 수 있습니다.PropertySource
도움이 될 수 있습니다.
예:
@PropertySource({"classpath:persistence/persistence.properties"})
다음 시간마다 이 주석을 사용할 수 있습니다.@Configuration
또는@SpringBootApplication
이 나다
콩 초기화의 일부를 소유하고 싶은 것처럼 들립니다.일반적으로 사람들은 봄이 콩 구성을 완료한다고 생각하지만, 당신의 경우 봄이 콩 구성을 시작한다고 생각하는 것이 더 쉬울 수 있습니다.
따라서 빈에는 구성할 속성과 Spring에서 구성할 속성이 있습니다.Spring에서 구성할 항목에 주석을 달기만 하면 됩니다(사용).@Autowire
또는@Inject
당신이을.@PostConstruct
또는InitializingBean
.
class MyMultiStageBoosterRocket {
private Foo foo;
private Bar bar;
private Cat cat;
@Autowire
public MyMultiStageBoosterRocket(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar'
}
// called *after* Spring has done its injection, but *before* the bean
// is registered in the context
@PostConstruct
public void postConstruct() {
// your magic property injection from whatever source you happen to want
ServiceLoader<CatProvider> loader = ServiceLoader.load(CatProvider.class);
// etc...
}
}
재산 은 어떻게든할 수 , 은 당신에게 것처럼 . 하지만 그것은 당신에게 맞는 것 같습니다.MyPropUtil
예문.
훨씬 더 많이 참여하게 되면 Bean Post Processor를 직접 살펴보기 시작합니다.@PostConstruct
는 종류의 단순한 변형입니다.
유용한 답변과 함께 이전 질문이 있습니다. 여기 스프링 빈 포스트 프로세서는 정확히 어떻게 작동합니까? 하지만 단순성을 위해 다음과 같은 작업을 수행할 수 있습니다.
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// fixme: detect if this bean needs fancy initialization
return bean;
}
}
확실히.@PostProcess
또는InitializingBean
더간지만사용지정포자스프트큰있이습다 니점이는 ▁it▁▁can. 다른 봄맞이 할 수 .그것은 다른 Spring 관리된 콩과 함께 주입될 수 있습니다., 무엇이든 할 수 , 으로 관리할 수 있습니다.', Spring은 Spring과 동일한 프로세스를 합니다.
이전에 필요한 모든 것을 로드해 보십시오.
SpringApplication.run()
불러
public static void main(String[] args) {
// before spring initialization
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
SpringApplication.run(CyberRiskApplication.class, args);
}
Application EnvironmentPreparatedEvent를 사용할 수는 있지만 EventListener 주석을 사용하여 구성할 수는 없습니다.이 시간이 되면 Beandr 정의가 로드되지 않기 때문입니다.이 이벤트를 구성하는 방법은 아래 링크를 참조하십시오.https://www.thetechnojournals.com/2019/10/spring-boot-application-events.html
언급URL : https://stackoverflow.com/questions/58725479/spring-boot-2-do-something-before-the-beans-are-initialized
'source' 카테고리의 다른 글
numpy 배열에서 n번째 항목마다 하위 샘플링 (0) | 2023.07.21 |
---|---|
Oracle에서 마지막으로 실행된 SQL 문을 가져오고 변수 값을 바인딩하는 방법 (0) | 2023.07.21 |
비어 있지 않은 오라클의 기존 테이블에 자동 증분 ID를 추가하는 중 (0) | 2023.07.21 |
Python에서 집합을 초기화하기 위해 물결 괄호 사용 (0) | 2023.07.21 |
SQL DateTime에 대한 링크 값은 로컬(Kind=Unspecified)입니다. UTC로 만들려면 어떻게 해야 합니까? (0) | 2023.07.21 |