精华内容
下载资源
问答
  • 如果解析无法成功完成,parseInt()将抛出异常。你可以改为使用整数,相应的对象类型,这使事情更清洁一点。所以你可能想要更接近:Integer s = null;try {s = Integer.valueOf(startField.getText());}catch ...

    如果解析无法成功完成,parseInt()将抛出异常。你可以改为使用整数,相应的对象类型,这使事情更清洁一点。所以你可能想要更接近:

    Integer s = null;

    try {

    s = Integer.valueOf(startField.getText());

    }

    catch (NumberFormatException e) {

    // ...

    }

    if (s != null) { ... }

    注意如果你决定使用parseInt()! parseInt()不支持良好的国际化,所以你必须跳过更多的箍:

    try {

    NumberFormat nf = NumberFormat.getIntegerInstance(locale);

    nf.setParseIntegerOnly(true);

    nf.setMaximumIntegerDigits(9); // Or whatever you'd like to max out at.

    // Start parsing from the beginning.

    ParsePosition p = new ParsePosition(0);

    int val = format.parse(str, p).intValue();

    if (p.getIndex() != str.length()) {

    // There's some stuff after all the digits are done being processed.

    }

    // Work with the processed value here.

    } catch (java.text.ParseFormatException exc) {

    // Something blew up in the parsing.

    }

    展开全文
  • 由于用户输入只能是字符串类型,如果输入了非法的信息,如在货币值中输入了字母“a”以及其他非数字字符,那么在运行时会抛出异常。通常我们可以通过捕获异常判断输入信息是否合法,但这并不是最好的处理方法。本...

    【实例描述】

    软件运行过程中,经常需要用户输入数值、货币值等信息,然后进行处理。由于用户输入

    只能是字符串类型,如果输入了非法的信息,如在货币值中输入了字母“a”以及其他非数字

    字符,那么在运行时会抛出异常。通常我们可以通过捕获异常来判断输入信息是否合法,但这

    并不是最好的处理方法。本实例将采用NumberUtils

    类中的方法处理此问题,让程序更加快捷

    方便。实例的运行效果如图4.11 所示。

    10e265331b6486134d64aeccce0e6baf.png

    【实现过程】

    在Eclipse 中新建项目CheckNumber,并在其中创建一个CheckNumber.java

    文件。在该类

    的主方法中创建标准输入流的扫描器对象,接收用户输入的金额。程序将对其是否为数字进行

    判断并输出提示结果。核心代码如下所示:

    protected

    void do_button_actionPerformed(ActionEvent e) {

    String text =

    textField.getText(); // 获取用户输入的金额字符串

    boolean isnum =

    NumberUtils.isNumber(text); // 判断是不是数字

    if (isnum) { //

    输出正确提示信息

    showMessageDialog(null, "输入正确,是数字格式");

    } else { //

    输出错误提示信息

    showMessageDialog(null,

    "输入错误,请确认格式再输入");

    }

    }

    【代码解析】

    本实例采用了Apache 提供的lang 包中的NumberUtils

    类来实现数字判断,该类的全路径

    为“org.apache.commom.lang.math.NumberUtils”,这个类中的isNumber()方法可以接收字符串

    参数,然后对字符串进行解析,如果字符串不能转换为数字格式,则返回false。其声明语法如

    下所示:

    public

    static boolean isNumber(String str);

    【知识扩展】

    本实例还可以通过Double

    类的parseDouble()方法把字符串转换为double

    类型。如果抛出

    异常说明字符串不是合法数字格式。但是建议不要使用这种方式做判断,那会降低系统的运行

    速度。因为它无法与简单逻辑判断相比,后者在速度上完全超越前者。

    展开全文
  • 当使用java时,在某些情况下,我们需要判断传入的字符串是否为数字,我们可以使用以下思路,利用字符串强转类型错误会抛出异常的特点: 思路如下: 将传进来的字符串强制转换为double类型,如果转换成功,返回...

         当使用java时,在某些情况下,我们需要判断传入的字符串是否为数字,我们可以使用以下思路,利用字符串强转类型错误会抛出异常的特点:

        思路如下:

    将传进来的字符串强制转换为double类型,如果转换成功,返回true,即该字符串是数字,否则转换失败,返回false;

    代码如下:

    public static boolean isNumber(String parm) {
        try {
            double num = Double.parseDouble(parm);
        } catch (NumberFormatException e) {
            return false;
        }
        return true;
    }

     

    展开全文
  • 来源:lrwinx.github.io/2016/04/28/如何优雅的设计java异常/导语异常处理是程序开发中必不可少操作之一,但如何正确优雅的对异常进行处理确是一门...如何选择异常类型异常的类别正如我们所知道的,java中的异常的...

    来源:lrwinx.github.io/2016/04/28/如何优雅的设计java异常/

    导语

    异常处理是程序开发中必不可少操作之一,但如何正确优雅的对异常进行处理确是一门学问,笔者根据自己的开发经验来谈一谈我是如何对异常进行处理的。

    由于本文只作一些经验之谈,不涉及到基础知识部分,如果读者对异常的概念还很模糊,请先查看基础知识。

    如何选择异常类型

    异常的类别

    正如我们所知道的,java中的异常的超类是java.lang.Throwable(后文省略为Throwable),它有两个比较重要的子类,java.lang.Exception(后文省略为Exception)和java.lang.Error(后文省略为Error),其中Error由JVM虚拟机进行管理,如我们所熟知的OutOfMemoryError异常等,所以我们本文不关注Error异常,那么我们细说一下Exception异常。

    Exception异常有个比较重要的子类,叫做RuntimeException。我们将RuntimeException或其他继承自RuntimeException的子类称为非受检异常(unchecked Exception),其他继承自Exception异常的子类称为受检异常(checked Exception)。本文重点来关注一下受检异常和非受检异常这两种异常。

    如何选择异常

    从笔者的开发经验来看,如果在一个应用中,需要开发一个方法(如某个功能的service方法),这个方法如果中间可能出现异常,那么你需要考虑这个异常出现之后是否调用者可以处理,并且你是否希望调用者进行处理,如果调用者可以处理,并且你也希望调用者进行处理,那么就要抛出受检异常,提醒调用者在使用你的方法时,考虑到如果抛出异常时如果进行处理,相似的,如果在写某个方法时,你认为这是个偶然异常,理论上说,你觉得运行时可能会碰到什么问题,而这些问题也许不是必然发生的,也不需要调用者显示的通过异常来判断业务流程操作的,那么这时就可以使用一个RuntimeException这样的非受检异常.

    好了,估计我上边说的这段话,你读了很多遍也依然觉得晦涩了。

    那么,请跟着我的思路,在慢慢领会一下。

    什么时候才需要抛异常

    首先我们需要了解一个问题,什么时候才需要抛异常?异常的设计是方便给开发者使用的,但不是乱用的,笔者对于什么时候抛异常这个问题也问了很多朋友,能给出准确答案的确实不多。其实这个问题很简单,如果你觉得某些”问题”解决不了了,那么你就可以抛出异常了。比如,你在写一个service,其中在写到某段代码处,你发现可能会产生问题,那么就请抛出异常吧,相信我,你此时抛出异常将是一个最佳时机。

    应该抛出怎样的异常

    了解完了什么时候才需要抛出异常后,我们再思考一个问题,真的当我们抛出异常时,我们应该选用怎样的异常呢?究竟是受检异常还是非受检异常呢(RuntimeException)呢?我来举例说明一下这个问题,先从受检异常说起,比如说有这样一个业务逻辑,需要从某文件中读取某个数据,这个读取操作可能是由于文件被删除等其他问题导致无法获取从而出现读取错误,那么就要从redis或mysql数据库中再去获取此数据,参考如下代码,getKey(Integer)为入口程序.

    public String getKey(Integer key){

        String  value;

        try {

            InputStream inputStream = getFiles("/file/nofile");

            //接下来从流中读取key的value指

            value = ...;

        } catch (Exception e) {

            //如果抛出异常将从mysql或者redis进行取之

            value = ...;

        }

    }

    public InputStream getFiles(String path) throws Exception {

        File file = new File(path);

        InputStream inputStream = null;

        try {

            inputStream = new BufferedInputStream(new FileInputStream(file));

        } catch (FileNotFoundException e) {

            throw new Exception("I/O读取错误",e.getCause());

        }

        return inputStream;

    }

    ok,看了以上代码以后,你也许心中有一些想法,原来受检异常可以控制义务逻辑,对,没错,通过受检异常真的可以控制业务逻辑,但是切记不要这样使用,我们应该合理的抛出异常,因为程序本身才是流程,异常的作用仅仅是当你进行不下去的时候找到的一个借口而已,它并不能当成控制程序流程的入口或出口,如果这样使用的话,是在将异常的作用扩大化,这样将会导致代码复杂程度的增加,耦合性会提高,代码可读性降低等问题。那么就一定不要使用这样的异常吗?其实也不是,在真的有这样的需求的时候,我们可以这样使用,只是切记,不要把它真的当成控制流程的工具或手段。那么究竟什么时候才要抛出这样的异常呢?要考虑,如果调用者调用出错后,一定要让调用者对此错误进行处理才可以,满足这样的要求时,我们才会考虑使用受检异常。

    接下来,我们来看一下非受检异常呢(RuntimeException),对于RuntimeException这种异常,我们其实很多见,比如java.lang.NullPointerException/java.lang.IllegalArgumentException等,那么这种异常我们时候抛出呢?当我们在写某个方法的时候,可能会偶然遇到某个错误,我们认为这个问题时运行时可能为发生的,并且理论上讲,没有这个问题的话,程序将会正常执行的时候,它不强制要求调用者一定要捕获这个异常,此时抛出RuntimeException异常,举个例子,当传来一个路径的时候,需要返回一个路径对应的File对象:

    public void test() {

        myTest.getFiles("");

    }

    public File getFiles(String path) {

        if(null == path || "".equals(path)){

            throw  new NullPointerException("路径不能为空!");

        }

        File file = new File(path);

        return file;

    }

    上述例子表明,如果调用者调用getFiles(String)的时候如果path是空,那么就抛出空指针异常(它是RuntimeException的子类),调用者不用显示的进行try…catch…操作进行强制处理.这就要求调用者在调用这样的方法时先进行验证,避免发生RuntimeException.如下:

    应该选用哪种异常

    通过以上的描述和举例,可以总结出一个结论,RuntimeException异常和受检异常之间的区别就是:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

    场景介绍和技术选型

    架构描述

    正如我们所知,传统的项目都是以MVC框架为基础进行开发的,本文主要从使用restful风格接口的设计来体验一下异常处理的优雅。

    我们把关注点放在restful的api层(和web中的controller层类似)和service层,研究一下在service中如何抛出异常,然后api层如何进行捕获并且转化异常。

    使用的技术是:spring-boot,jpa(hibernate),mysql,如果对这些技术不是太熟悉,读者需要自行阅读相关材料。

    业务场景描述

    选择一个比较简单的业务场景,以电商中的收货地址管理为例,用户在移动端进行购买商品时,需要进行收货地址管理,在项目中,提供一些给移动端进行访问的api接口,如:添加收货地址,删除收货地址,更改收货地址,默认收货地址设置,收货地址列表查询,单个收货地址查询等接口。

    构建约束条件

    ok,这个是设置好的一个很基本的业务场景,当然,无论什么样的api操作,其中都包含一些规则:

    添加收货地址:

    入参:

    • 用户id

    • 收货地址实体信息

    约束:

    • 用户id不能为空,且此用户确实是存在 的

    • 收货地址的必要字段不能为 空

    • 如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址 —

    删除收货地址:

    入参:

    • 用户id

    • 收货地址id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址不能为空,且此收货地址确实是存在的

    • 判断此收货地址是否是用户的收货地址

    • 判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除

    更改收货地址:

    入参:

    • 用户id

    • 收货地址id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址不能为空,且此收货地址确实是存在的

    • 判断此收货地址是否是用户的收货地址

    默认地址设置:

    入参:

    • 用户id

    • 收货地址id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址不能为空,且此收货地址确实是存在的

    • 判断此收货地址是否是用户的收货地址

    收货地址列表查询:

    入参:

    • 用户id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    单个收货地址查询:

    入参:

    • 用户id

    • 收货地址id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址不能为空,且此收货地址确实是存在的

    • 判断此收货地址是否是用户的收货地址

    约束判断和技术选型

    对于上述列出的约束条件和功能列表,我选择几个比较典型的异常处理场景进行分析:添加收货地址,删除收货地址,获取收货地址列表。

    那么应该有哪些必要的知识储备呢,让我们看一下收货地址这个功能:

    添加收货地址中需要对用户id和收货地址实体信息就行校验,那么对于非空的判断,我们如何进行工具的选择呢?传统的判断如下:

    /**

     * 添加地址

     * @param uid

     * @param address

     * @return

     */

    public Address addAddress(Integer uid,Address address){

        if(null != uid){

            //进行处理..

        }

        return null;

    }

    上边的例子,如果只判断uid为空还好,如果再去判断address这个实体中的某些必要属性是否为空,在字段很多的情况下,这无非是灾难性的。

    那我们应该怎么进行这些入参的判断呢,给大家介绍两个知识点:

    1. Guava中的Preconditions类实现了很多入参方法的判断

    2. jsr 303的validation规范(目前实现比较全的是hibernate实现的hibernate-validator)

    如果使用了这两种推荐技术,那么入参的判断会变得简单很多。推荐大家多使用这些成熟的技术和jar工具包,他可以减少很多不必要的工作量。我们只需要把重心放到业务逻辑上。而不会因为这些入参的判断耽误更多的时间。

    如何优雅的设计java异常

    domain介绍

    根据项目场景来看,需要两个domain模型,一个是用户实体,一个是地址实体.

    Address domain如下:

    @Entity

    @Data

    public class Address {

        @Id

        @GeneratedValue

        private Integer id;

        private String province;//省

        private String city;//市

        private String county;//区

        private Boolean isDefault;//是否是默认地址

        @ManyToOne(cascade={CascadeType.ALL})

        @JoinColumn(name="uid")

        private User user;

    }

    User domain如下:

    @Entity

    @Data

    public class User {

        @Id

       @GeneratedValue

       private Integer id;

       private String name;//姓名

        @OneToMany(cascade= CascadeType.ALL,mappedBy="user",fetch = FetchType.LAZY)

            private Set

    addresses;

    }

    ok,上边是一个模型关系,用户-收货地址的关系是1-n的关系。上边的@Data是使用了一个叫做lombok的工具,它自动生成了Setter和Getter等方法,用起来非常方便,感兴趣的读者可以自行了解一下。

    dao介绍

    数据连接层,我们使用了spring-data-jpa这个框架,它要求我们只需要继承框架提供的接口,并且按照约定对方法进行取名,就可以完成我们想要的数据库操作。

    用户数据库操作如下:

    @Repository

    public interface IUserDao extends JpaRepository {

    }

    收货地址操作如下:

    @Repository

    public interface IAddressDao extends JpaRepository

    {

    }

    正如读者所看到的,我们的DAO只需要继承JpaRepository,它就已经帮我们完成了基本的CURD等操作,如果想了解更多关于spring-data的这个项目,请参考一下spring的官方文档,它比不方案我们对异常的研究。

    Service异常设计

    ok,终于到了我们的重点了,我们要完成service一些的部分操作:添加收货地址,删除收货地址,获取收货地址列表.

    首先看我的service接口定义:

    public interface IAddressService {

    /**

     * 创建收货地址

     * @param uid

     * @param address

     * @return

     */

    Address createAddress(Integer uid,Address address);

    /**

     * 删除收货地址

     * @param uid

     * @param aid

     */

    void deleteAddress(Integer uid,Integer aid);

    /**

     * 查询用户的所有收货地址

     * @param uid

     * @return

     */

    List

    listAddresses(Integer uid);

    }

    我们来关注一下实现:

    添加收货地址

    首先再来看一下之前整理的约束条件:

    入参:

    • 用户id

    • 收货地址实体信息

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址的必要字段不能为空

    • 如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址

    先看以下代码实现:

    @Override

    public Address createAddress(Integer uid, Address address) {

        //============ 以下为约束条件   ==============

        //1.用户id不能为空,且此用户确实是存在的

        Preconditions.checkNotNull(uid);

        User user = userDao.findOne(uid);

        if(null == user){

            throw new RuntimeException("找不到当前用户!");

        }

        //2.收货地址的必要字段不能为空

        BeanValidators.validateWithException(validator, address);

        //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址

        if(ObjectUtils.isEmpty(user.getAddresses())){

            address.setIsDefault(true);

        }

        //============ 以下为正常执行的业务逻辑   ==============

        address.setUser(user);

        Address result = addressDao.save(address);

        return result;

    }

    其中,已经完成了上述所描述的三点约束条件,当三点约束条件都满足时,才可以进行正常的业务逻辑,否则将抛出异常(一般在此处建议抛出运行时异常-RuntimeException)。

    介绍以下以上我所用到的技术:

    1. Preconfitions.checkNotNull(T t)这个是使用Guava中的com.google.common.base.Preconditions进行判断的,因为service中用到的验证较多,所以建议将Preconfitions改成静态导入的方式:

    import static com.google.common.base.Preconditions.checkNotNull;

    当然Guava的github中的说明也建议我们这样使用。

    2. BeanValidators.validateWithException(validator, address);

    这个使用了hibernate实现的jsr 303规范来做的,需要传入一个validator和一个需要验证的实体,那么validator是如何获取的呢,如下:

    @Configuration

    public class BeanConfigs {

    @Bean

    public javax.validation.Validator getValidator(){

        return new LocalValidatorFactoryBean();

    }

    }

    他将获取一个Validator对象,然后我们在service中进行注入便可以使用了:

    @Autowired    

    private Validator validator ;

    那么BeanValidators这个类是如何实现的?其实实现方式很简单,只要去判断jsr 303的标注注解就ok了。

    那么jsr 303的注解写在哪里了呢?当然是写在address实体类中了:

    @Entity

    @Setter

    @Getter

    public class Address {

    @Id

        @GeneratedValue

        private Integer id;

        @NotNull

    private String province;//省

    @NotNull

    private String city;//市

    @NotNull

    private String county;//区

    private Boolean isDefault = false;//是否是默认地址

    @ManyToOne(cascade={CascadeType.ALL})

    @JoinColumn(name="uid")

    private User user;

    }

    写好你需要的约束条件来进行判断,如果合理的话,才可以进行业务操作,从而对数据库进行操作。

    这块的验证是必须的,一个最主要的原因是:这样的验证可以避免脏数据的插入。如果读者有正式上线的经验的话,就可以理解这样的一个事情,任何的代码错误都可以容忍和修改,但是如果出现了脏数据问题,那么它有可能是一个毁灭性的灾难。程序的问题可以修改,但是脏数据的出现有可能无法恢复。所以这就是为什么在service中一定要判断好约束条件,再进行业务逻辑操作的原因了。

    此处的判断为业务逻辑判断,是从业务角度来进行筛选判断的,除此之外,有可能在很多场景中都会有不同的业务条件约束,只需要按照要求来做就好。

    对于约束条件的总结如下:

    1. 基本判断约束(null值等基本判断)

    2. 实体属性约束(满足jsr 303等基础判断)

    3. 业务条件约束(需求提出的不同的业务约束)

    当这个三点都满足时,才可以进行下一步操作

    ok,基本介绍了如何做一个基础的判断,那么再回到异常的设计问题上,上述代码已经很清楚的描述如何在适当的位置合理的判断一个异常了,那么如何合理的抛出异常呢?

    只抛出RuntimeException就算是优雅的抛出异常吗?当然不是,对于service中的抛出异常,笔者认为大致有两种抛出的方法:

    1. 抛出带状态码RumtimeException异常

    2. 抛出指定类型的RuntimeException异常

    相对这两种异常的方式进行结束,第一种异常指的是我所有的异常都抛RuntimeException异常,但是需要带一个状态码,调用者可以根据状态码再去查询究竟service抛出了一个什么样的异常。

    第二种异常是指在service中抛出什么样的异常就自定义一个指定的异常错误,然后在进行抛出异常。

    一般来讲,如果系统没有别的特殊需求的时候,在开发设计中,建议使用第二种方式。但是比如说像基础判断的异常,就可以完全使用guava给我们提供的类库进行操作。jsr 303异常也可以使用自己封装好的异常判断类进行操作,因为这两种异常都是属于基础判断,不需要为它们指定特殊的异常。但是对于第三点义务条件约束判断抛出的异常,就需要抛出指定类型的异常了。

    对于

    throw new RuntimeException("找不到当前用户!");

    定义一个特定的异常类来进行这个义务异常的判断:

    public class NotFindUserException extends RuntimeException {

    public NotFindUserException() {

        super("找不到此用户");

    }

    public NotFindUserException(String message) {

        super(message);

    }

    }

    然后将此处改为:

    throw new NotFindUserException("找不到当前用户!");

    or

    throw new NotFindUserException();

    ok,通过以上对service层的修改,代码更改如下:

    @Override

    public Address createAddress(Integer uid, Address address) {

        //============ 以下为约束条件   ==============

        //1.用户id不能为空,且此用户确实是存在的

        checkNotNull(uid);

        User user = userDao.findOne(uid);

        if(null == user){

            throw new NotFindUserException("找不到当前用户!");

        }

        //2.收货地址的必要字段不能为空

        BeanValidators.validateWithException(validator, address);

        //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址

        if(ObjectUtils.isEmpty(user.getAddresses())){

            address.setIsDefault(true);

        }

        //============ 以下为正常执行的业务逻辑   ==============

        address.setUser(user);

        Address result = addressDao.save(address);

        return result;

    }

    这样的service就看起来稳定性和理解性就比较强了。

    删除收货地址:

    入参:

    • 用户id

    • 收货地址id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    • 收货地址不能为空,且此收货地址确实是存在的

    • 判断此收货地址是否是用户的收货地址

    • 判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除

    它与上述添加收货地址类似,故不再赘述,delete的service设计如下:

    @Override

    public void deleteAddress(Integer uid, Integer aid) {

        //============ 以下为约束条件   ==============

        //1.用户id不能为空,且此用户确实是存在的

        checkNotNull(uid);

        User user = userDao.findOne(uid);

        if(null == user){

            throw new NotFindUserException();

        }

        //2.收货地址不能为空,且此收货地址确实是存在的

        checkNotNull(aid);

        Address address = addressDao.findOne(aid);

        if(null == address){

            throw new NotFindAddressException();

        }

        //3.判断此收货地址是否是用户的收货地址

        if(!address.getUser().equals(user)){

            throw new NotMatchUserAddressException();

        }

        //4.判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除

        if(address.getIsDefault()){

           throw  new DefaultAddressNotDeleteException();

        }

        //============ 以下为正常执行的业务逻辑   ==============

        addressDao.delete(address);

    }

    设计了相关的四个异常类:NotFindUserException,NotFindAddressException,NotMatchUserAddressException,DefaultAddressNotDeleteException.根据不同的业务需求抛出不同的异常。

    获取收货地址列表:

    入参:

    • 用户id

    约束:

    • 用户id不能为空,且此用户确实是存在的

    代码如下:

    @Override

    public List

    listAddresses(Integer uid) {

        //============ 以下为约束条件   ==============

        //1.用户id不能为空,且此用户确实是存在的

        checkNotNull(uid);

        User user = userDao.findOne(uid);

        if(null == user){

            throw new NotFindUserException();

        }

        //============ 以下为正常执行的业务逻辑   ==============

        User result = userDao.findOne(uid);

        return result.getAddresses();

    }

    api异常设计

    大致有两种抛出的方法:

    • 抛出带状态码RumtimeException异常

    • 抛出指定类型的RuntimeException异常

    这个是在设计service层异常时提到的,通过对service层的介绍,我们在service层抛出异常时选择了第二种抛出的方式,不同的是,在api层抛出异常我们需要使用这两种方式进行抛出:要指定api异常的类型,并且要指定相关的状态码,然后才将异常抛出,这种异常设计的核心是让调用api的使用者更能清楚的了解发生异常的详细信息,除了抛出异常外,我们还需要将状态码对应的异常详细信息以及异常有可能发生的问题制作成一个对应的表展示给用户,方便用户的查询。(如github提供的api文档,微信提供的api文档等),还有一个好处:如果用户需要自定义提示消息,可以根据返回的状态码进行提示的修改。

    api验证约束

    首先对于api的设计来说,需要存在一个dto对象,这个对象负责和调用者进行数据的沟通和传递,然后dto->domain在传给service进行操作,这一点一定要注意,第二点,除了说道的service需要进行基础判断(null判断)和jsr 303验证以外,同样的,api层也需要进行相关的验证,如果验证不通过的话,直接返回给调用者,告知调用失败,不应该带着不合法的数据再进行对service的访问,那么读者可能会有些迷惑,不是service已经进行验证了,为什么api层还需要进行验证么?这里便设计到了一个概念:编程中的墨菲定律,如果api层的数据验证疏忽了,那么有可能不合法数据就带到了service层,进而讲脏数据保存到了数据库。

    所以缜密编程的核心是:永远不要相信收到的数据是合法的。

    api异常设计

    设计api层异常时,正如我们上边所说的,需要提供错误码和错误信息,那么可以这样设计,提供一个通用的api超类异常,其他不同的api异常都继承自这个超类:

    public class ApiException extends RuntimeException {

    protected Long errorCode ;

    protected Object data ;

    public ApiException(Long errorCode,String message,Object data,Throwable e){

        super(message,e);

        this.errorCode = errorCode ;

        this.data = data ;

    }

    public ApiException(Long errorCode,String message,Object data){

        this(errorCode,message,data,null);

    }

    public ApiException(Long errorCode,String message){

        this(errorCode,message,null,null);

    }

    public ApiException(String message,Throwable e){

        this(null,message,null,e);

    }

    public ApiException(){

    }

    public ApiException(Throwable e){

        super(e);

    }

    public Long getErrorCode() {

        return errorCode;

    }

    public void setErrorCode(Long errorCode) {

        this.errorCode = errorCode;

    }

    public Object getData() {

        return data;

    }

    public void setData(Object data) {

        this.data = data;

    }

    }

    然后分别定义api层异常:ApiDefaultAddressNotDeleteException,ApiNotFindAddressException,ApiNotFindUserException,ApiNotMatchUserAddressException。

    以默认地址不能删除为例:

    public class ApiDefaultAddressNotDeleteException extends ApiException {

    public ApiDefaultAddressNotDeleteException(String message) {

        super(AddressErrorCode.DefaultAddressNotDeleteErrorCode, message, null);

    }

    }

    AddressErrorCode.DefaultAddressNotDeleteErrorCode就是需要提供给调用者的错误码。错误码类如下:

    public abstract class AddressErrorCode {

        public static final Long DefaultAddressNotDeleteErrorCode = 10001L;//默认地址不能删除

        public static final Long NotFindAddressErrorCode = 10002L;//找不到此收货地址

        public static final Long NotFindUserErrorCode = 10003L;//找不到此用户

        public static final Long NotMatchUserAddressErrorCode = 10004L;//用户与收货地址不匹配

    }

    ok,那么api层的异常就已经设计完了,在此多说一句,AddressErrorCode错误码类存放了可能出现的错误码,更合理的做法是把他放到配置文件中进行管理。

    api处理异常

    api层会调用service层,然后来处理service中出现的所有异常,首先,需要保证一点,一定要让api层非常轻,基本上做成一个转发的功能就好(接口参数,传递给service参数,返回给调用者数据,这三个基本功能),然后就要在传递给service参数的那个方法调用上进行异常处理。

    此处仅以添加地址为例:

    @Autowired

    private IAddressService addressService;

    /**

     * 添加收货地址

     * @param addressDTO

     * @return

     */

    @RequestMapping(method = RequestMethod.POST)

    public AddressDTO add(@Valid @RequestBody AddressDTO addressDTO){

        Address address = new Address();

        BeanUtils.copyProperties(addressDTO,address);

        Address result;

        try {

            result = addressService.createAddress(addressDTO.getUid(), address);

        }catch (NotFindUserException e){

            throw new ApiNotFindUserException("找不到该用户");

        }catch (Exception e){//未知错误

            throw new ApiException(e);

        }

        AddressDTO resultDTO = new AddressDTO();

        BeanUtils.copyProperties(result,resultDTO);

        resultDTO.setUid(result.getUser().getId());

        return resultDTO;

    }

    这里的处理方案是调用service时,判断异常的类型,然后将任何service异常都转化成api异常,然后抛出api异常,这是常用的一种异常转化方式。相似删除收货地址和获取收货地址也类似这样处理,在此,不在赘述。

    api异常转化

    已经讲解了如何抛出异常和何如将service异常转化为api异常,那么转化成api异常直接抛出是否就完成了异常处理呢? 答案是否定的,当抛出api异常后,我们需要把api异常返回的数据(json or xml)让用户看懂,那么需要把api异常转化成dto对象(ErrorDTO),看如下代码:

    @ControllerAdvice(annotations = RestController.class)

    class ApiExceptionHandlerAdvice {

    /**

     * Handle exceptions thrown by handlers.

     */

    @ExceptionHandler(value = Exception.class)

    @ResponseBody

    public ResponseEntity exception(Exception exception,HttpServletResponse response) {

        ErrorDTO errorDTO = new ErrorDTO();

        if(exception instanceof ApiException){//api异常

            ApiException apiException = (ApiException)exception;

            errorDTO.setErrorCode(apiException.getErrorCode());

        }else{//未知异常

            errorDTO.setErrorCode(0L);

        }

        errorDTO.setTip(exception.getMessage());

        ResponseEntity responseEntity = new ResponseEntity<>(errorDTO,HttpStatus.valueOf(response.getStatus()));

        return responseEntity;

    }

    @Setter

    @Getter

    class ErrorDTO{

        private Long errorCode;

        private String tip;

    }

    }

    ok,这样就完成了api异常转化成用户可以读懂的DTO对象了,代码中用到了@ControllerAdvice,这是spring MVC提供的一个特殊的切面处理。

    当调用api接口发生异常时,用户也可以收到正常的数据格式了,比如当没有用户(uid为2)时,却为这个用户添加收货地址,postman(Google plugin 用于模拟http请求)之后的数据:

    {

      "errorCode": 10003,

      "tip": "找不到该用户"

    }

    总结

    本文只从如何设计异常作为重点来讲解,涉及到的api传输和service的处理,还有待优化,比如api接口访问需要使用https进行加密,api接口需要OAuth2.0授权或api接口需要签名认证等问题,文中都未曾提到,本文的重心在于异常如何处理,所以读者只需关注涉及到异常相关的问题和处理方式就可以了。希望本篇文章对你理解异常有所帮助。

    关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取一份栈长整理的 Java 最新技术干货。

    最近干货分享

    Google 出品的 Java 编码规范,强烈推荐!

    JDK 13 的 12 个新特性,学习一波

    加入我们的技术微信群和QQ群

    cd5eb34edce80efb7442559cc59a10ff.gif

    展开全文
  • Java——输入类型异常

    2019-10-04 14:03:53
    在我们写程序时,会要求写只输入数字的输入框,但在... 方法很简单,我们的输入框往往是String类型,当输入完毕,提交后,才会将String类型的的字符串转换成Int类型。 我们只需要在String转Int之前,先判断Stin...
  • 然后你在下面这个对double进行格式化的时候,就报异常了。 知道这个单词的意思:无穷 a point far away that can never be reached 因为double里面没有完完全全的正儿八经的0,你看到的0,实际上可能是0.00000....
  • 原标题:达内java知识点分享:java语言中如何判断字符串是否为数字达内java知识点分享:java语言中如何判断字符串是否为数字【实例描述】软件运行过程中,经常需要用户输入数值、货币值等信息,然后进行处理。...
  • 这个问题并不难,下面这一段代码,只要仿照它并稍作修改就可以了: public void actionPerformed(ActionEvent e) { //接受事件,并作出... try{ //可能产生异常 int num; num=Integer.parseInt(tx.getText()
  • lrwinx.github.io/2016/04/28/导语如何选择异常类型异常的类别如何选择异常什么时候才需要抛异常应该抛出怎样的异常应该选用哪种异常场景介绍和技术选型架构描述业务场景描述构建约束条件约束判断和技术选型如何优雅...
  • 2. 将8中基础类型的封装类赋值给对应的基础类。个人在做项目时,对NullPointerException的几点总结,请网友拍砖!!!多多提意见,1.对于别人接口的返回对象要做非空判断,因为我们不清楚获得的对象会不会为空,对于...
  • 前言:异常大体分成两种:已知异常、未知异常一、已知异常:例如:用户名或密码错误,导致的登陆异常二、未知异常:例如:,输入框限制只能输入number, 但输入了文字、导致的数据类型错误,还有空指针、数组越界 ...
  • 由于用户输入只能是字符串类型,如果输入了非法的信息,如在货币值中输入了字母“a”以及其他非数字字符,那么在运行时会抛出异常。通常我们可以通过捕获异常判断输入信息是否合法,但这并不是最好的处理方法。本...
  • throws + 异常类型5. 重写方法异常抛出的规则6. 开发中如何选择使用try-catch-finally 还是throws?7. 手动抛出异常 throw8. 自定义异常类9. 异常处理练习题9.1. 判断以下代码的输出内容9.2. 异常处理综合练.
  • 本文介绍如何避免出现空指针异常。 首先你要知道空指针异常为什么会出现: 引用直接指向空对象,大概率是你没有取到某个对象; 将8种基础类型的封装类赋值给对应的基础类。 接下来看怎么防止异常: 1. 前台...
  • java 代码中如何预防空指针异常 项目中遇到的NullPointerException 分两种情况: 1. 引用空对象,即调用空对象的方法或引用空对象的属性。 2. 将8中基础类型的封装类赋值给对应的基础类。   个人在做项目时,...
  • String类型判断(java基础)

    千次阅读 2018-10-31 18:44:37
    对于Android开发人员总是避免不了要判断类型,后台返回数据中某个值可能是"",有的时候某个值又是null,我们应该如何正确判断呢? 概念 null null表示这个字符串变量不指向内存中的任何一块地址,也就是...
  • java 如何保存、读取javaBean中定义String类型,oracle数据库字段为blob类型的值 这是我的业务逻辑代码 try { String encoding = "GBK"; File file = new File("E:\\workspaceNeuSoft\\tomcat7.0.40\\logs\\...
  • Java代码中如何防止空指针异常

    千次阅读 2016-08-08 09:55:16
    项目中遇到的NullPointerException 分两种情况: 1. 引用空对象,即调用空对象的方法或引用空对象的属性。...1.对于别人接口的返回对象要做非空判断,因为我们不清楚获得的对象会不会为空,对于Colle
  • 项目中遇到的NullPointerException 分两种情况: 引用空对象,即调用空对象的方法或引用空对象的属性。...1.对于别人接口的返回对象要做非空判断,因为我们不清楚获得的对象会不会为空,对于Collect...
  • 前言:异常大体分成两种:已知异常、未知异常一、已知异常:例如:用户名或密码错误,导致的登陆异常二、未知异常:例如:,输入框限制只能输入number, 但输入了文字、导致的数据类型错误,还有空指针、数组越界 ...
  • 如何判断一个字符串是否是数字? 如何判断一个字符串完全由字母组成? 可以用类型转换,如果失败 抛出异常用try/catch接受 可以判断, 我个人认为这种方法不好。感觉好像,用异常来做逻辑判断不是太好。 另为异常...
  • 然后就是java里面如何判断输入的是哪种类型?正则表达式有点不太懂咯。// BookSys类:package com.bob;public class BookSys {String[] bookName = { "数据结构", "高等数学", "中国近现代史纲要", "数字电路" };}// ...
  • 1. 计算机中的整数如何存储? 我们知道计算机中的符号化是根据ASCALL码来映射的。而计算机中的符合映射的基础是0和1. 即,符合在计算机中有对应的01序列。而0和1的序列组合的基础是二进制,因此,当我们想存 储...
  • 本节主要介绍 Java 空对象是什么及如何判断对象是否为空。经过《Java new运算符深入剖析》一节的学习,我们知道了对象的实例化就是为对象开辟内存空间。例如以下 3 条语句(如果理解下面内容有点困难,建议先学习...
  • 此任务和之前相似仍需要自行定义异常类型,而此处异常要考虑两种情况,一是输入的字符串并非数字;二是输入的字符串数字过大或过小超出了现实中成绩的常规范围。这也是此题的关键,即如何判断输入的字符串是否符合...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 261
精华内容 104
关键字:

java如何判断异常类型

java 订阅