项目中同事的分享,保存一下(由于原文链接为内网,所以标为原创)!
说明:商旅系统是以大唐商旅购票系统为基础,用户购票后,大唐系统将机票订单(出票、改签、退票)推送至我们系统, 推送的几个订单(出票、改签、退票)同一个接口,如果按照普通的代码编写方式,会存在大量的if...else.... ,如果后续需要改动某个业务逻辑,出票、改签、退票都会容易相互影响。为了解决该问题,引入策略模式编写代码
1.创建实体类、服务层、控制层
这里方便起见再写一边,具体内容有差别,但是实现功能不变。
@Data public class PushOrderBaseDto<T> { /** * 推送类型 */ @NotNull(message = "推送类型不能为空") private Integer notifyType; /** * 推送子类型 */ @NotNull(message = "推送子类型不能为空") private Integer subNotifyType; /** * 推送时间戳 */ private long notifyTime; /** * 推送签名 */ private String sign; /** * 是否加密 */ private Boolean isEncrypt; /** * 推送唯一秘钥 */ private String soleKey; /** * 推送数据主体 */ @NotNull(message = "推送数据主体不能为空") @JsonDeserialize(using = ObjectJsonDeserializer.class) private NotifyDataDto<T> notifyData; }
@RestController @RequestMapping("/airplaneTicketOrder") public class AirplaneTicketOrderController { @Autowired private IAirplaneTicketOrderService iAirplaneTicketOrderService; /** * 推送机票信息 * * @param dto 审批数据id * @return LiteRestResponse * @author yufangze. create time: 2020-10-13 */ @PostMapping("/push") public DatangPushResponseVo push(@Valid @RequestBody PushOrderBaseDto<Object> dto) { iAirplaneTicketOrderService.push(dto); return DatangPushResponseVo.success(); }
}
@Slf4j @Service public class AirplaneTicketOrderServiceImpl extends ServiceImpl<AirplaneTicketOrderMapper, AirplaneTicketOrder> implements IAirplaneTicketOrderService {
@Autowired(required = false) private HandlerContext handlerContext;
/** * 推送机票订单 * * @param pushOrderBaseDto 审批数据 * @return LiteRestResponse * @author yufangze. create time: 2020-10-13 */ @Override public void push(PushOrderBaseDto<Object> pushOrderBaseDto) { log.info("【推送机票订单】推送内容:{}", JSONObject.toJSONString(pushOrderBaseDto)); AirplaneTicketOrderStrategy strategy = handlerContext.getInstance(AirplaneOrderSeatPushEnums .getName(pushOrderBaseDto.getSubNotifyType())); strategy.push(pushOrderBaseDto.getNotifyData()); }
}
2、创建策略接口 策略实现类
/** * @description: 机票订单推送策略接口 * @author: yufangze * @create: 2020-10-13 16:35 */ public interface AirplaneTicketOrderStrategy { /** * 机票订单推送策略接口 * * @param notifyDataDto 推送主体数据 * @author yufangze. create time: 2020-10-13 */ void push(NotifyDataDto<Object> notifyDataDto); }
2.1 机票订单出票推送
@OrderPushType(type = "airplaneIssueTicket") @Component @Slf4j public class AirplaneIssueTicketStrategy implements AirplaneTicketOrderStrategy {
/** * 机票订单出票推送 * * @param * @return * @author yufangze. create time: 2020-10-13 */ @Override public void push(NotifyDataDto<Object> notifyDataDto) {
}
}
2.2 机票订单退票推送
@OrderPushType(type = "airplaneRefundTicket") @Component @Slf4j public class AirplaneRefundTicketStrategy implements AirplaneTicketOrderStrategy {
/** * 机票订单退票推送 * * @param * @return * @author yufangze. create time: 2020-10-13 */ @Override public void push(NotifyDataDto<Object> notifyDataDto) {
}
}
2.3推送机票订单改签
@OrderPushType(type = "airplaneChange") @Component @Slf4j public class AirplaneChangeStrategy implements AirplaneTicketOrderStrategy {
/** * 机票订单改签推送 * * @param * @return * @author yufangze. create time: 2020-10-13 */ @Override public void push(NotifyDataDto<Object> notifyDataDto) {
}
}
3、订单推送类型注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface OrderPushType { /** * 机票订单推送类型 * * @return * @author yufangze. create time: 2020-10-13 */ String type(); }
4、订单推送类型注解
@Component public class HandlerProcessor implements BeanFactoryPostProcessor { private static final String STRATEGY_PACKAGE = "com.runlion.middleground.businesstravel.strategy"; /** * 扫描@OrderPushType,初始化HandlerContext,将其注册到spring容器 * * @param beanFactory bean工厂 * @throws BeansException */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { Map<String, Class> handlerMap = Maps.newHashMapWithExpectedSize(3); ClassScanner.scan(STRATEGY_PACKAGE, OrderPushType.class).forEach(clazz -> { //获取注解中的类型值 String type = clazz.getAnnotation(OrderPushType.class).type(); //将注解中的类型值作为key,对应的类作为value,保存在map中 handlerMap.put(type, clazz); }); HandlerContext context = new HandlerContext(handlerMap); beanFactory.registerSingleton(HandlerContext.class.getName(), context); } }
5、机票订单处理器上下文
public class HandlerContext { private static final Logger logger = LoggerFactory.getLogger(HandlerContext.class); private Map<String, Class> handlerMap; public HandlerContext(Map<String, Class> handlerMap) { this.handlerMap = handlerMap; } public AirplaneTicketOrderStrategy getInstance(String type) { Class clazz = handlerMap.get(type); if (clazz == null) { logger.error("没有type为‘" + type + "’的策略处理器"); throw new IllegalArgumentException("not found strategy handler for type: " + type); } return (AirplaneTicketOrderStrategy) BeanTool.getBean(clazz); } }
5、Bean工具类
@Component public class BeanTool implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { if (applicationContext == null) { applicationContext = context; } } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } }
6、机票订单策略类扫描
public class ClassScanner implements ResourceLoaderAware { private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>(); private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>(); private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); @SafeVarargs public static Set<Class<?>> scan(String[] basePackages, Class<? extends Annotation>... annotations) { ClassScanner cs = new ClassScanner(); if (ArrayUtils.isNotEmpty(annotations)) { for (Class anno : annotations) { cs.addIncludeFilter(new AnnotationTypeFilter(anno)); } } Set<Class<?>> classes = new HashSet<>(); for (String s : basePackages) { classes.addAll(cs.doScan(s)); } return classes; } @SafeVarargs public static Set<Class<?>> scan(String basePackages, Class<? extends Annotation>... annotations) { return ClassScanner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations); } public final ResourceLoader getResourceLoader() { return this.resourcePatternResolver; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils .getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory( resourceLoader); } public void addIncludeFilter(TypeFilter includeFilter) { this.includeFilters.add(includeFilter); } public void addExcludeFilter(TypeFilter excludeFilter) { this.excludeFilters.add(0, excludeFilter); } public void resetFilters(boolean useDefaultFilters) { this.includeFilters.clear(); this.excludeFilters.clear(); } public Set<Class<?>> doScan(String basePackage) { Set<Class<?>> classes = new HashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + org.springframework.util.ClassUtils .convertClassNameToResourcePath(SystemPropertyUtils .resolvePlaceholders(basePackage)) + "/**/*.class"; Resource[] resources = this.resourcePatternResolver .getResources(packageSearchPath); for (int i = 0; i < resources.length; i++) { Resource resource = resources[i]; if (resource.isReadable()) { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if ((includeFilters.size() == 0 && excludeFilters.size() == 0) ) { try { classes.add(Class.forName(metadataReader .getClassMetadata().getClassName())); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else if (this.matches(metadataReader)){ try { classes.add(Class.forName(metadataReader .getClassMetadata().getClassName())); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } catch (IOException ex) { throw new BeanDefinitionStoreException( "I/O failure during classpath scanning", ex); } return classes; } protected boolean matches(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return true; } } return false; } }
完成。
最后调用推送接口,根据subNotifyType(推送子类型) 执行不同的业务