基於Spring實現策略模式

背景:

  看多很多策略模式,總結下來實現原理大體都差不多,在這裡主要是講解下自己基於Spring更優雅的實現方案;這個方案主要是看了一些開源rpc和Spring相關源碼後的一些思路,所以在此進行總結

首先看下比較常見的策略模式寫法

  • 一個接口或者抽象類,裏面兩個方法(一個方法匹配類型,一個可替換的邏輯實現方法)
  • 不同策略的差異化實現(就是說,不同策略的實現類)
  • 使用策略模式

1.3.1 一個接口,兩個方法

public interface IFileStrategy {
    
    //屬於哪種文件解析類型
    FileTypeResolveEnum gainFileType();
    
    //封裝的公用算法(具體的解析方法)
    void resolve(Object objectparam);
}

1.3.2 不同策略的差異化實現

A 類型策略具體實現

@Component
public class AFileResolve implements IFileStrategy {
    
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_A_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("A 類型解析文件,參數:{}",objectparam);
      //A類型解析具體邏輯
    }
}

B 類型策略具體實現

@Component
public class BFileResolve implements IFileStrategy {
   
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_B_RESOLVE;
    }


    @Override
    public void resolve(Object objectparam) {
      logger.info("B 類型解析文件,參數:{}",objectparam);
      //B類型解析具體邏輯
    }
}

默認類型策略具體實現

@Component
public class DefaultFileResolve implements IFileStrategy {

    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_DEFAULT_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("默認類型解析文件,參數:{}",objectparam);
      //默認類型解析具體邏輯
    }
}

1.3.3 使用策略模式

如何使用呢?我們藉助spring的生命周期,使用ApplicationContextAware接口,把對用的策略,初始化到map裏面。然後對外提供resolveFile方法即可。

 

/**
 *  
 */
@Component
public class StrategyUseService implements ApplicationContextAware{

  
    private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();

    public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) {
        IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum);
        if (iFileStrategy != null) {
            iFileStrategy.resolve(objectParam);
        }
    }

    //把不同策略放到map
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
        tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
    }
}

 


 

基於Spring服務策略實現

      稍微了解過Spring源碼都知道,在Spring裏面我們定義好的bean被@Autowired修飾後,實際這個bean是被Spring進行了統一管理,當需要調用的時候實際是從Spring工廠里拿到這個bean;所以大致思路就是在如何拿到bean之前注入一個代理類,讓代理類根據元數據的一些自定義規則後去組裝成一個能從Spring里拿到實際的bean元素;基於以上的思路進行編碼如下

註解定義

  • 自定義一個@RouteBizService註解(作用可以理解為@Autowired)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RouteBizService {

    String serviceName();
}
  • 自定義一個@RouteBizParam參數註解,用於給代理類組裝實際beanName
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouteBizParam {
}

 

  • 定義一個代理類:RouteServiceProxy

/**
 * 
 */
package com.gitee.adapter.proxy;

import org.springframework.context.ApplicationContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RouteServiceProxy<T> implements InvocationHandler{
    private String serviceName;
    private ApplicationContext context;

    public RouteServiceProxy(String serviceName, ApplicationContext context) {
        this.serviceName = serviceName;
        this.context = context;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String routeCode = null;
        Annotation[ /* 參數個數索引 */][ /* 註解個數索引 */ ] paramsAnno = method.getParameterAnnotations();
        if (paramsAnno != null) {
            for (int i = 0; i < paramsAnno.length; i++) {
                if (paramsAnno[i].length > 0) {
                    routeCode = (String) args[i]; // 獲取到路由的參數值
                    break;
                }
            }
        }

        return method.invoke(context.getBean(genBeanName(routeCode, serviceName)),  args);
    }

    /**
     *
     * @param sellerCode 用於區分是哪個Service 編碼
     * @param interfaceSimpleName 服務接口
     * @return
     */
    private String genBeanName(String sellerCode, String interfaceSimpleName) {
        return new StringBuilder(sellerCode.toLowerCase()).append(interfaceSimpleName).toString();
    }

}

View Code

 

  •  基於BeanFactoryPostProcessor 定義一個用於掃描 @RouteBizService修飾的實現類,該類的作用是為了注入代理類

package com.gitee.adapter.spring;

import com.gitee.adapter.annation.RouteBizService;
import com.gitee.adapter.proxy.RouteServiceProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Proxy;

/**
 * @Classname BizRouteServiceProcessor
 * @Description bean 後置處理器 獲取所有bean
 * 判斷bean字段是否被 {@link com.gitee.adapter.annation.RouteBizService } 註解修飾
 */
public class BizRouteServiceProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName != null) {
                Class<?> clazz = ClassUtils.resolveClassName(beanClassName, this.getClass().getClassLoader());
                ReflectionUtils.doWithFields(clazz, field -> {
                    RouteBizService routeBizService = AnnotationUtils.getAnnotation(field, RouteBizService.class);
                    if (routeBizService != null) {
                        Object bean = applicationContext.getBean(clazz);
                        field.setAccessible(true);
                        // 修改為代理對象
                        ReflectionUtils.setField(field, bean,
                                Proxy.newProxyInstance(field.getType().getClassLoader(), new Class[] { field.getType() }, new RouteServiceProxy(routeBizService.serviceName(),this.applicationContext)));
                    }
                });
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

View Code

 

測試

  

 

 

環境搭建

  • 操作系統:Windows
  • 集成開發工具:IntelliJ IDEA 2021
  • 項目技術棧:SpringBoot 2.2.11 + JDK 1.8 
  • 項目依賴管理工具:Maven 4.0.0

項目代碼地址

//gitee.com/kevin_zhan/spring_strategy

 

作者:DDZ_YYDS
出處:https://www.cnblogs.com/zdd-java/
本文版權歸作者和博客園共有,歡迎轉載!但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接!