天天看點

Spring服務定制問題總述使用@Profile注解多服務共存定制使用BFP來優雅定制服務實作總結

問題總述

​ 我們都知道如果使用Spring來進行bean管理的時候。如果同一個接口的實作類存在兩個,直接使用

@Autowired

注解來實作bean注入,會在啟動的時候報異常。我們通常的做法是使用

@Resource

注解來執行bean的名稱。不過通過

@Resource

注解類似于寫死的方式,如果我們想修改接口的具體實作,必須修改代碼。假設我們環境中針對所有接口,都有兩套實作,一套在測試環境中使用,一個在生産環境中使用。那麼當切換環境的時候,所有接口使用

@Resource

注入的地方都需要修改bean名稱。

使用@Profile注解

​ 針對前面兩套環境的情況,我們可以使用

@Profile

注解來輕松解決。具體代碼示例如下:

public interface HelloService {
    
    void saySomething(String msg);
}

@Profile("kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }
}

@Profile("kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
@ActiveProfiles(profiles={"kind1"})     // 啟用kind1注入的bean
public class HelloServiceTest {
    
    @Autowired
    private HelloService helloService;
    
    @Test
    public void testServiceSelector() {
        helloService.saySomething("I Love U!");
    }
}

           

​ 最終輸出的結果為:

HelloServiceImpl1 say:I Love U!

多服務共存定制

​ 考慮這樣一種情況,假設

HelloService

是針對全國通用的服務,對于不同的省市使用不同的方言來

saySomething

.假設系統都是使用一套,那麼在使用Spring進行bean管理的時候要麼針對不同的省市隻打包對應的目錄下的

HelloService

實作,要麼同前面一樣使用

@Profile

注解來區分不同的實作類型,最後針對不同的省市修改

@ActiveProfiles

的具體值。這兩種方法都需要針對不同的地區來進行相應的代碼修改,然後再重新打包。考慮到全國幾百個市,如果一次統一全部更新,估計光打包可能都要打包一天。。。

​ 更進一步的情況,東北三省大部分城市都是說國語,那麼實際上隻要使用一個預設的實作類就行了。換句話将,現在想實作這樣一種定制: 每個接口有一個預設實作,不同的城市有一個定制實作的類型碼。如果根據定制類型碼能夠找到對應的接口實作,則使用該實作類。如果未找到,則使用預設的實作類。

​ 很顯然,上面要實作的是在代碼運作過程中動态判斷最後接口的具體實作類。其中定制的類型碼可以通過資料庫或者配置檔案的方式指定,在代碼運作的過程中根據定制碼去擷取對應的服務實作。

​ 該方案的一種實作如下:

public interface ServiceSelector {
    /**
     * 得到定制碼
     * @return
     */
    String getCustCode();
}

public interface HelloService extends ServiceSelector {
    
    void saySomething(String msg);
}

public abstract class ServiceProvider <T, S extends T> implements BeanFactoryAware  {

    private ConfigurableListableBeanFactory beanFactory;
    
    private Map<String, T> serviceMap;
    
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof DefaultListableBeanFactory) {  
            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;  
        }  
    }
    
    @SuppressWarnings({"unchecked", "restriction"})
    @PostConstruct
    public void init(){
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type[] types = pt.getActualTypeArguments();
        
        Class<T> interfaceClazz = (Class<T>)types[0];
        // Class<S> defaultImplClazz = (Class<S>)types[1];  // defaultImplClazz為預設實作
        
        Map<String, T> serviceBeanMap = beanFactory.getBeansOfType(interfaceClazz);
        serviceMap = new HashMap<String , T>(serviceBeanMap.size());
        for (T processor : serviceBeanMap.values()) {
            if (!(processor instanceof ServiceSelector)) {
                // 如果實作類沒有實作OptionalServiceSelector接口,直接報錯
                throw new RuntimeException("可選服務必須實作ServiceSelector接口!");
            }
            
            // 如果已經存在相同定制碼的服務也直接抛異常
            ServiceSelector selector = (ServiceSelector)processor;
            if (null != serviceMap.get(selector.getCustCode())) {
                throw new RuntimeException("已經存在定制碼為【" + selector.getCustCode() + "】的服務");
            }
            
            // 加入Map中
            serviceMap.put(selector.getCustCode(), processor);
        }
    }
    
    public T getService() {
        // 從配置檔案或者資料庫擷取目前省市的定制碼
        String custCode = "kind11";
        if (null != serviceMap.get(custCode)) {
            return serviceMap.get(custCode);
        }
        
        // 如果未找到則使用預設實作
        return serviceMap.get("DEFAULT");
    }
}

@Service
public class DefaultHelloService implements HelloService {

    public String getCustCode() {
        return "DEFAULT";
    }

    public void saySomething(String msg) {
        System.out.println("DefaultHelloService say:" + msg);
    }

}

@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }

    public String getCustCode() {
        return "kind1";
    }
}

@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }

    public String getCustCode() {
        return "kind2";
    }
}

@Service
public class HelloServiceProvider extends ServiceProvider<HelloService, DefaultHelloService> {
    
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
    
    // 注入接口服務提供器,而不是接口
    @Autowired
    private HelloServiceProvider helloServiceProvider;
    
    @Test
    public void testServiceSelector() {
        helloServiceProvider.getService().saySomething("I Love U!");
    }
}
           

​ 上例的最終輸出為:

DefaultHelloService say:I Love U!

使用BFP來優雅定制服務實作

​ 上面的服務定制通過各種繞路實作了服務定制,但是不能看出上面的實作非常不優雅,存在很多問題:

  • 想實作一個接口的定制至少需要新增三個類。定制接口實作

    ServiceSelector

    接口,一個預設接口實作類,一個特定的定制服務實作類
  • 即使最終針對一個省市隻使用一個實作類,在spring初始化的時候也會初始化定制接口的所有實作類,必須通過代碼去判斷針對特定的定制碼是否隻存在一個實作類

​ 那麼針對這種情況,有沒有一個優雅的實作。既能滿足前面所說的業務場景需求,又能夠不初始化多餘的類?當然是有的,其中的一套實作方案如下:

// 定制服務的注解聲明,支援多個定制碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Customized {
    String[] custCodes() default {"DEFAULT"};
}


public interface HelloService {
    void saySomething(String msg);
}

@Component
public class CustomizedServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {

    private static String custCode;

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomizedServiceBeanFactoryPostProcessor.class);

    static {
        Properties properties = new Properties();
        /*try {
            // 讀取配置檔案定制碼
            properties.load(CustomizedServiceBeanFactoryPostProcessor.class.getClassLoader()
                    .getResource("app-config.properties").openStream());
        } catch (Exception e) {
            throw new RuntimeException("讀取配置檔案失敗!", e);
        }*/
        // 這裡假設取預設定制碼
        custCode = properties.getProperty("custCode", "DEFAULT");
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanDefNames = beanFactory.getBeanDefinitionNames();
        if (ArrayUtils.isEmpty(beanDefNames)) {
            return;
        }
        Class<?> beanClass = null;
        BeanDefinition beanDef = null;
        Customized customized = null;
        Set<Class<?>> foundCustomizedServices = new HashSet<Class<?>>();
        Map<String, Class<?>> waitDestroiedBeans = new HashMap<String, Class<?>>();
        String[] defaultCustCodes = {"DEFAULT"};
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (String name : beanDefNames) {
            beanDef = beanFactory.getBeanDefinition(name);
            if (beanDef == null || StringUtils.isEmpty(beanDef.getBeanClassName())) {
                continue;
            }
            try {
                // 加載類得到其上的注解的定義
                beanClass = classLoader.loadClass(beanDef.getBeanClassName());
            } catch (ClassNotFoundException e) {
                // 發生了異常,這裡直接跳過
            }
            if (beanClass == null) {
                continue;
            }
            customized = this.getCustomizedAnnotations(beanClass);

            // 非定制類直接跳過
            if (customized == null) {
                continue;
            }
            if (ArrayUtils.contains(customized.custCodes(), custCode)) {
                foundCustomizedServices.addAll(this.getCustomizedServices(beanClass));
                LOGGER.info("定制碼【{}】下裝載到定制服務實作類【{}】......", custCode, beanClass);
            } else {
                if (!ArrayUtils.isEquals(customized.custCodes(), defaultCustCodes)) {
                    ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(name);
                    LOGGER.info("定制碼【{}】下解除安裝定制服務實作類【{}】......", custCode, beanClass);
                } else {
                    // 預設實作類暫時不知道是否需要銷毀,先暫存
                    waitDestroiedBeans.put(name, beanClass);
                }
            }
        }

        // 沒有需要檢測的是否需要銷毀的預設實作類則直接傳回
        if (MapUtils.isEmpty(waitDestroiedBeans)) {
            return;
        }

        // 看定制服務的預設實作類是否已經找到特定的實作類,如果找到了則需要銷毀預設實作類
        for (Entry<String, Class<?>> entry : waitDestroiedBeans.entrySet()) {
            // 直接繼承定制服務類實作
            if (foundCustomizedServices.contains(entry.getValue())) {
                ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
                LOGGER.info("定制碼【{}】下解除安裝預設定制服務實作類【{}】......", custCode, entry.getValue());
            } else {
                // 通過定制服務接口實作定制
                Set<Class<?>> defaultCustServices = getCustomizedServices(entry.getValue());
                for (Class<?> clazz : defaultCustServices) {
                    if (foundCustomizedServices.contains(clazz)) {
                        ((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
                        LOGGER.info("定制碼【{}】下解除安裝預設定制服務實作類【{}】......", custCode, entry.getValue());
                        break;
                    }
                }
            }
        }
    }

    /**
     * 得到定制類的定制注解(一旦找到立即傳回)
     * 
     * @param clazz
     *            傳入的定制類
     * @return 得到定制類的定制注解
     */
    private Customized getCustomizedAnnotations(Class<? extends Object> clazz) {
        // 遞歸周遊尋找定制注解
        Annotation[] annotations = null;
        Class<?>[] interfaces = null;
        while (clazz != Object.class) {
            // 先直接找該類上的注解
            annotations = clazz.getAnnotations();
            if (annotations != null && annotations.length > 0) {
                for (Annotation one : annotations) {
                    if (one.annotationType() == Customized.class) {
                        return (Customized) one;
                    }
                }
            }

            // 再找該類實作的接口上的注解
            interfaces = clazz.getInterfaces();
            if (interfaces != null && interfaces.length > 0) {
                for (Class<?> intf : interfaces) {
                    annotations = intf.getAnnotations();
                    if (annotations != null && annotations.length > 0) {
                        for (Annotation one : annotations) {
                            if (one.annotationType() == Customized.class) {
                                return (Customized) one;
                            }
                        }
                    }
                }
            }

            // 未找到繼續找父類上的注解
            clazz = clazz.getSuperclass();
        }

        return null;
    }

    /**
     * 得到定制的服務類清單(即有Customized注解的類清單)
     * 
     * @param orginalClass
     *            傳入的原始類
     * @return 定制服務類的清單
     */
    private Set<Class<?>> getCustomizedServices(Class<?> orginalClass) {
        Class<?> class1 = orginalClass;
        Set<Class<?>> customizedInterfaces = new HashSet<Class<?>>();
        Class<?>[] interfaces = null;
        while (class1 != Object.class) {
            // 類也進行判斷,這樣能實作直接不通過接口的,而通過service繼承實作定制服務
            if (class1.getAnnotation(Customized.class) != null) {
                customizedInterfaces.add(class1);
            }

            // 周遊接口,看接口是是定制服務接口
            interfaces = class1.getInterfaces();
            if (interfaces == null || interfaces.length == 0) {
                class1 = class1.getSuperclass();
                continue;
            }

            // 接口的實作隻能有一個,是以一旦找到帶有注解的實作類的接口,都作為定制服務接口
            for (Class<?> clazz : interfaces) {
                customizedInterfaces.add(clazz);
            }

            // 尋找父類定制服務
            class1 = class1.getSuperclass();
        }

        return customizedInterfaces;
    }

    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}

@Customized     // 預設服務實作類
@Service
public class DefaultHelloService implements HelloService {

    public void saySomething(String msg) {
        System.out.println("DefaultHelloService say:" + msg);
    }

}

@Customized(custCodes="kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl1 say:" + msg);
    }
}

@Customized(custCodes="kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
    public void saySomething(String msg) {
        System.out.println("HelloServiceImpl2 say:" + msg);
    }

    public String getCustCode() {
        return "kind2";
    }
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
    
    @Autowired
    private HelloService helloService;
    
    @Test
    public void testServiceSelector() {
        helloService.saySomething("I Love U!");
    }
    
}
           

總結

​ 關于服務定制,其實前面所講的方法都可以。隻不過在特定的情況下各有各的優勢,需要根據具體情況來選擇合适的定制方案。而定制方案的選擇,依賴于深入地了解Spring的類管理和加載過程,會用BPP、BFP等來定制類的加載過程。

黎明前最黑暗,成功前最絕望!