【问题标题】:Is there a way to @Autowire a bean that requires constructor arguments?有没有办法 @Autowire 需要构造函数参数的 bean?
【发布时间】:2011-10-08 01:09:54
【问题描述】:

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

谢谢,

埃里克

【问题讨论】:

  • 也许我不清楚,或者我理解错了,但我不打算自动装配 constrArg;我正在寻找自动装配 MyConstructorClass,但构造函数需要一个字符串。如果我使用 XML 配置,我的 bean defn 将是:
  • 如果我使用 XML 配置,我的 bean defn 将类似于: 有没有办法把它翻译成注解?

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


【解决方案1】:

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

【讨论】:

  • 感谢您的提示,但我不明白这与为注入的 bean 设置构造函数 arg 有何关系。从我从 spring 文档中可以看出,它有利于设置默认值,但没有指定如何传递构造函数 arg。
  • @EricB。 @Value 就像 @Autowired 用于字符串参数。
  • @Eric B (awesome username, btw :-)),我添加了一些示例代码用于说明
  • 感谢您的提示。这里的问题是我必须有权修改 MyConstructorClass 代码。不幸的是,情况并非总是如此。我不一定有能力修改代码。此外,在我的示例中,我使用 String 作为参数,但在某些情况下,构造函数需要其他 bean 作为参数。
  • @SeanPatrickFloyd - 我想将值 - “Value1”传递给构造函数,我该怎么做,我尝试了你的语法,但它没有被编译,它是否正确..?你能帮帮我吗
【解决方案2】:

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

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】:

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

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

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

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

    【讨论】:

    • 可以将@Value 设置为完全限定的bean吗?他们会通过自动装配找到吗?
    【解决方案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。

    因此您可以根据需要自动装配它。

    【讨论】:

    • 你能告诉我如何自动接线,例如我在 MyConstructorClassConfig 中创建的客户端,如上图所示,例如我有一个用 Bean 注释的 createClient 方法,如何在服务类中使用这个 createClient 方法?
    【解决方案5】:

    另一种方法是将参数传递给构造函数,而不是将它们作为 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】:

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

      所以知道答案是:

      • 不提供真正的 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 老实说,我不记得了,因为那是很久以前的事了,但是是的,这可能只是我留下的一个错误,或者是我之前使用但最终不需要的变量。
      • 刚看到这个帖子,已经很老了,但是大家还在看,所以评论一下:这个方法不能正常工作,因为在c~tor中使用了applicationContext对象, 虽然它仅在构造对象后才设置(不要让static 误导您)。所以这个c~tor应该在第二行的NPE上失败,不是吗?
      【解决方案7】:

      另一种选择,如果您已经创建了对象的实例,并且希望将其添加为 @autowired 依赖项以初始化所有内部 @autowired 变量,则可能如下:

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

      【讨论】:

        【解决方案8】:

        我喜欢 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),那么上面定义的答案中的方法可以工作。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

          • 答案是认为它很短。本质上,答案是给你“@Autowired”和“@Value”。我本可以将其添加为评论,但这是很久以前的事了,我不确定我是否有评论权限...
          猜你喜欢
          • 2019-03-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-03
          • 1970-01-01
          相关资源
          最近更新 更多