气势凌人的红豆 · 618买五六个月前发布的旗舰机才真香!降价幅 ...· 4 月前 · |
乐观的豌豆 · 古永锵(原优酷土豆董事长兼CEO)_搜狗百科· 4 月前 · |
腼腆的小熊猫 · 瓦尔基里 - 萌娘百科 万物皆可萌的百科全书· 5 月前 · |
聪明伶俐的冰棍 · “一村一品一网红”促进农民增收致富· 5 月前 · |
安静的硬币 · 潘石屹维权有结果了!批望京SOHO风水差," ...· 7 月前 · |
使用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;
}
请注意,
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);
}
现在,我可以创建一个Factory,例如:
public interface ThingFactory {
public Thing createThing(String name);
}
但是,这违背了使用Spring来取代ServiceLocator和工厂设计模式的全部意义,而这种设计模式对于这个用例来说是非常理想的。
如果Spring Java Config可以做到这一点,我将能够避免:
为工厂implementation定义工厂implementation
对于Spring已经通过XML配置支持的微不足道的东西来说,这是一项繁重的工作(相对而言)。
在
@Configuration
类中,像这样的
@Bean
方法
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
用于注册
bean定义并提供用于创建bean
的工厂。它定义的
ApplicationContext
仅在请求时使用参数实例化,这些参数是直接或通过扫描该bean确定的。
对于
prototype
bean,每次都会创建一个新对象,因此也会执行相应的
@Bean
方法。
可以通过
ApplicationContext
的
BeanFactory#getBean(String name, Object... args)
方法从bean中检索bean,该方法声明
允许指定显式的构造函数参数/工厂方法参数,覆盖bean定义中指定的默认参数(如果有的话)。
参数:
使用静态工厂方法的显式参数创建原型时要使用的 参数 参数。在任何其他情况下,使用非空args值都是无效的。
换句话说,对于这个
prototype
作用域的bean,您将提供将要使用的参数,而不是在bean类的构造函数中,而是在
@Bean
方法调用中。(此方法具有非常弱的类型保证,因为它使用bean的名称查找。)
或者,您可以使用类型化的
BeanFactory#getBean(Class requiredType, Object... args)
方法,该方法按类型查找bean。
至少对于Spring版本的4+来说是这样的。
请注意,如果您不想从
ApplicationContext
或
BeanFactory
开始检索bean,则可以注入
ObjectProvider
(从Spring4.3开始)。
是专门为注入点设计的
ObjectFactory
变体,允许编程可选性和宽松的非唯一处理。
并使用它的
getObject(Object... args)
方法
返回此工厂管理的对象的实例(可能是共享的,也可能是独立的)。
允许按照
BeanFactory.getBean(String, Object)
的方式指定显式构造参数。
例如,
@Autowired
private ObjectProvider<Thing> things;
[...]
Thing newThing = things.getObject(name);
[...]
每条评论更新的
首先,我不明白为什么你会说“这不起作用”,因为它在Spring3.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方法)被调用了两次。
因此,接下来的问题是如何将单例注入原型中。上面的configuration类也展示了如何做到这一点!您应该将所有这样的引用传递给构造函数。这将允许创建的类是一个纯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;
使用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
)的时间(但是,您必须编写自定义的
@FunctionalInterface
来传递两个以上的参数)。
从Spring 4.3开始,就有了新的方法来解决这个问题。
ObjectProvider -它允许您将其作为依赖项添加到“带参数的”原型作用域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字符串。
只需使用 inner class 即可达到类似的效果
@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);
}
延迟回答,方法略有不同。这是引用这个问题本身的 recent question 的后续。
是的,正如前面所说的,您可以声明接受
@Configuration
类中的参数的原型bean,该类允许在每次注入时创建一个新的bean。
这将使这个
@Configuration
类成为一个工厂,并且为了不给这个工厂太多的责任,这不应该包括其他bean。
@Configuration
public class ServiceFactory {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Thing thing(String name) {
return new Thing(name);
}
但是您也可以注入该配置bean来创建
Thing
:
@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
// ...
}
它既是类型安全的,又是简洁的。
在bean xml文件中,使用属性 scope="prototype"
如果您需要创建一个 限定的bean ,您可以这样做:
@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
乐观的豌豆 · 古永锵(原优酷土豆董事长兼CEO)_搜狗百科 4 月前 |
腼腆的小熊猫 · 瓦尔基里 - 萌娘百科 万物皆可萌的百科全书 5 月前 |
聪明伶俐的冰棍 · “一村一品一网红”促进农民增收致富 5 月前 |