Les*_*ood 121 java spring scope prototype spring-java-config
使用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
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
注意事项name
是final
:它只能通过构造函数来提供,并保证不变性.其他依赖项是Thing
类的特定于实现的依赖项,并且不应该知道(紧密耦合到)请求处理程序实现.
此代码与Spring XML配置完美配合,例如:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Run Code Online (Sandbox Code Playgroud)
如何使用Java配置实现相同的功能?以下使用Spring 3.x无效:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Run Code Online (Sandbox Code Playgroud)
现在,我可以创建一个工厂,例如:
public interface ThingFactory {
public Thing createThing(String name);
}
Run Code Online (Sandbox Code Playgroud)
但是,这使得使用Spring取代ServiceLocator和Factory设计模式的整个过程失败了,这对于这个用例来说是理想的.
如果Spring Java Config可以做到这一点,我将能够避免:
这是一个很大的工作(相对而言),因为Spring已经通过XML配置支持了这么简单的事情.
Sot*_*lis 88
在@Configuration
课堂上,这样的@Bean
方法
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Run Code Online (Sandbox Code Playgroud)
用于注册bean定义并提供用于创建bean的工厂.它定义的bean仅在请求时使用直接或通过扫描确定的参数进行实例化ApplicationContext
.
在prototype
bean 的情况下,每次都创建一个新对象,因此@Bean
也执行相应的方法.
您可以ApplicationContext
通过其声明的BeanFactory#getBean(String name, Object... args)
方法检索bean
允许指定显式构造函数参数/工厂方法参数,覆盖bean定义中指定的默认参数(如果有).
参数:
如果使用静态工厂方法的显式参数创建原型,则使用args参数.在任何其他情况下使用非null args值无效.
换句话说,对于这个prototype
作用域bean,您将提供将要使用的参数,而不是在bean类的构造函数中,而是在@Bean
方法调用中.
对于Spring版本4+,这至少是正确的.
Rom*_*hev 44
使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
@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);
// ...
}
Run Code Online (Sandbox Code Playgroud)
所以现在你可以在运行时获取你的bean了.这当然是一种工厂模式,但是你可以节省一些时间来编写特定的类ThingFactory
(但是你必须编写自定义@FunctionalInterface
来传递两个以上的参数).
Joe*_*oeG 15
每条评论更新
首先,我不确定为什么你说"这不起作用"对于在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();
}
}
Run Code Online (Sandbox Code Playgroud)
- 要执行的测试文件:
@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());
}
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
所以'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;
}
....
}
Run Code Online (Sandbox Code Playgroud)
如果您编写单元测试,您将非常高兴您创建了这个类而没有@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;
Run Code Online (Sandbox Code Playgroud)
从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();
}
}
Run Code Online (Sandbox Code Playgroud)
当调用usePrototype时,这当然会打印问候字符串。