当前位置: 首页 > news >正文

JavaEE异常

本章讲讲在javaEE项目中的异常处理

  在javaEE项目中,异常处理,日志记录,权限控制是基本的功能需求,所以在项目开始阶段就设计好这些对整个项目的开发是很有必要的。今天我们就一起探讨一下javaEE中的异常处理机制。先来看看java中的异常图:

          

   在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
       Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

       Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

       Exception(异常):是程序本身可以处理的异常。

       Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

   注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

   通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)

       运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

      运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
       非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

 

javavEE项目异常处理机制思想

  现在基本上所有的javaEE项目都有分层的概念了(MVC);通过分层能达到很好的解耦,也能提高团队合作效率。

  程序中可能会发生很多的错误, 例如当执行删除记录、插入记录、修改记录和复杂的业务逻辑等错误,当出现了错误应该如何处理呢?传统的处理方法是采用编程的方式来提高应用程序的健壮性。 当发生错误时,由程序来控制给用户提示友好信息或者显示一个错误提示界面。 很显然这种处理方式的实质就是增加程序的代码量来弥补程序中的不足,治标没有治本,不能从根本上解决问题。  

  本文采用的错误处理策略是当发生错误时, 将错误和发生错误时转向的页面封装成一个异常对象将其抛出,然后将异常集中到一个统一的位置进行处理。 显而易见,采用这种错误处理的方式的优点在于:当运行中的程序发生错误时就抛出一个详细的异常对象,根据发生的异常信息来决定转向到不同的页。 避免因采用编程而被忽略的一些错误 ( 由于代码量的增加而导致的错误 ) 

  我们设计一个异常类BaseException继承RuntimeException然后根据业务需求去设计ServiceExpection和DaoExpection

              

  需要扩展直接扩展BaseException就行。然后在发生异常或者错误的时候throw出相应的异常就行,如在执行保存数据时发生异常,一般应该是在Dao层发生的异常,此时throw DaoException(“保存失败”),这样就将异常向上传递,直到传递到统一处理的地方。这样所有的异常都能到一个地方去维护,大大的减少了重复代码的开发,以及冗余的try{}catch{}语句。这也是spring设计异常的机制。在以前版本中,spring的Dao的异常是设置为checked异常的,但在随后的版本中改为unchecked异常,当程序发生错误时,向上层抛出异常。这样就形成一个异常链,在配合拦截机制,就能对异常进行很好的统一处理,减少程序不必要的try{}catch{}以及提供用户更友好的错误信息。

  以springMVC作为控制层为例(当然你也可以使用Struts),在springMVC中可以对control做统一的AOP处理,这样无论哪个control发生异常都能在一个地方进行处理,(struts就使用拦截器机制),具体代码如下:

 

 1 //@ControllerAdvice 该类为control的增强
 2 @ControllerAdvice
 3 public class DefaultExceptionHandler {
 4     private static final Logger logger = LoggerFactory
 5             .getLogger(UserService.class);
 6     /**
 7      * @ExceptionHandler当发生异常就能拦截,可以定义
 8      * 异常的类型,达到更精确的控制
 9      * 
10      */
11     @ExceptionHandler({ RuntimeException.class })
12     @ResponseBody
13     public ModelAndView processUnauthenticatedException(
14             NativeWebRequest request, RuntimeException e) {
15         //打印错误堆栈信息
16         StringWriter writer = new StringWriter();  
17         e.printStackTrace(new PrintWriter(writer));  
18         logger.error(writer.getBuffer().toString());
19         
20         //判断是ajax请求还是页面请求
21         String xRequestedWith = request.getHeader("X-Requested-With");
22         if (!StringUtils.isNullOrEmpty(xRequestedWith)) {
23             // ajax请求
24             Map<String, Object> mv = new HashMap<String, Object>();
25             mv.put("success", false);
26             mv.put("error", "系统错误请联系管理员");
27             return new ModelAndView(new MappingJacksonJsonView(),mv);
28         }else{
29             return new ModelAndView("error");
30         }
31     }
32 }

 

  在springmvc的xml文件还需要配置一下

    <!-- 控制器异常处理 -->
       <bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
    </bean>

    <bean class="com.maxvision.access.exception.DefaultExceptionHandler"/>

 

  spring中对异常就进行了统一的处理:

  以下内容摘入:http://my.oschina.net/u/218421/blog/38478

  要了解Spring为什么要提供统一的异常访问层次体系,得先从DAO模式说起.

  不管是一个逻辑简单的小软件系统,还是一个关系复杂的大型软件系统,都很可能涉及到对数据的访问和存储,而这些对数据的访问和存储往往随着场景的不同而各异。为了统一和简化相关的数据访问操作,J2EE核心模式提出了DAO(Data Access Object,数据访问对象)模式。使用DAO模式,可以完全分离数据的访问和存储,很好的屏蔽了数据访问的差异性。不论数据存储在普通的文本文件或者csv文件,还是关系数据库(RDBMS)或者LDAP(Lightweight Derectory Access Protocol 轻量级目录访问协议),使用DAO模式访问数据的客户端代码完全可以忽视这种差异,用统一的接口访问相关的数据。

  看一具体的场景:
对于大部分软件系统来说,访问用户信息是大家经常接触到的。以访问用户信息为例,使用DAO模式的话,需要先声明一个数据访问的接口,如下所示:

1 package com.google.spring.jdbc;
2  
3 public interface IUserDao
4 {
5     public User findUserByPK(Integer id);
6      
7     public void updateUser(User user);
8 }

 

 

 对于客户端代码,即通常的服务层代码来说,只需要声明依赖的DAO接口即可,即使数据访问方式方式发生了改变,只需要改变相关的DAO实现方式,客户端代码不需要做任何的调整。 

 
 1 package com.google.spring.jdbc;
 2  
 3 public class UserService
 4 {
 5     private IUserDao userDao;
 6  
 7     public IUserDao getUserDao()
 8     {
 9         return userDao;
10     }
11  
12     public void setUserDao(IUserDao userDao)
13     {
14         this.userDao = userDao;
15     }
16      
17     public void disableUser(Integer userId)
18     {
19         User user = this.userDao.findUserByPK(userId);
20         userDao.updateUser(user);
21     }
22 }

 


 通常情况下,用户信息存储在关系数据库中,所以,相应的我们会提供一个基于JDBC的DAO接口实现类: 

 1 package com.google.spring.jdbc;
 2  
 3 public class JDBCUserDao implements IUserDao
 4 {
 5  
 6     @Override
 7     public User findUserByPK(Integer id)
 8     {
 9         // TODO Auto-generated method stub
10         return null;
11     }
12  
13     @Override
14     public void updateUser(User user)
15     {
16         // TODO Auto-generated method stub
17  
18     }
19  
20 }

 

 

 可能随着系统需求的变更,顾客信息需要转移到LDAP服务,或者转而使用其它的LDAP服务,又或者别人需要使用我们的Service,但是他们用的是另外的数据访问机制,这时就需要提供一个基于LDAP的数据访问对象,如下所示: 

 
 1 package com.google.spring.jdbc;
 2  
 3 public class LdapUserDao implements IUserDao
 4 {
 5  
 6     @Override
 7     public User findUserByPK(Integer id)
 8     {
 9         // TODO Auto-generated method stub
10         return null;
11     }
12  
13     @Override
14     public void updateUser(User user)
15     {
16         // TODO Auto-generated method stub
17  
18     }
19  
20 }

  即使具体的实现类发生了变化,客户端代码完全可以忽视这种变化,唯一需要变化的是factory中几行代码的改变,或者是IOC容器中几行简单的替换而已,所以DAO模式可以很好的屏蔽不同的数据访问的差异。

  为了简化描述,上述省略了最基本的数据访问代码,当引入具体的数据访问代码的时候,问题就出现了。

 1 package com.google.spring.jdbc;
 2  
 3 import java.sql.Connection;
 4  
 5 import javax.sql.DataSource;
 6  
 7 public class JDBCUserDao implements IUserDao
 8 {
 9     private DataSource dataSource ;
10      
11  
12     public DataSource getDataSource()
13     {
14         return dataSource;
15     }
16  
17     public void setDataSource(DataSource dataSource)
18     {
19         this.dataSource = dataSource;
20     }
21  
22     @Override
23     public User findUserByPK(Integer id)
24     {
25         Connection conn = null;
26         try
27         {
28             conn = getDataSource().getConnection();
29             //....
30             User user = new User();
31             //........
32             return user;
33         }
34         catch (Exception e)
35         {
36             //是抛出异常,还是在当前位置处理。。。
37         }
38         finally
39         {
40             releaseConnection(conn);
41         }
42         return null;
43     }
44  
45     @Override
46     public void updateUser(User user)
47     {
48         // TODO Auto-generated method stub
49  
50     }
51      
52     public void releaseConnection(Connection conn)
53     {
54          
55     }
56 }
View Code

 

  使用JDBC进行数据库访问,当其间出现问题的时候,JDBC API会抛出SQLException来表明问题的发生。而SQLException属于checked     exception,所以,我们的DAO实现类要捕获这种异常并处理。 
  那如何处理DAO中捕获的SQLException呢,直接在DAO实现类处理掉?如果这样的话,客户端代码就无法得知在数据访问期间发生了什么变化?所以只好将SQLException抛给客户端,进而,DAO实现类的相应的签名 

public User findUserByPK(Integer id) throws SQLException

 

相应的,DAO接口中的相应的方法签名也需要修改: 

public User findUserByPK(Integer id) throws SQLException;

 

但是,这样并没有解决问题: 
  1、我们的数据访问接口对客户端应该是通用的,不管数据访问的机制发生了如何的变化,客户端代码都不应该受到牵连。但是,因为现在用的JDBC访问数据库,需要抛出特定的SQLException,这与数据访问对象模式的初衷是背离的。 
  2、当引入另外一种数据访问的模式的时候,比如,当加入LdapUserDao的时候,会抛出NamingException,如果要实现该接口,那么该方法签名又要发生改变,如下所示: 

public User findUserByPK(Integer id) throws SQLException,NamingException;

 

这是很糟糕的解决方案,如果不同的数据访问的对象的实现越来越多,以及考虑到数据访问对象中的其它的数据访问的方法,这种糟糕的问题还得继续下去吗? 
也就是说,因为数据访问的机制有所不同,我们的数据访问接口的定义现在变成了空中楼阁,我们无法最终确定这个接口!比如,有的数据库提供商采用SQLException的ErrorCode作为具体的错误信息标准,有的数据库提供商则通过SQLException的SqlState来返回相信的错误信息。即使将SQLException封装后抛给客户端对象,当客户端要了解具体的错误信息的时候,依然要根据数据库提供商的不同采取不同的信息提取方式,这种客户端处理起来将是非常的糟糕,我们应该向客户端对象屏蔽这种差异性。可以采用分类转译(Exception Translation) 
a>首先,不应该将特定的数据访问异常的错误信息提取工作留给客户端对象,而是应该由DAO实现类,或者某个工具类进行统一的处理。假如我们让具体的DAO实现类来做这个工作,那么,对于JdbcUserDao来说,代码如下: 

try
{
    conn = getDataSource().getConnection();
    //....
    User user = new User();
    Statement stmt = conn.createStatement();
    stmt.execute("");
    //........
    return user;
}
catch (SQLException e)
{
    //是抛出异常,还是在当前位置处理。。。
    if(isMysqlVendor())
    {
        //按照mysql数据库的规则分析错误信息然后抛出
        throw new RuntimeException(e);
    }
    if(isOracleVendor())
    {
        //按照oracle数据库的规则分析错误信息并抛出
        throw new RuntimeException(e);
    }
    throw new RuntimeException(e);
}

 

 

b>信息提出出来了,可是,只通过RuntimeException一个异常类型,还不足以区分不同的错误类型,我们需要将数据访问期间发生的错误进行分类,然后为具体的错误分类分配一个对应的异常类型。比如,数据库连接不上、ldap服务器连接失败,他们被认为是资源获取失败;而主键冲突或者是其它的资源冲突,他们被认为是数据访问一致性冲突。针对这些情况,可以为RuntimeException为基准,为获取资源失败这种情况分配一个RuntimeException子类型,称其为ResourceFailerException,而数据一致性冲突对应另外一个子类型DataIntegrityViolationException,其它的分类异常可以加以类推,所以我们需要的只是一套unchecked exception类型的面向数据访问领域的异常层次类型。

不需要重新发明轮子
我们知道unchecked exception类型的面向数据访问领域的异常层次体系存在的必要性,不需我们设计,spring已经提供了异常访问体系。
spring框架中的异常层次体系所涉及的大部分的异常类型均定义在org.springframework.dao包中,处于这个异常体系中的异常类型均是以org.springframework.dao.DataAccessException为统领,然后根据职能划分为不同的子类型,总体上看,整个异常体系如下所示:

CleanupFailureDataAccessException:当成功完成数据访问要对资源进行清理的时候,将抛出该异常,比如使用jdbc进行数据库进行访问的时候,查询或者更新完成之后需要关闭相应的数据库连接,如果在关闭的过程中出现了SQLException,那么导致数据库连接没有被释放,导致资源清理失败。
DataAccessResourceFailureException:在无法访问相应的数据资源的情况下,将抛出DataAccessResourceFailureException。对应这种异常出现最常见的场景就是数据库服务器挂掉的情况,这时,连接数据库的应用程序通过捕获该异常需要了解到是数据库服务器出现了问题。对于JDBC来说,服务器挂掉会抛出该类型的子类型,即org.springframework.dao.CannotGetJdbcConnectionException。
DataSourceLookupFailureException:当尝试对jndi服务或者是其它位置上的DataSource进行查找的时候,可以抛出DataSourceLookupFailureException。
ConcurrencyFailureException:并发访问失败的时候,可以抛出ConcurrencyFailureException 比如无法取得相应的数据库的锁,或者乐观锁更新冲突。根据不同的并发数据访问失败的情况,ConcurrencyFailureException细分为所个子类:

OptimisticLockingFailureException对应数据更新的时候出现乐观锁冲突的情况。PessimisticLockingFailureException对应的是悲观锁冲突,PessimisticLockingFailureException还可以细分为CannotAcquireLockException和DeadlockLoserDataAccessException子类型。
InvalidDataAccessApiUsageException:该异常不是因为数据库资源出现了问题,而是我们以错误的方式,使用了特定的数据访问API,比如使用Spring的JdbcTemplate的queryForObject()语义上只返回一个结果对象,所以我们在查询多行的时候不能使用此方法。
InvalidDataAccessResourceUsageException:以错误的方式访问数据资源,会抛出该异常,比如要访问数据库资源,却传入错误的sql语句,分为不同的子类,基于JDBC的访问会抛出BadSqlGrammarException 基于hibernate的会抛出HibernateQueryException异常。
DataRetrievalFailureException:在要获取预期的数据却失败的时候。
PermissionDeniedDataAccessException:要访问相关的数据资源却没相应的权限的时候。
DataIntegrityViolationException:数据一致性冲突异常,比如主键冲突。
UncategorizedDataAccessException:无法细分的其它的异常,可以子类化定义具体的异常。

 

转载于:https://www.cnblogs.com/gongdi/p/5010735.html

相关文章:

  • jQuery根据元素值删除数组元素的方法
  • 简单的原生ajax
  • restful命名
  • android Lifecycle源码分析--源码阅读100天(1)
  • Java-TreeSet的用法-入门
  • (四)鸿鹄云架构一服务注册中心
  • 占位子,考完试写
  • golang学习笔记 ---命令行参数
  • 森林病虫防治系统 (结束)
  • Linux内核中进程上下文、中断上下文、原子上下文、用户上下文的理解【转】...
  • 计算机
  • lucene4.7学习总结
  • python头部 #!/usr/bin/env python
  • nodejs安装及express
  • [error] 17755#0: *58522 readv() failed (104: Connection reset by peer) while reading upstream
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • ES学习笔记(12)--Symbol
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • java8-模拟hadoop
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • SpriteKit 技巧之添加背景图片
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • VUE es6技巧写法(持续更新中~~~)
  • vue中实现单选
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 如何解决微信端直接跳WAP端
  • 如何学习JavaEE,项目又该如何做?
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 学习笔记TF060:图像语音结合,看图说话
  • 用jQuery怎么做到前后端分离
  • 智能合约Solidity教程-事件和日志(一)
  • 湖北分布式智能数据采集方法有哪些?
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • 树莓派用上kodexplorer也能玩成私有网盘
  • 移动端高清、多屏适配方案
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #QT(串口助手-界面)
  • (12)Hive调优——count distinct去重优化
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (转)winform之ListView
  • ... 是什么 ?... 有什么用处?
  • .bat批处理(六):替换字符串中匹配的子串
  • .net MySql
  • .Net Web窗口页属性
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • ::before和::after 常见的用法
  • ??javascript里的变量问题
  • @NestedConfigurationProperty 注解用法