SpringPlugin-Core在業務中的應用

Seven發表於2021-10-12

前言

一直負責部門的訂單模組,從php轉到Java也是如此,換了一種語言來實現訂單相關功能。那麼Spring裡有很多已經搭建好基礎模組的設計模式來幫助我們解耦實際業務中的邏輯,用起來非常的方便!就比如我們的訂單操作模組。生成訂單後,有很多操作。比如:取消、支付、關閉....等等。那麼用設計模式的思想去處理這些不同的操作,最好用的就是策略模式來解決它們!把不同的操作分配到不同的實現類裡去。這不,我發現了一個不錯的東西Spring plugin!

Spring Plugin

Spring plugin這個小東西我也是在看一些開源專案才看到的,感覺還不錯。就立馬拿來用了下,把它帶到我們業務場景裡去。這不,帶大家體驗下。

下面用Spring plugin來重構下訂單的相關操作實現。這裡我們只模擬,支付和關閉的操作。最後再來簡單分析下Spring plugin的原理

實戰

首先呢、定義一個操作型別的列舉類,來邊界下當前我們系統支援的操作型別!

public enum OrderOperatorType {
    /**
     * 關閉
     */
    CLOSED,

    /**
     * 支付
     */
    PAY
    ;
}

第二步、定義操作介面,實現Spring pluginPlugin<S>介面,和配置外掛

public interface OrderOperatorPlugin extends Plugin<OrderOperatorDTO> {

    /**
     * 定義操作動作
     * @param operator
     * @return
     */
    public Optional<?> apply(OrderOperatorDTO operator);
}

//配置外掛,外掛寫好了,我們要讓外掛生效!
@Configuration
@EnablePluginRegistries({OrderOperatorPlugin.class})
public class OrderPluginConfig {
}

第三步 、定義具體的實現類(支付操作、關閉操作)

@Component
public class PayOperator implements OrderOperatorPlugin {

    @Override
    public Optional<?> apply(OrderOperatorDTO operator) {
        //支付操作
        //doPay()
        return Optional.of("支付成功");
    }

    @Override
    public boolean supports(OrderOperatorDTO operatorDTO) {
        return operatorDTO.getOperatorType() == OrderOperatorType.PAY;
    }
}



@Component
public class ClosedOperator implements OrderOperatorPlugin {

    @Override
    public Optional<?> apply(OrderOperatorDTO operator) {
        //關閉操作
        //doClosed()
        return Optional.of("關閉訂單成功");
    }

    @Override
    public boolean supports(OrderOperatorDTO operatorDTO) {
        return operatorDTO.getOperatorType() == OrderOperatorType.CLOSED;
    }
}

這裡要注意的是實現 supports方法,此方法返回的是一個boolean值,直觀的看起來就是一個選擇器的條件,這裡可直接認為,當Support返回True的時候,就找到了當前操作的實現類!

兩個不同的實現類定義好,那麼我們怎麼找到具體的實現類呢?

最後、定義業務介面,和業務實現類

public interface OrderService {

    /**
     * 操作訂單介面
     * @param operator
     * @return
     */
    Optional<?> operationOrder(OrderOperatorDTO operator);
}


@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    PluginRegistry<OrderOperatorPlugin, OrderOperatorDTO> orderOperatorPluginRegistry;

    @Override
    public Optional<?> operationOrder(OrderOperatorDTO operator) {
        OrderOperatorPlugin pluginFor = orderOperatorPluginRegistry.getPluginFor(operator);
        return pluginFor.apply(operator);
    }
}

在業務介面實現類裡我們注入了

@Resource
PluginRegistry<OrderOperatorPlugin, OrderOperatorDTO> orderOperatorPluginRegistry;

名字一定是 介面名 + Registry,我這裡是orderOperatorPluginRegistry至於為什麼要這樣寫,等回我們分析原始碼的時候看一下。目前這樣寫就對了。

接下來我們測試下

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderOperatorPluginTest {

    @Resource
    OrderService orderService;

    @Resource
    ApplicationContext applicationContext;

    @Test
    public void test_operation_closed() {
        final OrderOperatorDTO operator = new OrderOperatorDTO();
        operator.setOperatorType(OrderOperatorType.CLOSED);
        Optional<?> optionalO = orderService.operationOrder(operator);

        Assertions.assertEquals("關閉訂單成功", optionalO.get());
    }


    @Test
    public void test_operation_pay() {
        final OrderOperatorDTO operator = new OrderOperatorDTO();
        operator.setOperatorType(OrderOperatorType.PAY);
        Optional<?> optionalO = orderService.operationOrder(operator);

        Assertions.assertEquals("支付成功", optionalO.get());
    }
}

這個執行結果是沒有問題的,可以自己把程式碼下載下來,跑一下~~??

感受

如果我把整個訂單的流程都當作不同的外掛來開發的話...建立訂單是一個流程、在這個流程的過程中,我們分別在不同的位置插入不同的外掛。比如下圖!


最後,這隻把所以Plugin組織起來,是不是就可以搞定一套完整的訂單流程了,而我們做的只是面對外掛開發,把注意力集中到某個外掛中就可以了呢?或許下次訂單重構的時候,我可以會這樣的去嘗試下!

原始碼重點分析

首先看下注冊外掛的註釋EnablePluginRegistries

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({PluginRegistriesBeanDefinitionRegistrar.class})
public @interface EnablePluginRegistries {
    Class<? extends Plugin<?>>[] value();
}

value屬性是一個陣列,必須實現Plugin介面,這個是定義外掛的基本條件~。

再看Import註釋,PluginRegistriesBeanDefinitionRegistrar實現了ImportBeanDefinitionRegistrar介面,這個有點味道了,

public class PluginRegistriesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  
}

之前我有一個文章是分析相關載入類到容器的一篇文章,請看下面文章的介紹。

ImportBeanDefinitionRegistrar的作用

直接看核心程式碼

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  			//當前我只註冊了一個 外掛 OrderOperatorPlugin
        Class<?>[] types = (Class[])((Class[])importingClassMetadata.getAnnotationAttributes(EnablePluginRegistries.class.getName()).get("value"));
        Class[] var4 = types;
        int var5 = types.length;
				//長度也就為1
        for(int var6 = 0; var6 < var5; ++var6) {
            Class<?> type = var4[var6];
          	//是FactoryBean 見名思義。PluginRegistryFactoryBean#getObject 的方法最終返回的是 OrderAwarePluginRegistry 看名字是支援排序的功能。
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PluginRegistryFactoryBean.class);
            builder.addPropertyValue("type", type);
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            Qualifier annotation = (Qualifier)type.getAnnotation(Qualifier.class);
            if (annotation != null) {
                AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
                qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
                beanDefinition.addQualifier(qualifierMetadata);
            }
						
          	//外掛上沒有新增 Qualifier 所以為null, nulll的話就給拼接上 Registry! 這就是為啥注入的時候用 外掛名 + Registry、另外 PluginRegistryFactoryBean實現了PluginRegistry。
            String beanName = annotation == null ? StringUtils.uncapitalize(type.getSimpleName() + "Registry") : annotation.value();
            registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
        }

    }

那麼注入容器後,呼叫getPluginFor找到當前策略的實現類。

// OrderAwarePluginRegistry 類
public T getPluginFor(S delimiter) {
  Iterator var2 = super.getPlugins().iterator();

  Plugin plugin;
  do {
    if (!var2.hasNext()) {
      return null;
    }

    plugin = (Plugin)var2.next();
    //這裡判斷 supports的方法 返回true時即跳出Loop
  } while(plugin == null || !plugin.supports(delimiter));

  return plugin;
}


//另外 super.getPlugins裡 會呼叫 initializa的方法,即外掛是支援排序功能的,只要在外掛上加入Order()註釋即可。
protected List<T> initialize(List<T> plugins) {
  List<T> result = super.initialize(plugins);
  Collections.sort(result, this.comparator);
  return result;
}

程式碼在GitHub

相關文章