Programing

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

c10106 2022. 5. 14. 10:13
반응형

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

Spring의 Java Config를 사용하여, 런타임에만 얻을 수 있는 생성자 인수가 있는 프로토타입 범위 콩을 획득/인스턴트해야 한다.다음 코드 예제를 고려하십시오(간결성을 위해 단순화됨).

@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;
    }
}

공지name이다final할 수 시공자를 통해서만 공급할 수 있으며, 불변성을 보장한다.또 다.Thing요청 처리기 구현에 대해 (연관적으로) 알 수 없어야 한다.

이 코드는 다음과 같은 Spring XML 구성과 완벽하게 호환된다.

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

Java 구성을 사용하여 동일한 작업을 수행하는 방법다음은 Spring 3.x를 사용하지 않는다.

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

이제 공장을 만들 수 있을 겁니다. 예를 들어:

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

그러나 이는 Spring을 사용하여 ServiceLocator Factory 설계 패턴을 대체하는 전체 포인트를 능가하는 것으로, 이 사용 사례에 이상적일 것이다.

Spring Java Config가 이 작업을 수행할 수 있다면 다음을 피할 수 있을 것이다.

  • 공장 인터페이스 정의
  • 공장 구현 정의
  • 공장 구현을 위한 필기 테스트

스프링이 XML 구성을 통해 이미 지원하는 사소한 일에는 엄청난 양의 작업(상대적으로 말하면)을 할 수 있다.

@Configuration,@Bean그와 같은 방법

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

콩 정의를 등록하고 콩을 만드는 공장을 제공하기 위해 사용된다.정의한 콩은 직접 또는 스캔을 통해 결정된 인수를 사용하여 요청 시에만 인스턴스화된다.ApplicationContext.

의 .prototype콩, 새로운 물체가 매번 생성되어 그에 상응하는 것@Bean방법 또한 실행된다.

콩 한 냥을 건져올 수 있다.ApplicationContext그 방법에 의하여.

명시적 생성자 인수/공장 방법 인수를 지정하고 빈 정의에서 지정된 기본 인수(있는 경우)를 재정의할 수 있다.

매개 변수:

정적 공장 방법에 대한 명시적 인수를 사용하여 프로토타입을 작성할 경우 사용할 아그스 인수.다른 경우에는 null이 아닌 arg 값을 사용하는 것이 무효다.

다시 말해, 이것을 위해.prototype의 범위,, 의 구성자가 의 팥의 팥이 사용될 @Bean방법 호출(이 방법은 콩의 이름 조회를 사용하기 때문에 유형 보증이 매우 약하다.)

또는 종류별로 콩을 찾아보는 타이핑 방식을 사용할 수 있다.

적어도 봄 버전 4+에서는 그렇다.

다음 항목으로 시작하지 않으려면ApplicationContext또는BeanFactory콩을 회수하기 위해, 당신은 (4.3년 봄 이후)을 주입할 수 있다.

의의 .ObjectFactory프로그램적 옵션성과 관대한 미사용 취급이 가능하도록 특별히 주입 지점에 맞게 설계되었다.

그리고 그 방법을 사용하라.

이 공장에서 관리하는 객체의 인스턴스(공유 또는 독립)를 반환하십시오.

명시적 BeanFactory.getBean(String, Object).

예를 들어,

@Autowired
private ObjectProvider<Thing> things;

[...]
Thing newThing = things.getObject(name);
[...]

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세 개 이상의 매개변수를 전달한다.

봄 4.3 이후, 그 문제를 위해 꿰맨 새로운 방법이 있다.

ObjectProvider - "주장된" 프로토타입 범위 콩에 의존성으로 추가하고 인수를 사용하여 콩을 인스턴스화할 수 있도록 해준다.

사용 방법의 간단한 예는 다음과 같다.

@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 문자열을 인쇄한다.

의견당 업데이트됨

첫째, 당신이 왜 "이것은 효과가 없다"고 말하는지 잘 모르겠다. 나는 당신의 구성이 어딘가 잘못된 것이 틀림없다고 의심한다.

이 작업은 다음과 같다.

-- 구성 파일:

@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

그래서 '싱글톤' 빈은 두 번 요청받는다.그러나 우리가 예상한 대로 봄은 그것을 단 한번만 만들어낸다.두 번째로 그것이 그 콩을 가지고 있는 것을 보고 기존의 물체를 돌려준다.생성자(@Bean method)는 두 번째로 호출되지 않는다.이에 대해, 동일한 컨텍스트 객체로부터 '프로토타이프' Bean을 두 번 요청할 때, 우리는 출력에서 참조가 변경되고 생성자(@Bean 방법) IS가 두 번 호출되는 것을 본다.

그렇다면 문제는 프로토타입에 어떻게 싱글톤을 주입하느냐 하는 겁니다.위의 구성 클래스에서도 이 작업을 수행하는 방법을 보여준다.그러한 모든 참조를 시공자에게 전달해야 한다.이렇게 하면 생성된 클래스가 순수한 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를 쓴다면 당신은 모든 @Autoored 없이 이 수업을 만들게 되어 매우 행복할 것이다.자동 생성된 구성 요소가 필요한 경우 해당 구성 요소를 Java 구성 파일에 로컬로 유지하십시오.

이것은 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;

내부 클래스만 사용해도 비슷한 효과를 얻을 수 있다.

@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);
        }
    }
}

적합한 콩을 생성해야 하는 경우 다음과 같이 하십시오.

@Configuration
public class ThingConfiguration {

   @Bean
   @Scope(SCOPE_PROTOTYPE)
   public Thing simpleThing(String name) {
       return new Thing(name);
   }

   @Bean
   @Scope(SCOPE_PROTOTYPE)
   public Thing specialThing(String name) {
       Thing thing = new Thing(name);
       // some special configuration
       return thing;
   }

}

// Usage 

@Autowired
private ApplicationContext context;

AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
((DefaultListableBeanFactory) beanFactory).getBean("specialThing", Thing.class, "name");

약간 다른 접근으로 늦은 답변.그것은 이 질문 자체를 언급하는 최근의 질문의 후속이다.

네, 아까 말한 바와 같이 파라미터를 받아들이는 원두 원형을 선언할 수 있다.@Configuration각 주입 시 새로운 콩을 만들 수 있는 클래스.
그렇게 되면 이렇게 될 것이다.@Configuration공장을 분류하고 이 공장에 너무 많은 책임을 부여하지 않기 위해, 여기에는 다른 콩이 포함되지 않아야 한다.

@Configuration    
public class ServiceFactory {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Thing thing(String name) {
       return new Thing(name);
   }

}

하지만 구성 콩을 주입하여Things:

@Autowired
private ServiceFactory serviceFactory;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = serviceFactory.thing(name); // create a new bean at each invocation
    // ...    
}

그것은 유형 안전하면서도 간결하다.

콩 xml 파일에서 속성 범위="complete"를 사용하십시오.

참조URL: https://stackoverflow.com/questions/22155832/spring-java-config-how-do-you-create-a-prototype-scoped-bean-with-runtime-argu

반응형