在运行时通过构造函数通过 @AutoWired 初始化对象

Initiate object via constructor through @AutoWired during runtime

我是 Springboot 应用程序的新手,使用 @Autowired 执行依赖注入。我们可以通过为具有 none 或默认无参数构造函数的 class 启动 class 对象来直接使用 @Autowired。但是,如果 class 在其构造函数中有一些参数,我想在运行时有条件地自动启动它,是否可以这样做?

例如

@Component
public class SomeContext {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Component
public class SomeBuilder {
    private final SomeContext ctx;

    @Autowired
    public SomeBuilder(SomeContext ctx) {
        this.ctx = ctx;
    }
    public void test() {
        System.out.println("ctx name: " + ctx.getName());
    }
}

@Service
public class SomeService {
    @Autowired
    SomeBuilder someBuilder;

    public void run(SomeContext ctx) {
        if (ctx != null) {
            // I want someBuilder  to be initiated here in someway with my input ctx 
            // but NOT doing through new with constructor like below
            // someBuilder = new SomeBuilder(ctx);
            someBuilder.test();   // ctx name: null, I would expect to see "ctx name: someUser", while ctx was injected into someBuilder in any possible way
        }
    }
}

@RestController
public class HelloWorldController 
{
    @Autowired
    SomeService someService;

    @RequestMapping("/")
    public String hello() {
        SomeContext someContext = new SomeContext();
        someContext.setName("someUser");
        someService.run(someContext);

        return "Hello springboot";
    }
}

我不确定我是否答对了你的问题,但从代码来看,你似乎真的想在每次调用 run 方法时创建一个 SomeBuilder 的新实例共 SomeService.

如果是这样,我认为首先要了解的是,通常只有当 class 由 Spring 自行管理时才会发生注入魔法。阅读,如果 spring 创建 class 的对象 - 它会向其中注入内容,否则你就只能靠自己了。

接下来要理解的是,如果你有一个由spring管理的classSomeBuilder的对象,你想将SomeContext注入其中,这个SomeContext 实例也必须由 spring 管理。

最重要的是,spring 只能处理它管理的对象。这些对象存储在 spring.

中所有名为 ApplicationContext 的对象中的一个 'global registry'

现在 Spring 有了原型作用域与单例作用域的概念。默认情况下,所有 bean 都是单例的,但是您可以轻松地改变这种行为。这有两个有趣的结果:

  1. 您可以创建在每次调用时注入到单例中的原型对象(在您的情况下是方法 run,因此 SomeBuilder 可以而且我相信应该是原型)
  2. 原型对象未存储在应用程序上下文中,因此在运行时向其中注入内容的能力相当有限

考虑到所有这些:

如果你想像在控制器中那样创建 SomeContext,它不是由 spring 管理的,所以你不能像在构建器中那样使用 spring 的注入. 构建器是一个单例,因此如果您将它与常规 @Autowire 注入另一个单例(在您的情况下为 SomeService ),您将不得不处理构建器对象的相同实例 - 想想并发访问 SomeService 的方法 run 你就会明白这个解决方案并不是一个很好的解决方案。

所以这些是所提供解决方案中的“不准确之处”。

现在,在解决方案方面,您可以:

选项 1 不要在 Spring 中管理构建器,并非所有内容都应由 spring 管理,在这种情况下,您将保持代码不变。

选项 2

  • 这是一个解决方案,虽然相当先进:

使用 Java 配置在运行时创建原型 bean,能够将参数注入 bean。

这是一个例子:

// Note, I've removed all the annotations, I'll use java configurations for that, read below...
public class SomeBuilder {
    private final SomeContext ctx;

    public SomeBuilder(SomeContext ctx) {
        this.ctx = ctx;
    }
    public void test() {
        System.out.println("ctx name: " + ctx.getName());
    }
}

现在 class SomeService 也会略有变化:

public class SomeService {
   
    private Function<SomeContext, SomeBuilder> builderFactory;

    public void run(SomeContext ctx) {

        SomeBuilder someBuilder = builderFactory.apply(ctx);
        someBuilder.test();  

    }
}

现在您应该使用 Java 配置以高级方式将它“粘合”到 spring:

@Configuration
public class MyConfiguration {
    @Bean
    public Function<SomeContext, SomeBuilder> builderFactory() {
        return ctx -> someBuilder(ctx);
    } 

    @Bean
    @Scope(value = "prototype")
    public SomeBuilder someBuilder(SomeContext ctx) {
       return new SomeBuilder(ctx);
    }
    
    @Bean
    public SomeService someService() {
        return new SomeService(builderFactory());
    }    
}

有关真正相似示例的更多详细信息,请参阅 this tutorial