如何在Spring中提供参数化组件?

时间:2018-11-26 19:27:37

标签: java spring spring-boot spring-bean

当前,我有一个实现CommandLineRunner并使用Commons CLI解析命令行参数的组件。

java -jar app.jar --host 123.123.222 --port 8080

还有另一个组件Requester,它取决于这些参数(的一部分)。

@Component
public class Requester
{

  // host and port need to be configured once
  private final String host;
  private final int port;

  public Requester(String host, int port)
  {
    this.host = host;
    this.port = port;
  }

  public boolean doRequest(String name) throws Exception
  {
    String url = "http://" + host + ":" + port + "/?command=" + name;

    URL obj = new URL(url);
    HttpURLConnection connection = (HttpURLConnection) obj.openConnection();
    int responseCode = connection.getResponseCode();
    return 200 == responseCode;
  }

}

我该怎么做才能将已配置的Requester自动连接到将来的组件中? Spring创建参数化单例bean的方式是什么?


一种解决方案是让每个与程序参数有任何依赖性的组件都实现CommandLineRunner。这样,它可以解析程序参数本身,但这是一种高度冗余的方法。必须有更好的解决方案。

3 个答案:

答案 0 :(得分:5)

您是否已检查注释@Value?它允许您在运行时从属性文件中插入值。每次需要从资源注入一些外部价值时,都应该使用该资源。例如,您的代码可以是:

@Component
public class Requester
{
  @Value("${host}")
  private final String host;
  @Value("${port}")
  private final int port;

  public boolean doRequest(String name) throws Exception
  {
    String url = "http://" + host + ":" + port + "/?command=" + name;

    URL obj = new URL(url);
    HttpURLConnection connection = (HttpURLConnection) obj.openConnection();
    int responseCode = connection.getResponseCode();
    return 200 == responseCode;
  } 
}

在您的application.properties文件中:

...
host = myhost
port = 1234
...

如果要将参数作为命令行参数传递,则可以像执行操作一样简单地调用命令:

java -jar app.jar --host 123.123.222 --port 8080

这些参数将覆盖属性文件中的参数,因为它们的优先级更高,如documentation所示。

答案 1 :(得分:1)

使用帖子@PostConstruct进行处理。
假设这是您的CommandLineRunner实现:

@Component
public class CLIArgs implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        // Parse the args, and for each arg set system property
        System.setProperty("ArgName", "value");
    }
}

然后为所需的所有args创建Getter,然后Requester将如下所示:

@Component
public class Requester {
    @Autowired
    private Environment env; // This is a spring component

    // host and port need to be configured once
    private String host;
    private int port;

    @PostConstruct
    public void init () {
        this.host = env.getProperty("host");
        this.port = Integer.parseInt(env.getProperty("port"));
    }

    public boolean doRequest(String name) throws Exception {
        String url = "http://" + host + ":" + port + "/?command=" + name;

        URL obj = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) obj.openConnection();
        int responseCode = connection.getResponseCode();
        return 200 == responseCode;
    }
}

@PostConstruct将在创建组件时发生

答案 2 :(得分:1)

受JB Nizet的评论启发,我现在解析命令行参数并在启动Spring应用程序之前手动注册bean。

我确定了两种方法:提供c'tor参数,让Spring创建bean或为Spring提供供应商功能。

现在可以在应用程序的其他部分声明@Autowire private Requester requester;


为了使该解决方案有效,需要从@Component中删除Requester批注,因为否则,当Spring无法提供必要的构造函数参数时,可能会出现问题。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // parse args here
        String host = getHost();
        int port = getPort();

        SpringApplication application = new SpringApplication(Application.class);

        // provide c'tor arguments and let Spring create the instance
        BeanDefinitionCustomizer bdc = bd -> {
            // working with bd.getPropertyValues() uses setter instead of c'tor
            ConstructorArgumentValues values = bd.getConstructorArgumentValues();
            values.addIndexedArgumentValue(0, host);
            values.addIndexedArgumentValue(1, port);
        };
        application.addInitializers((GenericApplicationContext ctx) -> ctx.registerBean(Requester.class, bdc));

        // or create the instance yourself
        Requester requester = new Requester(host, port);
        application.addInitializers((GenericApplicationContext ctx) -> ctx.registerBean(Requester.class, () -> requester));
        application.run(args);
    }

}