org.springframework.boot
spring-boot-starter-aop
说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成遵守 java 规范的 class 文件。
通过设计切面对象,为目标业务方法做功能增强,关键步骤如下:
第一步:创建注解类型,应用于切入点表达式的定义,关键代码如下:
package com.cy.pj.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String operation();
}
第二步:创建切面对象,用于做日志业务增强,关键代码如下:
package com.cy.pj.sys.service.aspect;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.fasterxml.jackson.databind.ObjectMapper;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class SysLogAspect {
private static final Logger log= LoggerFactory.getLogger(SysLogAspect.class);
/**
-
@Pointcut注解用于定义切入点
-
@annotation(注解)为切入点表达式,后续由此注解描述的方法为切入
-
点方法
*/
@Pointcut(“@annotation(com.cy.pj.common.annotation.RequiredLog)”)
public void doLog(){}//此方法只负责承载切入点的定义
/**
-
@Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
-
@param jp 连接点对象,此对象封装了要执行的目标方法信息.
-
可以通过连接点对象调用目标方法.
-
@return 目标方法的执行结果
-
@throws Throwable
*/
@Around(“doLog()”)
public Object doAround(ProceedingJoinPoint jp)throws Throwable{
long t1=System.currentTimeMillis();
try {
//执行目标方法(切点方法中的某个方法)
Object result = jp.proceed();
long t2=System.currentTimeMillis();
log.info(“opertime:{}”,t2-t1); return result;//目标业务方法的执行结果
}catch(Throwable e){
e.printStackTrace();
long t2=System.currentTimeMillis();
log.info(“exception:{}”,e.getMessage());
throw e;
}
}
第三步:通过注解RequiredLog注解描述日志查询或删除业务相关方法,此时这个方法为日志切入点方法,例如:
@RequiredLog(operation=“公告查询”)
@Override
public List findLogs(SysLog sysLog) {
List list=syslogDao.selectLogs(sysLog);
return list;
}
第四步:测试通知业务方法,并检测日志输出以及了解其运行原理,如图所示:
第一步:定义日志pojo对象,用于封装日志信息,例如:
package com.pj.sys.pojo;
import java.util.Date;
public class SysLog {
private Integer id;
private String ip;
private String username;
private String operation;
private String method;
private String params;
private Long time;
private Integer status;
private String error;
private Date createdTime;
//自己添加set/get/toString等方法
}
第二步:修改日切面对象,获取并记录详细日志,关键代码如下:
package com.pj.sys.service.aspect;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.fasterxml.jackson.databind.ObjectMapper;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class SysLogAspect {
private static final Logger log=LoggerFactory.getLogger(SysLogAspect.class);
/**
-
@Pointcut注解用于定义切入点
-
@annotation(注解)为切入点表达式,后续由此注解描述的方法为切入
-
点方法
*/
@Pointcut(“@annotation(com.cy.pj.common.annotation.RequiredLog)”)
public void doLog(){}//此方法只负责承载切入点的定义
/**
-
@Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
-
@param jp 连接点对象,此对象封装了要执行的目标方法信息.
-
可以通过连接点对象调用目标方法.
-
@return 目标方法的执行结果
-
@throws Throwable
*/
@Around(“doLog()”)
public Object doAround(ProceedingJoinPoint jp)throws Throwable{
long t1=System.currentTimeMillis();
log.info(“Start:{}”,t1);
try {
//执行目标方法(切点方法中的某个方法)
Object result = jp.proceed();
long t2=System.currentTimeMillis();
log.info(“After:{}”,t2);
doLogInfo(jp,t2-t1,null);
return result;//目标业务方法的执行结果
}catch(Throwable e){
e.printStackTrace();
long t2=System.currentTimeMillis();
doLogInfo(jp,t2-t1,e);
throw e;
}
}
//记录用户行为日志
private void doLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws Exception {
//1.获取用户行为日志
//1.1获取登录用户名(没做登录时,可以先给个固定值)
String username=“cgb”;
//1.2获取ip地址
String ip= “202.106.0.20”;
//1.3获取操作名(operation)-@RequiredLog注解中value属性的值
//1.3.1获取目标对象类型
Class<?> targetCls=jp.getTarget().getClass();
//1.3.2获取目标方法
MethodSignature ms=(MethodSignature) jp.getSignature();//方法签名
Method targetMethod=targetCls.getMethod(ms.getName(),ms.getParameterTypes());
//1.3.3 获取方法上RequiredLog注解
RequiredLog annotation =targetMethod.getAnnotation(RequiredLog.class);
//1.3.4 获取注解中定义操作名
String operation=annotation.operation();
//1.4获取方法声明(类全名+方法名)
String classMethodName=targetCls.getName()+“.”+targetMethod.getName();
//1.5获取方法实际参数信息
Object[]args=jp.getArgs();
String params=new ObjectMapper().writeValueAsString(args);
//2.封装用户行为日志
SysLog sysLog=new SysLog();
sysLog.setUsername(username);
sysLog.setIp(ip);
sysLog.setOperation(operation);
sysLog.setMethod(classMethodName);
sysLog.setParams(params);
sysLog.setTime(time);
if(e!=null) {
sysLog.setStatus(0);
sysLog.setError(e.getMessage());
}
//3.打印日志
String userLog=new ObjectMapper().writeValueAsString(sysLog);
log.info(“user.oper {}”,userLog);
}
}
第三步:进行日志业务查询,并检测是否有详细日志输出。
=========================================================================
Spring框架AOP模块定义通知类型,有如下几种:
-
@Around (优先级最高的通知,可以在目标方法执行之前,之后灵活进行业务拓展.)
-
@Before (目标方法执行之前调用)
-
@AfterReturning (目标方法正常结束时执行)
-
@AfterThrowing (目标方法异常结束时执行)
-
@After (目标方法结束时执行,正常结束和异常结束它都会执行)
案例分析如下:(了解,可选择进行实现)
第一步:定义注解,代码如下:
package com.cy.pj.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredTime{}
第二步:定义时间切面对象对象演示通知执行,关键代码如下:
package com.cy.pj.sys.service.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SysTimeAspect {
@Pointcut(“@annotation(com.cy.pj.common.annotation.RequiredTime)”)
public void doTime(){}
@Before(“doTime()”)
public void doBefore(){
System.out.println(“@Before”);
}
@After(“doTime()”)
public void doAfter(){
System.out.println(“@After”);
}
@AfterReturning(“doTime()”)
public void doAfterReturning(){
System.out.println(“@AfterReturning”);
}
@AfterThrowing(“doTime()”)
public void doAfterThrowing(){
System.out.println(“@AfterThrowing”);
}
//最重要,优先级也是最高
@Around(“doTime()”)
public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
try {
System.out.println(“@Around.before”);
Object result = joinPoint.proceed();
System.out.println(“@Around.AfterReturning”);
return result;
}catch(Exception e){
System.out.println(“@Around.AfterThrowing”);
e.printStackTrace();
throw e;
}finally {
System.out.println(“@Around.after”);
}
}
}