junit 自动配置具有多个实现的接口的 Spring Boot

hyrbngr7  于 2022-11-11  发布在  Spring
关注(0)|答案(7)|浏览(154)

在普通的Spring中,当我们想要自动连接一个接口时,我们在Spring上下文文件中定义它的实现。
1.春 Boot 呢?
1.我们怎样才能做到这一点?
目前我们只自动连接不是接口的类。
这个问题的另一部分是关于在Sping Boot 项目中使用Junit类中的类。
如果我们想使用一个CalendarUtil,例如,如果我们自动连接CalendarUtil,它将抛出一个空指针异常。在这种情况下,我们该怎么办?我只是暂时使用“new”初始化...

1cklez4t

1cklez4t1#

使用@Qualifier注解用于区分相同接口的bean
查看Sping Boot 文档
此外,要注入同一接口的所有bean,只需 autowireList of interface
(The与Spring / Sping 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

6bc51xsx

6bc51xsx2#

您还可以通过为它指定实现的名称来使它工作。
例如:

@Autowired
MyService firstService;

@Autowired
MyService secondService;
cbeh67ev

cbeh67ev3#

假设您有GreetingService

public interface GreetingService {
    void doGreetings();
}

您有两个实施HelloService

@Service
@Slf4j
public class HelloService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hello world!");       
    }
}

HiService的值

@Slf4j
@Service
public class HiService implements GreetingService{
    @Override
    public void doGreetings() {
        log.info("Hi world!");
    }
}

然后,您有另一个接口,即BusinessService,用于调用一些业务

public interface BusinessService {
    void doGreetings();
}

有一些方法可以做到这一点

**#1.**使用@Autowired

@Component
public class BusinessServiceImpl implements BusinessService{

    @Autowired
    private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.

    @Autowired
    private GreetingService helloService;

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

如果您需要更改实现Bean名称,请参考其他答案,方法是为Bean设置名称,例如@Service("myCustomName")并应用@Qualifier("myCustomName")

**#2.**您还可以使用构造函数注入

@Component
public class BusinessServiceImpl implements BusinessService {

    private final GreetingService hiService;

    private final GreetingService helloService;

    public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
        this.hiService = hiService;
        this.helloService = helloService;
    }

    @Override
    public void doGreetings() {
        hiService.doGreetings();
        helloService.doGreetings();
    }

}

这可

public BusinessServiceImpl(@Qualifier("hiService") GreetingService hiService, @Qualifier("helloService") GreetingService helloService)

但我使用的是Sping Boot 2.6.5

public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)

工作正常,因为Spring会自动为我们获取名称。

#3。您也可以使用Map来执行此操作

@Component
@RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {

    private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key

    @Override
    public void doGreetings() {
        servicesMap.get("hiService").doGreetings();
        servicesMap.get("helloService").doGreetings();
    }

}

如果你运行所有的服务,List也能正常工作。但是如果你想得到一些特定的实现,你需要为它定义一个名字或类似的东西。我的参考is here
对于这一个,我使用来自Lombok岛的@RequiredArgsConstructor

tuwxkamq

tuwxkamq4#

正如注解中提到的,通过使用@Qualifier注解,您可以区分文档中描述的不同实现。
对于测试,您也可以使用执行相同的操作。例如:

@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....
    }
}
2admgd59

2admgd595#

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

  1. Spring @Primary annotation
    总之,它告诉我们的Spring应用程序,每当我们试图自动连接我们的接口时,要使用用@Primary注解标记的特定实现。它就像一个默认的自动连接设置。一个接口的每个实现簇只能使用一次。→ @Primary Docs
  2. Spring @Qualifier annotation
    这个Spring注解给了我们更多的控制权来选择具体的实现,无论我们在什么地方定义一个接口的引用来选择它的选项。→ @Qualifier Docs
    有关详细信息,请访问其文档的链接。
cwdobuhd

cwdobuhd6#

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;
    }
}
  • 再服务
@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");
    }
}
xn1cxnb4

xn1cxnb47#

如果我们有同一个接口的多个实现,Spring需要知道它应该自动连接到一个类中。下面是一个 * Employee的移动的号码和电子邮件地址的验证器 * 的简单示例:-

员工类别:

public class Employee {

     private String mobileNumber;
     private String emailAddress;

     ...

     /**Getters & Setters omitted**/
}

接口员工验证器:

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

    }   
}

相关问题