IoC / DI 特别是 Spring:没有 setter/constructor 的自动装配属性

IoC / DI especially Spring: Autowire properties without setter/constructor

我有一个我自己无法回答的问题 - 至少不是很好。

想象以下代码:

@Service
public class ServiceA {
    public void doService() {
        System.out.println("Doing ServiceA");
    }
}

@Service
public class ServiceB {

    @Autowired
    ServiceA serviceA;

    public void doService() {
        serviceA.doService();
    }
}

它有效,但它被认为是不好的做法吗?如果您想解耦这些 classes 或测试它们,您将无法手动设置依赖项。

另外,Spring 是如何处理的?是否创建了一个代理 class 并为 属性 添加了构造函数?

如果你不喜欢自动装配(我也是)。您可以使用构造函数依赖注入。

你不应该对 class byt 接口使用 dependy,spring 将服务器正确实现(解耦)

public interface ServiceA {
   public void doService();
}

@Service
public class ServiceAImpl implement ServiceA {
    public void doService() {
        System.out.println("Doing ServiceA");
    }
}

@Service
public class ServiceBImpl implements ServiceB {

    private final ServiceA serviceA;

    public ServiceBImpl(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doService() {
      serviceA.doService();
    }
}

这是否是一种不好的做法取决于您编写此代码的时代。在 EJB 时代,这是一个最佳实践,容器为您提供了生命周期的所有功能,即使在 Spring 中,它也很好,即使在 Spring 中,这是非常严格的模型 java config 或 xml 是更灵活的解决方案。

然而在 20xx 时代,尤其是在 TDD 和敏捷模型中,我可以说这是一个真正的 BAD PRACTICE。这些 bean 无法在 Spring 容器之外进行测试,即使在编译时,注释也会在 Spring 处耦合你。更好的解决方案可能是如下代码

class ServiceA {

    public void doService() {
        System.out.println("Doing ServiceA");
    }
}

class ServiceB {

    private final ServiceA serviceA;

    ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doService() {
        serviceA.doService();
    }
}

@Configuration
class ServiceConfig{

    @Bean
    public ServiceA serviceA(){
        return new ServiceA();
    }   

    @Bean
    public ServiceB serviceB(ServiceA serviceA){
        return new ServiceB(serviceA);
    }
}

这样,ServiceA 和 ServiceB classes 是两个完全 Spring 免费的 bean,特别是对于业务逻辑,这是最佳实践,bean 是可测试的,因为我们的依赖关系是显式的。

想象一下提供 ServiceB 的测试 class 以这种方式编写代码,您可以存根或模拟 serviceA 依赖项,并且可以单独测试 ServiceB bean。

对于代理的故事不用担心,因为我们提供了一个配置 class ServiceA 和 ServiceB 是两个 bean,就像注释的 class,Spring 管理一个 java 配置 bean 就像一个带注释的 bean。不同之处在于,现在我们可以受益于显式组合,并且可以提供更灵活的配置方案。我们可以再次受益于 Spring 的神奇 aop,因为正如之前所说,在 Java 配置中配置的 Spring bean 完全等同于带注释的 bean。

我建议您像示例一样使用 java 配置。

希望对您有所帮助

更新: 回复: 另外,Spring 是如何处理的?是否创建了一个代理 class 并为 属性 添加了构造函数?

我可以说:使用组件扫描功能,让我们说 @ComponentScan、spring 找到所有带有 @Component、@Service、@Repository 等原型注解的 bean这个注释中的一些是有用的,因为触发一些功能,例如@Repository,如果我们实现一个 JPA 存储库并注册一个 PersistanceExceptionTraslatorPostProcessor,它在 DataAccessException 层次结构的 Exception 中转换 SQL 本机异常,但其他注释只是一种方式例如注册一个带有注解@Component 的spring bean,@Service。

Spring 通过反射创建 bean 并注入用@Autowired 注释的字段,如果您通过反射在字段上使用@Autowired 直接设置该字段,否则使用 setter,在 xml 例如 setter 或构造函数是必需的。

如果你有两个构造函数,它是透明的,Spring 将使用空构造函数,然后通过反射提供@Autowired 属性,你甚至可以像下面这样:

@Service
class ServiceA {

    public void doService() {
        System.out.println("Doing ServiceA");
    }
}

@Service
class ServiceB {

    private ServiceA serviceA;

    public ServiceB() {
    }

    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doService() {
        serviceA.doService();
    }
}

在这种情况下,spring 认识到它使用带注释的构造函数和 @Autowired 来创建 bean 并提供依赖项。无论如何,最佳实践绝对是我回答中的第一段代码。它在依赖项中是明确的,在您的业务代码库中 Spring 免费