目录
- 导入相关依赖和配置文件
- 定义自定义注解
- @Target
- @Retention
- 自定义注解
- 定义切面类
- @Aspect
- @Pointcut
- @Before
- @After
- @Around
- @AfterReturning
- @AfterThrowing
- 自定义切面类
- 使用
导入相关依赖和配置文件
首先pom.xml添加如下依赖,然后日志的我这里用的是lombox自带的日志,在对应的类上面加入@Slf4j注解就可用使用了。
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
然后配置我们SpringBoot的yml文件
spring:
datasource:
url: jdbc:mysql://localhost:3307/crm?serverTimezone=Hongkong&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false
password: 123456
username: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
filter: stat
redis:
# redis数据库索引(默认为0),我们使用索引为3的数据库,避免和其他数据库冲突
database: 3
host: localhost # redis服务器地址(默认为localhost)
port: 6379 # redis端口(默认为6379)
password: # redis访问密码(默认为空)
jedis: # 集成redis一些命令操作
pool: # 连接池
max-active: 8 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没限制)
max-idle: 8 #最大空闲
min-idle: 0 #最小空闲
timeout: 20000 #连接超时时间
mybatis:
type-aliases-package: com.study.model.pojo
mapper-locations: classpath:mapper/*/*.xml
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
logging:
level:
com.study.model.mapper : debug #定义日志的打印级别
file: logs/crmsystem.log #存储日志文件的位置
定义自定义注解
首先我们先来了解一下自定义注解所需要的俩个注解(他们本身也是注解)
@Target
@Target:这个注解就是表明该注解类能够作用的范围,也就是能够注解在哪,比如 类、方法、参数等。下面是他的一些参数:
@Target(ElementType.TYPE):接口、类、枚举、注解
@Target(ElementType.FIELD):字段、枚举的常量
@Target(ElementType.METHOD):方法
@Target(ElementType.PARAMETER):方法参数
@Target(ElementType.CONSTRUCTOR):构造函数
@Target(ElementType.LOCAL_VARIABLE):局部变量
@Target(ElementType.ANNOTATION_TYPE):注解
@Target(ElementType.PACKAGE):包
里面的参数是可以多选的,使用方法比如@Target({ElementType.METHOD,ElementType.TYPE})
@Retention
@Retention:这个注解是保留说明,也就是表明这个注解所注解的类能在哪里保留,他有三个参数
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME: 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
自定义注解
package com.study.aop.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
/**
* 描述
* @return
*/
String description() default "";
}
定义切面类
@Aspect
@Aspect注解告诉Spring这是个切面类
@Pointcut
切点,用于定义哪个方法会被拦截,
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包 ,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(…):最后这个星号表示方法名,*号表示所有的方法, 后面括弧里面表示方法的参数,两个句点表示任何参数。
@Before
前置通知,表示在目标方法执行之前执行
@After
后置通知,在目标方执行完成后执行,如果目标方法异常,则后置通知不再执行,则跳转到@AfterThrowing所在的方法处
@Around
环绕通知,相当于前置通知和后置通知的结合体,但是环绕通知必须有返回参数,不然会没有数据,然后环绕通知必须执行proceed()方法,也就是调用的那个方法,不然会报错
@AfterReturning
目标方法返回后执行,也就是环绕通知或者后置通知之后,如果发生异常不执行
@AfterThrowing
在方法抛出异常后调用通知
自定义切面类
package com.study.aop.ascept;
import com.study.aop.annotation.MyAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
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.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
@Component
@Aspect
@Slf4j
public class MyAscept {
/**
* 切入点:有MyAnnotation注解的为切入点
* 1、execution(): 表达式主体。
* 2、第一个*号:表示返回类型,*号表示所有的类型。
* 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包
* ,com.sample.service.impl包、子孙包下所有类的方法。
* 4、第二个*号:表示类名,*号表示所有的类。
* 5、*(..):最后这个星号表示方法名,*号表示所有的方法,
* 后面括弧里面表示方法的参数,两个句点表示任何参数。
*
* 在这里是结合自定义注解一起使用,在指定包里面的类有MyAnnotation注解的方法都会打印日志
*/
@Pointcut("execution(* com.study.controller..*.*(..)) && @annotation(com.study.aop.annotation.MyAnnotation)")
public void Pointcut(){
}
/**
* 前置通知:在目标方法执行之前执行
* @param jp
*/
/*@Before("Pointcut()")
public void Before(JoinPoint jp){
StringBuilder headers = new StringBuilder();
HttpServletRequest request = this.getThisRequst();
String begintime = DateFormat.getDateTimeInstance().format(new Date());//开始时间
String ipaddress = request.getRemoteAddr();//IP地址
String request_url = request.getRequestURI();//请求URL
String request_model = request.getMethod();//请求方式
String method_name = jp.getSignature().getName();//方法名
String project_url = jp.getSignature().getDeclaringTypeName();//包+类路径
String class_method = project_url + "." + method_name;//完整路径
MyAnnotation annotationvalue = ((MethodSignature)jp.getSignature()).getMethod().getAnnotation(MyAnnotation.class);//自定义注解的值
String param_names = Arrays.toString(((MethodSignature) jp.getSignature()).getParameterNames());//参数名称
String param_values = Arrays.toString(jp.getArgs());//参数值
//获取请求头
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
headers.append(name + ":" + value).append(",");
}
log.info("Before ----------------------------------------------------------->");
log.info("开始时间:{}",begintime);
log.info("IP地址:{}",ipaddress);
log.info("请求URL:{}",request_url);
log.info("请求头信息:{}",headers);
log.info("请求方式 {}", request_model);
log.info("自定义注解的值:{}",annotationvalue.description());
log.info("参数名称:{}",param_names);
log.info("参数值:{}",param_values);
log.info("完整路径:{}",class_method);
}*/
/**
* 后置通知:在目标方执行完成后执行,如果目标方法异常,则后置通知不再执行
* @param jp
*/
/*@After("Pointcut()")
public void After(JoinPoint jp){
String endtime = DateFormat.getDateTimeInstance().format(new Date());//结束时间
log.info("结束时间:{}",endtime);
log.info("After ----------------------------------------------------------->");
}*/
/**
* 环绕通知:相当于前置通知和后置通知的结合体,但是环绕通知必须有返回参数
*/
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
StringBuilder headers = new StringBuilder();
HttpServletRequest request = this.getThisRequst();
String begintime = DateFormat.getDateTimeInstance().format(new Date());//开始时间
String ipaddress = request.getRemoteAddr();//IP地址
String request_url = request.getRequestURI();//请求URL
String request_model = request.getMethod();//请求方式
String method_name = pjp.getSignature().getName();//方法名
String project_url = pjp.getSignature().getDeclaringTypeName();//包+类路径
String class_method = project_url + "." + method_name;//完整路径
MyAnnotation annotationvalue = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(MyAnnotation.class);//自定义注解的值
String param_names = Arrays.toString(((MethodSignature) pjp.getSignature()).getParameterNames());//参数名称
String param_values = Arrays.toString(pjp.getArgs());//参数值
//获取请求头
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
headers.append(name + ":" + value).append(",");
}
log.info("Around Before ----------------------------------------------------------->");
log.info("开始时间:{}",begintime);
log.info("IP地址:{}",ipaddress);
log.info("请求URL:{}",request_url);
log.info("请求头信息:{}",headers);
log.info("请求方式 {}", request_model);
log.info("自定义注解的值:{}",annotationvalue);
log.info("参数名称:{}",param_names);
log.info("参数值:{}",param_values);
log.info("完整路径:{}",class_method);
//执行要执行的方法,o就是执行方法的返回值
Object result = pjp.proceed();
String endtime = DateFormat.getDateTimeInstance().format(new Date());//结束时间
log.info("{}方法的返回值: {}", method_name,result);
log.info("结束时间:{}",endtime);
log.info("Around After ----------------------------------------------------------->");
return result;
}
/**
* 目标方法返回后执行,如果发生异常不执行
*/
@AfterReturning(value = "Pointcut()")
public void AfterReturning(JoinPoint jp){
String methodname = jp.getSignature().getName();//方法名
String projecturl = jp.getSignature().getDeclaringTypeName();//包+类路径
String class_method = projecturl + "." + methodname;//完整路径
log.info("{} is destroy",class_method);
}
/**
* 目标发生异常时执行
* @param jp
* @param e
*/
@AfterThrowing(value = "Pointcut()",throwing = "e")
public void AfterThrowing(JoinPoint jp, Exception e){
String methodname = jp.getSignature().getName();//方法名
String projecturl = jp.getSignature().getDeclaringTypeName();//包+类路径
String class_method = projecturl + "." + methodname;//完整路径
log.error("{}位置发生异常,异常的信息为{}",class_method,e.getMessage());
}
/**
* 获取request对象
* @return
*/
public HttpServletRequest getThisRequst(){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request;
}
}
使用
在对应的方法上加上自己的自定义注解就可以使用了
日志打印结果