1. Description of the problem

In the actual system application development I often encounter such a class of requirements, and I believe that you will often encounter in your work.

  • The same system is deployed in multiple provinces.
  • A business in Beijing is one way of implementation, based on the needs of Beijing users.
  • The same business is implemented in Shanghai in another way, much the same way as in Beijing.

When we encounter such a requirement, we usually define an interface for the business implementation, e.g.

1
2
3
public interface IDemoService {
  public void doSomething();
}

Implemented this way in the Beijing environment, e.g.

1
2
3
4
5
@Component
public class DemoServiceBeijing implements IDemoService {
  @Override
  public void doSomething() {System.out.println("北京的业务实现");}
}

Implemented this way in the Shanghai environment, e.g.

1
2
3
4
5
@Component
public class DemoServiceShanghai implements IDemoService {
  @Override
  public void doSomething() {System.out.println("上海的业务实现");}
}

Then we write a mock business test case.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@SpringBootTest
class DemoApplicationTests {
    //这里注入的demoService是DemoServiceShanghai,还是DemoServiceBeijing?
    @Resource
    IDemoService demoService;  
    @Test
    void testDemoService() {
        demoService.doSomething();
    }
}

When we execute this test case an exception must be thrown. This is because Spring has found two IDemoService implementation classes. It does not know which implementation class to instantiate as the actual business processing bean for IDemoService. of course we expect the following state.

  • When deploying the system in Beijing, use DemoServiceBeijing as the IDemoService implementation class to complete the dependency injection
  • When deploying the system in Shanghai, use DemoServiceShanghai as the implementation class of IDemoService to complete the dependency injection.

2. Relatively low-level solutions

In the face of the above requirements, let’s start with a few relatively low-level solutions, which can achieve our desired state, but are not friendly enough for operation and maintenance.

2.1. Option 1: Use @Primary annotation

If you deploy the system in Beijing, add @Primary to the DemoServiceBeijing class. The purpose of this annotation is to force a single implementation class to be chosen from multiple implementations, and if Spring doesn’t know which one to choose, we tell it the default one.

1
2
3
@Primary
@Component
public class DemoServiceBeijing implements IDemoService ..

2.2. Option 2: Use the @Resource annotation

Because the Resource annotation uses names for dependency injection by default. So what is used is the DemoServiceBeijing implementation class (the variable name is explicitly called demoServiceBeijing, with lowercase initials).

1
2
3
@Resource
IDemoService demoServiceBeijing;  //这里的变量名称指定了bean名称
//IDemoService demoService;  被替换掉

or

1
2
@Resource(name = "demoServiceBeijing")  //使用resource注解明确指定名称
IDemoService demoService;

2.3. Option 3: Use the @Qualifier annotation

For the same reason as above, use the @Qualifier annotation to specify the name of the bean for dependency injection.

1
2
3
@Qualifier("demoServiceBeijing")  //使用Qualifier注解明确指定名称
@Resource
IDemoService demoService;

The three solutions mentioned above can solve the problem of using different interface implementation classes for different deployment environments to accomplish dependency injection. But this is not good, because once we change the deployment environment from beijing to shanghai, we need to change the location or content of the above annotations all over again (all the implementation class code should be changed).

3. A relatively advanced solution

We propose a further expectation: that is, the operation of switching deployment environments can be done by modifying only one configuration. For example.

1
2
deploy:
  province: beijing

When we want to switch the deployment environment from Beijing to Shanghai, we just need to change beijing to shanghai in the above configuration, how to achieve this?

  • Add the ConditionalOnProperty annotation to the Beijing implementation class, with the value of havingValue as beijing.

    1
    2
    3
    
    @Component
    @ConditionalOnProperty(value="deploy.province",havingValue = "beijing")
    public class DemoServiceBeijing implements IDemoService {
    
  • Add the ConditionalOnProperty annotation to the Shanghai implementation class, with the value of havingValue as shanghai.

    1
    2
    3
    
    @Component
    @ConditionalOnProperty(value="deploy.province",havingValue = "shanghai")
    public class DemoServiceShanghai implements IDemoService {
    

What the ConditionalOnProperty annotation does here is: it reads the configuration file and finds deploy.since, and matches the value of that configuration with havingValue, and instantiates whichever class matches as the implementation bean of the interface to inject into the Spring container (of course the injection process needs to be with the @Component annotation).

Reference https://blog.csdn.net/hanxiaotongtong/article/details/123252830