Keep Thinking Keep Moving

整理自《编写高质量代码:改善Java程序的151条建议》

一、Java开发中通用的方法和准则

  • 不要在常量和变量中出现易混淆的字母;
  • 莫让常量蜕变成变量;
  • 三元操作符的类型务必一致;
  • 避免带有变长参数的方法重载;
  • 别让null值和空值威胁到变长方法;
  • 覆写变长方法也要循规蹈矩;
  • 警惕自增的陷阱;
  • 不要让旧语法困扰你;
  • 少用静态导入;
  • 不要在本类中覆盖静态导入的变量和方法;
  • 养成良好习惯,显式声明UID;
  • 避免用序列化类在构造函数中为不变量赋值
  • 避免为final变量复杂赋值;
  • 使用序列化类的私有方法巧妙解决部分属性持久化问题;
  • break万万不可忘;
  • 易变业务使用脚本语言编写;
  • 慎用动态编译;
  • 避免instantceof非预期结果;
  • 断言对决不是鸡肋;
  • 不要只替换一个类;

二、基本类型

  • 使用偶判断,不用奇判断;
  • 用整数类型处理货币;
  • 不要让类型默默转换;
  • 边界,边界,还是边界;
  • 不要让四舍五入亏了一方;
  • 提防包装类型的null值;
  • 谨慎包装类型的大小比较;
  • 优先使用整型池;
  • 优先选择基本类型;
  • 不要随便设置随机种子;

三、类、对象及方法

  • 在接口中不要存在实现代码;
  • 静态变量一定要先声明后赋值;
  • 不要覆写静态方法;
  • 构造函数尽量简化;
  • 避免在构造函数中初始化其他类;
  • 使用构造代码块精炼程序;
  • 使用静态内部类提供封装性;
  • 使用匿名类的构造函数;
  • 匿名类的构造函数很特殊;
  • 让多重继承成为现实;
  • 让工具类不可实例化;
  • 避免对象的浅拷贝;
  • 推荐使用序列化实现对象的拷贝;
  • 覆写equals方法时不要识别不出自己;
  • equals应该考虑null值情景;
  • 在equals中使用getClass进行类型判断;
  • 覆写equals方法必须覆写hashCode方法;
  • 推荐覆写toString方法;
  • 使用package-info类为包服务;
  • 不要主动进行垃圾回收;

2019-12-31 0 评论 180 浏览
阅读全文

  • 尽可能使用局部基本数据类型变量。
  • 及时关闭流。
  • 尽可能多使用三目运算符,代码看起来会比较清晰
  • 尽量减少对变量的重复计算。
  • 尽量采用懒加载的策略,即在需要的时候才创建。
    如果已知列表的长度,为底层以数组方式实现的集合、工具类指定初始长度。
  • 基于效率和类型检查的考虑,应该尽可能使用Array,无法确定数组大小时才使用ArrayList。
  • 当复制大量数据时,使用System.arraycopy()命令。
  • 乘法和除法可以使用移位操作进行优化。除非所需性能要求高,不然不必用移位来进行优化。例如我们平常的业务开发使用移位进行优化,反而使得代码的可读性降低。
  • 接口中的方法、属性尽量不要加任何访问修饰符。依据Alibaba的java代码规范。
  • 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
  • 在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
  • 尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销。
  • 不要将数组声明为public static final。否则不管这个数组有没有使用大,JVM都会为这个数组保留内存空间。
  • 尽量在合适的场合使用单例。
  • 尽量避免随意使用静态变量。

2019-12-30 0 评论 38 浏览
阅读全文

之前读完《重构,改善代码既有设计》一书,书中内容虽然简单,但却有效。一些小小的重构积累起来便使得我们的代码开始变得更优雅。印象很深的是作者倡导的事不过三的原则,代码出现三次重复即需要进行重构。我深有体会,经常觉得自己的某些设计不好,但是又跟自己说下次再一起重构吧?结果日复一日需要重构的设计越来越多,导致重构的成本越高,越不敢重构。这篇文章再温习一下重构一书中的内容。

尽信书不如无书。书中只是建议并非标准,有些时候我们需要具体情况具体分析。

代码的坏味道

1. 重复代码

重复代码意味着冗余,当重复的代码需要修改时要修改所有重复的地方,稍微疏忽便会出现bug。

2. 过长函数

当函数过长时,人很难一下掌握太多的细节,使得修改这个函数的成本很高。

3. 过大的类

单个类承担的责任过多,违反单一职责而形成过大的类。

4. 过长的参数列

长的参数列简直是噩梦,因为稍有不慎将类型相同的参数填错顺序就引发Bug了。(好在IDEA编译器能对填入的每个参数提示参数名)

5. 发散式变化

就是类受多个变化的影响,是违反迪米特法则的结果。类应该尽可能少的与其他类打交道,避免非必要的关联。

6. 霰弹式修改

单个变化引发多个类的相应修改。

7. 依恋情节

当一个类的行为严重依赖其他类的时候,我们需要思考这个行为真正的归属。

8. 数据泥团

两个类中相同的字段或者方法签名中相同的参数总是一起出现,可能这些字段可以自成一类。

9. 基本类型偏执

此基本类型并不是指int、long这类,而是指类中出现很多小字段,比如省、市、区、住址可以封装成一个地址对象作为User类中的一个属性。

10. Switch问题

当出现Switch重复时,同样的Switch散布在不同的地方,增加一个新的case,需要找到所有Switch进行增加。

11. 平行继承体系

每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。

12. 夸夸奇谈的未来性

过度设计。

13. 冗赘类

用处微乎其微以致于不如不作为一个类。或者作为内部类。

14. 令人迷惑的临时字段

类中的某些字段仅为特殊情况而定,或者类中的字段仅为了某个函数的方便声明为成员变量而没有其他用处。

15. 过度耦合的调用链

一个对象向另一个对象发起一个请求,再由另一个对象请求其他对象。

16. 中间人

无用的委托,过多中间层。

17. 狎昵关系

两个类关系过于紧密。一个类过于关注另一个类的成员,使得高耦合。

18. 异曲同工的类

不同的类或者函数,做着相同的事。

19. 不完美的类库

类库不能满足实际开发需求。

20. 纯数据类

类似于DDD中所阐述的贫血模型,仅有数据没有行为的类。

21. 被拒绝的遗赠

子类继承了父类不必要的函数或者数据。

22. 过多的注释

过多的注释说明代码的自解释能力很差。需要重构。


2019-12-30 0 评论 187 浏览
阅读全文

原文地址:《The JVM Architecture Explained 》

每个Java开发者都知道字节码是被JRE(Java运行时环境)所执行的。但是许多人并不知道实际上JRE是Java虚拟机(JVM)的实现。它分析字节码,解释代码并执行它。作为一个开发者,了解JVM的体系结构是非常重要的,因为它使我们能更有效的编写代码。在这篇文章中,我们将更深入的了解Java中的JVM体系结构和JVM的不同组件。

什么是JVM?

虚拟机是物理机器的软件实现。Java语言当时是基于编写一次到处运行的理念被开发出来的,其中到处运行指的是运行在虚拟机上。编译器将java文件编译成.class文件,然后.class文件被输入到JVM中,JVM会加载并运行这些.class文件。下图展示了JVM的体系结构。

JVM体系结构
JVMArchitecture.png

2019-12-22 2 评论 307 浏览
阅读全文

看到一篇关于Java内存模型的技术文章于是翻译一下供大家学习交流。

原文地址:《Java Memory Model》

java内存模型指定了java虚拟机如何与计算机的内存(RAM)进行工作。java虚拟机是一整个计算机的抽象模型,所以这个模型天然的包括了内存模型,它被称之为java内存模型。

如果你想设计正确运作的并发程序的话,那么理解java内存模型是非常重要的。Java内存模型指定了不同线程如何以及何时可以看到其他线程写入共享变量的值,以及在必要时如何同步对共享变量的访问。
在最初版本的Java内存模型是不足的,所以在Java 1.5版本中进行了修正。这个版本的Java内存模型沿用至Java 8中。

Java内存模型的内部

在JVM的内部使用了Java内存模型将内存划分给线程栈和堆。下图从逻辑视角说明了Java内存模型。

javamemorymodel1.png

每一个运行在虚拟机的线程都拥有一个它独有的线程栈。线程栈包含了关于线程方法调用中已到达当前执行点方法的信息。我们将它称之为调用栈。当线程执行到它的代码,它的调用栈就会改变。

线程栈同时包含了被调用的每一个方法的所有局部变量(所有方法都在调用栈上)。一个线程只能访问它自己的线程栈。局部变量除了创建它们的线程之外,对于其他线程都是不可见的。甚至两个线程正在执行同一段代码,这两个线程仍然会创建代码中的局部变量在它们各自的线程栈之中。因此,每个线程都有每个自己版本的局部变量。

2019-12-11 0 评论 169 浏览
阅读全文
[译]Java NIO vs. IO 有更新!

英文原文地址:《Java NIO vs. IO》

当学习Java NIO和IO的API用法时,一个问题就从脑海里冒出:什么时候我们该用IO,什么时候我们该用NIO呢?在这篇文章中我会阐明Java NIO和IO之间的不同点和使用场景以及它们是如何影响代码的设计。

NIO和IO主要的不同点

以下的表格总结了NIO和IO之间主要的不同点。我会以更多的细节去阐明这些不同点在接下来的几节中。

IONIO
面向流面向缓冲区(Buffer)
阻塞IO非阻塞IO
Selector(选择器)

面向流与面向缓冲区

第一个不同点在于IO是面向流的而NIO是面向缓冲区的。所以这意味着什么?

Java IO是面向流的意味着你从一个流中一次读取一个或者多个字节。如何处理读出的字节取决于你。它们不会缓存到任何地方。此外你不能在流中来回移动数据。如果你需要来回移动从流中读取的数据,你需要将其先缓存到一个缓冲区中。

Java面向缓冲区的方法稍微有点不同。数据先被读入到一个缓冲区,然后再处理。你也可以将你的数据在缓冲区中来回移动。这使得你在处理过程中多了一些灵活性。但是,你必须检查缓冲区内是否包含了完整处理过程所需的所有数据。并且你必须确保当读入更多数据到缓冲区时,不能覆盖尚未处理的数据。

2019-12-17 0 评论 194 浏览
阅读全文
2019-12-10 0 评论 178 浏览
阅读全文

在从事开发多年之后,你是否会感觉自己只是一个业务CRUD Boy,并认为业务没有多少技术含量。你是否会陷入业务的泥潭中,各种复杂交错的业务规则使得代码开始腐烂开始失控,项目开始变得难以维护,迭代举步维艰。如果你开始意识到这个问题的话,那么我十分推荐你开始学习领域驱动设计面向领域建模的设计方式。

DDD是什么呢?

DDD即Domain Driven Design,翻译成中文的话就是领域驱动设计,首先我们应该先理解这里的领域是什么意思?假设公司内部正在开发一套电商平台,而电商平台中包含了库存、订单、商品等核心业务。这些核心业务逻辑其实呈现的就是电商平台领域。通俗的理解就是一整套体系的业务知识即代表了一个领域。好比在线教育平台,它需要有一套体系的业务,包括招生、线上教学、课程等内容。我们将这些业务抽象出领域模型,而这些领域模型表达了产品经理所阐述的业务需求,我们反复地用这些领域模型与产品经理进行讨论沟通最终确定初步的领域模型。再使用初步的领域模型指导代码设计开发。

简而言之就是:

业务知识 --> 领域模型 --> 项目设计与代码开发

不同的电商平台的核心业务逻辑大都是相似的,这部分领域知识是可以进行复用,区别在于不同公司使用的不同的编程语言,不同的前端控制框架,数据库框架。采用领域驱动设计的好处在于项目以领域模型为核心,而Spring MVC、Struts等前端控制框架或者Hibernate、Mybatis对象数据库框架属于外围技术基础,领域模型其实并不与这些基础技术产生耦合,所以在领域模型不变的情况下,我们是很容易对我们的基础设施进行更换的。

2019-11-06 0 评论 359 浏览
阅读全文

上下文映射图的英文是Context Map其实这个翻译挺难理解的,上下文映射图其实就是不同上下文是如何进行交流的关系。由于上下文映射图内容比较少。以下内容摘自《领域驱动设计精粹》。

三种集成方式

  1. RPC方式
  2. 消息队列或者发布-订阅机制。
  3. RESTful方式

上下文映射的种类

  • 合作关系:
    合作关系存在于两个团队之间。每个团队各自负责一个限界上下文。两个团队通过互相依赖的一整套目标联合起来形成合作关系。一损俱损,一荣俱荣。由于相互之间的联系非常紧密,他们经常会对同步日程安排和相互关联的工作。他们还必须使用持续集成对保持集成工作协调一致。

  • 共享内核:
    两个或者多个团队之间共享着一个小规模但却通用的模型。团队必须就要共享的模型元素达成一致。有可能他们当中只有一个团队会维护,构建及测试共享模型的代码。

2019-11-29 0 评论 227 浏览
阅读全文

战术设计是从微观视角对单个微服务的编码设计,而战略设计是从宏观视角对多个微服务的交互设计。从人体学来说,一个器官的内部构造属于战术设计,多个器官之间的协作属于战略设计。好比心肺器官之间的协作配合。

为什么需要战略设计?

假设我们在设计订单模型时,下单操作涉及到会员等相关规则,比如增加积分等操作。(此时我们还未将订单子域和会员子域进行剥离)。其实一个用户的下单操作不需要与会员的积分产生强烈耦合。一旦会员的积分制度发生变动,又得需要在订单子域中进行修改,而订单子域又是我们的核心域,频繁的对核心域进行更改的风险较大。那如果将会员体系从订单核心域剥离出去呢?我们便使得两个子域的职能更为清晰并且解耦,这也体现了单一职责的设计原则。

假设一个开发小组既负责订单逻辑又要负责会员积分逻辑,则使得这个开发小组本身的职责变得复杂。如果使将订单和会员拆分开来,通过上下文之间的交互(即微服务之间的交互)使得订单开发小组成员、会员开发小组各自关心自己的领域(即各自只关心各自的业务逻辑)。

好比流水线上的工人,有利于分工,大家专注于自己负责的工序。

战略设计的元素主要有子域、限界上下文、上下文映射图。

2019-11-28 0 评论 238 浏览
阅读全文

为什么使用资源库?

如果完全按照领域模型的角度,完全通过遍历对象的方法来获取所有关联的对象。这种模型会过于错综复杂。对象嵌套的层级或者关联的层级非常深。例如通过Customer.order.product.price层层遍历来获取当时客户订单的商品的价格。

那如果完全按照数据库模型的角度,模型中的对象不需要完全连接起来,对象关系网就能保持在一个可控范围。但是这又会回到之前传统开发模式中,零散的使用各个DAO从各个表抽取数据自行拼凑出我们想要的模型。

这里就会出现一个问题,Customer类需要保持客户所有已订的Order,还是通过CustomerID在数据库中查找Order列表呢?

对象关联还是纯数据库?

客户需要一种有效的方式来获取已存在的领域对象的引用。如果基础设施提供了这方面的遍历,那么开发人员可能会增加很多可遍历的关联,这会使模型变得非常混乱。另一方面,开发人员可能使用查询从数据库中提取他们所需的数据,或是直接提取具体的对象,而不是通过聚合的根来得到这些对象。这样就导致领域逻辑进入查询和客户代码中,而实体和值对象变成了单纯的数据容器。采用大多数处理数据库访问的技术复杂性很快就会使客户代码变得混乱,这将导致开发人员简化领域层,最终使模型变得无关紧要。

我们需要找到一个恰当的方式。

我们不需要对那么很容易通过遍历来找到的持久对象进行查询访问,如不需要单独的去获取地址,而可以通过Person对象来获取地址,意味着当我们获取Person对象的时候,地址值对象已经是填充好的。我们不需要特地利用另外一个资源库的方法,获取地址再进行填充。但当获取Person的所有订单记录时,由于订单的数量有时候过于庞大,加载Person的时候其实并不一定需要查看订单记录,我们应该使用Repository的另外方法来获取Person的订单记录。

对值对象的全局搜索通常是没有意义的。如果确实需要在数据库中搜索一个已知的值对象,那么值得考虑一下,搜索结果可能实际上是一个实体,尚未识别出它的标识。

2019-11-21 0 评论 416 浏览
阅读全文

为什么需要聚合?

当我们设计一个订单模块,用户下单时,我们需要确保用户的余额可供支付这笔订单,并且保存这个订单。通俗的理解就是当下单的时候,必须生成订单表记录,并且检查用户余额是否足够支付,并修改用户的余额表。再转换到我们领域驱动设计中,我们必须利用订单模型和账户模型联合来完成操作,并检查保证业务规则(余额可供支付)。那此时有两点缺陷,在保证事务的应用服务中,这些领域知识(业务规则)便从领域模型泄露应用服务层。代码中除了订单模型和账户模型在同一个应用服务中这点之外,客户程序员很难知道在下单请求中订单模型和账户模型之间必须在同一个事务中进行修改。

聚合描述

每个聚合都有一个根和一个边界,边界内定义了聚合的内部有什么。根则是聚合所包含的一个特定的实体。外部对象可以引用根,但不能引用聚合内部的其他对象,聚合内的对象之间可以相互引用,除了根实体外,其他实体拥有本地标识。

举个例子:

在汽修厂的软件中会使用到汽车的模型,这里的汽车就是根实体,因为它具有全局唯一的标识:汽车识别号。汽修厂想要跟踪每台汽车上四个轮胎的使用情况。轮胎在汽车里才是实体,它们拥有本地标识,如汽车的四轮分为左前轮,右前轮,左后轮,右后轮。当轮胎报废了之后我们便不再关心这些轮胎的生命周期了,我们也不会在系统中寻找某一个轮胎现在安在哪台车上。因此,汽车便是这个聚合的根实体,轮胎就在这个聚合的边界之内。

再举个例子:
在订单模块中,下单操作必须生成订单主表和购买商品附表,订单主表即是一个根,通过这个根我们可以找到当时这笔订单购买了哪些商品。但是我们并不会单独去查购买商品附表里的记录,脱离了订单主表,这些记录就没有了意义。

定义

我们应该将实体和值对象分门别类的聚集到聚合当中,并定义聚合的边界。在每个聚合当中,选择一个实体作为根。并通过根来控制边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保聚合中的对象满足所有固定规则,也可以确保在任何状态变化时聚合作为一个整体满足固定规则。

2019-11-18 0 评论 292 浏览
阅读全文

什么是领域服务?

在战术建模当中,并非所有模型都是事物。有些模型是对领域中的一些行为操作进行建模。此类模型我们称之为领域服务。当一些重要的领域操作无法放到实体、值对象或者聚合时,他们本质是行为而不是事物。如果我们不寻找一些对象来封装这些领域行为的话,又会演变成之前过程式的编程方式。我们希望在领域设计当中统一用模型对象进行交互。此时领域服务使用细粒度的领域对象如实体或者值对象进行交互,在服务内部描述领域知识得出结果并将其返回。

领域服务的参数和返回类型应该是领域对象。

三个特征:
  • 它是与领域相关的操作如执行一个显著的业务操作过程,但它又并不适合放入实体与值对象中。
  • 操作是无状态的。
  • 对领域对象进行转换,或以多个领域对象作为输入进行计算,结果产生一个值对象。

2019-11-16 0 评论 297 浏览
阅读全文

为什么需要工厂

  1. 当创建一个复杂对象或聚合的过程很复杂并且暴露出了过多的内部结构时,我们则可以使用工厂进行封装。一个对象在它的生命周期中要承担大量的职责,如果再让复杂对象负责自身的创建,那么职责过载将会导致问题。

  2. 我们设计好领域模型供客户方调用,但如果客户方也必须使用如何装配这个对象,则必须知道对象的内部结构。好比你去驾校学车,却得先学会发动机的原理。对客户方开发来说这是很不友好的。其次,复杂对象或者聚合当中的领域知识(业务规则)需要得到满足,如果让客户方自己装配复杂对象或聚合的话,就会将领域知识泄露到客户方代码中去。

  3. 对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或聚合的封装,而且导致客户与被创建对象的实现之间产生过于紧密的耦合。

最重要的一点就是隐藏创建对象的细节

2019-11-19 0 评论 293 浏览
阅读全文

什么是实体?

实体最主要有两点特征,一是唯一标识,二是连续性。

  • 唯一标志:
    当一些对象不是由属性定义,而是由一个唯一标志定义的话,我们就可以认为它是一个实体。好比我们不能通过一个人的外在特征去唯一定位一个人,因为人从小到大,从年轻到衰老其外在特征都是在改变的。而身份证号码可以贯穿一个人的一生而不发生变化。而且唯一标识不一定仅有一个属性表示,有可能通过多个属性标识某一个唯一对象,就好比数据库中的联合外键(可能有根据电话以及姓名唯一确定一个实体的情况)。

  • 连续性:
    对象的连续性体现在对象是有生命周期的。在这个生命周期内,对象内的属性可能是变化着的。好比银行账户表,它就属于一个实体,用户的银行卡号可以唯一的确定一个人的账户,而账户的内的余额随着时间变化(利息)或者随着交易变化。

2019-11-08 0 评论 275 浏览
阅读全文

值对象是什么?

之前我们讲到实体,它最主要的特征在于概念上的标识。而领域中存在一些不需要进行标识的对象,它们主要是对事物的描述。如何理解这段话呢?可以通俗的理解,之前我们认为实体是数据库中的一条记录,这条记录会发生更改,例如学生的信息表。而值对象对应到数据库中的什么呢?它们不会单独地映射一张表而是可能对应表中的几个字段。比如学生信息表中拥有省、市、区、地址四个字段构成学生的住址,这四个字段构成的是一个整体用来描述学生的详细地址。

为什么我们将学生详细地址建模成值对象呢?

因为我们并不需要追踪学生详细地址的变化,我们并不关系它的连续性。


在很多DDD的博客文章中讲解到值对象都会用到了地址这一概念,可能有的人会误解地址天然就是值对象。并非如此,它有时可能作为值对象,有时可能作为实体。这需要看我们的业务场景。

值对象的情形:

  1. 当你和你的舍友在淘宝上购买商品,你们在订单上填的地址是一样的,并不需要严格区分这两个地址,快递直接送到这个地址即可。(地址映射到订单表中的几个字段上,如:省市区地址)

实体的情形:
2. 当你们宿舍的宽带坏了,需要报修,这时候你和你的舍友都打了电话报修。但是宽带人员并不会派两个人来抢修宽带。因为宽带人员知道你们是同一个地址。(宿舍地址映射到宽带公司系统中的一条地址用户表,以地址来区别用户)


2019-11-09 0 评论 213 浏览
阅读全文

业务场景

在进行业务开发时经常会有状态值的业务需要,例如一场考试有未开考、考试中、考试结束等状态或者一年四季有春天、夏天、秋天、冬天等状态。从前台传参到我们的业务模型再到数据库,其实这些状态是贯穿整个开发流程的。如果仅仅使用1、2、3、4来代表春夏秋冬的话,那代码的自解释能力就太差了。那如何利用好枚举类来更优雅的编码呢?

枚举类需要满足的功能

  1. 消除代码中的魔术数

  2. 根据数值获取状态的名称

  3. 根据数值获取状态

  4. 获取数值和状态名称为键值对的Map作为前端筛选项。

第一种枚举类实现

public enum SeasonEnum {

    /**/
    SPRING(1,"春天"),
    SUMMER(2,"夏天"),
    AUTUMN(3,"秋天"),
    WINTER(4,"冬天");

    public final int id;
    public final String desc;

    SeasonEnum(int id, String desc) {
        this.id = id;
        this.desc = desc;
    }


    /**
     * 根据数值获取对应的枚举
     * @param id
     * @return
     */
    public static SeasonEnum getEnumById(int id) {
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            if (seasonEnum.id == id) {
                return seasonEnum;
            }
        }
        return null;
    }


    /**
     * 根据数值获取对应的枚举描述
     * @param id
     * @return
     */
    public static String getEnumDescById(int id) {
        final String unknown = "未知";
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            if (seasonEnum.id == id) {
                return seasonEnum.desc;
            }
        }
        return unknown;
    }


    /**
     * 获取数值-描述键值对map
     * @return
     */
    public static Map getEnumMap() {
        Map<Object, Object> map = new HashMap<>();
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            map.put(seasonEnum.id, seasonEnum.desc);
        }
        return map;
    }

}

2019-10-16 0 评论 243 浏览
阅读全文

代理模式

一说到代理,很多人都会立马想到设计模型中的代理模式,通过持有被代理对象并继承被代理对象的类便可以实现代理。假设我们要给ServiceA代理日志功能,就需要声明并实现日志代理类。如果要给ServiceA代理事务功能,就又需要声明并实现事务代理类。这时如何整合日志和事务代理功能就是一个问题了。其次,假设ServiceA当中有100个方法,都需要手工加上100次日志代码。再其次,假设ServiceA、ServiceB、ServiceC都需要代理日志的话,还得针对这三个Service生成不同的代理类。
我们可以看到静态代理的局限性:

  • 难以整合不同的代理类去代理同一个对象。
  • 难以在类内部的各个方法复用代理逻辑。
  • 难以在不同的类之间复用代理逻辑。
  • 需要大量的代理类才能满足我们的庞大的业务需求。

而动态代理是如何灵活的解决这些问题的呢?

JDK动态代理

首先我们先使用动态代理最基础的用法,不涉及Spring框架的最原始方法。

先声明一个HelloService接口:

public interface HelloService {
    void hello();
}

HelloService的实现类:

public class HelloServiceImpl implements HelloService {
    @Override
    public void hello() {
        System.out.println("hello!");
    }
}

2019-10-11 0 评论 298 浏览
阅读全文

Cglib动态代理

在之前的文章中我们介绍了JDK动态代理的解析,今天我们来剖析一下Cglib的动态代理解析。

Cglib代理例子

按照惯例我们先用一个简单的例子来说明

HelloService被代理类:

public class HelloService {
    public void hello() {
        System.out.println("hello!");
    }
}

事务增强类MethodInterceptor:

public class TransactionIntercepter implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开启事务!");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("结束事务!");
        return object;
    }
    
}

2019-10-15 0 评论 74 浏览
阅读全文