springboot:@ConditionalOnProperty根據不同配置注入不同實現的bean

一、引言

在開發中經常會碰到這樣的情形,一個接口會有不同的實現,但在開發中都是基於接口的注入,那麼怎麼根據不同的需求注入不同的類型就是一個值得考慮的問題。在注入屬性時常用的兩個註解是@Autowired和@Resource,使用它們便可以實現,同時spring提供了很多@ConditionalXXX的註解,可以很好的完成上述功能;

二、代碼演示

1、問題代碼描述

使用代碼的方式描述下上面提到的問題,後面給出解決方案。

controller類,TestConditionalOnProperty.java

package com.atssg.controller;

import com.atssg.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestConditionalOnProperty {

//注入MyService @Autowired
private MyService myService; @GetMapping("/test/test1") public void test(){ myService.test(); } }

下面是MyService接口,MyService.java

package com.atssg.service;

public interface MyService {
     void test();
}

下面是兩個實現類,MyServiceImpl.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;


@Service
@Slf4j
public class MyServiceImpl implements MyService {

    @Override
    public void test() {
        log.info("I am Myservice");
    }
}

下面是MyServiceImpl2.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class MyServiceImpl2 implements MyService {

    @Override
    public void test() {
        log.info("I am MyServiceImpl2");
    }
}

程序啓動報錯,

Description:

Field myService in com.atssg.controller.TestConditionalOnProperty required a single bean, but 2 were found:
    - myServiceImpl: defined in file [D:\code\cloud2020\cloud-sync-7002\target\classes\com\atssg\service\impl\MyServiceImpl.class]
    - myServiceImpl2: defined in file [D:\code\cloud2020\cloud-sync-7002\target\classes\com\atssg\service\impl\MyServiceImpl2.class]

大體意思是TestConditionalOnProperty需要一個單例bean,但是發現了兩個,也就是MyServiceImpl和MyServicImpl2。那如何才能注入一個那。

2、解決方案

2.1、@Qualifier

@Autowired默認條件下會按照id注入,找不到id會按照類型注入,上面的錯誤便是這種情況,我們可以給@Autowired指定要注入的id即可,使用@Qualifier可以實現指定id。

TestConditionalOnProperty改動如下,

package com.atssg.controller;

import com.atssg.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestConditionalOnProperty {

    @Qualifier("myServiceImpl2")
    @Autowired
    private MyService myServiceImpl;

    @GetMapping("/test/test1")
    public void test(){

        myServiceImpl.test();
    }

}

同理,兩個MyService的實現類也要指定生成bean的id。默認情況下是其類名首字母小寫,如MyServiceImpl如果不指定生成id則爲myServiceImpl。修改如下

MyServiceImpl.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;


@Service("myServiceImpl")
@Slf4j
public class MyServiceImpl implements MyService {

    @Override
    public void test() {
        log.info("I am Myservice");
    }
}

MyServiceImpl2.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service("myServiceImpl2")
@Slf4j
public class MyServiceImpl2 implements MyService {

    @Override
    public void test() {
        log.info("I am MyServiceImpl2");
    }
}

@Service可以指定value值,也就是指定生成bean的id值。

測試結果如下,

 從上面可以看出已經可以實現注入一個MyService的實現類,但是這種方式有一個弊端,那就是不夠靈活,雖然實現了加載一個實現類,但是每次都需要修改代碼,而且有可能會修改錯誤,而且是硬編碼。

其實還有另外一種方式,也是使用@Autowired,只不過被@Autowired註解修飾的變量名必須是要注入的bean的id,如

 這裏注入的是myServiceImpl,也就是MyServiceImpl的實現類,測試結果如下,

 這樣便實現了注入一個bean的目的,但這種方式和上面的方式是一樣的,不夠靈活且是硬編碼。下面看springboot爲我們提供的另外一種方式。

2.2、@ConditionalOnProperty

@ConditionalOnProperty註解是springboot開發的衆多@ConditionalXX註解中的一個,根據properties文件中的屬性值來決定注入哪一個。

先在applicaiton.properties文件中定義一個變量

myService=service1

TestConditionalOnProperty中使用@Resource進行注入

package com.atssg.controller;

import com.atssg.service.MyService;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestConditionalOnProperty {


    @Resource
    private MyService myService;

    @GetMapping("/test/test1")
    public void test(){

        myService.test();
    }

}

MyService的兩個實現類,MyServiceImpl.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(name = "myService",havingValue = "service1")
@Slf4j
public class MyServiceImpl implements MyService {

    @Override
    public void test() {
        log.info("I am Myservice");
    }
}

MyServiceImpl2.java

package com.atssg.service.impl;

import com.atssg.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(name = "myService",havingValue = "service2")
@Slf4j
public class MyServiceImpl2 implements MyService {

    @Override
    public void test() {
        log.info("I am MyServiceImpl2");
    }
}

每個實現類中均使用了@ConditionalOnProperty註解,並指定了name和havingValue屬性,name指定applicaiton.properties文件中的屬性名,havingValue指定了屬性值,在上面的配置的是service1,即調用MyServiceImpl中的test()方法,測試如下

2021-09-20 19:18:44.499  INFO 23892 --- [)-192.168.117.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2021-09-20 19:23:38.576  INFO 23892 --- [nio-8080-exec-1] com.atssg.service.impl.MyServiceImpl     : I am Myservice

從測試結果來看,調用的的確是MyServiceImpl中的test()方法,那麼改成service2的結果如下,

 這樣只需要修改配置文件便可以實現動態加載不同的實現類。

三、總結

要想實現加載不同的實現類,還有其他的方式,這裏不一一列舉,本文旨在介紹@ConditionalOnProperty註解的使用。@ConditionalOnProperty註解可以實現根據配置文件中的值注入不同的實現類。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章