복붙노트

[SPRING] Spring Java Config : 런타임 인수를 사용하여 프로토 타입 범위의 @Bean을 만드는 방법은 무엇입니까?

SPRING

Spring Java Config : 런타임 인수를 사용하여 프로토 타입 범위의 @Bean을 만드는 방법은 무엇입니까?

Spring의 Java Config를 사용하여 런타임에만 생성 가능한 생성자 인수를 사용하여 프로토 타입 범위 Bean을 획득 / 인스턴스화해야한다. 간략하게하기 위해 단순화 된 다음 코드 예제를 고려하십시오.

@Autowired
private ApplicationContext appCtx;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = appCtx.getBean(Thing.class, name);

    //System.out.println(thing.getName()); //prints name
}

여기서 Thing 클래스는 다음과 같이 정의됩니다.

public class Thing {

    private final String name;

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private AnotherComponent anotherComponent;

    public Thing(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

통지의 이름은 final입니다. 이것은 constructor을 통해서만 제공 할 수 있습니다 만, 불변성을 보증합니다. 다른 종속성은 Thing 클래스의 구현 별 종속성이며 요청 처리기 구현에 밀접하게 결합되어서는 안됩니다.

이 코드는 Spring XML 설정과 완벽하게 잘 작동합니다 (예 :

<bean id="thing", class="com.whatever.Thing" scope="prototype">
    <!-- other post-instantiation properties omitted -->
</bean>

어떻게하면 자바 설정으로 같은 것을 얻을 수 있습니까? 다음은 작동하지 않습니다.

@Bean
@Scope("prototype")
public Thing thing(String name) {
    return new Thing(name);
}

이제 공장을 만들 수 있습니다 (예 :

public interface ThingFactory {
    public Thing createThing(String name);
}

그러나 이것은 ServiceLocator와 Factory 디자인 패턴을 대체하기 위해 Spring을 사용하는 전체적인 포인트를 무효화합니다. 이는이 사용 사례에 이상적입니다.

Spring Java Config가 이것을 할 수 있다면, 나는 피할 수있을 것이다 :

그것은 Spring이 XML 설정을 통해 이미 지원하고있는 사소한 일에 대한 많은 일 (상대적으로 말하기)입니다.

해결법

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

    1.@Configuration 클래스에서, @Bean 메소드는

    @Configuration 클래스에서, @Bean 메소드는

    @Bean
    @Scope("prototype")
    public Thing thing(String name) {
        return new Thing(name);
    }
    

    Bean 정의를 등록하고 Bean 작성을위한 팩토리를 제공하는 데 사용됩니다. 그것이 정의한 bean은 직접적으로 또는 ApplicationContext를 스캔함으로써 결정되는 인자를 사용하여 요청시 인스턴스화된다.

    프로토 타입 빈의 경우 매번 새로운 객체가 생성되므로 해당 @Bean 메소드도 실행됩니다.

    BeanFactory # getBean (String name, Object ... args) 메소드를 통해 ApplicationContext에서 Bean을 검색 할 수있다.

    즉,이 프로토 타입 범위 Bean의 경우 Bean 클래스의 생성자가 아닌 @Bean 메서드 호출에서 사용할 인수를 제공합니다.

    Spring 버전 4 이상에서는 사실입니다.

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

    2.Spring> 4.0 및 Java 8을 사용하면보다 안전하게 유형을 안전하게 처리 할 수 ​​있습니다.

    Spring> 4.0 및 Java 8을 사용하면보다 안전하게 유형을 안전하게 처리 할 수 ​​있습니다.

    @Configuration    
    public class ServiceConfig {
    
        @Bean
        public Function<String, Thing> thingFactory() {
            return name -> thing(name); // or this::thing
        } 
    
        @Bean
        @Scope(value = "prototype")
        public Thing thing(String name) {
           return new Thing(name);
        }
    
    }
    

    용법:

    @Autowired
    private Function<String, Thing> thingFactory;
    
    public void onRequest(Request request) {
        //request is already validated
        String name = request.getParameter("name");
        Thing thing = thingFactory.apply(name);
    
        // ...
    }
    

    이제 런타임시 빈을 얻을 수 있습니다. 이것은 물론 팩토리 패턴이지만 ThingFactory와 같은 특정 클래스를 작성하는 데 시간을 절약 할 수 있습니다 (단, 두 개 이상의 매개 변수를 전달하려면 사용자 정의 @FunctionalInterface를 작성해야합니다).

  3. ==============================

    3.댓글 당 업데이트 됨

    댓글 당 업데이트 됨

    첫째로, 나는 왜 이것이 Spring 3.x에서 잘 작동하는 무언가를 위해 "이것이 작동하지 않는다"는 것을 확신 할 수 없다. 어딘가에서 당신의 구성에 뭔가 틀린 것이 틀림 없다고 생각합니다.

    작동 방식 :

    - 구성 파일 :

    @Configuration
    public class ServiceConfig {
        // only here to demo execution order
        private int count = 1;
    
        @Bean
        @Scope(value = "prototype")
        public TransferService myFirstService(String param) {
           System.out.println("value of count:" + count++);
           return new TransferServiceImpl(aSingletonBean(), param);
        }
    
        @Bean
        public AccountRepository aSingletonBean() {
            System.out.println("value of count:" + count++);
            return new InMemoryAccountRepository();
        }
    }
    

    - 실행할 테스트 파일 :

    @Test
    public void prototypeTest() {
        // create the spring container using the ServiceConfig @Configuration class
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class);
        Object singleton = ctx.getBean("aSingletonBean");
        System.out.println(singleton.toString());
        singleton = ctx.getBean("aSingletonBean");
        System.out.println(singleton.toString());
        TransferService transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter One");
        System.out.println(transferService.toString());
        transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter Two");
        System.out.println(transferService.toString());
    }
    

    Spring 3.2.8과 Java 7을 사용하면 다음과 같은 결과를 얻을 수있다.

    value of count:1
    com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
    com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
    value of count:2
    Using name value of: simulated Dynamic Parameter One
    com.spring3demo.account.service.TransferServiceImpl@634d6f2c
    value of count:3
    Using name value of: simulated Dynamic Parameter Two
    com.spring3demo.account.service.TransferServiceImpl@70bde4a2
    

    따라서 'Singleton'Bean은 두 번 요청됩니다. 그러나 우리가 예상 하듯이 Spring은 한 번만 생성합니다. 두 번째로 그 bean을 가지고 있고 기존 객체를 반환한다는 것을 알게됩니다. 생성자 (@Bean 메서드)가 두 번 호출되지 않습니다. 이 점을 고려하여 'Prototype'Bean이 동일한 컨텍스트 개체에서 두 번 요청되면 출력에서 ​​참조가 변경되고 생성자 (@Bean 메서드)가 두 번 호출됨을 알 수 있습니다.

    그래서 문제는 프로토 타입에 싱글 톤을 주입하는 방법입니다. 위의 구성 클래스는이를 수행하는 방법을 보여줍니다! 그러한 모든 참조를 생성자에 전달해야합니다. 이렇게하면 생성 된 클래스가 순수한 POJO 일뿐만 아니라 포함 된 참조 객체를 필요에 따라 변경할 수 없게됩니다. 따라서 전송 서비스는 다음과 같이 보일 수 있습니다.

    public class TransferServiceImpl implements TransferService {
    
        private final String name;
    
        private final AccountRepository accountRepository;
    
        public TransferServiceImpl(AccountRepository accountRepository, String name) {
            this.name = name;
            // system out here is only because this is a dumb test usage
            System.out.println("Using name value of: " + this.name);
    
            this.accountRepository = accountRepository;
        }
        ....
    }
    

    Unit Tests를 작성하면 모든 @Autowired없이 클래스를 생성하게되어 매우 기쁩니다. autowired 컴포넌트가 필요하다면 java config 파일에 로컬 컴포넌트를 보관하십시오.

    이것은 BeanFactory에서 아래 메소드를 호출합니다. 이 설명이 정확한 사용 사례를위한 것임을 설명합니다.

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>Allows for specifying explicit constructor arguments / factory method arguments,
     * overriding the specified default arguments (if any) in the bean definition.
     * @param name the name of the bean to retrieve
     * @param args arguments to use if creating a prototype using explicit arguments to a
     * static factory method. It is invalid to use a non-null args value in any other case.
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException if arguments have been given but
     * the affected bean isn't a prototype
     * @throws BeansException if the bean could not be created
     * @since 2.5
     */
    Object getBean(String name, Object... args) throws BeansException;
    
  4. ==============================

    4.Spring 4.3 이후로, 그 문제를 해결하기위한 새로운 방법이 있습니다.

    Spring 4.3 이후로, 그 문제를 해결하기위한 새로운 방법이 있습니다.

    ObjectProvider - "인수가있는"Prototype 범위 bean에 의존성으로 추가하고 인수를 사용하여 인스턴스화 할 수 있습니다.

    다음은이를 사용하는 간단한 예제입니다.

    @Configuration
    public class MyConf {
        @Bean
        @Scope(BeanDefinition.SCOPE_PROTOTYPE)
        public MyPrototype createPrototype(String arg) {
            return new MyPrototype(arg);
        }
    }
    
    public class MyPrototype {
        private String arg;
    
        public MyPrototype(String arg) {
            this.arg = arg;
        }
    
        public void action() {
            System.out.println(arg);
        }
    }
    
    
    @Component
    public class UsingMyPrototype {
        private ObjectProvider<MyPrototype> myPrototypeProvider;
    
        @Autowired
        public UsingMyPrototype(ObjectProvider<MyPrototype> myPrototypeProvider) {
            this.myPrototypeProvider = myPrototypeProvider;
        }
    
        public void usePrototype() {
            final MyPrototype myPrototype = myPrototypeProvider.getObject("hello");
            myPrototype.action();
        }
    }
    

    물론 이것은 usePrototype을 호출 할 때 hello 문자열을 출력합니다.

  5. ==============================

    5.내부 클래스를 사용하는 것과 비슷한 효과를 얻을 수 있습니다.

    내부 클래스를 사용하는 것과 비슷한 효과를 얻을 수 있습니다.

    @Component
    class ThingFactory {
        private final SomeBean someBean;
    
        ThingFactory(SomeBean someBean) {
            this.someBean = someBean;
        }
    
        Thing getInstance(String name) {
            return new Thing(name);
        }
    
        class Thing {
            private final String name;
    
            Thing(String name) {
                this.name = name;
            }
    
            void foo() {
                System.out.format("My name is %s and I can " +
                        "access bean from outer class %s", name, someBean);
            }
        }
    }
    
  6. from https://stackoverflow.com/questions/22155832/spring-java-config-how-do-you-create-a-prototype-scoped-bean-with-runtime-argu by cc-by-sa and MIT license