基於Spring實現策略模式
- 2022 年 1 月 10 日
- 筆記
- springboot
背景:
看多很多策略模式,總結下來實現原理大體都差不多,在這裡主要是講解下自己基於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/ 本文版權歸作者和博客園共有,歡迎轉載!但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接!