当前位置: 代码网 > it编程>编程语言>Java > SpringBoot中如何进行全局异常处理方式

SpringBoot中如何进行全局异常处理方式

2024年11月05日 Java 我要评论
前言在springboot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同

前言

在springboot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未来想改变异常的处理逻辑,也变得比较困难。这篇文章带大家了解一下如何优雅的进行全局异常处理。

为了实现全局拦截,这里使用到了spring中提供的两个注解,@restcontrolleradvice和@exceptionhandler,结合使用可以拦截程序中产生的异常,并且根据不同的异常类型分别处理。下面我会先介绍如何利用这两个注解,优雅的完成全局异常的处理,接着解释这背后的原理。

如何实现全局拦截?

1.1 自定义异常处理类

在下面的例子中,我们继承了responseentityexceptionhandler并使用@restcontrolleradvice注解了这个类,接着结合@exceptionhandler针对不同的异常类型,来定义不同的异常处理方法。

这里可以看到我处理的异常是自定义异常,后续我会展开介绍。

  • responseentityexceptionhandler中包装了各种springmvc在处理请求时可能抛出的异常的处理,处理结果都是封装成一个responseentity对象。
  • responseentityexceptionhandler是一个抽象类,通常我们需要定义一个用来处理异常的使用@restcontrolleradvice注解标注的异常处理类来继承自responseentityexceptionhandler。
  • responseentityexceptionhandler中为每个异常的处理都单独定义了一个方法,如果默认的处理不能满足你的需求,则可以重写对某个异常的处理。
@log4j2  
@restcontrolleradvice  
public class globalexceptionhandler extends responseentityexceptionhandler {  

    /**  
     * 定义要捕获的异常 可以多个 @exceptionhandler({})     *  
     * @param request  request  
     * @param e        exception  
     * @param response response  
     * @return 响应结果  
     */  
    @exceptionhandler(auroraruntimeexception.class)  
    public genericresponse customexceptionhandler(httpservletrequest request, final exception e, httpservletresponse response) {  
        auroraruntimeexception exception = (auroraruntimeexception) e;  

       if (exception.getcode() == responsecode.user_input_error) {  
           response.setstatus(httpstatus.bad_request.value());  
       } else if (exception.getcode() == responsecode.forbidden) {  
           response.setstatus(httpstatus.forbidden.value());  
       } else {  
           response.setstatus(httpstatus.internal_server_error.value());  
       }  

        return new genericresponse(exception.getcode(), null, exception.getmessage());  
    }  

    @exceptionhandler(notloginexception.class)  
    public genericresponse tokenexceptionhandler(httpservletrequest request, final exception e, httpservletresponse response) {  
        log.error("token exception", e);  
        response.setstatus(httpstatus.forbidden.value());  
        return new genericresponse(responsecode.authentication_needed);  
    }  

}

1.2 定义异常码

这里定义了常见的几种异常码,主要用在抛出自定义异常时,对不同的情形进行区分。

@getter  
public enum responsecode {  

    success(0, "success"),  

    internal_error(1, "服务器内部错误"),  

    user_input_error(2, "用户输入错误"),  

    authentication_needed(3, "token过期或无效"),  

    forbidden(4, "禁止访问"),  

    too_frequent_visit(5, "访问太频繁,请休息一会儿");  

    private final int code;  

    private final string message;  

    private final response.status status;  

    responsecode(int code, string message, response.status status) {  
        this.code = code;  
        this.message = message;  
        this.status = status;  
    }  

    responsecode(int code, string message) {  
        this(code, message, response.status.internal_server_error);  
    }  

}

1.3 自定义异常类

这里我定义了一个auroraruntimeexception的异常,就是在上面的异常处理函数中,用到的异常。

每个异常实例会有一个对应的异常码,也就是前面刚定义好的。

@getter  
public class auroraruntimeexception extends runtimeexception {  

    private final responsecode code;  

    public auroraruntimeexception() {  
        super(string.format("%s", responsecode.internal_error.getmessage()));  
        this.code = responsecode.internal_error;  
    }  

    public auroraruntimeexception(throwable e) {  
        super(e);  
        this.code = responsecode.internal_error;  
    }  

    public auroraruntimeexception(string msg) {  
        this(responsecode.internal_error, msg);  
    }  

    public auroraruntimeexception(responsecode code) {  
        super(string.format("%s", code.getmessage()));  
        this.code = code;  
    }  

    public auroraruntimeexception(responsecode code, string msg) {  
        super(msg);  
        this.code = code;  
    }  

}

1.4 自定义返回类型

为了保证各个接口的返回统一,这里专门定义了一个返回类型。

@getter  
@setter  
public class genericresponse<t> {  

    private int code;  

    private t data;  

    private string message;  

    public genericresponse() {};  

    public genericresponse(int code, t data) {  
        this.code = code;  
        this.data = data;  
    }  

    public genericresponse(int code, t data, string message) {  
        this(code, data);  
        this.message = message;  
    }  

    public genericresponse(responsecode responsecode) {  
        this.code = responsecode.getcode();  
        this.data = null;  
        this.message = responsecode.getmessage();  
    }  

    public genericresponse(responsecode responsecode, t data) {  
        this(responsecode);  
        this.data = data;  
    }  

    public genericresponse(responsecode responsecode, t data, string message) {  
        this(responsecode, data);  
        this.message = message;  
    }  
}

实际测试异常

下面的例子中,我们想获取到用户的信息,如果用户的信息不存在,可以直接抛出一个异常,这个异常会被我们上面定义的全局异常处理方法所捕获,然后根据不同的异常编码,完成不同的处理和返回。

public user getuserinfo(long userid) {  
    // some logic

    user user = daofactory.getextendedusermapper().selectbyprimarykey(userid);  
    if (user == null) {  
        throw new auroraruntimeexception(responsecode.user_input_error, "用户id不存在");  
    }

    // some logic
    ....
}

以上就完成了整个全局异常的处理过程,接下来重点说说为什么@restcontrolleradvice和@exceptionhandler结合使用可以拦截程序中产生的异常?

全局拦截的背后原理?

下面会提到@controlleradvice注解,简单地说,@restcontrolleradvice与@controlleradvice的区别就和@restcontroller与@controller的区别类似,@restcontrolleradvice注解包含了@controlleradvice注解和@responsebody注解。

public class dispatcherservlet extends frameworkservlet {
    // ......
    protected void initstrategies(applicationcontext context) {
        initmultipartresolver(context);
        initlocaleresolver(context);
        initthemeresolver(context);
        inithandlermappings(context);
        inithandleradapters(context);

        // 重点关注
        inithandlerexceptionresolvers(context);

        initrequesttoviewnametranslator(context);
        initviewresolvers(context);
        initflashmapmanager(context);
    }
    // ......
}

在inithandlerexceptionresolvers(context)方法中,会取得所有实现了handlerexceptionresolver接口的bean并保存起来,其中就有一个类型为exceptionhandlerexceptionresolver的bean,这个bean在应用启动过程中会获取所有被@controlleradvice注解标注的bean对象做进一步处理,关键代码在这里:

public class exceptionhandlerexceptionresolver extends abstracthandlermethodexceptionresolver
        implements applicationcontextaware, initializingbean {
    // ......
    private void initexceptionhandleradvicecache() {
        // ......
        list<controlleradvicebean> advicebeans = controlleradvicebean.findannotatedbeans(getapplicationcontext());
        annotationawareordercomparator.sort(advicebeans);

        for (controlleradvicebean advicebean : advicebeans) {
            exceptionhandlermethodresolver resolver = new exceptionhandlermethodresolver(advicebean.getbeantype());
            if (resolver.hasexceptionmappings()) {
                // 找到所有exceptionhandler标注的方法并保存成一个exceptionhandlermethodresolver类型的对象缓存起来
                this.exceptionhandleradvicecache.put(advicebean, resolver);
                if (logger.isinfoenabled()) {
                    logger.info("detected @exceptionhandler methods in " + advicebean);
                }
            }
            // ......
        }
    }
    // ......
}

当controller抛出异常时,dispatcherservlet通过exceptionhandlerexceptionresolver来解析异常,而exceptionhandlerexceptionresolver又通过exceptionhandlermethodresolver 来解析异常, exceptionhandlermethodresolver 最终解析异常找到适用的@exceptionhandler标注的方法是这里:

public class exceptionhandlermethodresolver {
    // ......
    private method getmappedmethod(class<? extends throwable> exceptiontype) {
        list<class<? extends throwable>> matches = new arraylist<class<? extends throwable>>();
        // 找到所有适用于controller抛出异常的处理方法,例如controller抛出的异常
        // 是auroraruntimeexception(继承自runtimeexception),那么@exceptionhandler(auroraruntimeexception.class)和
        // @exceptionhandler(exception.class)标注的方法都适用此异常
        for (class<? extends throwable> mappedexception : this.mappedmethods.keyset()) {
            if (mappedexception.isassignablefrom(exceptiontype)) {
                matches.add(mappedexception);
            }
        }
        if (!matches.isempty()) {
        /* 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如
    controller抛出的异常是是auroraruntimeexception(继承自runtimeexception),那么auroraruntimeexception
    相对于@exceptionhandler(auroraruntimeexception.class)声明的auroraruntimeexception.class其深度是0,
    相对于@exceptionhandler(exception.class)声明的exception.class其深度是2,所以
    @exceptionhandler(bizexception.class)标注的方法会排在前面 */
            collections.sort(matches, new exceptiondepthcomparator(exceptiontype));
            return this.mappedmethods.get(matches.get(0));
        }
        else {
            return null;
        }
    }
    // ......
}

整个@restcontrolleradvice处理的流程就是这样,结合@exceptionhandler就完成了对不同异常的灵活处理。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com