基于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/
本文版权归作者和博客园共有,欢迎转载!但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接!