sourcetip

스프링 - 프로그래밍 방식으로 콩 세트 생성

fileupload 2023. 9. 15. 21:17
반응형

스프링 - 프로그래밍 방식으로 콩 세트 생성

구성 목록의 각 구성에 대해 12개 정도의 콩을 생성해야 하는 Dropwizard 애플리케이션이 있습니다.건강검진이나 석영 스케쥴러 같은 것들.

이와 같은 것:

@Component
class MyModule {
    @Inject
    private MyConfiguration configuration;

    @Bean
    @Lazy
    public QuartzModule quartzModule() {
        return new QuartzModule(quartzConfiguration());
    }


    @Bean
    @Lazy
    public QuartzConfiguration quartzConfiguration() {
        return this.configuration.getQuartzConfiguration();
    }

    @Bean
    @Lazy
    public HealthCheck healthCheck() throws SchedulerException {
        return this.quartzModule().quartzHealthCheck();
    }
}

나는 이런 콩이 필요한 MyConfiguration의 인스턴스를 여러 개 가지고 있습니다.지금은 이러한 정의를 복사하여 붙여넣고 새 구성마다 이름을 변경해야 합니다.

어떻게든 구성 클래스를 반복하고 각 클래스에 대한 일련의 빈 정의를 생성할 수 있습니까?

새로운 서비스를 추가해야 할 때마다 동일한 코드를 복사하여 붙여넣고 메소드 이름을 바꾸게 하지 않아도 서브클래싱 솔루션이나 안전한 타입이면 됩니다.

편집: 이 콩에 의존하는 다른 성분이 있다는 것을 추가해야 합니다(주사를 맞음).Collection<HealthCheck>예를 들어.)

따라서 새로운 콩을 즉시 선언하고 Spring의 애플리케이션 컨텍스트에 단순히 일반 콩인 것처럼 주입해야 합니다. 즉, 프록싱, 후처리 등의 절차를 거쳐야 합니다. 즉, Spring beans 라이프사이클을 거쳐야 합니다.

method javadocs 를 참조해주세요.이것이 바로 필요한 것입니다. 이는 일반적인 빈 정의로드된 후 단 하나의 빈이 인스턴스화되기 전에 Spring의 응용 프로그램 컨텍스트를 수정할 수 있기 때문입니다.

@Configuration
public class ConfigLoader implements BeanDefinitionRegistryPostProcessor {

    private final List<String> configurations;

    public ConfigLoader() {
        this.configurations = new LinkedList<>();
        // TODO Get names of different configurations, just the names!
        // i.e. You could manually read from some config file
        // or scan classpath by yourself to find classes 
        // that implement MyConfiguration interface.
        // (You can even hardcode config names to start seeing how this works)
        // Important: you can't autowire anything yet, 
        // because Spring has not instantiated any bean so far!
        for (String readConfigurationName : readConfigurationNames) {
            this.configurations.add(readConfigurationName);
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // iterate over your configurations and create the beans definitions it needs
        for (String configName : this.configurations) {
            this.quartzConfiguration(configName, registry);
            this.quartzModule(configName, registry);
            this.healthCheck(configName, registry);
            // etc.
        }
    }

    private void quartzConfiguration(String configName, BeanDefinitionRegistry registry) throws BeansException {
        String beanName = configName + "_QuartzConfiguration";
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzConfiguration.class).setLazyInit(true); 
        // TODO Add what the bean needs to be properly initialized
        // i.e. constructor arguments, properties, shutdown methods, etc
        // BeanDefinitionBuilder let's you add whatever you need
        // Now add the bean definition with given bean name
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

    private void quartzModule(String configName, BeanDefinitionRegistry registry) throws BeansException {
        String beanName = configName + "_QuartzModule";
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzModule.class).setLazyInit(true); 
        builder.addConstructorArgReference(configName + "_QuartzConfiguration"); // quartz configuration bean as constructor argument
        // Now add the bean definition with given bean name
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

    private void healthCheck(String configName, BeanDefinitionRegistry registry) throws BeansException {
        String beanName = configName + "_HealthCheck";
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HealthCheck.class).setLazyInit(true); 
        // TODO Add what the bean needs to be properly initialized
        // i.e. constructor arguments, properties, shutdown methods, etc
        // BeanDefinitionBuilder let's you add whatever you need
        // Now add the bean definition with given bean name
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

    // And so on for other beans...
}

이를 통해 필요한 콩을 효과적으로 선언하고 각 구성별로 한 세트의 콩 세트인 Spring의 애플리케이션 컨텍스트에 콩을 주입할 수 있습니다.어떤 작명 패턴에 의존한 다음 필요한 곳에 이름을 붙여 콩을 자동으로 재배해야 합니다.

@Service
public class MyService {

    @Resource(name="config1_QuartzConfiguration")
    private QuartzConfiguration config1_QuartzConfiguration;

    @Resource(name="config1_QuartzModule")
    private QuartzModule config1_QuartzModule;

    @Resource(name="config1_HealthCheck")
    private HealthCheck config1_HealthCheck;

    ...

}

주의:

  1. 파일에서 수동으로 구성 이름을 읽는 방법으로 이동하는 경우 스프링을 사용합니다.

  2. 클래스 경로를 직접 스캔하는 방법으로 진행한다면, 놀라운 리플렉션 라이브러리를 사용하는 것을 강력히 추천합니다.

  3. 모든 속성과 종속성을 각 bean 정의에 수동으로 설정해야 합니다.각 콩 정의는 다른 콩 정의와 독립적입니다. 즉, 재사용할 수 없으며, 다른 콩 정의 안에 설정할 수 없습니다.그들을 옛날 XML 방식으로 선언하는 것처럼 생각해 보세요.

  4. 자세한 내용은 BeanDefinition Builder javadocsGeneric BeanDefinition javadocs를 확인하십시오.

다음과 같은 작업을 수행할 수 있어야 합니다.

@Configuration
public class MyConfiguration implements BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @PostConstruct
    public void onPostConstruct() {
        ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
        for (..) {
            // setup beans programmatically
            String beanName= ..
            Object bean = ..
            configurableBeanFactory.registerSingleton(beanName, bean);
        }
     }

}

그냥 여기서 돈을 내겠습니다.다른 사람들은 구성이 주입되는 빈을 만들어야 한다고 언급했습니다.그러면 이 콩은 구성을 사용하여 다른 콩을 만들고 컨텍스트에 삽입합니다(또한 한 가지 형태로 주입해야 함).

다른 사람들은 이해하지 못했던 것은, 다른 콩들이 이 역동적으로 만들어진 콩들에 의존할 것이라고 말씀하셨기 때문입니다.이것은 당신의 역동적인 콩 공장이 의존적인 콩보다 먼저 인스턴스화되어야 한다는 것을 의미합니다.다음을 사용하여 주석 세계에서 이 작업을 수행할 수 있습니다.

@DependsOn("myCleverBeanFactory")

당신의 영리한 콩 공장이 어떤 종류의 물건인지에 관해, 다른 사람들은 이것을 하는 더 좋은 방법들을 추천합니다.하지만 내 기억이 맞다면, 당신은 봄 2세계에서 실제로 이와 같이 할 수 있습니다.

public class MyCleverFactoryBean implements ApplicationContextAware, InitializingBean {
  @Override
  public void afterPropertiesSet() {
    //get bean factory from getApplicationContext()
    //cast bean factory as necessary
    //examine your config
    //create beans
    //insert beans into context
   } 

..

Michaas의 답변을 확대하는 것입니다. 제가 이렇게 설정하면 Michaas의 해결책이 효과가 있습니다.

public class ToBeInjected {

}

public class PropertyInjected {

    private ToBeInjected toBeInjected;

    public ToBeInjected getToBeInjected() {
        return toBeInjected;
    }

    @Autowired
    public void setToBeInjected(ToBeInjected toBeInjected) {
        this.toBeInjected = toBeInjected;
    }

}

public class ConstructorInjected {
    private final ToBeInjected toBeInjected;

    public ConstructorInjected(ToBeInjected toBeInjected) {
        this.toBeInjected = toBeInjected;
    }

    public ToBeInjected getToBeInjected() {
        return toBeInjected;
    }

}

@Configuration
public class BaseConfig implements BeanFactoryAware{

    private ConfigurableBeanFactory beanFactory;

    protected ToBeInjected toBeInjected;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }

    @PostConstruct
    public void addCustomBeans() {
        toBeInjected = new ToBeInjected();
        beanFactory.registerSingleton(this.getClass().getSimpleName() + "_quartzConfiguration", toBeInjected);
    }

    @Bean
    public ConstructorInjected test() {
        return new ConstructorInjected(toBeInjected);
    }

    @Bean
    public PropertyInjected test2() {
        return new PropertyInjected();
    }

}

한 가지 주의할 점은 구성 클래스의 속성으로 사용자 지정 원두를 생성하고 @PostConstruct 메서드에서 초기화하고 있습니다.이렇게 하면 객체를 콩으로 등록하게 되고(그래서 @Autowire 및 @Inject는 예상대로 작동합니다), 나중에 필요한 콩에 대한 컨스트럭터 인젝션에 동일한 인스턴스를 사용할 수 있습니다.속성 가시성은 하위 클래스에서 생성된 개체를 사용할 수 있도록 protected로 설정됩니다.

우리가 들고 있는 인스턴스가 실제로는 Spring proxy가 아니기 때문에 몇 가지 문제가 발생할 수 있습니다(발화되지 않는 부분 등).다음과 같이 등록한 후 콩을 회수하는 것이 실제로 좋은 생각일 수 있습니다.

toBeInjected = new ToBeInjected();
String beanName = this.getClass().getSimpleName() + "_quartzConfiguration";
beanFactory.registerSingleton(beanName, toBeInjected);
toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class);

된 를 해야 로 확장된 기본 를 만들어야 .Configuration수업.그런 다음 모든 구성 클래스에서 다음과 같이 반복할 수 있습니다.

// Key - name of the configuration class
// value - the configuration object
Map<String, Object> configurations = applicationContext.getBeansWithAnnotation(Configuration.class);
Set<String> keys = configurations.keySet();
for(String key: keys) {
    MyConfiguration conf = (MyConfiguration) configurations.get(key);

    // Implement the logic to use this configuration to create other beans.
}

제가 생각해낼 수 있는 "최상의" 접근법은 제 Quartz 구성과 스케줄러를 모두 하나의 우버빈에 포장한 후에 그것을 수동으로 배선한 다음 코드를 리팩터링하여 우버빈 인터페이스와 함께 작동하는 것입니다.

우버빈은 PostConstruct에 필요한 모든 객체를 생성하고 ApplicationContextAware를 구현하여 자동으로 연결할 수 있습니다.이상적이지는 않지만, 제가 생각해낼 수 있는 최선이었습니다.

봄은 단순히 콩을 안전한 방법으로 동적으로 첨가하는 좋은 방법이 없습니다.

언급URL : https://stackoverflow.com/questions/28374000/spring-programmatically-generate-a-set-of-beans

반응형