统一处理异常和记录日志

统一处理异常

SpringBoot设计,如果出现错误404或500,自动调用特定路径下的html页面(路径和名字都特定)。/templates/error/404.html、/templates/error/500.html。程序中有错误自动就调用该页面。
但是错误有异步请求错误,也想同时记录日志。则使用统一处理的方式,即全局配置。

@ControllerAdvice 是 Spring MVC 中的一个注解,用于定义全局控制器的通知(advice)。它允许您在整个应用程序范围内定义对控制器的异常处理、绑定属性以及其他全局控制器通知的方法。
具体来说,@ControllerAdvice 通常与 @ExceptionHandler、@InitBinder 和 @ModelAttribute 注解一起使用:

  • @ExceptionHandler: 用于定义在控制器中抛出指定类型异常时的处理方法。
  • @InitBinder: 用于定义在控制器中自定义数据绑定规则的方法。
  • @ModelAttribute: 用于定义在所有请求处理方法之前执行的方法,通常用于在模型中添加公共属性。

通过将 @ControllerAdvice 注解添加到类上,您可以在该类中定义这些通知方法,并在整个应用程序中共享它们,以便统一处理异常、数据绑定和模型属性。这样可以提高代码的重用性和可维护性,并使全局控制器的配置更加简洁和清晰。

	//手动重定向错误页面
    @RequestMapping(path = "/error", method = RequestMethod.GET)
    public String getErrorPage() {
        return "/error/500";
    }
// 是Controller全局配置类,不用对任何Controller再做配置,可以统一做Controller的全局配置。@ControllerAdvice用来修饰类。
// 异常处理方案@ExceptionHandler、绑定数据方案@ModelAttribute、绑定参数方案@DataBinder. 他们都用来修饰方法。
// 这里只演示,统一处理异常(@ExceptionHandler)
@ControllerAdvice(annotations = Controller.class) // 限定注解@Controller,否则组件扫描所有的bean
public class ExceptionAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    @ExceptionHandler({Exception.class})// 处理哪些异常?Exception是所有异常的父类,所有异常都处理
    // 有异常controller会传过来Exception
    public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {

        // 记录日志
        logger.error("服务器发生异常:" + e.getMessage());//异常的概括
        for (StackTraceElement element : e.getStackTrace()) {//把异常所有栈的信息都记录下来
            logger.error(element.toString());
        }

        // 给浏览器响应
        // 要看是什么请求,想要服务器返回网页html/异步请求JSON(xml).从请求的消息头获取。
        String xRequestedWith = request.getHeader("x-requested-with");
        if ("XMLHttpRequest".equals(xRequestedWith)) {// 异步请求
            response.setContentType("application/plain;charset=utf-8");
            PrintWriter writer = response.getWriter();// 输出流
            writer.write(CommunityUtil.getJSONString(1,"服务器异常!"));// 输出JSON字符串
        }else{// 请求html,重定向到错误页面
            response.sendRedirect(request.getContextPath() + "/error");
        }

    }
}

统一记录日志

记录日志,不一定有异常。拦截器也是针对控制器的。没有对业务组件、数据访问层统一处理。

想对业务层统一记录日志,而统一记录日志是系统功能,不要和业务功能混在一起实现。否则在想对记录日志的位置进行改变时,将会非常麻烦,因为业务bean有很多个,需要修改的时候得一个个改。

由此引入了AOP的方式,即面向切面编程,切面是一个一个组件。业务Bean是一个一个target。我们要先声明切点的位置,再通知要做什么事。只需要对切面组件编程即可,不需要再进到业务Bean中去改,提升了编程效率。
Aspect切面:

  • 注解@Component @Aspect
  • 声明切点的位置@Pointcut(切点的位置:返回值 包.类.方法.参数) pointcut()
  • 通知具体逻辑,5个注解@Before @After AfterReturning @AfterThrowing @Around
    Target: 是业务Bean
    在这里插入图片描述
    AOP实现有两种:
    AspectJSpring AOP。一般用后者即可。它是运行时织入,通过代理的方式,只在方法处有连接点。Spring AOP(面向切面编程)通常通过代理的方式来实现,主要有以下几个原因:
  • 无侵入性: 通过代理方式实现 AOP 可以避免对现有代码的侵入性。即使目标类没有实现任何接口,也可以通过 Spring AOP实现切面功能。
  • 动态性: 代理方式允许在运行时动态地应用切面。这意味着可以在运行时决定是否应用切面,以及如何应用切面,而无需在编译时硬编码切面逻辑。
  • 单一职责原则: 通过代理方式实现 AOP可以使目标类专注于自身的业务逻辑,而将横切关注点(如日志记录、事务管理等)从目标类中解耦出来,符合单一职责原则。
  • 多个切面组合:代理方式允许将多个切面组合应用于目标类,而无需修改目标类的代码。这种灵活性使得可以根据需求组合不同的切面,实现更加复杂的功能。
  • 易于管理: 通过代理方式实现的切面可以集中管理,例如在配置文件中声明切面和通知的关系,而无需在每个目标类中显式地声明切面逻辑。

统一记录日志示例

@Component
@Aspect
public class ServiceLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);

    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    public void pointcut() {

    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {// 参数:连接点
        // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteHost();
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        String target = joinPoint.getSignature().getDeclaringTypeName() + "." +joinPoint.getSignature().getName();// 得到该连接点的类名和方法名
        logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
    }
    
    @After("pointcut()")
    public void after() {
        System.out.println("after");
    }

    @AfterReturning("pointcut()")
    public void afterRetuning() {
        System.out.println("afterRetuning");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 参数:连接点
        System.out.println("around before");
        Object obj = joinPoint.proceed();// 连接点调用目标组件的方法,返回目标组件的返回值
        System.out.println("around after");
        return obj;
    }
}

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值