有没有一种方法可以自动装配需要构造函数参数的bean?

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

  ....
}
在这个例子中,我如何使用@Autowire注释来指定"MyBeanService"的"constrArg"值?有什么方法可以做到这一点吗?
谢谢, Eric
答案是:

在这个例子中,如何使用@Autowire注解来指定MyBeanService中的"constrArg"值?是否有任何方法可以实现这一点?

谢谢,
Eric


1
也许我表达不够清晰,或者我误解了,但我不是想要自动装配constrArg;我想要自动装配MyConstructorClass,但构造函数需要一个字符串。如果我使用XML配置,我的bean定义将是: - Eric B.
如果我使用XML配置,我的bean定义将类似于:<bean id="myBeanService" class="MyBeanService"> <property name="myConstructorClass"> <bean class="MyConstructorClass"> <constructor-arg value="MyStringHere" /> </bean> </property> </bean> 有没有一种方法将其转换为注释? - Eric B.
https://www.baeldung.com/spring-autowire - Smart Coder
9个回答

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


2
谢谢你的提示,但我不明白它与设置注入bean的构造函数参数有什么关系。从Spring文档中我所了解到的是,它适用于设置默认值,但没有说明如何传递构造函数参数。 - Eric B.
3
@Value就像是用于字符串参数的@Autowired - Bozho
1
@Eric B(顺便说一下,你的用户名真棒:http://en.wikipedia.org/wiki/Eric_B._%26_Rakim),我已经添加了一些示例代码以作说明。 - Sean Patrick Floyd
如果构造函数参数是自定义的Java对象,而不是字符串,这个答案会改变吗? - Kevin Meredith
1
@SeanPatrickFloyd - 我想将值“Value1”传递给构造函数,我该怎么做?我尝试了您的语法,但它无法编译,这正确吗..?您能帮助我吗? - Ashish Shetkar
显示剩余7条评论

25

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

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应该是相当合适的。


10
在这个示例中,我如何使用@Autowire注解来指定"MyBeanService"中的"constrArg"的值?有没有办法做到这一点?
不,不是以您所说的方式。代表MyConstructorClass的bean必须可配置,而无需要求任何客户端bean,因此MyBeanService不能决定如何配置MyConstructorClass
这不是一个自动装配的问题,问题在于Spring如何实例化MyConstructorClass,因为MyConstructorClass是一个@Component(并且您正在使用组件扫描,因此没有在配置中显式指定MyConstructorClass)。
正如@Sean所说,其中一种方法是在构造函数参数上使用@Value,以便Spring从系统属性或属性文件中获取构造函数的值。另一种选择是让MyBeanService直接实例化MyConstructorClass,但如果这样做,MyConstructorClass将不再是Spring bean。

@Value可以设置为完全限定的bean吗?然后通过自动装配找到它? - Kevin Meredith

10

你也可以像这样配置你的组件:

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


   @Bean
   public MyConstructorClass myConstructorClass(){
      return new myConstructorClass("foobar");
   }
  }
}
使用Bean注解,您告诉Spring将返回的bean注册到BeanFactory中。因此,您可以根据需要进行自动装配。

请问如何在MyConstructorClassConfig中自动装配我创建的客户端,例如我有一个使用@Bean注解的createClient方法,我该如何在服务类中使用这个createClient方法? - Tarnished-Coder
如果参数是动态的呢? - Farid

0
一个替代方案是,不要将参数传递给构造函数,而是将它们作为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;

  ....
}

这甚至没有增加多少价值。不要经过所有这些步骤,只需在配置文件中通过BeanFactory获取您的bean并在那里设置变量即可。 - Farid

0

如果您已经创建了一个对象实例,并且想将它添加为@autowired依赖项以初始化所有内部的@autowired变量,另一个选择是:

@Autowired 
private AutowireCapableBeanFactory autowireCapableBeanFactory;

public void doStuff() {
   YourObject obj = new YourObject("Value X", "etc");
   autowireCapableBeanFactory.autowireBean(obj);
}

0

大多数答案都相当老旧,所以在那时可能还不可能,但实际上有一种解决方案可以满足所有可能的用例。

所以现在的答案是:

  • 不提供真正的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 - Dan
@Dan,老实说我不记得了,因为那是一段时间之前的事情,但可能只是我犯的一个错误,或者是我之前使用过的一个变量,但最终没有用到。 - AntoineB
刚看到这个帖子,虽然它很旧,但人们仍然在查看它,所以我要评论一下:这种方法不能正常工作,因为applicationContext对象在构造函数中使用,而它只有在对象构造之后才被设置(不要让static误导你)。所以这个构造函数应该在第二行失败并抛出NPE异常,不是吗? - GullerYA

0

我喜欢Zakaria的答案,但如果你在一个团队项目中,你的团队不想使用那种方法,并且你被困在尝试用字符串、整数、浮点数或原始类型从属性文件构造某些东西并传递到构造函数中的情况下,那么你可以在构造函数的参数上使用Spring的@Value注解。

例如,我曾经遇到过这样的问题:我正在尝试将一个字符串属性传递到我的带有@Service注解的类的构造函数中。我的方法适用于@Service,但我认为只要它有一个注解(如@Service@Component等),指示Spring将构造类的实例,这种方法就应该适用于任何Spring Java类。

假设在某个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),则上面定义的方法适用。

-2
你需要使用 @Autowired 和 @Value。请参考此 post 获取更多关于这个主题的信息。

1
答案虽然简短,但已经在那里了。本质上,答案是使用'@Autowired'和'@Value'关键字。我本来可以将它添加为评论,但那是很久以前的事情了,我不确定我是否有评论权限... - Fahim Farook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接