本项目基于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原理就不多说,笔者这里直接放实战代码,我们看下项目结构:
这里一个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..)
具体的大家可以去网上查看,我也不多说,这里主要实战为主。
至于通知笔者根据自己的经验给大家总结下具体使用场景:
- 前置通知@Before:
- 权限控制(权限不足抛出异常)
- 记录方法调用信息日志
- 正常返回通知@AfterReturning
- 于业务相关的, 如银行在存取款结束后的发送短信消息,或者短信验证码
- 异常通知@AfterThrowing
- 处理异常(一般不可预知)
- 记录日志
- 通知管理员(短信、邮件)哪里出现异常,方便处理
- 环绕通知@Around
- 日志
- 缓存
- 权限
- 性能监控
- 事务管理
- 最终后置通知@After(类似于finally代码功能)
- 释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
注意多个AOP之间的执行顺序可以使用使用注解@Order比如@Order(1),可以放类上,可以是方法上,具体场景根据业务来定
最后我们看下效果:
因为笔者这里切了所有的com.yzl.*包下的方法,看上去比较乱,这里是只是展示下效果,实际项目中可以只切serviceImpl中的方法。
谢谢!
本项目的github地址:https://github.com/weiess/SpringBoot-AOP.git