@autowired注解 抽象类_别再用ifelse了,用注解去代替他吧

来自公众号:咖啡拿铁

策略模式

经常在网上看到一些名为“别再if-else走天下了”,“教你干掉if-else”等之类的文章,大部分都会讲到用策略模式去代替if-else。策略模式实现的方式也大同小异。主要是定义统一行为(接口或抽象类),并实现不同策略下的处理逻辑(对应实现类)。客户端使用时自己选择相应的处理类,利用工厂或其他方式。

注解实现

本文要说的是用注解实现策略模式的方式,以及一些注意点。
话不多说,还是以最常 见的订单处理为例。首先定义这样一个订单实体类:

@Data
public class Order {
    /**
     * 订单来源
     */
    private String source;
    /**
     * 支付方式
     */
    private String payMethod;
    /**
     * 订单编号
     */
    private String code;
    /**
     * 订单金额
     */
    private BigDecimal amount;
    // ...其他的一些字段
}

假如对于不同来源(pc端、移动端)的订单需要不同的逻辑处理。项目中一般会有OrderService这样一个类,如下,里面有一坨if-else的逻辑,目的是根据订单的来源的做不同的处理。

@Service
public class OrderService {

    public void orderService(Order order) {
        if(order.getSource().equals("pc")){
            // 处理pc端订单的逻辑
        }else if(order.getSource().equals("mobile")){
            // 处理移动端订单的逻辑
        }else {
            // 其他逻辑
        }
    }
}

策略模式就是要干掉上面的一坨if-else,使得代码看起来优雅且高大上。
现在就让我们开始干掉这一坨if-else。先总览下结构:

35c439640f61bce590a543577921d0d1.png

1.首先定义一个OrderHandler接口,此接口规定了处理订单的方法。
public interface OrderHandler {
    void handle(Order order);
}

2.定义一个OrderHandlerType注解,来表示某个类是用来处理何种来源的订单。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface OrderHandlerType {
    String source();
}

3.接下来就是实现pc端和移动端订单处理各自的handler,并加上我们所定义的OrderHandlerType注解。

@OrderHandlerType(source = "mobile")
public class MobileOrderHandler implements OrderHandler {
    @Override
    public void handle(Order order) {
        System.out.println("处理移动端订单");
    }
}

@OrderHandlerType(source = "pc")
public class PCOrderHandler implements OrderHandler {
    @Override
    public void handle(Order order) {
        System.out.println("处理PC端订单");
    }
}

4.以上准备就绪后,就是向spring容器中注入各种订单处理的handler,并在OrderService.orderService方法中,通过策略(订单来源)去决定选择哪一个OrderHandler去处理订单。我们可以这样做:

@Service
public class OrderService {

    private Map orderHandleMap;@Autowiredpublic void setOrderHandleMap(List orderHandlers) {// 注入各种类型的订单处理类
        orderHandleMap = orderHandlers.stream().collect(
                Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class).source(),
                        v -> v, (v1, v2) -> v1));
    }public void orderService(Order order) {// ...一些前置处理// 通过订单来源确定对应的handler
        OrderHandler orderHandler = orderHandleMap.get(order.getSource());
        orderHandler.handle(order);// ...一些后置处理
    }
}

在OrderService中,维护了一个orderHandleMap,它的key为订单来源,value为对应的订单处理器Handler。通过@Autowired去初始化orderHandleMap(这里有一个lambda表达式,仔细看下其实没什么难度的)。这样一来,OrderService.orderService里的一坨if-else不见了,取而代之的仅仅是两行代码。即,先从orderHandleMap中根据订单来源获取对应的OrderHandler,然后执行OrderHandler.handle方法即可。
这种做法的好处是,不论以后业务如何发展致使订单来源种类增加,OrderService的核心逻辑不会改变,我们只需要实现新增来源的OrderHandler即可,且团队中每人开发各自负责的订单来源对应的OrderHandler即可,彼此间互不干扰。

到此,似乎已经讲完了通过注解实现策略模式,干掉if-else的方法,就这样结束了吗?不,真正的重点从现在才开始。

现在回过头看orderHandleMap这个Map,它的key是订单来源,假如,我们想通过订单来源+订单支付方式这两个属性来决定到底使用哪一种OrderHandler怎么办?比如PC端支付宝支付的订单是一种处理逻辑(PCAliPayOrderHandler),PC端微信支付的订单是另外一种处理逻辑(PCWeChatOrderHandler),对应的还有移动端支付宝支付(MobileAliPayOrderHandler)和移动端微信支付(MobileWeChatOrderHandler)。

这时候我们的key应该存什么呢,可能有人会说,我直接存订单来源+订单支付方式组成的字符串不就行了吗?确实可以,但是如果这时业务逻辑又变了(向pm低头),PC端支付宝支付和微信支付是同一种处理逻辑,而移动端支付宝支付和微信支付是不同的处理逻辑,那情况就变成了PCAliPayOrderHandler和PCWeChatOrderHandler这两个类是同一套代码逻辑。我们干掉了if-else,但却造出了两份相同的代码,这是一个作为有强迫症的程序员所不能容忍的。怎么干掉这两个逻辑相同的类呢?

首先,我们可以回顾下,注解它究竟是个什么玩意?不知道大家有没有注意到定义注解的语法,也就是@interface,与定义接口的语法想比,仅仅多了一个@。翻看jdk,可以找到这么一个接口Annotation,如下

/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does not define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * More information about annotation types can be found in section 9.6 of
 * The Java™ Language Specification.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation type from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
  // …省略
}

开头就表明了,The common interface extended by all annotation types。说的很明白了,其实注解它就是个接口,对,它就是个接口而已,@interface仅仅是个语法糖。那么,注解既然是个接口,就必然会有相应的实现类,那实现类哪里来呢?上述中我们仅仅定义了OrderHandlerType注解,别的什么也没有做。这时候不得不提动态代理了,一定是jdk在背后为我们做了些什么。
为了追踪JVM在运行过程中生成的JDK动态代理类。我们可以设置VM启动参数如下:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

该参数可以保存所生成的JDK动态代理类到本地。额外说一句,若我们想追踪cglib所生成的代理类,即对应的字节码文件,可以设置参数:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "保存的路径");

添加参数后再次启动项目,可以看到我们项目里多了许多class文件(这里只截取了部分,笔者启动的项目共生成了97个动态代理类。由于我的项目是springboot环境,因此生成了许多如ConditionalOnMissingBean、Configuration、Autowired等注解对应的代理类):

2f6f6dce6dad386658816b976c32e40b.png

那接下来就是要找到我们所关心的,即jdk为我们自定义的OrderHandlerType注解所生成的代理类。由于jdk生成的动态代理类都会继承Proxy这个类,而java又是单继承的,所以,我们只需要找到实现了OrderHandlerType的类即可。遍历这些类文件,发现$Proxy63.class实现了我们定义的OrderHandlerType注解:

public final class $Proxy63 extends Proxy implements OrderHandlerType {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public $Proxy63(InvocationHandler var1) throws  {
        super(var1);
    }

    // …省略
}

我们知道,jdk动态代理其实现的核心是:

acd89f31bc70b8f730cc3037a78964f1.png

也就是这个构造函数的InvocationHandler对象了。那么注解的InvocationHandler是哪个具体实现呢?不难发现就是:
6aec2b6932a5e82ec663fb2f42fd2ba6.png

这个类里面的核心属性,就是那个 memberValues,我们在使用注解时给注解属性的赋值,都存储在这个map里了。而代理类中的各种方法的实现,实际上是调用了 AnnotationInvocationHandler 里的 invoke 方法。
好了,现在我们知道了注解就是个接口,且通过动态代理实现其中所定义的各种方法。那么回到我们的OrderService,为什么不把key的类型设置为OrderHandlerType?就像这样。
private Map orderHandleMap;

如此一来,不管决定订单处理器orderhandler的因素怎么变,我们便可以以不变应万变(这不就是我们所追求的代码高扩展性和灵活性么)。那当我们的map的key变成了OrderHandlerType之后,注入和获取的逻辑就要相应改变,注入的地方很好改变,如下:

public class OrderService {
    private Map orderHandleMap;@Autowiredpublic void setOrderHandleMap(List orderHandlers) {// 注入各种类型的订单处理类
        orderHandleMap = orderHandlers.stream().collect(
            Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class),
                    v -> v, (v1, v2) -> v1));
    }// ...省略
}

那获取的逻辑要怎么实现?我们怎么根据order的来源和支付方式去orderHandleMap里获取对应的OrderHandler呢?问题变成了如何关联order的来源和支付方式与OrderHandlerType注解。
还记得刚才所说的注解就是个接口吗,既然是个接口,我们自己实现一个类不就完事了么,这样就把order的来源和支付方式与OrderHandlerType注解关联起来了。说干就干,现在我们有了这么一个类,

public class OrderHandlerTypeImpl implements OrderHandlerType {

    private String source;
    private String payMethod;

    OrderHandlerTypeImpl(String source, String payMethod) {
        this.source = source;
        this.payMethod = payMethod;
    }

    @Override
    public String source() {
        return source;
    }

    @Override
    public String payMethod() {
        return payMethod;
    }

    @Override
    public Class extends Annotation> annotationType() {
        return OrderHandlerType.class;
    }

}

在获取对应OrderHandler时我们可以这样写,

public void orderService(Order order) {
    // ...一些前置处理

    // 通过订单来源确以及支付方式获取对应的handler
    OrderHandlerType orderHandlerType = new OrderHandlerTypeImpl(order.getSource(), order.getPayMethod());
    OrderHandler orderHandler = orderHandleMap.get(orderHandlerType);
    orderHandler.handle(order);

    // ...一些后置处理
}

看起来没什么问题了,来运行一下。不对劲啊,空指针,那个异常它来了。

5d9b9db3f8d5072ee6cbc2048aa12a4b.png

我们断点打在NPE那一行,
678b033003ae723b69abb22d2d825a4b.png

一定是姿势不对,漏掉了什么。那我们就来分析下。orderHandleMap中确实注入了所定义的几个OrderHandler(PCAliPayOrderHandler、PCWeChatOrderHandler、MobileAliPayOrderHandler、MobileWeChatOrderHandler),但是get却没有获取到,这是为什么呢?我们来回忆下,map的get方法逻辑,还记得最开始学习java时的hashCode和equals方法吗?
不知道大家注意到没有,map中key对应的类型是$Proxy63(jdk动态代理给我们生成的),跟我们自己实现的OrderHandlerTypeImpl是不同类型的。
梳理下,在Autowied时,我们放进orderHandleMap的key是动态代理类对象,而获取时,自定义了OrderHandlerTypeI实现类OrderHandlerTypeImpl,而又没有重写hashCode和equals方法,才导致从map中没有获取到我们所想要的OrderHandler,那么,我们把实现类OrderHandlerTypeImpl的hashCode和equals这两个方法重写,保持跟动态代理类生成的一样不就行了吗?再回看下动态代理给我们生成的这两个方法,前面说了,注解对应的代理类方法调用实际上都是AnnotationInvocationHandler里面的方法,翻看AnnotationInvocationHandler里面的hashCode和equals方法:
private int hashCodeImpl() {
    int var1 = 0;
    Entry var3;
    for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
        var3 = (Entry)var2.next();
    }
    return var1;
}

private Boolean equalsImpl(Object var1) {
    if (var1 == this) {
        return true;
    } else if (!this.type.isInstance(var1)) {
        return false;
    } else {
        Method[] var2 = this.getMemberMethods();
        int var3 = var2.length;
        for(int var4 = 0; var4             Method var5 = var2[var4];
            String var6 = var5.getName();
            Object var7 = this.memberValues.get(var6);
            Object var8 = null;
            AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
            if (var9 != null) {
                var8 = var9.memberValues.get(var6);
            } else {
                try {
                    var8 = var5.invoke(var1);
                } catch (InvocationTargetException var11) {
                    return false;
                } catch (IllegalAccessException var12) {
                    throw new AssertionError(var12);
                }
            }
            if (!memberValueEquals(var7, var8)) {
                return false;
            }
        }
        return true;
    }
}

具体的逻辑也比较简单,就不分析了。那我们就按照AnnotationInvocationHandler中的实现,在我们的OrderHandlerTypeImpl中按照相同的逻辑重写下这两个方法,如下

public class OrderHandlerTypeImpl implements OrderHandlerType {

    // …省略

    @Override
    public int hashCode() {
        int hashCode = 0;
        hashCode += (127 * "source".hashCode()) ^ source.hashCode();
        hashCode += (127 * "payMethod".hashCode()) ^ payMethod.hashCode();
        return hashCode;
    }


    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof OrderHandlerType)) {
            return false;
        }
        OrderHandlerType other = (OrderHandlerType) obj;
        return source.equals(other.source()) && payMethod.equals(other.payMethod());
    }
}

再次运行看看是否达到我们预期,果不其然,这次可以正常获取到了handler,至此,大功告成。

96ef857a9bd18f7e13499b1d3fea5492.png

这样以来,不管以后业务怎么发展,OrderService核心逻辑不会改变,只需要扩展OrderHandler即可。

如果大家觉得这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:


●编号1299,输入编号直达本文

●输入m获取文章目录

程序员求职面试

b49de7ba009772f7e426119dd7b69a2a.png

分享程序员找工作经验

程序员笔试、面试题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

卸载mysql8.0卸载程序_程序卸载失败,来使用微软官方的Windows卸载工具试试

在我们实际的Windows操作系统使用中,总会遇到一些程序无法成功卸载的情况。当然,我们可以使用第三方软件来进行尝试卸载。要么,第三方软件会进行收费。要么,免费的会带给你捆绑软件的安装或者烦人的广告。还可能会造成系统文件的误…

英特网rst服务器未在运行,技术员示范win7系统提示“英特尔(R)RST服务未在运行”的详细办法...

不知道大家有没有遇到过win7系统提示“英特尔(R)RST服务未在运行”的问题,最近有很多朋友来向我咨询win7系统提示“英特尔(R)RST服务未在运行”的问题如何解决,于是小编就个大家整理的win7系统提示“英特尔(R)RST服务未在运行”的解决步骤,就…

小米手机电池恢复代码_解决小米手机屏幕问题的方法

屏幕在我们的小米手机中永远不会失败,因为没有它,我们将无法使用可用的应用程序和功能。为了帮助大家,我们汇总了小米和Redmi手机中最常见的问题,为您提供了针对我们所面临的每种情况的解决方案。MIUI定制层汇集了适用于小米手机的…

【原】winform高效导出Excel带格式设置

参考网上的方法修改,1000条记录导出只要3S(1GRDM,C2.8CPU).1.项目添加excel对象类库的引用,Microsoft Excel 11.0 object library(不同版本的Excel,类库不同,这是2003的) 2.代码1//-…

vscode 逗号不换行_来自一个插件的疯狂夸赞,VS Code「彩虹屁」插件问世,网友:我想要郭德纲版...

听说有人开发了「虚拟程序员鼓励师」,安装使用后的我疯狂捶墙,导致我家变成了单间。机器之心报道,机器之心编辑部。你可能从未想过,会有这么一个人:对你写的每一行代码都不吝夸赞,知道你什么时候想骂人&…

电压源和电流的关联参考方向_结点电压法解题系列之四:电流源支路

利用结点电压法求解时,有的结点之间连接的是电流源支路。如图1所示电路中,结点2与参考结点4之间连接的是5A电流源。对结点2列写标准形式KCL,或直接利用KCL列出结点电压方程,都很容易,也不易出错。只需要注意&#xff1…

狼人杀服务器紧急维护中,狼人杀最可怕的武器是那张嘴?禁言长老:你已被管理员禁言一天!...

语言的能力有多强?不论是用来攻击对手,或是讨好他人,讽刺或是欺骗,语言有着神秘的力量,仿佛能够颠倒黑白。对于那些"口吐芬芳"的人,更是恨不得在他嘴上贴个封条,让他好好安静一会。相…

炎炎夏日需要一个清凉的地 - 自制水冷系统(十一 指尖的思绪之程序篇)

前段时间接手了一个项目,所以DIY的进程有些停滞。实际编写的程序并没有多长时间,得益于Keil这个强大的IDE。能在第一次做51开发的时候,如此顺利的完成代码。不多说废话了,说明下代码的具体思路。具体思路根据(八 系统设…

wordpress 后台慢_建站经验-wordpress用户注册收不到验证邮件

背景我的一个博客http://axuretop.com,已经在后台开放注册了,注册用户默认为“订阅者”。一旦发布新的文章,会订阅邮件给用户。后台有300多个订阅用户,但是发现评论文章的很少,严重怀疑他们无法订阅我的文章&#xff0…

ai字体行间距怎么调整_字体基础知识(一)

本期教程主要讲解字体基础知识:字体、字号、字形、字重、行高、行距、字间距、字偶间距。本文所用软件为Microsoft PowerPoint 2013。1 字体文字的风格样式,就是我们平时常说的黑体、宋体、楷体、Segoe UI Black、Courier New、Consolas。如下图所示&…

免费的crm系统部署在自己的服务器,crm系统本地部署与云端部署的区别

CRM系统的部署方式通常有三种模式,分别是:本地部署、云端部署、公有云部署等三种模式。通常企业在选择的部署方式是本地部署与云端部署。那么他们部署方式得区别是什么?本地部署CRM系统本地部署的CRM软件:是意味着保存在企业自身的服务器中。…

gitee怎么仓库间传文件_实现一个简单的基于码云(Gitee) 的 Storage

实现一个简单的基于码云(Gitee) 的 StorageIntro上次在 asp.net core 从单机到集群 一文中提到存储还不支持分布式,并立了一个 flag基于 github 或者 开源中国的码云实现一个 storage于是这两天就来填坑了。。实现了一个简单的基于开源中国的码云的 storage准备工作…

载波聚合或双连接的方式进行_智能电表常用远程抄表方式,您想知道吗?--老兵聊电之...

智能电表的远程抄表方式有多种方式,但老王更喜欢以下几种抄表方式,希望您能接受。一、智能电表的工作原理1.智能电表主要是由电子元器件构成,其工作原理是先通过对用户供电电压和电流的实时采样,再采用专用的电能表集成电路&#…

搞个服务器安装黑群晖系统,牛人闲置电脑大改造!超低成本组建家用黑群晖NAS...

一、前言:大家好,俺又来了。这篇原创很早就在构思怎么写了,因为确实作为一个数码爱好者来说,当得知有 群晖 威联通 这种NAS 的东西存在的时候,就忍不住想体验体验。但是奈何自己的资金不够,想一想&#xff…

立体旋转查看图片

声明:如果程序有问题,请各位大虾多多指点,谢谢。 基于psoft.js制作的一款立体旋转查看图片应用 1.可以通过鼠标滑动来操作图片的旋转,可以向右,向左拖动 a。向左滑动 b。向右滑动 c。向左转到一下 2.代码实现&#xff…

asterisk架构

从架构的角度看来,Asterisk是由许多不同的模块组成的。在设计基于Asterisk的系统时,这种模块化的特性,提供了几乎无限的灵活必。作为Asterisk系统管理员,你拥有选择加载模块的权利。你所加载的每一个模块,都提供了不同…

esp虚拟服务器,esp8266接入云服务器

esp8266接入云服务器 内容精选换一换公有云平台提供的云监控,可以对云耀云服务器的运行状态进行日常监控。您可以通过管理控制台,直观地查看云耀云服务器的各项监控指标。云耀云服务器正常运行。关机、故障、删除状态的云耀云服务器,无法在云…

c++ primer 第六版 pdf_A3N630 塑壳断路器如何更换.pdf

太阳能单晶炉专用配套变压器,是我公司研发的生产太阳能单晶硅的单晶炉设备专用变压器,本产品作为太阳能单晶炉设备的配套产品,它将电网电源的三相380V电压转换成较低的三相交流50V电压,为加热提供电源,以得到设备加热所…

datareader对象直接转化为int_Integer、new Integer() 和 int 比较的面试题

作者:chenxiangxiang来源:https://www.cnblogs.com/cxxjohnson/p/10504840.html基本概念的区分:1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型2、Integer 变量必须实例化后才能使用,而int变量不需要3、…

个人推荐的SIP software Phone

开源免费的LinPhone,国内很多人基于他继续二次开发 http://www.linphone.org/ Zoiper 有免费的,有收费的,有SDK,支持SIP/IAX(没有NAT问题) 官方下载的免费版本里面就支持中文! 免费版本支持:for Window…