Spring 实现策略模式--自定义注解方式解耦if...else
策略模式
定义
定义一簇算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端
场景
使用策略模式,可以避免冗长的if-else 或 switch分支判断
实现
策略的定义
策略的定义需要定义一个策略接口和一组实现这个接口的策略类,因为所有的策略类都实现相同的接口
public interface Strategy{
void algorithm();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
//具体的算法...
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
//具体的算法...
}
}
策略的创建
在使用的时候,一般会通过类型来判断创建哪个策略来使用,在策略上下文中,可以使用map维护好策略类
策略的使用
策略模式包含一组可选策略,在使用策略时,一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略。程序在运行期间,根据配置、计算结果、网络等这些不确定因素,动态决定使用哪种策略
public class StrategyContext{
private static final Map<String, Strategy> strategies = new HashMap<>();
static {
strategies.put("A", new ConcreteStrategyA());
strategies.put("B", new ConcreteStrategyB());
}
private static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return strategies.get(type);
}
public void algorithm(String type){
Strategy strategy = this.getStrategy(type);
strategy.algorithm();
}
}
UML

策略模式的创建和使用--Spring和自定义注解
在介绍策略模式时,在上下文中使用了map存储好的策略实例,在根据type获取具体的策略,调用策略算法。
当需要添加一种策略时,需要修改context代码,这违反了开闭原则:对修改关闭,对扩展开放。
要实现对扩展开放,就要对type和具体的策略实现类在代码中进行关联,可以使用自定义注解的方式,在注解中指定策略的type。
策略上下文实现类实现 BeanPostProcessor 接口,在该接口中编写策略类型与bean的关系并维护到策略上下文中。
package com.masterlink.strategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class StrategyDemoBeanPostProcessor implements BeanPostProcessor, Ordered {
private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
private final StrategyContext strategyContext;
private StrategyDemoBeanPostProcessor(StrategyContext context) {
this.strategyContext = context;
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
// 获取使用 @StrategyDemo 注解的Class信息
Class<?> targetClass = AopUtils.getTargetClass(bean);
Class<Strategy> orderStrategyClass = (Class<Strategy>) targetClass;
StrategyDemo ann = findAnnotation(targetClass);
if (ann != null) {
processListener(ann, orderStrategyClass);
}
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
protected void processListener(StrategyDemo annotation,
Class<Strategy> classes) {
// 注册策略
this.strategyContext
.registerStrategy(annotation.type(), classes);
}
private StrategyDemo findAnnotation(Class<?> clazz) {
StrategyDemo ann = AnnotatedElementUtils.findMergedAnnotation(clazz, StrategyDemo.class);
return ann;
}
}
@Component
public class StrategyContext implements ApplicationContextAware {
private final Map<String, Class<Strategy>> strategyClassMap = new ConcurrentHashMap<>(64);
private final Map<String, Strategy> beanMap = new ConcurrentHashMap<>(64);
private ApplicationContext applicationContext;
/**
* 注册策略
* @param type
* @param strategyClass
*/
public void registerStrategy(String type, Class<Strategy> strategyClass){
if (strategyClassMap.containsKey(type)){
throw new RuntimeException("strategy type:"+type+" exist");
}
strategyClassMap.put(type, strategyClass);
}
/**
* 执行策略
* @param type
*/
public void algorithm(String type){
Strategy strategy = this.getStrategy(type);
strategy.algorithm();
}
private Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
Class<Strategy> strategyClass = strategyClassMap.get(type);
return createOrGetStrategy(type, strategyClass);
}
private Strategy createOrGetStrategy(String type,Class<Strategy> strategyClass ){
if (beanMap.containsKey(type)){
return beanMap.get(type);
}
Strategy strategy = this.applicationContext.getBean(strategyClass);
beanMap.put(type, strategy);
return strategy;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
实用案例
在我们的平台中,有一部分是使用的netty框架编写的tcp服务,在服务端,需要将二进制转换为对象,在协议设计阶段,定义第一个字节表示对象类型,比如int,String等,第二三个字节,表示数据长度,后面的字节位传输内容。
比如,
0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09,解析出来的内容是int类型数字9。
0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的内容是String类型,内容是 123。
在不使用策略模式的时候,需要将第一个字节解析出来,然会使用if--else判断类型,对后继的字节进行解析。
在实际的实现过程中,是使用了策略模式,并且使用注解的方式表示数据类型,实现过程如下。
定义策略接口和注解
定义 CodecStrategyType 注解和编码解码器的策略接口 CodecStrategy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CodecStrategyType {
/**
* 编码解码类型
* @return
*/
byte type();
}
public interface CodecStrategy<T> {
T decoding(byte[] buffer);
}
/*
* 通用解码接口
*/
public interface Codec {
Object decoding(byte[] bytes);
}
策略实现
实现两种类型的解码器: Integer 和 String
/**
* integer解码
*/
@CodecStrategyType(type = (byte)0x01)
@Service
public class IntgerCodecStrategy implements CodecStrategy<Integer> {
@Override
public Integer decoding(byte[] buffer) {
int value;
value = (int) ((buffer[3] & 0xFF)
| ((buffer[2] & 0xFF)<<8)
| ((buffer[1] & 0xFF)<<16)
| ((buffer[0] & 0xFF)<<24));
return value;
}
}
@CodecStrategyType(type = (byte)0x02)
@Service
public class StringCodecStrategy implements CodecStrategy<String> {
@Override
public String decoding(byte[] bufferr) {
return new String(bufferr);
}
}
策略上下文和策略注册
策略上下文类 CodecStrategyContext 提供了统一解码入口,将 byte[] 转换为 Object 类型,同时提供策略的注解接口 void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass) ,注册解码类型对应的策略实现类。
策略上下文类同时还提供了策略Bean的创建,根据类型从Spring 的 ApplicationContext 获取策略bean,并缓存到map。
策略Bean处理类 CodecStrategyTypeBeanPostProcessor 中解析 CodecStrategyType 注解中指定的类型。
@Component
public class CodecStrategyContext implements ApplicationContextAware, Codec {
private final Map<Byte, Class<CodecStrategy<?>>> strategyClassMap = new ConcurrentHashMap<>(64);
private final Map<Byte, CodecStrategy<?>> beanMap = new ConcurrentHashMap<>(64);
private ApplicationContext applicationContext;
/**
* 注册策略
* @param type
* @param strategyClass
*/
public void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass){
if (strategyClassMap.containsKey(type)){
throw new RuntimeException("strategy type:"+type+" exist");
}
strategyClassMap.put(type, strategyClass);
}
/**
* 执行策略
*/
@Override
public Object decoding(byte[] bytes){
Byte type = bytes[0];
CodecStrategy<?> strategy =this.getStrategy(type);
byte l1 = bytes[1];
byte l2= bytes[2];
short length = (short) ((l2 & 0xFF)
| ((l1 & 0xFF)<<8));
byte[] contentBytes = new byte[length];
arraycopy(bytes,3,contentBytes,0, length);
return strategy.decoding(contentBytes);
}
private CodecStrategy<?> getStrategy(Byte type) {
Class<CodecStrategy<?>> strategyClass = strategyClassMap.get(type);
return createOrGetStrategy(type, strategyClass);
}
private CodecStrategy<?> createOrGetStrategy(Byte type, Class<CodecStrategy<?>> strategyClass ){
if (beanMap.containsKey(type)){
return beanMap.get(type);
}
CodecStrategy<?> strategy = this.applicationContext.getBean(strategyClass);
beanMap.put(type, strategy);
return strategy;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
@Component
public class CodecStrategyTypeBeanPostProcessor implements BeanPostProcessor, Ordered {
private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
private final CodecStrategyContext strategyContext;
private CodecStrategyTypeBeanPostProcessor(CodecStrategyContext context) {
this.strategyContext = context;
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
// 获取使用 @StrategyDemo 注解的Class信息
Class<?> targetClass = AopUtils.getTargetClass(bean);
Class<CodecStrategy<?>> orderStrategyClass = (Class<CodecStrategy<?>>) targetClass;
CodecStrategyType ann = findAnnotation(targetClass);
if (ann != null) {
processListener(ann, orderStrategyClass);
}
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
protected void processListener(CodecStrategyType annotation,
Class<CodecStrategy<?>> classes) {
// 注册策略
this.strategyContext
.registerStrategy(annotation.type(), classes);
}
private CodecStrategyType findAnnotation(Class<?> clazz) {
CodecStrategyType ann = AnnotatedElementUtils.findMergedAnnotation(clazz, CodecStrategyType.class);
return ann;
}
}
使用和测试
测试Integer和String类型的策略:
- 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09,解析出来的内容是int类型数字9。
- 0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的内容是String类型,内容是 123。
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {CodecStrategyTest.CodecStrategyTestConfig.class})
public class CodecStrategyTest {
@Resource
Codec codec;
@Test
public void testInterDecoding(){
byte[] buffer = new byte[]{
0x01,0x00, 0x04, 0x00, 0x00,0x00, 0x09
};
Integer decoding = (Integer)codec.decoding(buffer);
assertThat(decoding)
.isNotNull()
.isEqualTo(9);
}
@Test
public void testStringDecoding(){
byte[] buffer = new byte[]{
0x02, 0x00, 0x03, 0x31, 0x32,0x33
};
String decoding = (String)codec.decoding(buffer);
assertThat(decoding)
.isNotNull()
.isEqualTo("123");
}
@ComponentScan({"com.masterlink.strategy"})
@Configuration
public static class CodecStrategyTestConfig {
}
}
扩展复杂类型
自定义复杂类型User类,对应协议类型为 0xA0, 第2 、3 字节表示整个对象的字段长度,紧接着是 Integer 类型的age 和 String 类型的name,
比如 0xA0, 0x00 0x10 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x00, 0x08, 0x5A,0x68,0x61,0x6E,0x67,0x53, 0x61,0x6E, 对应的user对象是
{
"age": 23,
"name": "ZhangSan"
}
@Data
public class User {
private Integer age;
private String name;
}
实现解码策略类
已知 User 中的基础类型依赖了 Integer 和 String ,所以在User的解码策略类中,依赖了 IntgerCodecStrategy 和 StringCodecStrategy
@CodecStrategyType(type = (byte) (0xA0))
@Service
public class UserCodeStrategy implements CodecStrategy<User> {
private final StringCodecStrategy stringCodecStrategy;
private final IntgerCodecStrategy intgerCodecStrategy;
public UserCodeStrategy(StringCodecStrategy stringCodecStrategy, IntgerCodecStrategy intgerCodecStrategy) {
this.stringCodecStrategy = stringCodecStrategy;
this.intgerCodecStrategy = intgerCodecStrategy;
}
@Override
public User decoding(byte[] buffer) {
byte ageL1 = buffer[0];
byte ageL2 = buffer[1];
short ageLength = (short) ((ageL2 & 0xFF)
| ((ageL1 & 0xFF)<<8));
byte[] ageBytes = new byte[ageLength];
System.arraycopy(buffer,2, ageBytes,0,ageLength);
byte nameL1 = buffer[0+ageLength];
byte nameL2 = buffer[1+ageLength];
short nameLength = (short) ((nameL2 & 0xFF)
| ((nameL1 & 0xFF)<<8));
byte[] nameBytes = new byte[nameLength];
System.arraycopy(buffer,2+ageLength+2, nameBytes,0,nameLength);
User user = new User();
user.setAge(intgerCodecStrategy.decoding(ageBytes));
user.setName(stringCodecStrategy.decoding(nameBytes));
return user;
}
}
测试
通过测试可以发现很轻松的就扩展了一个复杂类型的解码算法,这样随着协议的增加,可以做到对修改代码关闭,对扩展代码开放,符合开闭原则。
@Test
public void testUserDecoding(){
byte[] buffer = new byte[]{
(byte)0xA0, (byte)0x00 ,(byte)0x10 ,(byte)0x00, (byte)0x04,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x17, (byte)0x00,
(byte)0x08, (byte)0x5A, (byte)0x68, (byte)0x61, (byte)0x6E,
(byte)0x67, (byte)0x53, (byte)0x61, (byte)0x6E
};
User user = (User)codec.decoding(buffer);
assertThat(user)
.isNotNull();
assertThat(user.getAge()).isEqualTo(23);
assertThat(user.getName()).isEqualTo("ZhangSan");
}
总结
- 使用策略模式,可以避免冗长的if-else 或 switch分支判断
- 掌握自定义注解的是使用方式
- 与使用
@Service("name")注解相比,自定义注解方式支撑和扩展的类型或更灵活
关注我的公众号,一起探索新知识新技术

Spring 实现策略模式--自定义注解方式解耦if...else的更多相关文章
- 你可能使用了Spring最不推荐的注解方式
前言 使用Spring框架最核心的两个功能就是IOC和AOP.IOC也就是控制反转,我们将类的实例化.依赖关系等都交由Spring来处理,以达到解耦合.利用复用.利于测试.设计出更优良程序的目的.而对 ...
- (转)Spring的bean管理(注解方式)
http://blog.csdn.net/yerenyuan_pku/article/details/69663779 Spring的bean管理(注解方式) 注解:代码中的特殊标记,注解可以使用在类 ...
- Spring 的 Bean 管理(注解方式)
Spring 的 Bean 管理(注解方式) 1. 导入必要的 jar 包和 xml 文件 使用注解需要导入 spring-aop 的 jar 包. applicationContext.xml 文件 ...
- spring AOP自定义注解方式实现日志管理
今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...
- 基于Spring实现策略模式
背景: 看多很多策略模式,总结下来实现原理大体都差不多,在这里主要是讲解下自己基于Spring更优雅的实现方案:这个方案主要是看了一些开源rpc和Spring相关源码后的一些思路,所以在此进行总结 首 ...
- 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版
欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...
- springboot aop 自定义注解方式实现完善日志记录(完整源码)
版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 一:功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能. 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型 ...
- springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)
https://www.cnblogs.com/wenjunwei/p/9639909.html https://blog.csdn.net/tyrant_800/article/details/78 ...
- Spring知识点总结(三)之注解方式实现IOC和DI
1. 注解概念 所谓注解就是给程序看的提示信息,很多时候都用来作为轻量级配置的方式. 关于注解的知识点,参看java基础课程中java基础加强部分的内容. 2 ...
随机推荐
- pwnable.kr第三题bof
Running at : nc pwnable.kr 9000 IDA查看 1 unsigned int __cdecl func(int a1) 2 { 3 char s; // [esp+1Ch] ...
- 使用C# (.NET Core) 实现模板方法模式 (Template Method Pattern)
本文的概念内容来自深入浅出设计模式一书. 项目需求 有一家咖啡店, 供应咖啡和茶, 它们的工序如下: 咖啡: 茶: 可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, ...
- Detach blobs with a contact point
https://answers.opencv.org/question/87583/detach-blobs-with-a-contact-point/ 一.问题描述 带有接触点的斑点时遇到问题,需要 ...
- 批量SSH key-gen无密码登陆认证脚本 附件脚本
# 批量实现SSH无密码登陆认证脚本 ## 问题背景 使用为了让linux之间使用ssh不需要密码,可以采用了数字签名RSA或者DSA来完成.主要使用ssh-key-gen实现. 1.通过 ssh-k ...
- 「HTML+CSS」--自定义加载动画【010】
前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...
- 百度开源中国(Java)面经
一.自我介绍 面试嘛,万年不变还是自我介绍,就说说你是干嘛的(专业是啥),为什么会选择该公司(说一说自己为何向往Java开发),再谈谈自己的优点(兴趣爱好).如果人家叫停了,就别一股脑接着讲了,停下来 ...
- [树形DP]战略游戏
战 略 游 戏 战略游戏 战略游戏 题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题.他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上 ...
- 神奇的魔方阵--(MagicSquare)(1)
本篇文章只对奇数阶以及偶数阶中阶数n = 4K的魔方阵进行讨论.下面就让我们进入正题: 1 :魔方阵的相关信息:(百度百科) https://baike.baidu.com/item/%E9%AD%9 ...
- java面试-谈谈你对OOM的理解
一.OOM(OutOfMemoryError): 对象无法释放或无法被垃圾回收,造成内存浪费,导致程序运行速度减慢,甚至系统崩溃等严重后果,就是内存泄漏.多个内存泄漏造成可使用内存变少,会导致内存溢出 ...
- Azure Digital Twins(2)- 在本地使用ADT Explorer 管理数字孪生
本文介绍: 在本地运行ADT Explorer 并连接Azure Digital Twins 实例: 使用 VS CODE DTDL插件开发第一个 模型文件: ADT Explorer的几个基本功能: ...