Spring之AOP日志记录
在 Spring 中,可以使用 AOP(面向切面编程)来实现日志记录功能。
Aop通知类型
1. 前置通知:在方法调用之前执行
2. 后置通知:在方法正常调用之后执行
3. 环绕通知:在方法调用之前和之后,都分别可以执行的通知
4. 异常通知:如果在方法调用过程中发生异常,则通知
5. 最终通知:在方法调用之后执行
切面表达式
@Around("execution(* cn.ybzy.service.impl..*.*(..))")
execution 代表所要执行的表达式主体
1. * 代表方法返回类型 *代表所有类型
2. 包名代表aop监控的类所在的包
3. .. 代表该包以及其子包下的所有类方法
4. * 代表类名,*代表所有类
5. *(..) *代表类中的方法名,(..)表示方法中的任何参数
日志封装对象
public class SystemLog {
private String id;
/**
* 访问时间
*/
private Date createTime;
/**
* 操作人
*/
private String username;
/**
* 访问ip
*/
private String ip;
/**
* 访问资源url
*/
private String url;
/**
* 执行时间
*/
private Long executionTime;
/**
* 访问方法
*/
private String method;
}
定义Aop切面类
创建一个切面类,用于捕获需要记录日志的方法。
@Component
@Aspect
public class SystemLogAop {
private static final Logger log = LoggerFactory.getLogger(SystemLogAop .class);
//方式一:
@Autowired
private HttpServletRequest request;
/* 在web.xml中配置RequestContextListener对象,通过此对象获取request对象或session对象
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>*/
//方式二:
//ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//HttpServletRequest request = requestAttributes.getRequest();
//访问时间
private Date createTime;
//访问类
private Class clazz;
//访问方法
private Method method;
//前置通知
@Before("execution(* cn.ybzy.controller.*.*(..))")
public void doBefore(JoinPoint jp) throws NoSuchMethodException {
createTime = new Date();
//具体访问的类
clazz = jp.getTarget().getClass();
//获取访问方法的名称
String methodName = jp.getSignature().getName();
//获取访问方法的参数
Object[] args = jp.getArgs();
//获取具体执行方法的Method对象
if (args == null || args.length == 0) {
//获取无参数的方法
method = clazz.getMethod(methodName);
} else {
// 有参数,就将args中所有元素遍历,获取对应的Class,装入到一个Class[]
Class[] classArgs = new Class[args.length];
for (int i = 0; i < args.length; i++) {
classArgs[i] = args[i].getClass();
}
method = clazz.getMethod(methodName, classArgs);
}
}
//后置通知
@After("execution(* cn.ybzy.controller.*.*(..))")
public void doAfter(JoinPoint jp) throws Exception {
//获取访问的时长
long time = System.currentTimeMillis() - createTime.getTime();
String url = "";
//获取url
if (clazz != null && method != null && clazz != SystemLogAop.class) {
//获取类上的@RequestMapping对象
RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
if (classAnnotation != null) {
String[] classValue = classAnnotation.value();
//获取方法上的@RequestMapping对象
RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
if (methodAnnotation != null) {
String[] methodValue = methodAnnotation.value();
// 类上的@RequestMapping的value+方法上的@RequestMapping的value
url = classValue[0] + methodValue[0];
//获取访问的ip
String ip = request.getRemoteAddr();
//取用户信息,此处使用spirng-security框架获取用户信息,从上下文中获当前登录的用户
SecurityContext context = SecurityContextHolder.getContext();
User user = (User) context.getAuthentication().getPrincipal();
String username = user.getUsername();
//将日志信息封装到SystemLog 对象
SystemLog SystemLog = new SystemLog();
SystemLog.setExecutionTime(time);
SystemLog.setIp(ip);
SystemLog.setMethod(clazz.getName() + ":" + method.getName());
SystemLog.setUrl(url);
SystemLog.setUsername(username);
SystemLog.setCreateTime(createTime);
//TODO 保存日志到数据库
System.out.println("SystemLog = " + SystemLog);
}
}
}
}
/**
* 环绕通知
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("execution(* cn.ybzy.service.impl..*.*(..))")
public Object recordTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("====== 开始执行 {}.{} ======", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
// 记录开始时间
long begin = System.currentTimeMillis();
// 执行目标 service
Object result = joinPoint.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
long takeTime = end - begin;
if (takeTime > 3000) {
log.error("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
} else if (takeTime > 2000) {
log.warn("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
} else {
log.info("====== 执行结束,耗时:{} 毫秒 ======", takeTime);
}
return result;
}
}
开启Spring AOP支持
在Spring配置文件中配置 AOP
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在Spring配置类中配置 AOP
@Configuration
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
API接口
@Controller
@RequestMapping("/demo")
public class TestController {
@RequestMapping("/test.do")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String test1(){
return "test1";
}
}
测试日志记录
当目标方法被调用时,切面会捕获该调用,并输出相应的日志信息到控制台。
SystemLog = SystemLog{id='null', createTime=Mon Oct 12 22:02:53 CST 2020, username='user', ip='0:0:0:0:0:0:0:1', url='/demo/test.do', executionTime=38, method='cn.ybzy.controller.TestController:test1'}
使用AOP注解
定义AOP注解实现AOP日志记录
定义一个注解,用于标记需要被记录的方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
}
定义一个切面类,实现日志记录逻辑
@Aspect
@Component
public class LogAspect {
@Before("@annotation(LogAnnotation)")
public void methodBefore(JoinPoint joinPoint) {
}
}
使用Spring AOP框架配置切面
@Configuration
@EnableAspectJAutoProxy
public class LogConfig {
}
在需要被记录的方法上添加该注解
public class ProductService {
@LogAnnotation
public void save(Product product) {
}
}