问题

使用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;
    }
}

Noticenameisfinal:它只能通过构造函数提供,并保证不变性。其他依赖项是Thing类的特定于实现的依赖项,并且不应该知道(紧密耦合到)请求处理程序实现。

此代码与Spring XML配置完美配合,例如:

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

如何使用Java配置实现相同的功能?以下不起作用:

@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可以做到这一点,我将能够避免:

  • 定义Factory界面
  • 定义Factory实现
  • 为Factory实施编写测试

对于一些如此微不足道的东西,Spring已经通过XML配置支持了大量工作(相对而言)。


#1 热门回答(71 赞)

在a@Configuration类中,a@Bean方法就像这样

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

用于注册abean定义并提供用于创建bean的工厂。它定义的bean仅在请求时使用直接或通过扫描214141345确定的参数进行实例化。

在aprototypebean的情况下,每次都会创建一个新对象,因此也会执行相应的@Bean方法。

你可以通过其所述的8370202121方法从ApplicationContext中检索一个bean

允许指定显式构造函数参数/工厂方法参数,覆盖bean定义中指定的默认参数(如果有)。参数:如果使用静态工厂方法的显式参数创建原型,则使用args参数。在任何其他情况下使用非null args值无效。

换句话说,对于thisprototypescoped bean,你提供的是将使用的参数,而不是在bean类的构造函数中,而是在@Bean方法调用中。

对于Spring版本4,这至少是正确的。


#2 热门回答(25 赞)

使用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);

    // ...
}

所以现在你可以在运行时获取你的bean了。这当然是工厂模式,但是你可以节省一些时间来编写特定的类,如ThingFactory(但是你必须编写custom@FunctionalInterface以传递两个以上的参数)。


#3 热门回答(14 赞)

更新时间发表评论
首先,我不确定为什么你说"这不起作用"的东西在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;
    }
    ....
}

如果你编写单元测试,你将非常高兴你在没有@Autowired的情况下创建了这个类。如果确实需要自动装配的组件,请保留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;

原文链接