2017-07-07 44 views
1

这有点难以解释...希望问题的领域一点都不含糊......如何指示Spring来自动装配在紧密耦合的对象链

你可以看看代码的想法...

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(在同一封装的其他Java类文件)

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

当我执行的主要方法,在ClassA的自动装配Autowired场“ABCD”没有得到注入,导致NullPointerException

当我宣布ClassA作为它仅@Component并得到它的豆...间接自动装配没有发生

我应该从ClassC去耦ClassA并使一切松耦合?

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

注意 请不要告诉我使用的ApplicationContext在ClassC创造ClassA豆。

任何Spring Geek谁可以找到答案?

+2

我认为在这种情况下Spring 101可能是合适的。因此,让我们从那里开始 - 如果您在应用程序中的任何位置使用'new' **,那么您的错误是__错误。在Spring应用程序中,只有**的地方应该看到'new',它在配置类中向Spring解释如何创建bean。它被称为_Dependency Injection_框架 - 您绝对必须** _inject dependencies_才能使用它。 –

+1

此外,任何告诉你在'ClassC'中使用'ApplicationContext'来创建'ClassA'_'的bean的人也不理解DI。你永远不应该这样做。正确的做法是注入'Provider '并在需要时调用'get'。然后你告诉Spring'ClassA'是'PROTOTYPE'的作用域,所以每次创建一个新的实例。正如我所说 - 春天101. –

+0

@BoristheSpider,我得到你的漂移......问题是我正在经历的是将传统的Java项目转换为Spring ...所以我想也许我们可以混合匹配紧密耦合的对象松散耦合的弹簧注入对象来完成春季转换/迁移过程,代码修改更少...我所描述的例子只有一个紧耦合......在一个层面上...应用Spring正确的方式似乎是一个很大的现有项目的痛苦 –

回答

1

经过激烈的谷歌搜索,Spring Documentation Skimming,我确信有更多可能的解决方案来解决这个难题。

可能的解决方案:

  1. 使用JSR 330 Provider<T>@Autowired
  2. 使用​​与初始化代码在getObject()(但由工厂返回的豆是不是Spring管理和由此的任何自动装配Autowired场原型类将返回NullPointerException)
  3. 使用查找方法注入(包括CGLIB库)(我不喜欢 这个,因为它修改编译的co德和听起来像它创建bean对象的抽象类 )(Java的纯度受到侵犯)
  4. 实现ApplicationContextAware接口和获取上下文 对象(不推荐)
  5. 的Autowire ApplicationContext和使用getBean()(不推荐)
上述中

最巧妙的办法就是JSR330提供

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

现在,它完美的作品和初始化的对象是Spring管理太...

注: JSR330的依赖在Maven的pom.xml中进行设置

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

完美 - 在研究上做得很好。这是完全正确的方法。你不应该需要投射,因为'Provider'是通用的。 –

1

的问题是在ClassC

ClassA cls = new ClassA(); 

如果调用的ClassA这样的构造,春天不会做它的魔力。如果需要带注入字段的ClassA实例,请向Spring请求一个实例(使用注入或getBean())。

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

+1

Ctor injection每次调用doFromAbove时都不会创建新的实例,除非你进入范围代理的领域。 –

+0

与野外注射不一样吗? –

+0

是的,但我的观点是“_to解决这些问题,我建议使用构造函数注入_”不是一个有用的声明。 –

0

豆(无论是通过XML或类似@Component)注解是Spring容器内声明Spring管理 - Spring会照顾他们的,将确保当您通过ApplicationContext.getBean(),他们的依赖是要求他们,他们存在也注入。

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

有些混乱可能与您注释的类与Spring的注解干 - 但它确实对象(实例),它们实际上是在Java中使用;即使该类被注释了,注解也只会应用于由Spring创建和管理的实例。

0

如果启用加载时织入,并且使对象的每个新实例成为托管弹簧组件,那么您可以使用@Configurable,以便使用新的语句。

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

+0

小心给我一个'@ Configurable'和FactoryBeans的简单例子吗?如果可能的话,请使用问题中的示例代码,这样可以使任何人都受益。这可能是我一直在寻找的答案......但我需要确保......如果这样做,我会奖励+50赏金 –

+0

这是一个独立的基本命令行java程序,带有'public静态无效主'thingy ...将LTW甚至在它上面工作?我认为它的工作原理只有当它的一个web应用程序加载在像tomcat这样的容器中时......但是我可能是错的......启发请... –

0

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

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

所以主要方法获得classC的spring bean并调用doFromAbove()方法。这又调用getNewInstanceOfClassA方法,该方法返回类型为classA的spring bean,其构造函数参数为objA,objB。由于我们将其注释为prototype bean,因此每次调用此方法时都会得到一个新的classA实例。您无需实施getNewInstanceOfClassA方法。 Spring在运行时添加它自己的代码。

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

+0

除了@Lookup(正如我在回答点编号(3)中提到的)使用CGLib来更改Java中间字节码的子类。(Java的纯度被违反)现在你可能会觉得可以扩展java类并返回一个bean,它是我想要的子类。但它感觉有点不对 –

+0

Spring扩展了您用子类创建的Bean类并覆盖了该方法......它看起来不正确... –

+0

我不确定你的意思是否违反了Java的纯度。如果CGLib添加字节码,你有什么缺点?您可以使用Lookup注释以更简洁明了的方式做你想做的事(获取原型bean)。Spring在许多其他地方使用CGLib。事务注释,spring-data-jpa,配置注释和许多其他弹簧功能在内部使用CGLib或JDK动态代理。 – yaswanth