如何指示Spring在一系列紧密耦合的对象中自动连接一个字段

时间:2022-08-31 11:58:41

It's kinda hard to explain... Hope the question isn't vague...

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

you can look at the code to get the idea...

你可以看看代码来获得想法......

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();
    }
}

Interface InterA.java

public interface InterA {
    public void doit();
}

Configuration ClassConfig.java (on the same package of other java class files)

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

@Configuration
@ComponentScan
public class ClassConfig {
}

Main Method

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

When I execute the main method, the Autowired field "abcd" in ClassA didn't get injected and results in a NullPointerException

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

It works only when I declare ClassA as a @Component and get it's bean... indirect autowiring is not happening

它仅在我将ClassA声明为@Component并获取它的bean时才有效...间接自动装配不会发生

Should I decouple ClassA from ClassC and make everything loosely coupled?

我应该将ClassA与ClassC分离并使所有内容松散耦合吗?

Is there any simple annotation that I can use to tell Spring auto inject the @Autowired field even when the object is created in a tight coupled fashion?

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

Note please don't tell me to use ApplicationContext in ClassC to create the bean of ClassA .

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

Any Spring Geek who could find an answer?

任何能找到答案的Spring Geek?

5 个解决方案

#1


1  

The issue is in ClassC:

问题出在ClassC中:

    ClassA cls = new ClassA();

If you invoke the constructor of ClassA like this, Spring won't do its magic. If you need an instance of ClassA with injected fields, ask Spring for an instance (using injection or getBean()).

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

(To avoid having null fields where injection is assumed, I recommend using constructor injection.)

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

#2


1  

After intense googling, Spring Documentation Skimming, I'm convinced there are more possible solutions to this dilemma...

经过激烈的谷歌搜索,Spring Documentation Skimming,我确信有更多可能的解决方案来应对这种困境......

Possible Solutions:

  1. Use JSR 330 Provider<T> with @Autowired
  2. 将JSR 330 Provider 与@Autowired一起使用

  3. Use FactoryBean<T> with initialization code in getObject() (but the bean returned by the factory is not spring managed and thus any autowired field in the prototype class will return NullPointerException)
  4. 将FactoryBean 与getObject()中的初始化代码一起使用(但是工厂返回的bean不是Spring管理的,因此原型类中的任何autowired字段都将返回NullPointerException)

  5. Use Lookup Method Injection(include CGLIB library)(I don't prefer this, as it modifies compiled code and sounds like it creates bean objects of Abstract classes)(Java's Purity is violated)
  6. 使用查找方法注入(包括CGLIB库)(我不喜欢这个,因为它修改了编译代码和声音,就像它创建了抽象类的bean对象)(Java的纯度被违反)

  7. Implement ApplicationContextAware interface and get the context object (not recommended)
  8. 实现ApplicationContextAware接口并获取上下文对象(不推荐)

  9. Autowire ApplicationContext and use getBean() (not recommended)
  10. Autowire ApplicationContext并使用getBean()(不推荐)

Most Subtle approach among the above is JSR330 Provider

以上最微妙的方法是JSR330 Provider

ClassA

@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();
    }
}

ClassB

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

}

ClassC

@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);
    }
}

Now it works flawlessly and the initialized object is spring managed too...

现在它完美无缺,初始化对象也是弹簧管理的......

Note: JSR330 dependency has to be set in maven pom.xml

注意:必须在maven pom.xml中设置JSR330依赖项

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

#3


0  

The beans declared within Spring container (either through XML or annotations like @Component) are Spring-managed - Spring will take care of them, will make sure they exist when you request them via ApplicationContext.getBean() and that their dependencies are injected as well.

在Spring容器中声明的bean(通过XML或@Component之类的注释)是Spring管理的 - Spring会处理它们,当你通过ApplicationContext.getBean()请求它们时它们会确保它们存在并且它们的依赖项被注入好。

When you create an instance yourself (cls = new ClassA()), that instance is not Spring-managed and Spring will not do anything to it. In fact, Spring won't (and cannot) even know the object exists.

当你自己创建一个实例(cls = new ClassA())时,该实例不是Spring管理的,Spring也不会对它做任何事情。实际上,Spring不会(也不可能)知道对象存在。

Some confusion may stem from that you annotate the class with the Spring annotations - but it's really objects (instances) that are actually used in Java; even if the class is annotated, the annotations will only apply on instances that are created and managed by Spring.

一些混淆可能源于您使用Spring注释对类进行注释 - 但它实际上是Java中实际使用的对象(实例);即使对类进行了注释,注释也只适用于由Spring创建和管理的实例。

#4


0  

You could use @Configurable if you enable load time weaving and that would make each new instance of the object a managed spring component, so the new statement you used will work.

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

Besides this you could create a prototype scoped bean definition and a factory bean that would reference that bean, meaning it will give you a new bean each time, so you would inject the factory and only call the get method for a new instance.

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

#5


0  

Why not use @Lookup annotation? Based on the accepted answer, I assume you require a new instance of ClassA every time in ClassC.

为什么不使用@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;
    }
}

The rest of the class implementations remain the same. I have included objA and objB in the implementation for some clarity on injecting constructor arguments.

其余的类实现保持不变。为了清楚注入构造函数参数,我在实现中包含了objA和objB。

So the main method gets a spring bean of classC and calls doFromAbove() method. This in turn calls getNewInstanceOfClassA method which returns a spring bean of type classA with the constructor arguments objA, objB. Since we annotated it as prototype bean, we get a new instance of classA every time this method is invoked. You don't need to implement getNewInstanceOfClassA method. Spring adds it's own code during the runtime.

因此main方法获取classC的spring bean并调用doFromAbove()方法。这反过来调用getNewInstanceOfClassA方法,该方法返回类型为classA的spring bean,其构造函数参数为objA,objB。由于我们将其注释为原型bean,因此每次调用此方法时都会得到一个新的classA实例。您不需要实现getNewInstanceOfClassA方法。 Spring在运行时添加了自己的代码。

Essentially your problem boils down to injecting a prototype bean in a singleton bean. Lookup annotation is the best way to solve that issue.

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

#1


1  

The issue is in ClassC:

问题出在ClassC中:

    ClassA cls = new ClassA();

If you invoke the constructor of ClassA like this, Spring won't do its magic. If you need an instance of ClassA with injected fields, ask Spring for an instance (using injection or getBean()).

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

(To avoid having null fields where injection is assumed, I recommend using constructor injection.)

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

#2


1  

After intense googling, Spring Documentation Skimming, I'm convinced there are more possible solutions to this dilemma...

经过激烈的谷歌搜索,Spring Documentation Skimming,我确信有更多可能的解决方案来应对这种困境......

Possible Solutions:

  1. Use JSR 330 Provider<T> with @Autowired
  2. 将JSR 330 Provider 与@Autowired一起使用

  3. Use FactoryBean<T> with initialization code in getObject() (but the bean returned by the factory is not spring managed and thus any autowired field in the prototype class will return NullPointerException)
  4. 将FactoryBean 与getObject()中的初始化代码一起使用(但是工厂返回的bean不是Spring管理的,因此原型类中的任何autowired字段都将返回NullPointerException)

  5. Use Lookup Method Injection(include CGLIB library)(I don't prefer this, as it modifies compiled code and sounds like it creates bean objects of Abstract classes)(Java's Purity is violated)
  6. 使用查找方法注入(包括CGLIB库)(我不喜欢这个,因为它修改了编译代码和声音,就像它创建了抽象类的bean对象)(Java的纯度被违反)

  7. Implement ApplicationContextAware interface and get the context object (not recommended)
  8. 实现ApplicationContextAware接口并获取上下文对象(不推荐)

  9. Autowire ApplicationContext and use getBean() (not recommended)
  10. Autowire ApplicationContext并使用getBean()(不推荐)

Most Subtle approach among the above is JSR330 Provider

以上最微妙的方法是JSR330 Provider

ClassA

@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();
    }
}

ClassB

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

}

ClassC

@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);
    }
}

Now it works flawlessly and the initialized object is spring managed too...

现在它完美无缺,初始化对象也是弹簧管理的......

Note: JSR330 dependency has to be set in maven pom.xml

注意:必须在maven pom.xml中设置JSR330依赖项

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

#3


0  

The beans declared within Spring container (either through XML or annotations like @Component) are Spring-managed - Spring will take care of them, will make sure they exist when you request them via ApplicationContext.getBean() and that their dependencies are injected as well.

在Spring容器中声明的bean(通过XML或@Component之类的注释)是Spring管理的 - Spring会处理它们,当你通过ApplicationContext.getBean()请求它们时它们会确保它们存在并且它们的依赖项被注入好。

When you create an instance yourself (cls = new ClassA()), that instance is not Spring-managed and Spring will not do anything to it. In fact, Spring won't (and cannot) even know the object exists.

当你自己创建一个实例(cls = new ClassA())时,该实例不是Spring管理的,Spring也不会对它做任何事情。实际上,Spring不会(也不可能)知道对象存在。

Some confusion may stem from that you annotate the class with the Spring annotations - but it's really objects (instances) that are actually used in Java; even if the class is annotated, the annotations will only apply on instances that are created and managed by Spring.

一些混淆可能源于您使用Spring注释对类进行注释 - 但它实际上是Java中实际使用的对象(实例);即使对类进行了注释,注释也只适用于由Spring创建和管理的实例。

#4


0  

You could use @Configurable if you enable load time weaving and that would make each new instance of the object a managed spring component, so the new statement you used will work.

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

Besides this you could create a prototype scoped bean definition and a factory bean that would reference that bean, meaning it will give you a new bean each time, so you would inject the factory and only call the get method for a new instance.

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

#5


0  

Why not use @Lookup annotation? Based on the accepted answer, I assume you require a new instance of ClassA every time in ClassC.

为什么不使用@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;
    }
}

The rest of the class implementations remain the same. I have included objA and objB in the implementation for some clarity on injecting constructor arguments.

其余的类实现保持不变。为了清楚注入构造函数参数,我在实现中包含了objA和objB。

So the main method gets a spring bean of classC and calls doFromAbove() method. This in turn calls getNewInstanceOfClassA method which returns a spring bean of type classA with the constructor arguments objA, objB. Since we annotated it as prototype bean, we get a new instance of classA every time this method is invoked. You don't need to implement getNewInstanceOfClassA method. Spring adds it's own code during the runtime.

因此main方法获取classC的spring bean并调用doFromAbove()方法。这反过来调用getNewInstanceOfClassA方法,该方法返回类型为classA的spring bean,其构造函数参数为objA,objB。由于我们将其注释为原型bean,因此每次调用此方法时都会得到一个新的classA实例。您不需要实现getNewInstanceOfClassA方法。 Spring在运行时添加了自己的代码。

Essentially your problem boils down to injecting a prototype bean in a singleton bean. Lookup annotation is the best way to solve that issue.

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