博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot学习三:异常处理和记录日志
阅读量:6198 次
发布时间:2019-06-21

本文共 9556 字,大约阅读时间需要 31 分钟。

hot3.png

相关文章

前言

在上两篇文章中,学习了 SpringBoot 的一个初步使用和通过 SpringBoot 来操作数据库的相关知识,接下来就学习下如何使用 SpringBoot 来进行异常的处理和记录日志的功能。

异常处理

在处理业务的时候,有时候需要捕获一些特定的异常,进行相应的处理,如跳转到一个特定的错误页面之类的,在 SpringBoot 中,处理异常一般使用 @ControllerAdvice 和 @ExceptionHandler 处理进行处理,接下来,先看看这两个注解:

@ControllerAdvice

首先来看下该注解的一个定义:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface ControllerAdvice {}

从上述定义中可以看到,该注解的 Target 为 ElementType.TYPE,也就是说,该注解只能应用于 类, 接口和枚举上;该注解还被 修改,表示被 @ControllerAdvice 修改的类也可以被注入到 Spring 容器中去,被 Spring 进行管理。

此外,被 @ControllerAdvice 修饰的类,也是一个 Controller 层;被 @ControllerAdvice 修饰的类的内部用 @ExceptionHandler、@InitBinder、@ModelAttribute 注解修改的方法,只能应用于被 @RequestMapping修饰的方法(靠,怎么感觉有点绕啊),看下 javadoc的描述吧:

It is typically used to define {@link ExceptionHandler @ExceptionHandler},* {@link InitBinder @InitBinder}, and {@link ModelAttribute @ModelAttribute}* methods that apply to all {@link RequestMapping @RequestMapping} methods.

@ExceptionHandler

先看下定义:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExceptionHandler {}

该注解只能用于在方法上,也就是当特定异常发生的时候,会执行相应的方法。

下面就用这两个注解来进行一个异常的处理:

首先,先自定义一个异常:

/** * 自定义异常 */public class MyException extends Exception {    public MyException(String message) {        super(message);    }}

接下来,就使用  @ControllerAdvice 和 @ExceptionHandler 编写异常的处理类:

/** * 异常处理, 当出现异常的时候,会执行相应的方法 */@ControllerAdvicepublic class MyExceptionHandler {    @ExceptionHandler(value = Exception.class)    public void defaultExceptionHandler(Exception e){        System.out.println(String.format("开始异常处理: %s", e.getMessage()));    }    @ExceptionHandler(value = MyException.class)    public void myExceptionHandler(MyException e){        System.out.println("出现自定义异常了 : " + e.getMessage());    }}

当程序产生异常的时候,会进入相应的 @ExceptionHandler  方法,但是被 @ExceptionHandler  修饰的方法只能捕获到被 @RequestMapping 修饰方法中方法的异常,如果在 service 层发生了异常,则 @ExceptionHandler  修饰的方法是不能捕获到的;

此外, @ControllerAdvice 注解还有两个属性 value 和 basePackages,它们都是一个意思,都是指定捕获对应包下的异常,默认的是捕获类路径(classpath)下的异常:

测试一波:

/** * 测试 */@RestControllerpublic class ControllerTest {    @RequestMapping("/testException/{i}")    public String testException(@PathVariable("i") int i) throws Exception{        int result = 100 / i;        return String.format("100 除以 %s = %s", i, result);    }    @RequestMapping("/testMyException")    public void testMyException() throws MyException{        throw new MyException("手动抛出自定义异常...");    }}

之后,启动应用程序:

在浏览器输入 http://localhost:8080/testException/0, 在后台会打印:

开始异常处理: / by zero

在浏览器输入 http://localhost:8080/testMyException, 在后台会打印:

出现自定义异常了 : 手动抛出自定义异常...

可以看到,当在被  @RequestMapping 修饰的方法发生异常的时候,会进入对应的 @ExceptionHandler 方法。

 

接下来测试下,在 service  层发生异常的时候,异常处理器会不会也捕获的到:

先模拟一个 service  层,抛出异常:

@Servicepublic class ServiceTest {    public void add(String string) throws MyException {        System.out.println(string);        throw new MyException("Service 层抛出的自定义异常...");    }    public void delete(String id) throws Exception{        System.out.println(Integer.parseInt(id));    }}

Junit测试:

@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = MyspringbootApplication.class)public class TestException {    @Autowired    private ServiceTest serviceTest;    @Test    public void test() throws MyException {        serviceTest.add("tsmyk");    }    @Test    public void test2() throws Exception {        serviceTest.delete("a");    }}

在控制台没有打印上述信息,也就是异常处理器没有捕获到异常,这也证明了只能应用于被 @RequestMapping 修饰的方法。

 

下面来测试一下,只捕获对应包下的异常,通过 value 和 basePackages 属性来控制:

先定义两个异常处理器,分别捕获 ex1 和 ex2 包下的异常:

@ControllerAdvice(basePackages = "myspringboot.myspringboot.exception.ex1")public class MyExceptionHandler2 {    @ExceptionHandler(value = Exception.class)    public void defaultExceptionHandler(Exception e){        System.out.println(String.format("处理ex1包下的异常: %s", e.getMessage()));    }    @ExceptionHandler(value = MyException.class)    public void myExceptionHandler(MyException e){        System.out.println("处理ex1包下的异常 : " + e.getMessage());    }}
@ControllerAdvice(basePackages = "myspringboot.myspringboot.exception.ex2")public class MyExceptionHandler3 {    @ExceptionHandler(value = Exception.class)    public void defaultExceptionHandler(Exception e){        System.out.println(String.format("处理ex2包下的异常: %s", e.getMessage()));    }    @ExceptionHandler(value = MyException.class)    public void myExceptionHandler(MyException e){        System.out.println("处理ex2包下的异常 : " + e.getMessage());    }}

测试一下:

package myspringboot.myspringboot.exception.ex1;/** * 测试 */@RestController@RequestMapping("/ex1")public class ControllerTest2 {    @RequestMapping("/testException/{i}")    public String testException(@PathVariable("i") int i) throws Exception{        int result = 100 / i;        return String.format("100 除以 %s = %s", i, result);    }    @RequestMapping("/testMyException")    public void testMyException() throws MyException{        throw new MyException("手动抛出自定义异常...");    }}

之后,启动应用程序:

在浏览器输入 http://localhost:8080/ex1/testException/0, 在后台会打印:

处理ex1包下的异常: / by zero

在浏览器输入 http://localhost:8080/ex1/testMyException, 在后台会打印:

处理ex1包下的异常 : 手动抛出自定义异常...

可以看到只有 MyExceptionHandler2 能捕获到异常,而 MyExceptionHandler3 并没有。

记录日志

在程序中有时候需要记录下用户的操作日志,如谁增加了用户,谁删除了用户之类的,就需要记录操作日志,可以使用 Spring 的 AOP 技术来统一记录,下面来看看 SpringBoot 是如何使用的。

首先需要定义一个注解,需要记录日志的方法添加该注解即可:

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 标注该该注解的方法需要记录操作日志 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Logger {    String value() default "";}

之后,定义切面,即解析我们定义的注解:

import myspringboot.myspringboot.log.annotation.Logger;import myspringboot.myspringboot.log.dao.SysLogDao;import myspringboot.myspringboot.log.pojo.SysLog;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;import org.springframework.stereotype.Component;import java.lang.reflect.Method;import java.util.Date;/** * 日志切面 */@Aspect@Componentpublic class LoggerAspect {    @Autowired    private SysLogDao sysLogDao;    @Pointcut("@annotation(myspringboot.myspringboot.log.annotation.Logger)")    public void pointcut(){}    @Around("pointcut()")    public void around(ProceedingJoinPoint joinPoint){        try {            joinPoint.proceed(); // 执行方法        }catch (Throwable e){        }        saveSysLog(joinPoint);    }    private void saveSysLog(ProceedingJoinPoint joinPoint){        SysLog sysLog = getSysLog(joinPoint);        sysLogDao.saveSysLog(sysLog);    }    private SysLog getSysLog(ProceedingJoinPoint joinPoint) {        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        // 方法        Method method = methodSignature.getMethod();        // 方法上的注解        Logger loggerAnnotation = method.getAnnotation(Logger.class);        SysLog sysLog = new SysLog();        if (loggerAnnotation != null){            // 注解上的描述            sysLog.setOperation(loggerAnnotation.value());        }        // 方法名        String className = joinPoint.getTarget().getClass().getName();        String methonName = methodSignature.getName();        sysLog.setMethod(className + "." + methonName + "()");        // 方法参数值        Object[] args = joinPoint.getArgs();        // 方法参数名        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();        String[] paramNames = discoverer.getParameterNames(method);        if (args != null && paramNames != null){            String params = "";            for (int i = 0; i < args.length; i++){                params += " " + paramNames[i] + ": " + args[i];            }            sysLog.setParams(params);        }        sysLog.setUsername("admin");        sysLog.setLogDate(new Date());        return sysLog;    }}

定义dao层的入库方法:

import myspringboot.myspringboot.log.pojo.SysLog;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;@Mapper@Repositorypublic interface SysLogDao {    void saveSysLog(SysLog sysLog);}

模拟service层:

@Servicepublic class MyServiceImpl {    @Logger("添加用户")    public void add(String name){        System.out.println("添加用户:" + name);    }    @Logger("删除用户")    public void delete(String name, String job){        System.out.println("删除用户:" + name + " , " + job);    }    @Logger("修改用户")    public void update(int id){        System.out.println("修改用户:" + id);    }    @Logger("查询用户")    public void query(String name){        System.out.println("查询用户:" + name);    }}

pojo 类:

import lombok.Getter;import lombok.Setter;import lombok.ToString;import java.io.Serializable;import java.util.Date;/** * 系统日志类 */@Getter@Setter@ToStringpublic class SysLog implements Serializable {    private int id;    private String username;    private String operation;    private String method;    private String params;    private Date logDate;}

单元测试:

@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = MyspringbootApplication.class)public class LogTest {    @Autowired    private MyServiceImpl service;    @Test    public void test(){        service.add("tsmyk0715");        service.delete("tsmyk0715", "java");        service.update(100);        service.query("tsmyk");    }}

结果:

6a2fbf7d18c815804ea6f9b16604b6a1aac.jpg

以上就是 SpringBoot 来记录操作日志,即通过 AOP 技术来实现的。

转载于:https://my.oschina.net/mengyuankan/blog/2222140

你可能感兴趣的文章
Liunx 中kill命令详解
查看>>
一个“通讯簿”程序
查看>>
frida so hook 未导出的函数
查看>>
linux调试器
查看>>
『最短Hamilton路径 状态压缩DP』
查看>>
appcan打包后产生的问题总结
查看>>
mysql5.1.X安装plugin
查看>>
php数组取模
查看>>
PHP环境下配置WebGrind——让你的网站性能看得见
查看>>
中级学员:2015年10月20日作业
查看>>
小鸟chacha初学linux 高级文件操作
查看>>
linux下安装apache
查看>>
上传数据脚本
查看>>
经验篇第七期:群里的那点事儿(七)
查看>>
Perl中BEGIN语句块
查看>>
Eclipse maven工程 Missing artifact com.sun:tools:jar:1.6.0:system 解决方法
查看>>
HDU - 2018 - 母牛的故事(dp)
查看>>
(linux)Centos 7 xfsdump文件系统的备份和恢复
查看>>
Kosaraju算法解析: 求解图的强连通分量
查看>>
Linux系统硬链接和软链接
查看>>