标签 Java 下的文章 - 第 38 页 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
29 阅读
技术
登录
/
注册
找到
191
篇与
Java
相关的结果
- 第 38 页
2025-01-22
你以为用了BigDecimal后,计算结果就一定精确了?
BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。 很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。 所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。但是,如果误以为只要使用BigDecimal表示数字,结果就一定精确,那就大错特错了! 在之前的一篇文章中,我们介绍过,使用BigDecimal的equals方法并不能验证两个数是否真的相等(为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?)。 除了这个情况,BigDecimal的使用的第一步就是创建一个BigDecimal对象,如果这一步都有问题,那么后面怎么算都是错的! 那到底应该如何正确的创建一个BigDecimal? 关于这个问题,我Review过很多代码,也面试过很多一线开发,很多人都掉进坑里过。这是一个很容易被忽略,但是又影响重大的问题。 关于这个问题,在《阿里巴巴Java开发手册》中有一条建议,或者说是要求:  这是一条【强制】建议,那么,这背后的原理是什么呢? 想要搞清楚这个问题,主要需要弄清楚以下几个问题: 1、为什么说double不精确? 2、BigDecimal是如何保证精确的? 在知道这两个问题的答案之后,我们也就大概知道为什么不能使用BigDecimal(double)来创建一个BigDecimal了。 double为什么不精确 首先,计算机是只认识二进制的,即0和1,这个大家一定都知道。 那么,所有数字,包括整数和小数,想要在计算机中存储和展示,都需要转成二进制。 十进制整数转成二进制很简单,通常采用”除2取余,逆序排列”即可,如10的二进制为1010。 但是,小数的二进制如何表示呢? 十进制小数转成二进制,一般采用”乘2取整,顺序排列”方法,如0.625转成二进制的表示为0.101。 但是,并不是所有小数都能转成二进制,如0.1就不能直接用二进制表示,他的二进制是0.000110011001100… 这是一个无限循环小数。 所以,计算机是没办法用二进制精确的表示0.1的。也就是说,在计算机中,很多小数没办法精确的使用二进制表示出来。 那么,这个问题总要解决吧。那么,人们想出了一种采用一定的精度,使用近似值表示一个小数的办法。这就是IEEE 754(IEEE二进制浮点数算术标准)规范的主要思想。 IEEE 754规定了多种表示浮点数值的方式,其中最常用的就是32位单精度浮点数和64位双精度浮点数。 在Java中,使用float和double分别用来表示单精度浮点数和双精度浮点数。 所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。 所以,大家也就知道为什么double表示的小数不精确了。 接下来,再回到BigDecimal的介绍,我们接下来看看是如何表示一个数的,他如何保证精确呢? BigDecimal如何精确计数? 如果大家看过BigDecimal的源码,其实可以发现,实际上一个BigDecimal是通过一个”无标度值”和一个”标度”来表示一个数的。 在BigDecimal中,标度是通过scale字段来表示的。 而无标度值的表示比较复杂。当unscaled value超过阈值(默认为Long.MAX_VALUE)时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空。 涉及到的字段就是这几个: public class BigDecimal extends Number implements Comparable { private final BigInteger intVal; private final int scale; private final transient long intCompact; } 关于无标度值的压缩机制大家了解即可,不是本文的重点,大家只需要知道BigDecimal主要是通过一个无标度值和标度来表示的就行了。 那么标度到底是什么呢? 除了scale这个字段,在BigDecimal中还提供了scale()方法,用来返回这个BigDecimal的标度。 /** * Returns the scale of this {@code BigDecimal}. If zero * or positive, the scale is the number of digits to the right of * the decimal point. If negative, the unscaled value of the * number is multiplied by ten to the power of the negation of the * scale. For example, a scale of {@code -3} means the unscaled * value is multiplied by 1000. * * @return the scale of this {@code BigDecimal}. */ public int scale() { return scale; } 那么,scale到底表示的是什么,其实上面的注释已经说的很清楚了: 如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。 如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。 而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。 我们都知道,想要创建一个对象,需要使用该类的构造方法,在BigDecimal中一共有以下4个构造方法: BigDecimal(int) BigDecimal(double) BigDecimal(long) BigDecimal(String) 以上四个方法,创建出来的的BigDecimal的标度(scale)是不同的。 其中 BigDecimal(int)和BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0。 而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。 BigDecimal(double)有什么问题 BigDecimal中提供了一个通过double创建BigDecimal的方法——BigDecimal(double) ,但是,同时也给我们留了一个坑! 因为我们知道,double表示的小数是不精确的,如0.1这个数字,double只能表示他的近似值。 所以,当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是正好等于0.1的。 而是0.1000000000000000055511151231257827021181583404541015625。这是因为doule自身表示的只是一个近似值。  所以,如果我们在代码中,使用BigDecimal(double) 来创建一个BigDecimal的话,那么是损失了精度的,这是极其严重的。 使用BigDecimal(String)创建 那么,该如何创建一个精确的BigDecimal来表示小数呢,答案是使用String创建。 而对于BigDecimal(String) ,当我们使用new BigDecimal(“0.1”)创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。 那么他的标度也就是1。 但是需要注意的是,new BigDecimal(“0.10000”)和new BigDecimal(“0.1”)这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,具体原因和解决办法参考为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较? 那么,想要创建一个能精确的表示0.1的BigDecimal,请使用以下两种方式: BigDecimal recommend1 = new BigDecimal("0.1"); BigDecimal recommend2 = BigDecimal.valueOf(0.1); 这里,留一个思考题,BigDecimal.valueOf()是调用Double.toString方法实现的,那么,既然double都是不精确的,BigDecimal.valueOf(0.1)怎么保证精确呢? 总结 因为计算机采用二进制处理数据,但是很多小数,如0.1的二进制是一个无线循环小数,而这种数字在计算机中是无法精确表示的。 所以,人们采用了一种通过近似值的方式在计算机中表示,于是就有了单精度浮点数和双精度浮点数等。 所以,作为单精度浮点数的float和双精度浮点数的double,在表示小数的时候只是近似值,并不是真实值。 所以,当使用BigDecimal(Double)创建一个的时候,得到的BigDecimal是损失了精度的。 而使用一个损失了精度的数字进行计算,得到的结果也是不精确的。 想要避免这个问题,可以通过BigDecimal(String)的方式创建BigDecimal,这样的情况下,0.1就会被精确的表示出来。 其表现形式是一个无标度数值1,和一个标度1的组合。
技术
# Java
酷游
1月22日
0
4
0
2025-01-22
[转]使用JMockit编写java单元测试
JMockit是基于JavaSE5中的java.lang.instrument包开发,内部使用ASM库来动态修改java的字节码,使得java这种静态语言可以想动态脚本语言一样动态设置被Mock对象私有属性,模拟静态、私有方法行为等等,对于手机开发,嵌入式开发等要求代码尽量简洁的情况下,或者对于被测试代码不想做任何修改的前提下,使用JMockit可以轻松搞定很多测试场景。 通过如下方式在maven中添加JMockit的相关依赖: com.googlecode.jmockit jmockit 1.5 test com.googlecode.jmockit jmockit-coverage 0.999.24 test JMockit有两种Mock方式:基于行为的Mock方式和基于状态的Mock方式: 引用单元测试中mock的使用及mock神器jmockit实践中JMockit API和工具如下: (1).基于行为的Mock方式: 非常类似与EasyMock和PowerMock的工作原理,基本步骤为: 1.录制方法预期行为。 2.真实调用。 3.验证录制的行为被调用。 通过一个简单的例子来介绍JMockit的基本流程: 要Mock测试的方法如下: public class MyObject { public String hello(String name){ return "Hello " + name; } } 使用JMockit编写的单元测试如下: @Mocked //用@Mocked标注的对象,不需要赋值,jmockit自动mock MyObject obj; @Test public void testHello() { new NonStrictExpectations() {//录制预期模拟行为 { obj.hello("Zhangsan"); returns("Hello Zhangsan"); //也可以使用:result = "Hello Zhangsan"; } }; assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法 new Verifications() {//验证预期Mock行为被调用 { obj.hello("Hello Zhangsan"); times = 1; } }; } JMockit也可以分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。 而Expectations块一般由Expectations类和NonStrictExpectations类定义,类似于EasyMock和PowerMock中的Strict Mock和一般性Mock。 用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,所以可以省略掉Verifications块; 而用NonStrictExpectations类定义的,则没有这些限制,所以如果需要验证,则要添加Verifications块。 上述的例子使用了非局部模拟,下面我们使用局部模拟来改写上面的测试,代码如下: @Test public void testHello() { final MyObject obj = new MyObject(); new NonStrictExpectations(obj) {//录制预期模拟行为 { obj.hello("Zhangsan"); returns("Hello Zhangsan"); //也可以使用:result = "Hello Zhangsan"; } }; assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法 new Verifications() {//验证预期Mock行为被调用 { obj.hello("Hello Zhangsan"); times = 1; } }; } 模拟静态方法: @Test public void testMockStaticMethod() { new NonStrictExpectations(ClassMocked.class) { { ClassMocked.getDouble(1);//也可以使用参数匹配:ClassMocked.getDouble(anyDouble); result = 3; } }; assertEquals(3, ClassMocked.getDouble(1)); new Verifications() { { ClassMocked.getDouble(1); times = 1; } }; } 模拟私有方法: 如果ClassMocked类中的getTripleString(int)方法指定调用一个私有的multiply3(int)的方法,我们可以使用如下方式来Mock: @Test public void testMockPrivateMethod() throws Exception { final ClassMocked obj = new ClassMocked(); new NonStrictExpectations(obj) { { this.invoke(obj, "multiply3", 1);//如果私有方法是静态的,可以使用:this.invoke(null, "multiply3") result = 4; } }; String actual = obj.getTripleString(1); assertEquals("4", actual); new Verifications() { { this.invoke(obj, "multiply3", 1); times = 1; } }; } 设置Mock对象私有属性的值: 我们知道EasyMock和PowerMock的Mock对象是通过JDK/CGLIB动态代理实现的,本质上是类的继承或者接口的实现,但是在java面向对象编程中,基类对象中的私有属性是无法被子类继承的,所以如果被Mock对象的方法中使用到了其自身的私有属性,并且这些私有属性没有提供对象访问方法,则使用传统的Mock方法是无法进行测试的,JMockit提供了设置Mocked对象私有属性值的方法,代码如下: 被测试代码: public class ClassMocked { private String name = "name_init"; public String getName() { return name; } private static String className="Class3Mocked_init"; public static String getClassName(){ return className; } } 使用JMockit设置私有属性: @Test public void testMockPrivateProperty() throws IOException { final ClassMocked obj = new ClassMocked(); new NonStrictExpectations(obj) { { this.setField(obj, "name", "name has bean change!"); } }; assertEquals("name has bean change!", obj.getName()); } 使用JMockit设置静态私有属性: @Test public void testMockPrivateStaticProperty() throws IOException { new NonStrictExpectations(Class3Mocked.class) { { this.setField(ClassMocked.class, "className", "className has bean change!"); } }; assertEquals("className has bean change!", ClassMocked.getClassName()); } (2).基于状态的Mock方式: JMockit上面的基于行为Mock方式和传统的EasyMock和PowerMock流程基本类似,相当于把被模拟的方法当作黑盒来处理,而JMockit的基于状态的Mock可以直接改写被模拟方法的内部逻辑,更像是真正意义上的白盒测试,下面通过简单例子介绍JMockit基于状态的Mock。 被测试的代码如下: public class StateMocked { public static int getDouble(int i){ return i*2; } public int getTriple(int i){ return i*3; } } 改写普通方法内容: @Test public void testMockNormalMethodContent() throws IOException { StateMocked obj = new StateMocked(); new MockUp() {//使用MockUp修改被测试方法内部逻辑 @Mock public int getTriple(int i) { return i * 30; } }; assertEquals(30, obj.getTriple(1)); assertEquals(60, obj.getTriple(2)); Mockit.tearDownMocks();//注意:在JMockit1.5之后已经没有Mockit这个类,使用MockUp代替,mockUp和tearDown方法在MockUp类中 } 修改静态方法的内容: 基于状态的JMockit改写静态/final方法内容和测试普通方法没有什么区别,需要注意的是在MockUp中的方法除了不包含static关键字以外,其他都和被Mock的方法签名相同,并且使用@Mock标注,测试代码如下: @Test public void testGetTriple() { new MockUp() { @Mock public int getDouble(int i){ return i*20; } }; assertEquals(20, StateMocked.getDouble(1)); assertEquals(40, StateMocked.getDouble(2)); } 原文链接: http://blog.csdn.net/chjttony/article/details/17838693
技术
# Java
酷游
1月22日
0
15
0
2025-01-22
接口优于反射
接口优于反射 写在前面:最近在做一个需求:要针对不同的度量模型来执行不同的方法,同时要让后面接手的开发同学用最少的代码、最简单的方式来复用并实现更多度量模型。该需求一方面学到了一个新的设计模式——模板方法设计模式。另外一方面还得到一个经验————接口优于反射。 Effective Java的作者说接口优先于反射机制,这个之前就有看过,但是当时也没搞明白接口和反射能有啥联系,直到最近。才发现这句话的确是有道理的。 我的需求:有一批度量模型,要根据这些度量模型定义的规则给所有应用计算分数,不同的度量模型的计算方法需要对应不同的算法。整个计算过程需要离线计算(通过定时任务定时执行)。 我刚开始的做法(使用反射): 定义一个枚举: public enum MeasureModelEnum { CHECK_SPECIFIC_DEV_OWNER("execute","com.alibaba.intl.batch.sla.service.impl.SpecificDevOwnerCalculator"); /** * 要执行的方法名 */ String method; /** * 要执行的类的全路径名 */ String clazz; private MeasureModelEnum(String method ,String clazz){ this.method = method; this.clazz = clazz; } public String getMethod(){ return this.method; } public String getClazz(){ return this.clazz; } } 然后用以下代码遍历枚举项,执行对应的方法: for(MeasureModelEnum measureModel :MeasureModelEnum.values()){ String calculateMethod = measureModel.getMethod(); String calculateClass = measureModel.getClazz(); Class class1 = null; class1 = Class.forName(calculateClass); Method method = class1.getMethod(calculateMethod,List.class,LeafMeasureModel.class); method.invoke(class1.newInstance(),items,measureModel); } 后来的做法(使用接口): 定义一个枚举: public enum MeasureModelLeafNode { CHECK_SPECIFIC_DEV_OWNER(new SpecificDevOwnerCalculator()); //SpecificDevOwnerCalculator是AbstractNodeCalculator的实现类 /** * 实现类 */ AbstractNodeCalculator calculateNode ; private MeasureModelLeafNode(AbstractNodeCalculator calculateNode){ this.calculateNode = calculateNode; } public AbstractNodeCalculator getMeasureModel(){ return this.calculateNode; } } 然后用以下代码遍历枚举项,执行对应的方法: for(MeasureModelLeafNode measureModel:MeasureModelLeafNode.values()){ measureModel.getMeasureModel().execute(items, measureModel); } 总结 以上两部分代码的内容都是遍历枚举项,然后执行execute()方法进行逻辑处理。区别在于一个使用反射调用方法,一个使用接口+多态的形式执行方法。
技术
# Java
酷游
1月22日
0
20
0
2025-01-22
[译]空引用真的有那么糟糕吗?
本文翻译自国外编程问答网站Programmers Stack Exchange中的一个热门问答:Are null references really a bad thing? 问题 我曾听说有人说过,包含空引用的编程语言是一个价值十亿美元的错误(译者注:图灵奖得主Tony Hoare说过)。但是为什么呢?当然,他们可能会导致NullReferenceException,但那又怎样?只要使用不当,一个语言的任何一个元素都可导致错误啊。 还有其他的选择吗?我一般在代码中这么写: Customer c = Customer.GetByLastName("Goodman"); // returns null if not found if (c != null) { Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); } else { Console.WriteLine("There was no customer named Goodman. How lame!"); } 你可能这么用: if (Customer.ExistsWithLastName("Goodman")) { Customer c = Customer.GetByLastName("Goodman") // throws error if not found Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); } else { Console.WriteLine("There was no customer named Goodman. How lame!"); } 但你的做法又比我的好在哪里呢?无论哪种方式,如果你忘了检查Customer是否存在,你会得到一个异常。我猜测可能是因为调试CustomerNotFoundException可能比NullReferenceException更容易,因为他描述的更清楚。这就是全部原因吗? 最佳答案 null是邪恶的(译者注:回答者在回答提问者的关于为什么空引用能导致十亿美金的损失的问题) 在infoQ上有一篇关于这个的演讲:Null References: The Billion Dollar Mistake by Tony Hoare Option类型(译者注:回答者告诉提问者可以使用什么方式避免使用空引用) 在函数式编程中有一个选择就是使用Option类型,他可以包含某个值和NONE The “Option” Pattern讨论了Option类型,并介绍了java中的实现。(译者注:在java8中提供了Optional类型) 我还发现了一个Java中关于这个问题的错误报告:Add Nice Option types to Java to prevent NullPointerExceptions. 另一个高分回答 问题在于,理论上任何一个对象都有可能是null,并且当你试图使用他的时候就会抛出异常。你的面向对象代码基本上就是一个定时炸弹。 你是对的,使用if语句进行非空检查是一种优雅的处理方式。但是如果一个你确信不可能为null的对象的值为null了会怎么样?敢肯定的有两件事。1、这绝对不是一件好事儿。2、你肯定不希望他发生。 还有,千万不要忽视”容易debug”这个好处,成熟的代码都是庞然大物。一个好的错误提示会节省你几个小时的时间。
技术
# Java
酷游
1月22日
0
14
0
2025-01-22
Java Web应用的代码分层最佳实践。
代码分层,对于任何一个Java Web开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。 从另外一个角度来看,好的代码分层架构,应该是可以很好的匹配上单一职责原则的。这样就可以降低层与层之间的依赖,还能最大程度的复用各层的逻辑。本文就来介绍下Java Web项目的代码到底应该如何分层。 三层架构 在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。这也是Java Web中重要的三层架构中的三个层次。区分层次的目的即为了“高内聚低耦合”的思想。 所谓三层体系结构,是在客户端与数据库之间加入了一个“中间层”,也叫组件层。这里所说的三层体系,不是指物理上的三层,不是简单地放置三台机器就是三层体系结构,也不仅仅有B/S应用才是三层体系结构,三层是指逻辑上的三层,即把这三个层放置到一台机器上。 数据访问层 主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。 业务逻辑层 主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。 界面层 主要表示WEB方式。如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。 三层架构与MVC的区别 MVC(模型Model-视图View-控制器Controller)是一种架构模式,可以用它来创建在域对象和UI表示层对象之间的区分。 同样是架构级别的,相同的地方在于他们都有一个表现层,但是他们不同的地方在于其他的两个层。 在三层架构中没有定义Controller的概念。这是最不同的地方。而MVC也没有把业务的逻辑访问看成两个层,这是采用三层架构或MVC搭建程序最主要的区别。 更加细致的分层 随着网站的用户量的不断提升,系统架构也在不断的调整。有时候,随着业务越来越复杂,有时候三层架构好像不够用了。比如,我们的应用除了要给用户提供页面访问以外,还需要提供一些开放接口,供外部系统调用。这个接口既不属于界面层,也不应该属于业务逻辑层,因为他还可能包含一些和业务逻辑无关的处理,如权限控制、流量控制等。 还有,随着微服务的盛行,我们应用中可能要依赖很多外部接口或第三方平台。这部分代码放下业务逻辑层和数据访问层也都不合适。 所以,渐渐的,在三层架构的基础上,系统架构的分层变得更加复杂了。也正是因为复杂,就非常考验架构设计能力,因为层次划分的不好,很可能会影响后面的开发,给代码维护带来很大的困难。 下图,是阿里巴巴(参考《阿里巴巴Java开发手册》)提倡的应用分层结构: 开放接口层 可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行网关安全控制、流量控制等。 终端显示层 各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。 Web 层 主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。 Service 层 相对具体的业务逻辑服务层。 Manager 层 通用业务处理层,它有如下特征: 1) 对第三方平台封装的层,预处理返回结果及转化异常信息; 2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理; 3) 与 DAO 层交互,对多个 DAO 的组合复用。 DAO 层 数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。 外部接口或第三方平台 包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。 事务处理 在了解了分层之后,我们再来看一下写Java Web代码的时候,大家比较关心的一个问题,那就是涉及到数据库操作的时候,事务处理应该在哪一层控制呢? 关于这个问题,仁者见仁,智者见智。作者认为,事务处理应该放在Service层和Manager层。 DAO层不应该有事务,应该只是很纯的 CRUD 等比较通用的数据访问方法。一个DAO应该只处理和自己相关的操作,不要有任何组合。组合的事情交给上层。 Service层和Manager层一般会组合多个DAO的CRUD操作,例如:在注册一个用户的时候需要往日志表里 INSERT 日志,那么就在 Service 层构造事务,在该事务中调用 Dao 层的 User.Insert () 与 Log.Insert ()。 异常处理 异常处理是Java中比较重要的一个话题,在《Effective Java》中有很多关于异常处理的最佳实践,这里不详细介绍了,本文主要简单说一下在应用代码分层之后,各个层次之间的异常应该如何处理,是自己捕获,还是向上一层抛出。 首先,每一层都是可能发生异常的。由于每一层的职责都不通,处理方式也可能有差别。 DAO层 在 DAO 层,产生的异常类型可能有很多,可能是SQL相关的异常,也可能是数据库连接相关的异常。 这一层的处理方式可以简单一点,直接try-catch(Exception),然后封装成DAOException抛给上一层。这一层一般不需要打印日志,交给Service或者Manager层来打印。 try{ CRUD }catch(Exception e){ throw new DAOException(e); } Manager/Service 首先,对于DAO层抛上来的异常一定要捕获的,并且记录日志打印现场。 但是值得注意的是,如果是需要事务控制的方法,要注意捕获到异常之后再向上抛一个新的异常,如 TransactionRolledbackException,否则事务无法回滚。 这两层发生的异常可以根据情况决定是继续向上抛还是自己处理掉。如果是自己可以处理的异常,就捕获,打日志,然后通过ErrorCode等方式返回给上一层。如果是自己无法处理或者不知道该如何处理的异常,就直接抛给上一层来处理。 Web 首先,可以明确的一点:Web层不应该再往外抛异常,因为这一层一旦抛异常,就可能会导致用户跳转到不友好的错误页面甚至看到错误信息等。 如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。 开放接口层 这一层和Web层一样,不可以抛出异常。一般通过ErrorCode和ErrorMessage反馈给外部调用方。 这一层,要自己处理好所有的异常,定义好ErrorCode,并记录好日志,便于日后排查问题。 总结 本文主要介绍了Java Web项目中代码分层的方案,通过分层之后可以使没一层更加专注,解除耦合。并简单介绍了一下分层之后的事务处理和异常处理的逻辑。
技术
# Java
酷游
1月22日
0
16
0
上一页
1
...
37
38
39
下一页
易航博客