如何指示 Spring 自动装配紧耦合对象链中的字段

How to instruct Spring to Autowire a field in a chain of tightly coupled objects

有点难以解释...希望问题不含糊...

你可以看看代码来了解一下...

ClassA.java

public class ClassA {
    @Autowired
    InterA abcd;
    public void dododo() {
        abcd.doit();
    }
}

ClassB.java

@Component
public class ClassB implements InterA {
    @Override
    public void doit() {
        System.out.println("hoo hoo");
    }
}

ClassC.java

@Component("classc")
public class ClassC {
    public void doFromAbove() {
        ClassA cls = new ClassA();
        cls.dododo();
    }
}

界面InterA.java

public interface InterA {
    public void doit();
}

配置ClassConfig.java(在其他javaclass文件的同一个包上)

@Configuration
@ComponentScan
public class ClassConfig {
}

主要方法

public static void main(String[] args) {
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        ClassC obj = (ClassC) appctx.getBean("classc");
        obj.doFromAbove();
    }
}

当我执行 main 方法时,ClassA 中的 Autowired 字段 "abcd" 没有被注入并导致 NullPointerException

只有当我将 ClassA 声明为 @Component 并获取它的 bean 时它才有效...间接自动装配没有发生

我是否应该将 ClassAClassC 分离并使所有内容松散耦合?

是否有任何简单的注解可用于告诉 Spring 自动注入 @Autowired 字段,即使对象是以紧密耦合的方式创建的?

备注 请不要告诉我在 ClassC 中使用 ApplicationContext 来创建 ClassA 的 bean。

哪位Spring极客能找到答案?

问题在ClassC:

    ClassA cls = new ClassA();

如果你像这样调用 ClassA 的构造函数,Spring 将不会发挥它的魔力。如果您需要带有注入字段的 ClassA 实例,请向 Spring 索要实例(使用注入或 getBean())。

(为避免 null 假定注入的字段,我建议使用构造函数注入。)

在 Spring 容器中声明的 beans(通过 XML 或像 @Component 这样的注解)是 Spring-managed - Spring 会处理它们,当您通过 ApplicationContext.getBean() 请求它们时会确保它们存在,并且它们的依赖项也会被注入。

当您自己创建实例 (cls = new ClassA()) 时,该实例不受 Spring 管理,因此 Spring 不会对其执行任何操作。事实上,Spring 甚至不会(也不能)知道对象的存在。

一些混淆可能源于您使用 Spring 注释对 class 进行注释 - 但它实际上是 objects(实例)在Java中实际使用;即使 class 被注解,注解也只会应用于由 Spring.

创建和管理的实例

如果启用加载时编织,则可以使用 @Configurable,这将使对象的每个新实例成为托管 spring 组件,因此您使用的新语句将起作用。

除此之外,您可以创建一个原型范围的 bean 定义和一个引用该 bean 的工厂 bean,这意味着它每次都会给您一个新的 bean,因此您可以注入工厂并只调用 get 方法新实例。

经过大量谷歌搜索,Spring 文档略读,我相信有更多可能的解决方案来解决这个难题...

可能的解决方案:

  1. 将 JSR 330 Provider<T>@Autowired
  2. 结合使用
  3. 使用 FactoryBean<T>getObject() 中的初始化代码(但是工厂 returned 的 bean 不是 spring 管理的,因此原型中的任何自动装配字段 class 将 return NullPointerException)
  4. 使用查找方法注入(包括CGLIB库)(我不喜欢 这个,因为它修改了编译后的代码,听起来像是在创建 bean 对象 摘要 classes)(Java 的纯度被违反)
  5. 实现ApplicationContextAware接口并获取上下文 对象(不推荐)
  6. 自动装配 ApplicationContext 并使用 getBean()(不推荐)

上述方法中最微妙的方法是 JSR330 Provider

A类

@Component("classa")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ClassA implements InterB {
    private static int counter=0;

    private int objectid = 0;
    @Autowired
    InterA abcd;

    public ClassA() {
        super();
        this.objectid = ++counter;
    }

    @Override
    public void dododo() {
        System.out.println("instance number "+objectid++);
        abcd.doit();
    }
}

B级

@Component
public class ClassB implements InterA {
    @Override
    public void doit() {
        System.out.println("hoo hoo");
    }

}

C类

@Component("classc")
public class ClassC {

    @Autowired
    Provider<InterB> classAPrototypeobj;

    public void doFromAbove() {
        //you can do a for loop here and get a set of objects for use
        InterB cls = (InterB) classAPrototypeobj.get();
        InterB cls1 = (InterB) classAPrototypeobj.get();
        cls.dododo();
        cls1.dododo();
        System.out.println(cls);
        System.out.println(cls1);
    }
}

现在它可以完美地工作并且初始化的对象也被 spring 管理...

注: 必须在 maven pom.xml

中设置 JSR330 依赖项
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

为什么不使用@Lookup 注解?根据已接受的答案,我假设您每次 ClassC.

中都需要一个 ClassA 的新实例
@Component("classA")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ClassA {
    @Autowired
    InterA abcd;

    private ObjectA objA;

    private ObjectB objB;

    public ClassA(objA, objB) {
        this.objA = objA;
        this.objB = objB;
    }

    public void dododo() {
        abcd.doit();
    }
}

@Component("classc")
public class ClassC {
    public void doFromAbove() {
        ClassA cls = getNewInstanceOfClassA(objA, objB);
        cls.dododo();
    }

    @Lookup("classA")
    private getNewInstanceOfClassA(ObjectA objA, ObjectB objB) {
        //Spring creates a runtime implementation of this method
        return null;
    }
}

其余 class 实现保持不变。我在实现中包含了 objA 和 objB,以便清楚地了解注入构造函数参数。

所以main方法获取classC的一个springbean,调用doFromAbove()方法。这依次调用 getNewInstanceOfClassA 方法,该方法 returns 类型为 classA 的 spring bean 具有构造函数参数 objA、objB。由于我们将其注释为原型 bean,因此每次调用此方法时我们都会得到一个新的 classA 实例。您不需要实施 getNewInstanceOfClassA 方法。 Spring 在运行时添加它自己的代码。

从本质上讲,您的问题归结为在单例 bean 中注入原型 bean。查找注释是解决该问题的最佳方法。