自定義bean容器提升代碼可讀性

開發中經常有這樣的場景:

根據某個類型標識走不同的業務邏輯,通常我們會使用if(type.equals(xxxxx)) 或者 switch語句來進行邏輯處理。

這樣做當然是沒什麼問題的。

當業務邏輯變得越來越複雜,類型標識增多之後,難免會出現if判斷增加,或者switch case分支變多,這樣的代碼往往會過於冗長,代碼重複性較大,或者說逼格不夠高。

本文介紹一種基於自定義Bean容器的開發方式,消除代碼中的判斷分支,提升代碼可讀性。

我們通過一個demo來看如何實現這種編碼方式。

1、定義接口

首先定義一個接口,主要有兩個方法:

public interface AbstractService<T> {

    /**
    * 返回serviceName
    * 作爲bean選擇標識
    * @return
    */
    String serviceName();

    /**
    * 具體的service方法
    * @param parm
    * @return
    */
    T execute(Object parm);
}

實現類需要實現serviceName,返回具體的類型,注意不同的bean實現類該返回值不能重複

execute方法爲業務方法,這裏只是做個示範,實際開發中可以是任意的通用業務方法。

2、實現接口

接着編寫實現類,實現接口

2.1、ServiceAImpl標記類型爲 ServiceA

@Component
public class ServiceAImpl implements AbstractService<DemoA> {

    @Override
    public String serviceName() {
        return "ServiceA";
    }

    @Override
    public DemoA execute(Object parm) {
        System.out.println("ServiceAImpl execute");
        return new DemoA().setName("DemoA");
    }
}

2.2、ServiceBImpl標記類型爲 ServiceB

@Component
public class ServiceBImpl implements AbstractService<DemoB> {

    @Override
    public String serviceName() {
        return "ServiceB";
    }

    @Override
    public DemoB execute(Object parm) {
        System.out.println("ServiceBImpl execute");
        return new DemoB().setName("DemoB");
    }
}

2.3、編寫自定義Bean上下文

這裏是重頭戲,我們需要編寫一個Bean上下文,並注入AbstractService集合。

@Component
public class ServiceContext {

    // IService容器,key=serviceName,velue=實例
    private static Map<String, AbstractService> SERVICE_CONTEXT;

    @Autowired
    List<AbstractService> services;

    @PostConstruct
    void init() {
        SERVICE_CONTEXT = new ConcurrentHashMap<> ();
        if (services == null) {
            return;
        }
        // 將IService所有的實現類註冊到serviceContext
        for(AbstractService service : services) {
            SERVICE_CONTEXT.put(service.serviceName(), service);
        }
        System.out.println(JSON.toJSONString(SERVICE_CONTEXT));
    }

    /**
    * 根據serviceName獲取實例
    * @param serviceName
    * @return
    */
    public AbstractService getServiceImpl(String serviceName) {
        return SERVICE_CONTEXT.get(serviceName);
    }
}

其實註釋已經很清楚了,首先定義一個Map,key爲String,代表我們上文中接口返回的serviceName。

value爲接口實現類bean實例。

接着通過@Autowired注入AbstractService集合,這裏是一個List。當Spring容器初始化完成,會將AbstractService的實現類都加載到List中。

在@PostConstruct標記的初始化方法中,遍歷 List<AbstractService>,並依次加載到我們初始化好的Map中。key=AbstractService.serviceName()的返回值,value爲AbstractService實例。

定義一個getServiceImpl(String serviceName)提供給業務使用,能夠讓我們通過具體的serviceName標識獲取到Bean實例。這也是爲何serviceName不能重複的原因。

2.4、測試

到此主要的邏輯編寫就完成了,我們編寫一個測試類測試一下具體如何使用。

public static void main(String[] args) {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
    // 獲取bean Context
    ServiceContext serviceContext = applicationContext.getBean("serviceContext", ServiceContext.class);
    // 根據serviceName獲取具體的接口實現類
    AbstractService serviceA = serviceContext.getServiceImpl("ServiceA");
    AbstractService serviceB = serviceContext.getServiceImpl("ServiceB");
    // 調用service方法
    serviceA.execute(null);
    serviceB.execute(null);
}

這裏從Spring上下文中獲取到ServiceContext,並通過具體的serviceName獲取到對應的Bean實例,並調用實例的execute方法。執行結果如下:

ServiceAImpl execute
ServiceBImpl execute

可能這還不算很直觀,我們模擬一個業務場景。

業務需要先判斷serviceName,再根據具體的值選擇不同的執行邏輯。

正常情況下,我們會這樣編寫業務代碼:

if ("ServiceA".equals(serviceName)) {
    serviceA.execute()
    return;
}

if ("ServiceB".equals(serviceName)) {
    serviceB.execute()
    return;
}

...

如果有一百個serviceName,那麼這裏就要有100個if分支,switch也同理。

但是採取本文中的編碼方式則只需要這麼寫:

...省略獲取serviceContext過程,最簡單的方法是通過@Autowired/@Resource注入...
AbstractService service = serviceContext.getServiceImpl(serviceName);
service.execute()

這樣我們就只需要在新增serviceName類型後,開發一個對應的實現類即可。

如果是傳統的編碼方式,則除了新增service實現,還需要修改if/switch判斷邏輯,不夠靈活且容易出錯。

這裏其實就是開放封閉原則的體現。傳統的方式對修改和擴展都是開放的,而這種方式則是對擴展開發,對修改封閉的。尤其適用於複雜業務場景的開發。

 

3、原理

簡單講一下原理。

Spring框架支持對集合類型進行依賴注入,對於集合類型依賴注入與查找起作用的ApplicationContext實現類爲 ListableBeanFactory

我們看下源碼是如何實現該特性的:

具體的邏輯在 org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency 這個方法中

打開該方法,重點關注下面這行

Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);

進入resolveDependency方法,看到下面這一行,跳入doResolveDependency方法

result = doResolveDependency(descriptor, requestingBeanName, 
autowiredBeanNames, typeConverter);

重點關注下面的邏輯

Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
    return multipleBeans;
}

此處的resolveMultipleBeans方法邏輯爲,如果解析到了多個匹配條件的Bean,就直接返回解析結果。

那具體的解析結果又是什麼呢?我們進入resolveMultipleBeans方法

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

    Class<?> type = descriptor.getDependencyType();
    // 數組類型
    if (type.isArray()) {
        Class<?> componentType = type.getComponentType();
        ResolvableType resolvableType = descriptor.getResolvableType();
        Class<?> resolvedArrayType = resolvableType.resolve();
        if (resolvedArrayType != null && resolvedArrayType != type) {
            type = resolvedArrayType;
            componentType = resolvableType.getComponentType().resolve();
        }
        if (componentType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof Object[]) {
            Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    // 集合類型,如List set
    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        if (elementType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof List) {
            ((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    // Map類型
    else if (Map.class == type) {
        ResolvableType mapType = descriptor.getResolvableType().asMap();
        Class<?> keyType = mapType.resolveGeneric(0);
        if (String.class != keyType) {
            return null;
        }
        Class<?> valueType = mapType.resolveGeneric(1);
        if (valueType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        return matchingBeans;
    }
    else {
        return null;
    }
}

這裏便是@Autowired注入集合類型的核心。

  • 首先判斷注入類型,如果是數組、Collection、Map等類型,則注入元素數據,即查找與元素類型相同的Bean,並注入到集合中。
  • 這裏重點強調下Map類型,我們能夠看出,Map的 key 爲Bean的 name,value 爲 與定義的元素類型相同的Bean。

    // Map的key
    Class<?> keyType = mapType.resolveGeneric(0);
    if (String.class != keyType) {
        return null;
    }
    // Map的value
    Class<?> valueType = mapType.resolveGeneric(1);
    if (valueType == null) {
        return null;
    }

    也就是說,如果業務上不依賴外部的type,那麼我們可以直接注入一個Map集合,比如:

    @Autowired
    private Map<String, BeanInterface> map;

    這樣就能夠將接口BeanInterface的實現都注入到Map中,key的值爲具體Bean的name,value爲Bean實例。4、小結

4、小結

本文中,我們通過案例與源碼,全方位呈現了Spring對集合類型的注入方式。總結一下:

  1. Spring在注入集合類的同時,會將集合泛型類的實例填入集合中,作爲集合的初始值。

  2. 對於list、set填入的是注入類型Spring管理的實例,對於map,Spring會將service的名字作爲key,對象作爲value封裝進入Map。

  3. 對於List類型,可以通過@Order指定加入List的順序。只需要在實現類中加入@Order(value) 註解即可 ,值越小越先被初始化越先被放入List

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