我正在使用 Spring 3.0.5,并尽可能为我的班级成员使用 @Autowire 注释。我需要自动装配的 bean 之一需要为其构造函数提供参数。我查看了 Spring 文档,但似乎找不到任何关于如何注释构造函数参数的参考。
在 XML 中,我可以将其用作 bean 定义的一部分。 @Autowire 注释是否有类似的机制?
前任:
@Component
public class MyConstructorClass{
String var;
public MyConstructorClass( String constrArg ){
this.var = var;
}
...
}
@Service
public class MyBeanService{
@Autowired
MyConstructorClass myConstructorClass;
....
}
在这个例子中,如何在 MyBeanService 中使用 @Autowire 注解指定“constrArg”的值?有没有办法做到这一点?
谢谢,
埃里克
您需要 @Value
注释。
一个常见的用例是使用“#{systemProperties.myProp}”样式表达式分配默认字段值。
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
参见: Expression Language > Annotation Configuration
更清楚地说:在您的场景中,您将连接两个类,MybeanService
和 MyConstructorClass
,如下所示:
@Component
public class MyBeanService implements BeanService{
@Autowired
public MybeanService(MyConstructorClass foo){
// do something with foo
}
}
@Component
public class MyConstructorClass{
public MyConstructorClass(@Value("#{some expression here}") String value){
// do something with value
}
}
更新:如果您需要多个具有不同值的 MyConstructorClass
实例,您应该 use Qualifier annotations
好吧,我不时遇到同样的问题。据我所知,当想要向构造函数添加动态参数时,不能这样做。但是,工厂模式可能会有所帮助。
public interface MyBean {
// here be my fancy stuff
}
public interface MyBeanFactory {
public MyBean getMyBean(/* bean parameters */);
}
@Component
public class MyBeanFactoryImpl implements MyBeanFactory {
@Autowired
WhateverIWantToInject somethingInjected;
public MyBean getMyBean(/* params */) {
return new MyBeanImpl(/* params */);
}
private class MyBeanImpl implements MyBean {
public MyBeanImpl(/* params */) {
// let's do whatever one has to
}
}
}
@Component
public class MyConsumerClass {
@Autowired
private MyBeanFactory beanFactory;
public void myMethod() {
// here one has to prepare the parameters
MyBean bean = beanFactory.getMyBean(/* params */);
}
}
现在,MyBean
本身不是 spring bean,但它已经足够接近了。依赖注入有效,尽管我注入了工厂而不是 bean 本身 - 如果想要替换它,则必须在他自己的新 MyBean
实现之上注入一个新工厂。
此外,MyBean
可以访问其他 bean - 因为它可能可以访问工厂的自动装配的东西。
显然有人可能想为 getMyBean
函数添加一些逻辑,这是我允许的额外努力,但不幸的是我没有更好的解决方案。由于问题通常是动态参数来自外部源,如数据库或用户交互,因此我必须仅在运行中期实例化该 bean,只有当该信息随时可用时,所以 Factory
应该是相当充足。
在这个例子中,如何在 MyBeanService 中使用 @Autowire 注解指定“constrArg”的值?有没有办法做到这一点?
不,不是你说的那样。代表 MyConstructorClass
的 bean 必须是可配置的,而不需要它的任何客户端 bean,因此 MyBeanService
对如何配置 MyConstructorClass
没有发言权。
这不是自动装配问题,这里的问题是 Spring 如何实例化 MyConstructorClass
,因为 MyConstructorClass
是 @Component
(并且您正在使用组件扫描,因此没有明确指定 MyConstructorClass
你的配置)。
正如@Sean 所说,这里的一个答案是在构造函数参数上使用 @Value
,以便 Spring 将从系统属性或属性文件中获取构造函数值。另一种方法是让 MyBeanService
直接实例化 MyConstructorClass
,但如果这样做,则 MyConstructorClass
不再是 Spring bean。
您还可以像这样配置组件:
package mypackage;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConstructorClassConfig {
@Bean
public MyConstructorClass myConstructorClass(){
return new myConstructorClass("foobar");
}
}
}
使用 Bean
注释,您告诉 Spring 在 BeanFactory
中注册返回的 bean。
因此,您可以根据需要自动装配它。
另一种方法是将参数传递给构造函数,而不是将它们作为 getter 和 setter,然后在 @PostConstruct 中根据需要初始化值。在这种情况下,Spring 将使用默认构造函数创建 bean。下面是一个例子
@Component
public class MyConstructorClass{
String var;
public void setVar(String var){
this.var = var;
}
public void getVar(){
return var;
}
@PostConstruct
public void init(){
setVar("var");
}
...
}
@Service
public class MyBeanService{
//field autowiring
@Autowired
MyConstructorClass myConstructorClass;
....
}
大多数答案都相当陈旧,所以当时可能不可能,但实际上有一个解决方案可以满足所有可能的用例。
所以知道答案是:
不提供真正的 Spring 组件(工厂设计)
或者不适合所有情况(使用@Value,您必须在某个配置文件中拥有该值)
解决这些问题的解决方案是使用 ApplicationContext
手动创建对象:
@Component
public class MyConstructorClass
{
String var;
public MyConstructorClass() {}
public MyConstructorClass(String constrArg) {
this.var = var;
}
}
@Service
public class MyBeanService implements ApplicationContextAware
{
private static ApplicationContext applicationContext;
MyConstructorClass myConstructorClass;
public MyBeanService()
{
// Creating the object manually
MyConstructorClass myObject = new MyConstructorClass("hello world");
// Initializing the object as a Spring component
AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
factory.autowireBean(myObject);
factory.initializeBean(myObject, myObject.getClass().getSimpleName());
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
}
这是一个很酷的解决方案,因为:
它使您可以访问对象上的所有 Spring 功能(显然是 @Autowired,但也可以使用 @Async ),
您可以为构造函数参数使用任何来源(配置文件、计算值、硬编码值……),
它只需要您添加几行代码而无需更改任何内容。
它还可以用于动态创建 Spring 管理的类的未知数量的实例(例如,我正在使用它来动态创建多个异步执行器)
唯一要记住的是,您必须在要实例化的类中有一个不带参数(并且可以为空)的构造函数(如果需要,还必须有一个 @Autowired
构造函数)。
MyConstructorClass myConstructorClass;
有什么意义?你从来不用它?您是否使用 myConstructorClass
而不是 myObject
?
applicationContext
对象在 c~tor 中使用,而它只是在之后设置对象已构建(不要让 static
误导您)。所以这个c~tor应该在第二行的NPE上失败,不是吗?
另一种选择是,如果您已经创建了对象的实例,并且希望将其添加为 @autowired 依赖项以初始化所有内部 @autowired 变量,则可能如下:
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
public void doStuff() {
YourObject obj = new YourObject("Value X", "etc");
autowireCapableBeanFactory.autowireBean(obj);
}
我喜欢 Zakaria 的回答,但是如果您在一个项目中,您的团队不想使用这种方法,并且您被困在尝试从属性文件中构建具有字符串、整数、浮点数或原始类型的东西到构造函数,那么您可以在构造函数中的参数上使用 Spring 的 @Value
注释。
例如,我有一个问题,我试图将一个字符串属性拉入我的构造函数中,用于一个用 @Service
注释的类。我的方法适用于 @Service
,但我认为这种方法应该适用于任何 spring java 类,如果它有一个注释(例如 @Service
、@Component
等),表明 Spring 将是构造实例的一个类的。
假设在某个 yaml 文件(或您正在使用的任何配置)中,您有这样的内容:
some:
custom:
envProperty: "property-for-dev-environment"
你有一个构造函数:
@Service // I think this should work for @Component, or any annotation saying Spring is the one calling the constructor.
class MyClass {
...
MyClass(String property){
...
}
...
}
这不会运行,因为 Spring 无法找到字符串 envProperty
。因此,这是获得该值的一种方式:
class MyDynamoTable
import org.springframework.beans.factory.annotation.Value;
...
MyDynamoTable(@Value("${some.custom.envProperty}) String property){
...
}
...
在上面的构造函数中,Spring 将调用该类并知道在调用它时使用从我的 yaml 配置中提取的字符串 "property-for-dev-environment"
。
注意:我相信 @Value 注释适用于字符串、整数,并且我相信原始类型。如果您尝试传递自定义类(bean),那么上面定义的答案中的方法将起作用。
您需要使用@Autowired 和@Value。有关此主题的更多信息,请参阅此 post。
@Value
类似于@Autowired
用于字符串参数。