动态地将属性注入弹簧中

时间:2014-08-21 09:45:52

标签: java spring dependency-injection

我有一个spring config xml文件,可以创建多个bean并将其自动装入其他bean

例如

<bean id="a" class="com.xyz.A">
        <property name="prop1" value="?" />
   </bean>

   <bean id="b" class="com.xyz.B">
        <property name="prop2" ref="a" />   
   </bean>

我通过阅读上面的spring文件来创建应用程序上下文。但是A的prop1的值在运行时是动态已知的。如何动态注入此属性?动态是指应用程序启动时的情况。例如,某些属性作为命令行输入到应用程序。 然后应将此属性设置为bean的属性

我已经给出了这个例子来简化问题。真正的我的动态参数是数据库服务器url,我想动态创建连接池

4 个答案:

答案 0 :(得分:4)

更多涉及,但这是一种方法:

    {li>在您的main()方法中定义了您正在创建的应用程序上下文。您的想法是定义一个List,其中将存储命令行参数,并在此应用程序上下文中将此列表定义为bean(标识为args):
public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    BeanDefinition beanDefinition = BeanDefinitionBuilder
            .rootBeanDefinition(Arrays.class, "asList")
            .addConstructorArgValue(args)
            .getBeanDefinition();
    beanFactory.registerBeanDefinition("args", beanDefinition);

    GenericApplicationContext parentContext= new GenericApplicationContext(beanFactory);
    parentContext.refresh();
    ...
}
  • 使用先前定义的应用程序上下文作为main()方法中已有的应用程序上下文的父级。假设您正在创建ClassPathXmlApplicationContext,则代码为:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
            new String[] { "app-context.xml" }, parentContext);

其中parentContext是先前创建的用于保存List参数的上下文。

  • 然后,在您的xml中,您可以使用SPeL轻松访问参数列表:
    <bean id="a" class="com.foo.bar.ClassA">
        <property name="prop1" value="#{args[0]}" />
    </bean>

    <bean id="b" class="com.foo.bar.ClassB">
        <property name="prop2" ref="a" />
    </bean>

注意在xml中,SPeL表达式中使用的参数名称为args,这是用于定义ListparentContext的确切名称。

答案 1 :(得分:2)

这是另一种注入财产的方法。我的建议类似于 geoand ,因为我也使用java配置而不是xml。但是使用SimpleCommandLinePropertySource并且更灵活一点也更简单。 在我的例子下面可以使用或不使用弹簧启动。

主要课程

public static void main(String [] args) {
    SimpleCommandLinePropertySource ps = new SimpleCommandLinePropertySource(args);
    @SuppressWarnings("resource")
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.getEnvironment().getPropertySources().addFirst(ps);
    ctx.register(ApplicationConfig.class);
    ctx.refresh();
}

<强> ApplicationConfig.java

这是java配置类,默认情况下读取包内的application.properties,但也尝试从命令行选项中读取外部属性文件--config.location = / some / path / app.properties 如果没有给出外部属性文件,那么 ignoreResourceNotFound = true

将优雅地忽略它
@Configuration
@EnableScheduling
@ComponentScan("com.mycompany.package")
@PropertySource(
        value = {"classpath:/application.properties", "file:${config.location}"},
        ignoreResourceNotFound = true
    )
public class ApplicationConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

属性注入注释

现在任何地方都有 @Value 注释或其他属性注入注释,例如 @Scheduled ,期望该值受外部文件或命令行选项上给出的属性值的约束都。

@Component
public class MyComponent {

    @Value("${my.property.data}")
    private String myPropertyData;


    @Scheduled(fixedDelayString = "${schedule.delay.period}")
    public void run() {
         :
    }
}

因此,您可以将下面的属性放在外部文件上,并通过命令行选项 - config.location = / some / path / app.properties

提供该文件

<强> app.properties

my.property.data=test
schedule.delay.period=60000

或者您可以通过命令行单独注入每个属性值

--my.property.data=test --schedule.delay.period=60000

仅供参考,spring-boot已经为外部属性文件使用了--spring.config.location命令行选项。

答案 2 :(得分:1)

由于您要做的是将命令行参数注入bean,我建议您查看this

提到的内容的本质是你可以添加CommandLinePropertySource(与使用属性源从属性文件中读取参数的方式完全相同)然后只使用常规引用它们Spring方法(通过环境获取它们或使用Spring EL引用它们)。

为了完整性,我发布了第一个链接中的代码

public class Main {
    public static void main(String... args) {
        //initialize the command line parsing stuff
        OptionParser parser = new OptionParser();
        parser.accepts("greeting").withRequiredArg();
        OptionSet options = parser.parse(args);

        //create the actual Spring PropertySource
        PropertySource<?> ps = new JOptCommandLinePropertySource(options);

        //setup the Spring context
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().getPropertySources().addLast(ps); //register the property source with the environment

        ctx.register(Greeter.class);
        ctx.refresh();
        Greeter greeter = ctx.getBean(Greeter.class);
        greeter.sayGreeting();
    }
}

@Component
class Greeter {
    @Inject private Environment env;


    //the following would also work
    //@Value("${greeting}")
    //private String greeting;        

    /**
     * Print out the 'greeting' property if it exists, and otherwise, "Welcome!".
     */
    public void sayGreeting() {
        System.out.println(env.getProperty("greeting", "Welcome!"));
    }
}

在您的情况下,如果您使用CommandLinePropertySource,xml配置将如下所示:

   <bean id="a" class="com.xyz.A">
        <property name="prop1" value="${db.url}" />
   </bean>

   <bean id="b" class="com.xyz.B">
        <property name="prop2" ref="a" />   
   </bean>

最后,如果你使用的是Spring Boot,那么Spring Boot会自动将命令行属性添加到Spring环境中,这意味着你不必自己注册PropertySource(查看this更多细节)。

答案 3 :(得分:0)

您可以使用Supplier模式(例如Java 8或Guava库附带)

<bean id="a" class="com.xyz.A">
    <property name="prop1" value="prop1Supplier" />
</bean>

<bean id="b" class="com.xyz.B">
    <property name="prop2" ref="a" />   
</bean>

<bean id="prop1Supplier" class="com.xyz.Prop1Supplier"/>

然后,您需要使用prop1.get()才能在运行时检索值