zoukankan      html  css  js  c++  java
  • 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:无法细分的其它的异常,可以子类化定义具体的异常。

  • 相关阅读:
    mysql六:数据备份、pymysql模块
    mysql五:索引原理与慢查询优化
    mysql四:数据操作
    sql查询作业答案
    mysql四-2:多表查询
    mysql四-1:单表查询
    mysql五补充部分:SQL逻辑查询语句执行顺序
    mysql三-3:完整性约束
    mysql三:表操作
    mysql三-2:数据类型
  • 原文地址:https://www.cnblogs.com/gongdi/p/5010735.html
Copyright © 2011-2022 走看看