springboot实战实现AOP统一日志,异常处理

本项目基于springboot+ springboot-starter-logging+ spring-boot-starter-aop
简单的boot demo可以参考:SpringBoot实战基础搭建
github地址:https://github.com/weiess/SpringBoot-AOP.git
先看下maven依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.yzl</groupId>
    <artifactId>springboot-demoweb</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.0.5</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

实现最简单的异常或者日志统一处理需要spring的aop,aop原理就不多说,笔者这里直接放实战代码,我们看下项目结构:


AOP.jpg

这里一个ExceptionAdvice是统一异常处理
这个LogAspect是统一日志处理

yml配置:

spring:
    aop:
      auto: true
      proxy-target-class: true

aop主要配置就是这两个属性,

其中proxy-target-class表示增强代理,true表示基于类的代理将起作用(这时需要cglib库,由于AOP-starter里依赖了cglib的jar,所以笔者就没有另外加maven坐标),false表示按JDK proxy来处理的。

我们先看下最简单的统一异常处理:

package com.yzl.aop;


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
@Slf4j
public class ExceptionAdvice {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Map defaultErrorHandler(HttpServletRequest request,Exception e)throws Exception{
        Map map =new HashMap(5);
        //e.printStackTrace();
        map.put("data",500);
        map.put("issuccess","false");
        return map;
    }
}

这边的代码很简单,

主要注意类上用的是@ControllerAdvice注解,表示它作用是用来辅助Controller的
@ExceptionHandler(value = Exception.class)表示捕获所有异常,注意这里面的参数表示捕获的异常种类,你可以写@ExceptionHandler({InsertException.class,DeleteException.class}),这样表示只捕捉InsertException,DeleteException,如果写Exception.class,表示捕捉Exception或者Exception的子类。
这里的@ResponseBody,我就不多说
假如要返回视图,就可以用ModelAndView作为返回值,但是要把@ResponseBody这个注解去掉。

最简单的统一异常处理就完成了。
接着我们看下统一的日志处理logAspect

package com.yzl.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;


@Slf4j
@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(public * com.yzl.*.*.*(..))")
    public void logPointcut(){

    }

    @Before("logPointcut()")
    public void  methodBefore(JoinPoint joinPoint){
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //打印请求内容
        log.info("---------------请求内容---------------");
        log.info("请求地址:"+request.getRequestURL().toString());
        log.info("请求方式:"+request.getMethod());
        log.info("请求类方法:"+joinPoint.getSignature().getName());
        log.info("请求类方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        log.info("---------------请求内容---------------");
    }


    @AfterReturning(returning = "o",pointcut = "logPointcut()")
    public void methodAfterReturning(Object o){
        log.info("===============返回内容===============");
        log.info("返回的内容:"+ o.toString());
        log.info("===============返回内容===============");
    }


    @AfterThrowing(pointcut = "logPointcut()",throwing = "e")
    public void logThrowing(JoinPoint joinPoint,Throwable e){
        log.info("***************抛出异常***************");

        log.info("请求类方法:"+joinPoint.getSignature().getName());
        log.info("异常内容:"+e);
        log.info("***************抛出异常***************");
    }

}

这里的类注解@Aspect表示他是一个切面
@compent表示把普通的类实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
这里我们看下AOP的基本概念:

@Aspect(切面): 通常是一个类,里面可以定义切入点和通知
JointPoint(连接点): 程序执行过程中明确的点,一般是方法的调用
Advice(通知): AOP在特定的切入点上执行的增强处理:

@Before:前置通知,主要在方法前执行。
@After: final增强,不管是抛出异常或者正常退出都会执行。
@AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行
@AfterThrowing: 异常抛出增强,相当于ThrowsAdvice
@Around: 环绕增强,相当于MethodInterceptor

Pointcut(切入点): 带有通知的连接点,在程序中主要体现为书写切入点表达式

我们下@Pointcut注解下的方法,public * com.yzl...*(..)表示只能是public方法,且包名必须是com.yzl开头的,里面写法有很多

1)execution(* (..))
表示匹配所有方法
2)execution(public * com. yzl.service.UserService.
(..))
表示匹配com.yzl.server.UserService中所有的公有方法
3)execution(* com.yzl.server...(..))
表示匹配com.yzl.server包及其子包下的所有方法

或者

1)aop包里的任意类.
within(com.yzl.aop.)
2)pointcutexp包和所有子包里的任意类.
within(com.yzl.aop..
)

具体的大家可以去网上查看,我也不多说,这里主要实战为主。
至于通知笔者根据自己的经验给大家总结下具体使用场景:

  1. 前置通知@Before:
- 权限控制(权限不足抛出异常)
- 记录方法调用信息日志
  1. 正常返回通知@AfterReturning
- 于业务相关的, 如银行在存取款结束后的发送短信消息,或者短信验证码
  1. 异常通知@AfterThrowing
 - 处理异常(一般不可预知)
 - 记录日志
 - 通知管理员(短信、邮件)哪里出现异常,方便处理
  1. 环绕通知@Around
- 日志
- 缓存
- 权限
- 性能监控
- 事务管理
  1. 最终后置通知@After(类似于finally代码功能)
- 释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )

注意多个AOP之间的执行顺序可以使用使用注解@Order比如@Order(1),可以放类上,可以是方法上,具体场景根据业务来定

最后我们看下效果:


exception.jpg

因为笔者这里切了所有的com.yzl.*包下的方法,看上去比较乱,这里是只是展示下效果,实际项目中可以只切serviceImpl中的方法。

谢谢!
本项目的github地址:https://github.com/weiess/SpringBoot-AOP.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容