【问题标题】:Spring boot autowiring an interface with multiple implementationsSpring Boot自动装配具有多个实现的接口
【发布时间】:2019-01-16 20:37:54
【问题描述】:

在普通的 Spring 中,当我们想要自动装配一个接口时,我们在 Spring 上下文文件中定义它的实现。

  1. Spring Boot 呢?
  2. 我们怎样才能做到这一点?

目前我们只自动装配非接口类。

这个问题的另一部分是关于在 Spring Boot 项目中使用 Junit 类中的类。

例如,如果我们想使用 CalendarUtil,如果我们自动装配 CalendarUtil,它将抛出空指针异常。在这种情况下我们能做什么?我现在只是使用“new”初始化...

【问题讨论】:

  • 与 Spring 中的方法相同(提示:Spring Boot 实际上是 Spring):您可以使用注释定义 bean,或者使用 Bean-annotated 方法,如 Spring 文档中所述,并且你自动装配这个 bean 实现的接口。如果你展示代码而不是模糊地描述它,一切都会变得更容易。
  • 例如,如果我们有一个名为 ChargeInterface 的接口,它有两个实现:ChargeInDollars 和 ChrageInEuro,并且您有另一个包含特定业务逻辑的类 AmericanStoreManager,它应该使用 ChargeInterface 的 ChargeInDollars 实现。您定义了一个自动装配的 ChargeInterface,但您如何决定使用什么实现?
  • 使用限定符,与 Spring 中完全相同,因为 Spring-boot 是 Spring。因此,请阅读 Spring 文档,并查找“Qualifier”。或者,既然你想要一个特定的实现,你可以简单地自动装配类,而不是接口。

标签: spring spring-boot junit autowired


【解决方案1】:

使用@Qualifier注解用于区分同一接口的bean
看看 Spring Boot documentation
此外,要注入同一接口的所有 bean,只需 autowire List of interface
(在 Spring/Spring Boot/SpringBootTest 中的方法相同)
示例如下:

@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

public interface MyService {

    void doWork();

}

@Service
@Qualifier("firstService")
public static class FirstServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("firstService work");
    }

}

@Service
@Qualifier("secondService")
public static class SecondServiceImpl implements MyService {

    @Override
    public void doWork() {
        System.out.println("secondService work");
    }

}

@Component
public static class FirstManager {

    private final MyService myService;

    @Autowired // inject FirstServiceImpl
    public FirstManager(@Qualifier("firstService") MyService myService) {
        this.myService = myService;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("firstManager start work");
        myService.doWork();
    }

}

@Component
public static class SecondManager {

    private final List<MyService> myServices;

    @Autowired // inject MyService all implementations
    public SecondManager(List<MyService> myServices) {
        this.myServices = myServices;
    }

    @PostConstruct
    public void startWork() {
        System.out.println("secondManager start work");
        myServices.forEach(MyService::doWork);
    }

}

}

对于你问题的第二部分,看看这个有用的答案first / second

【讨论】:

  • 对于单元测试,我使用了以下注释:@SpringBootTest(classes=CalendarUtil.class) @RunWith(SpringRunner.class) 然后我自动装配了类。
  • 您对@Autowired 不再有问题了吗?
  • 是的,当我们添加spring boot测试并使用注解运行时,我们可以成功使用autowired注解
  • 每当我在 Junit 中与 Mocking 一起使用上述内容时,自动连线再次失败。对此有何建议?问题是我不想模拟所有的类,我希望一些类被模拟而另一些类被自动装配。我怎样才能做到这一点?
  • 我设法在 Junit 中使用 @Spy 而不是 Autowired 作为注释来做到这一点,并在不使用 Spring 运行注释的情况下自己初始化类。
【解决方案2】:

您也可以通过给它提供实现的名称来使其工作。

例如:

@Autowired
MyService firstService;

@Autowired
MyService secondService;

【讨论】:

  • 这很重要!
  • 这种方法通过使用限定符和 Autowired 注释对我有用。我也在文档docs.spring.io/spring/docs/4.3.12.RELEASE/… 中看到了相同的内容。请举一个例子,如果它对你有用,如上所述。谢谢。
  • 这样更好
  • 这很好,但这真的是依赖注入吗?因为我们将变量与服务名称本身耦合。
  • 非常小。谢谢!
【解决方案3】:

如 cmets 中所述,通过使用 @Qualifier 注解,您可以区分 docs 中描述的不同实现。

对于测试,您也可以使用同样的方法。例如:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyClassTests {

        @Autowired
        private MyClass testClass;
        @MockBean
        @Qualifier("default")
        private MyImplementation defaultImpl;

        @Test
        public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
    // your test here....
    }
}

【讨论】:

    【解决方案4】:

    如果我们有同一个接口的多个实现,Spring 需要知道应该将哪一个自动装配到一个类中。这是一个员工手机号码和电子邮件地址验证器的简单示例:-

    员工等级:

    公共类员工{

     private String mobileNumber;
     private String emailAddress;
     
     ...
     
     /** Getters & Setters omitted **/
    

    }

    接口 EmployeeValidator:

    public interface EmployeeValidator {
        public Employee validate(Employee employee);
    }
    

    手机号码验证器的第一个实现类:

    @Component(value="EmployeeMobileValidator")
    public class EmployeeMobileValidator implements EmployeeValidator {
        @Override
        public Employee validate(Employee employee) {
            //Mobile number Validation logic goes here.
        }
    }
    

    电子邮件地址验证器的第二个实现类:

    @Component(value="EmployeeEmailValidator")
    public class EmployeeEmailValidator implements EmployeeValidator {
        @Override
        public Employee validate(Employee employee) {
            //Email address validation logic goes here.
        }
    }
    

    我们现在可以将上面的这些验证器单独自动装配到一个类中。

    员工服务接口:

    public interface EmployeeService {
        public void handleEmployee(Employee employee);
    }
    

    员工服务实现类

    @Service
    public class EmployeeServiceImpl implements EmployeeService {
        
        /** Autowire validators individually **/
        
        @Autowired
        @Qualifier("EmployeeMobileValidator")    // Autowired using qualifier for mobile validator
        private EmployeeValidator mobileValidator;
    
        @Autowired
        @Qualifier("EmployeeEmailValidator")    // Autowired using qualifier for email valodator
        private EmployeeValidator emailValidator;
    
        @Override
        public void handleEmployee(Employee employee) {
        
            /**You can use just one instance if you need**/
            
            employee = mobileValidator.validate(employee);        
            
            
        }   
    }
    

    【讨论】:

      【解决方案5】:

      当我们对具有多个实现的接口进行自动装配时,有两种方法:

      1. Spring @Primary annotation

      简而言之,每当我们尝试自动装配接口以使用标有@Primary 注释的特定实现时,它都会告诉我们的 Spring 应用程序。它就像一个默认的自动装配设置。每个接口的实现集群只能使用一次。 →@Primary Docs

      1. Spring @Qualifier annotation

      这个 Spring 注解让我们可以更好地控制选择确切的实现,只要我们定义对接口的引用,就可以在其选项中进行选择。 →@Qualifier Docs

      有关更多详细信息,请访问其文档的链接。

      【讨论】:

        【解决方案6】:
        public interface SomeInterfaces {
        
            void send(String message);
        
            String getType();
        }
        
        • kafka 服务
        @Component
        public class SomeInterfacesKafkaImpl implements SomeInterfaces {
        
            private final String type = "kafka";
        
            @Override
            public void send(String message) {
                System.out.println(message + "through Kafka");
            }
        
            @Override
            public String getType() {
                return this.type;
            }
        }
        
        • redis 服务
        @Component
        public class SomeInterfacesRedisImpl implements SomeInterfaces {
        
            private final String type = "redis";
        
            @Override
            public void send(String message) {
                System.out.println(message + "through Redis");
            }
        
            @Override
            public String getType() {
                return this.type;
            }
        }
        
        • 主人
        @Component
        public class SomeInterfacesMaster {
        
            private final Set<SomeInterfaces> someInterfaces;
        
        
            public SomeInterfacesMaster(Set<SomeInterfaces> someInterfaces) {
                this.someInterfaces = someInterfaces;
            }
        
            public void sendMaster(String type){
        
                Optional<SomeInterfaces> service =
                        someInterfaces
                                .stream()
                                .filter(service ->
                                        service.getType().equals(type)
                                )
                                .findFirst();
                SomeInterfaces someService =
                        service
                                .orElseThrow(() -> new RuntimeException("There is not such way for sending messages."));
        
                someService .send(" Hello. It is a letter to ....");
        
            }
        }
        
        • 测试
        
        @SpringBootTest
        public class MultiImplementation {
        }
        
        @TestInstance(TestInstance.Lifecycle.PER_CLASS)
        class SomeInterfacesMasterTest extends MultiImplementation {
        
            @Autowired
            private SomeInterfacesMaster someInterfacesMaster;
        
            @Test
            void sendMaster() {
                someInterfacesMaster.sendMaster("kafka");
            }
        }
        
        

        因此,根据Open/Closed原则,我们只需要添加一个实现而不破坏现有代码。

        @Component
        public class SomeInterfacesRabbitImpl implements SomeInterfaces {
        
           private final String type = "rabbit";
        
            @Override
            public void send(String message) {
                System.out.println(message + "through Rabbit");
            }
        
            @Override
            public String getType() {
                return this.type;
            }
        }
        
        
        • 测试-v2
        @TestInstance(TestInstance.Lifecycle.PER_CLASS)
        class SomeInterfacesMasterTestV2 extends MultiImplementation {
        
            @Autowired
            private SomeInterfacesMaster someInterfacesMaster;
        
        
            @Test
            void sendMasterV2() {
                someInterfacesMaster.sendMaster("rabbit");
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-07-29
          • 2020-11-15
          • 1970-01-01
          • 2015-03-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多