是否有办法@Autowire需要构造函数参数的bean?

时间:2011-07-18 21:17:49

标签: spring constructor autowired spring-annotations spring-bean

我正在使用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”的值?有没有办法做到这一点?

谢谢,

埃里克

9 个答案:

答案 0 :(得分:53)

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


更清楚一点:在您的方案中,您要连接两个类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的多个不同实例,则应use Qualifier annotations

答案 1 :(得分:9)

  

在此示例中,如何使用MyBeanService注释在@Autowire中指定“constrArg”的值?有没有办法做到这一点?

不,不是你的意思。表示MyConstructorClass的bean必须是可配置的,而不需要任何客户端bean,因此MyBeanService无法说明MyConstructorClass的配置方式。

这不是一个自动装配问题,这里的问题是Spring如何实例化MyConstructorClass,假设MyConstructorClass@Component(并且您正在使用组件扫描,并且因此,未在配置中明确指定MyConstructorClass

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

答案 2 :(得分:9)

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

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应该是足够的。

答案 3 :(得分:4)

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

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


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

使用Bean注释,您告诉Spring在BeanFactory中注册返回的bean。

所以你可以按照自己的意愿自动装配它。

答案 4 :(得分:1)

您需要使用@Autowired和@Value。有关此主题的更多信息,请参阅此post

答案 5 :(得分: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;

  ....
}

答案 6 :(得分: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构造函数)

答案 7 :(得分:0)

另一种替代方法是,如果您已经创建了对象的实例,并且想要将其添加为@autowired依赖项以初始化所有内部@autowired变量,则可能是以下情况:

@Autowired 
private AutowireCapableBeanFactory autowireCapableBeanFactory;

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

答案 8 :(得分:0)

我喜欢Zakaria的答案,但是如果您所在的项目中团队不希望使用该方法,并且您被困在尝试使用String,integer,float或primitive类型构造某些对象,属性文件添加到构造函数中,然后可以在构造函数中的参数上使用Spring的@Value批注。

例如,我遇到一个问题,我试图将用@Service注释的类的字符串属性拉到构造函数中。我的方法适用于@Service,但我认为该方法应该适用于任何spring java类,只要它具有表示Spring的注释(例如@Service@Component等)即可。将是该类的一个构造实例。

比方说,在某个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),则可以采用上面定义的答案中的方法。