복붙노트

[SPRING] 런타임시 스프링 관리 Bean을 인스턴스화하는 방법은 무엇입니까?

SPRING

런타임시 스프링 관리 Bean을 인스턴스화하는 방법은 무엇입니까?

나는 단순한 자바에서 봄에 이르는 간단한 리팩터링을 고수했다. 응용 프로그램에는 런타임에 해당 부분을 인스턴스화하는 "컨테이너"객체가 있습니다. 코드를 설명해 드리겠습니다.

public class Container {
    private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();

    public void load() {
        // repeated several times depending on external data/environment
        RuntimeBean beanRuntime = createRuntimeBean();
        runtimeBeans.add(beanRuntime);
    }

    public RuntimeBean createRuntimeBean() {
         // should create bean which internally can have some 
         // spring annotations or in other words
         // should be managed by spring
    }
}

기본적으로,로드 컨테이너에서 일부 외부 시스템에게 각 RuntimeBean의 수와 구성에 대한 정보를 제공하도록 요청한 다음 주어진 사양에 따라 빈을 작성합니다.

문제는 다음과 같습니다. 일반적으로 봄에 할 때

ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Container container = (Container) context.getBean("container");

우리의 객체는 완전히 구성되었으며 모든 종속성이 주입됩니다. 하지만 제 경우에는 load () 메소드를 실행 한 후에 종속성 주입이 필요한 객체를 인스턴스화해야합니다. 어떻게하면 될까요?

Java 기반 구성을 사용하고 있습니다. 이미 RuntimeBeans를위한 팩토리를 만들려고했습니다.

public class BeanRuntimeFactory {

    @Bean
    public RuntimeBean createRuntimeBean() {
        return new RuntimeBean();
    }
}

@Bean이 소위 '라이트'모드에서 작동 할 것으로 기대하고 있습니다. 불행히도 새로운 RuntimeBean ()을 사용하는 것과는 차이가 없다는 것을 알았습니다. 비슷한 문제가있는 게시물이 있습니다 : FactoryBean spring에서 생성 된 bean을 어떻게 관리 할 수 ​​있습니까?

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Configurable.html도 있지만 내 경우에는 망치처럼 보입니다.

나는 또한 ApplicationBean ( "runtimeBean", args)을 사용하여 ApplicationBean을 "Prototype"스코프를 가지고 있지만 getBean은 끔찍한 해결책 인 ApplicationContext.getBean을 시도했다.

Upd1. 좀 더 구체적으로이 클래스를 리팩터링하려고합니다. https://github.com/apache/lucene-solr/blob/trunk/solr/core/src/java/org/apache/solr/core/CoreContainer.java @load # 메소드를 호출하고 "return create (cd, false);"를 찾습니다.

Upd2. 나는 봄 문서에서 "lookup method injection"이라는 아주 흥미로운 것을 발견했다 : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-lookup-method-injection

흥미로운 jira 티켓 https://jira.spring.io/browse/SPR-5192 Phil Webb은 https://jira.spring.io/browse/SPR-5192?focusedCommentId=86051&page=com.atlassian.jira라고 말합니다. .plugin.system.issuetabpanels : comment-tabpanel # comment-86051 여기에 javax.inject.Provider를 사용해야합니다 (Guice에게 상기시켜줍니다).

Upd3. http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html도 있습니다.

Upd4. 이 모든 'lookup'메소드의 문제점은 인수를 전달하는 것을 지원하지 않는다는 것입니다. 또한 applicationContext.getBean ( "runtimeBean", arg1, arg2)과 같이 인수를 전달해야합니다. https://jira.spring.io/browse/SPR-7431로 고정 된 것처럼 보입니다.

Upd5. Google Guice에는 AssistedInject라는 깔끔한 기능이 있습니다. https://github.com/google/guice/wiki/AssistedInject

해결법

  1. ==============================

    1.솔루션을 찾은 것처럼 보입니다. 자바 기반 설정을 사용하고 있기 때문에 상상할 수있는 것보다 훨씬 간단합니다. xml의 ​​다른 방법은 lookup-method 일 것이지만, 스프링 버전 4.1.X에서만 메소드에 인수를 전달할 수 있기 때문에 가능합니다.

    솔루션을 찾은 것처럼 보입니다. 자바 기반 설정을 사용하고 있기 때문에 상상할 수있는 것보다 훨씬 간단합니다. xml의 ​​다른 방법은 lookup-method 일 것이지만, 스프링 버전 4.1.X에서만 메소드에 인수를 전달할 수 있기 때문에 가능합니다.

    다음은 완전한 작업 예제입니다.

    public class Container {
        private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
        private RuntimeBeanFactory runtimeBeanFactory;
    
        public void load() {
            // repeated several times depending on external data/environment
            runtimeBeans.add(createRuntimeBean("Some external info1"));
            runtimeBeans.add(createRuntimeBean("Some external info2"));
        }
    
        public RuntimeBean createRuntimeBean(String info) {
             // should create bean which internally can have some 
             // spring annotations or in other words
             // should be managed by spring
             return runtimeBeanFactory.createRuntimeBean(info)
        }
    
        public void setRuntimeBeanFactory(RuntimeBeanFactory runtimeBeanFactory) {
            this.runtimeBeanFactory = runtimeBeanFactory
        }
    }
    
    public interface RuntimeBeanFactory {
        RuntimeBean createRuntimeBean(String info);
    }
    
    //and finally
    @Configuration
    public class ApplicationConfiguration {
    
        @Bean
        Container container() {
            Container container = new Container(beanToInject());
            container.setBeanRuntimeFactory(runtimeBeanFactory());
            return container;
        }
    
        // LOOK HOW IT IS SIMPLE IN THE JAVA CONFIGURATION
        @Bean 
        public BeanRuntimeFactory runtimeBeanFactory() {
            return new BeanRuntimeFactory() {
                public RuntimeBean createRuntimeBean(String beanName) {
                    return runtimeBean(beanName);
                }
            };
        }
    
        @Bean
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        RuntimeBean runtimeBean(String beanName) {
            return new RuntimeBean(beanName);
        }
    }
    
    class RuntimeBean {
        @Autowired
        Container container;
    }
    

    그게 전부 야.

    모두에게 감사드립니다.

  2. ==============================

    2.나는 당신의 개념이 잘못되었다고 생각한다. RuntimeBean beanRuntime = createRuntimeBean (); 당신은 Spring 컨테이너를 우회하고 일반 자바 생성자를 사용하고 있으므로 factory 메소드의 주석은 무시되고이 bean은 Spring에 의해 결코 관리되지 않습니다.

    나는 당신의 개념이 잘못되었다고 생각한다. RuntimeBean beanRuntime = createRuntimeBean (); 당신은 Spring 컨테이너를 우회하고 일반 자바 생성자를 사용하고 있으므로 factory 메소드의 주석은 무시되고이 bean은 Spring에 의해 결코 관리되지 않습니다.

    여기에 하나의 방법으로 여러 프로토 타입 빈을 만드는 해결책이 있지만, 잘 보이지는 않지만 작동해야한다. RuntimeBean의 컨테이너를 autowiring의 증거로 로그에 표시한다. 로그를 보면 모든 빈이 이것을 실행할 때 프로토 타입의 새로운 인스턴스라는 것을 알 수있다. .

    '

    @Configuration
    @ComponentScan
    @EnableAutoConfiguration
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
    
            ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
            Container container = (Container) context.getBean("container");
            container.load();
        }
    }
    
    @Component
    class Container {
        private List<RuntimeBean> runtimeBeans = new ArrayList<RuntimeBean>();
        @Autowired
        ApplicationContext context;
    
        @Autowired
        private ObjectFactory<RuntimeBean> myBeanFactory;
    
        public void load() {
    
            // repeated several times depending on external data/environment
            for (int i = 0; i < 10; i++) {
                // **************************************
                // COMENTED OUT THE WRONG STUFFF 
                // RuntimeBean beanRuntime = context.getBean(RuntimeBean.class);
                // createRuntimeBean();
                // 
                // **************************************
    
                RuntimeBean beanRuntime = myBeanFactory.getObject();
                runtimeBeans.add(beanRuntime);
                System.out.println(beanRuntime + "  " + beanRuntime.container);
            }
        }
    
        @Bean
        @Scope(BeanDefinition.SCOPE_PROTOTYPE)
        public RuntimeBean createRuntimeBean() {
            return new RuntimeBean();
        }
    }
    
    // @Component
    
    class RuntimeBean {
        @Autowired
        Container container;
    
    } '
    
  3. ==============================

    3.모든 런타임 객체가 ApplicationContext에 의해 생성, 유지 및 관리되어야하기 때문에 컨테이너가 필요하지 않습니다. 웹 응용 프로그램에 대해 생각해보십시오. 웹 응용 프로그램은 거의 동일합니다. 각 요청에는 위에서 언급 한 외부 데이터 / 환경 정보가 들어 있습니다. 당신이 필요로하는 것은 프로토 타입 / 요청 범위가있는 ExternalData 또는 EnvironmentInfo와 같은 빈입니다. 정적 인 팩토리 메소드를 사용하여 런타임 데이터를 정적 인 방법으로 읽고 저장할 수 있습니다.

    모든 런타임 객체가 ApplicationContext에 의해 생성, 유지 및 관리되어야하기 때문에 컨테이너가 필요하지 않습니다. 웹 응용 프로그램에 대해 생각해보십시오. 웹 응용 프로그램은 거의 동일합니다. 각 요청에는 위에서 언급 한 외부 데이터 / 환경 정보가 들어 있습니다. 당신이 필요로하는 것은 프로토 타입 / 요청 범위가있는 ExternalData 또는 EnvironmentInfo와 같은 빈입니다. 정적 인 팩토리 메소드를 사용하여 런타임 데이터를 정적 인 방법으로 읽고 저장할 수 있습니다.

    <bean id="externalData" class="ExternalData"
        factory-method="read" scope="prototype"></bean>
    
    <bean id="environmentInfo" class="EnvironmentInfo"
        factory-method="read" scope="prototype/singleton"></bean>
    
    <bean class="RuntimeBean" scope="prototype">
        <property name="externalData" ref="externalData">
        <property name="environmentInfo" ref="environmentInfo">
    </bean> 
    

    런타임 객체를 저장할 컨테이너가 필요한 경우 코드가 있어야합니다.

    class Container {
    
        List list;
        ApplicationContext context;//injected by spring if Container is not a prototype bean
    
        public void load() {// no loop inside, each time call load() will load a runtime object
            RuntimeBean bean = context.getBean(RuntimeBean.class); // see official doc
            list.add(bean);// do whatever
        }
    }
    

    프로토 타입 bean 의존성을 가진 공식적인 doc Singleton beans.

  4. ==============================

    4.BeanFactoryPostProcesor를 사용하여 Bean을 동적으로 등록 할 수 있습니다. 여기에서는 애플리케이션이 부팅되는 동안 (스프링의 애플리케이션 컨텍스트가 초기화 됨)이를 수행 할 수 있습니다. bean latet를 등록 할 수는 없지만 다른 한편으로는 "true"스프링 빈이되어 콩에 대한 의존성 주입을 사용할 수 있습니다.

    BeanFactoryPostProcesor를 사용하여 Bean을 동적으로 등록 할 수 있습니다. 여기에서는 애플리케이션이 부팅되는 동안 (스프링의 애플리케이션 컨텍스트가 초기화 됨)이를 수행 할 수 있습니다. bean latet를 등록 할 수는 없지만 다른 한편으로는 "true"스프링 빈이되어 콩에 대한 의존성 주입을 사용할 수 있습니다.

    public class DynamicBeansRegistar implements BeanFactoryPostProcessor {
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            if (! (beanFactory instanceof BeanDefinitionRegistry))  {
                throw new RuntimeException("BeanFactory is not instance of BeanDefinitionRegistry);
            }   
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
    
            // here you can fire your logic to get definition for your beans at runtime and 
            // then register all beans you need (possibly inside a loop)
    
            BeanDefinition dynamicBean = BeanDefinitionBuilder.    
                 .rootBeanDefinition(TheClassOfYourDynamicBean.class) // here you define the class
                 .setScope(BeanDefinition.SCOPE_SINGLETON)
                 .addDependsOn("someOtherBean") // make sure all other needed beans are initialized
    
                 // you can set factory method, constructor args using other methods of this builder
    
                 .getBeanDefinition();
    
            registry.registerBeanDefinition("your.bean.name", dynamicBean);           
    
    }
    
    @Component
    class SomeOtherClass {
    
        // NOTE: it is possible to autowire the bean
        @Autowired
        private TheClassOfYourDynamicBean myDynamicBean;
    
    }
    

    위에서 제시 한 것처럼 포스트 프로세서는 실제 어플리케이션 컨텍스트에서 작동하기 때문에 Spring의 Dependency Injection을 여전히 사용할 수 있습니다.

  5. from https://stackoverflow.com/questions/27809838/how-to-instantiate-spring-managed-beans-at-runtime by cc-by-sa and MIT license