有没有一种方法可以@Autowire 一个需要构造函数参数的 bean?

我正在使用 Spring3.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;


....
}

在这个例子中,我如何使用@Autowire 注释在 MyBeanService 中指定“ strarg”的值?有什么办法吗?

谢谢,

艾瑞克

213381 次浏览

您需要 @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;
}


// ...
}

请参见: 表达式语言 > 注释配置


更明确地说: 在您的场景中,您将连接两个类 MybeanServiceMyConstructorClass,类似于下面这样:

@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实例,则应该使用 使用限定符注释

在这个例子中,如何使用 @Autowire注释在 MyBeanService中指定“ strArg”的值?有什么办法吗?

不,不是你想的那样。表示 MyConstructorClass的 bean 必须是可配置的,而不需要它的任何客户端 bean,因此 MyBeanService在如何配置 MyConstructorClass方面没有发言权。

这不是一个自动装配的问题,这里的问题是 Spring 如何实例化 MyConstructorClass,因为 MyConstructorClass@Component(而且您使用的是组件扫描,因此没有在配置中显式指定 MyConstructorClass)。

正如@Sean 所说,这里的一个答案是对构造函数参数使用 @Value,这样 Spring 就可以从系统属性或属性文件中获取构造函数值。另一种方法是让 MyBeanService直接实例化 MyConstructorClass,但是如果您这样做了,那么 MyConstructorClass就不再是一个 Spring bean 了。

您需要使用@Autowired 和@Value。有关本主题的更多信息,请参考此 邮寄

有时候我也会遇到同样的问题。据我所知,当想要向构造函数添加动态参数时,不能这样做。但是,工厂模式可能有所帮助。

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本身并不是一颗小豆子,但它已经足够接近了。依赖注入工作,尽管我注入的是工厂而不是 bean 本身——如果想要替换它,就必须在自己的新 MyBean实现之上注入一个新工厂。

此外,MyBean还可以访问其他 bean ——因为它可以访问工厂的自动化设备。

显然,人们可能想要为 getMyBean函数添加一些逻辑,这是我允许的额外努力,但不幸的是,我没有更好的解决方案。由于问题通常是动态参数来自外部源,比如数据库,或者用户交互,因此我必须在运行中才能实例化 bean,只有当信息可用时才能实例化,所以 Factory应该足够了。

另一种方法是不将参数传递给构造函数,而是将它们作为 getter 和 setter,然后在@PostConstruction 中根据需要初始化值。在这种情况下,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构造函数)。

您还可以像下面这样配置您的组件:

package mypackage;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConstructorClassConfig {




@Bean
public MyConstructorClass myConstructorClass(){
return new myConstructorClass("foobar");
}
}
}

通过 Bean注释,您可以告诉 Spring 在 BeanFactory中注册返回的 bean。

这样你就可以随心所欲地自动装配了。

另一种选择是,如果您已经创建了一个对象的实例,并且您希望添加它作为@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 配置中提取的 String "property-for-dev-environment"

注意: 我认为@Value 注释是针对字符串、整数的,并且我相信主要类型。如果您试图传递自定义类(bean) ,那么使用上面定义的答案就可以了。