AOP 称为面向切面编程, 在程序开发中主要用来解决一些系统层面上的问题, 比如日志, 事务, 权限等等.
Spring AOP 模块提供截取拦截应用程序的拦截器, 例如, 当执行方法时, 可以在执行方法之前或之后添加额外的功能.
一 AOP 的基本概念
(1)Aspect(切面): 通常是一个类, 里面可以定义切入点和通知
(2)JointPoint(连接点): 程序执行过程中明确的点, 一般是方法的调用
(3)Advice(通知):AOP 在特定的切入点上执行的增强处理, 有 before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点): 就是带有通知的连接点, 在程序中主要体现为书写切入点表达式
(5)AOP 代理: AOP 框架创建的对象, 代理就是目标对象的加强. Spring 中的 AOP 代理可以使 JDK 动态代理, 也可以是 CGLIB 代理, 前者基于接口, 后者基于子类
二 AOP 常用注解
@JoinPoint
切入点 (PointCut) 是一组一个或多个连接点, 在其中应该执行的通知. 您可以使用表达式或模式指定切入点, 我们将在 AOP 示例中看到. 在 Spring 中切入点有助于使用特定的连接点来应用通知. 请考虑以下示例:
- @Pointcut("execution(* com.demo.controller.*.*(..))")
- @Pointcut("execution(* com.demo.StudentServiceImpl.getName(..))")
语法
- @Aspectpublic class Logging {
- // 切所有的 controller 包下表所有方法
- @Pointcut("execution(* com.demo.controller.*.*(..))")
- private void controllerPoint(){}
@Aspect - 将类标记为包含通知方法的类.
@Pointcut - 将函数标记为切入点
execution( expression ) - 涵盖应用通知的方法的表达式.
@Before Advice
@Before 是一种通知类型 (前置), 可以确保在方法执行之前运行通知. 以下是 @Before 通知(advice) 的语法:
- @Pointcut("execution(* com.demo.controller.*.*(..))")
- private void controllerPoint(){}
- @Before("controllerPoint()")
- public void beforeAdvice(){
- System.out.println("在方法执行之前输出");
- }
- @After Advice
@After 是一种通知类型(后置), 可确保在方法执行后运行通知. 以下是 @After 通知类的语法:
- @Pointcut("execution(* com.demo.controller.*.*(..))")p
- rivate void controllerPoint(){}
- @After("controllerPoint()")
- public void afterAdvice(){
- System.out.println("方法执行后输出.");
- }
- @After Returning Advice
@AfterReturning 是一种通知类型, 可确保方法执行成功后运行通知. 以下是 @AfterReturning 通知的语法:
- @AfterReturning(pointcut="execution(* com.demo.controller.*.*(..))", returning="retVal")
- public void afterReturningAdvice(JoinPoint jp, Object retVal){
- System.out.println("Method Signature:" + jp.getSignature());
- System.out.println("Returning:" + retVal.toString() );
- }
ps:returning - 要返回的变量的名称.
@AfterThrowing
@AfterThrowing 是一种通知类型, 可以确保在方法抛出异常时运行一个通知
- @AfterThrowing(pointcut="execution(* com.demo.controller.*.*(..))", throwing= "error")
- public void afterThrowingAdvice(JoinPoint jp, Throwable error){
- System.out.println("Method Signature:" + jp.getSignature());
- System.out.println("Exception:"+error);
- }
ps:throwing - 返回的异常名称
@Around
@Around 是一种环绕通知, 通过环绕通知, 我们可以在一个方法内完成前置, 后置, 异常 (@AfterThrowing) 等通知所实现的功能.
- @Around("controllerPoint()")
- public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
- System.out.println("方法开始");
- // 执行方法
- Object result=jp.proceed(args);
- System.out.println("方法结束");
- return result.toString();
- }
三 使用 AOP 记录每个 servie 方法执行日志
- package com.example.dubbo.demo.service.aop;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.commons.lang3.ArrayUtils;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.AfterThrowing;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONObject;
- import javassist.ClassClassPath;
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtMethod;
- import javassist.Modifier;
- import javassist.bytecode.CodeAttribute;
- import javassist.bytecode.LocalVariableAttribute;
- import javassist.bytecode.MethodInfo;
- @Component
- @Aspect
- public class LogAspect {
- private final Logger logger = LoggerFactory.getLogger(LogAspect.class);
- // 定义切点
- @Pointcut("execution(public * com.example.dubbo.demo.service.impl..*.*(..))")
- public void serviceLog(){}
- @Around("serviceLog()")
- public Object logBefore(ProceedingJoinPoint pj) throws Throwable{
- // 接口请求的开始时间
- Long startTimeMillis = System.currentTimeMillis();
- JSONObject paramJson = this.printMethodParams(pj,String.valueOf(startTimeMillis));
- logger.info("请求前:{}",paramJson.toString());
- Object retVal = pj.proceed();
- JSONObject returnJson = new JSONObject();
- returnJson.put("class_name",paramJson.get("class_name"));
- returnJson.put("method_name",paramJson.get("method_name"));
- returnJson.put("class_name_method",paramJson.get("class_name_method"));
- returnJson.put("return_name",retVal);
- Long endTimeMillis = System.currentTimeMillis();
- returnJson.put("endTimeMillis",endTimeMillis);
- returnJson.put("times",endTimeMillis - startTimeMillis);
- logger.info("请求后:"+returnJson.toString());
- return retVal;
- }
- @AfterThrowing(pointcut = "serviceLog()", throwing = "e")// 切点在 webpointCut()
- public void handleThrowing(JoinPoint joinPoint, Exception e) throws IOException {
- Long startTimeMillis = System.currentTimeMillis();
- JSONObject paramJson = this.printMethodParams(joinPoint,String.valueOf(startTimeMillis));
- // 获取错误详细信息
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- paramJson.put("errorMsg", e.getMessage());
- paramJson.put("StackTrace", sw.toString());
- logger.info("请求错误:"+paramJson.toString());
- pw.flush();
- sw.flush();
- }
- /**
- * 打印类 method 的名称以及参数
- * @param point 切面
- */
- public JSONObject printMethodParams(JoinPoint point,String startTimeMillis){
- if(point == null){
- return new JSONObject();
- }
- /**
- * Signature 包含了方法名, 申明类型以及地址等信息
- */
- String class_name = point.getTarget().getClass().getName();
- String method_name = point.getSignature().getName();
- logger.info("class_name = {},startTimeMillis:"+startTimeMillis,class_name);
- logger.info("method_name = {},startTimeMillis:"+startTimeMillis,method_name);
- JSONObject paramJson = new JSONObject();
- paramJson.put("class_name",class_name);
- paramJson.put("method_name",method_name);
- paramJson.put("startTimeMillis",startTimeMillis);
- paramJson.put("class_name_method", String.format("%s.%s", class_name,method_name));
- /**
- * 获取方法的参数值数组.
- */
- Object[] method_args = point.getArgs();
- try {
- // 获取方法参数名称
- String[] paramNames = getFieldsName(class_name, method_name);
- // 打印方法的参数名和参数值
- String param_name = logParam(paramNames,method_args);
- paramJson.put("param_name",JSONObject.parse(param_name));
- } catch (Exception e) {
- e.printStackTrace();
- }
- return paramJson;
- }
- /**
- * 使用 javassist 来获取方法参数名称
- * @param class_name 类名
- * @param method_name 方法名
- * @return
- * @throws Exception
- */
- private String[] getFieldsName(String class_name, String method_name) throws Exception {
- Class<?> clazz = Class.forName(class_name);
- String clazz_name = clazz.getName();
- ClassPool pool = ClassPool.getDefault();
- ClassClassPath classPath = new ClassClassPath(clazz);
- pool.insertClassPath(classPath);
- CtClass ctClass = pool.get(clazz_name);
- CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
- MethodInfo methodInfo = ctMethod.getMethodInfo();
- CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
- LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
- if(attr == null){
- return null;
- }
- String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
- // 如果是静态方法, 则第一就是参数
- // 如果不是静态方法, 则第一个是 "this", 然后才是方法的参数
- // 我接口中没有写 public 修饰词, 导致我的数组少一位参数, 所以再往后一位, 原本应该是 XX ? 0 : 1
- int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
- for (int i=0;i<paramsArgsName.length;i++){
- paramsArgsName[i] = attr.variableName(i+pos);
- }
- return paramsArgsName;
- }
- /**
- * 判断是否为基本类型: 包括 String
- * @param clazz clazz
- * @return true: 是; false: 不是
- */
- private boolean isPrimite(Class<?> clazz){
- if (clazz.isPrimitive() || clazz == String.class){
- return true;
- }else {
- return false;
- }
- }
- /**
- * 打印方法参数值 基本类型直接打印, 非基本类型需要重写 toString 方法
- * @param paramsArgsName 方法参数名数组
- * @param paramsArgsValue 方法参数值数组
- */
- private String logParam(String[] paramsArgsName,Object[] paramsArgsValue){
- StringBuffer buffer = new StringBuffer();
- if(ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)){
- buffer.append("{\"noargs\":\" 该方法没有参数 \"}");
- return buffer.toString();
- }
- for (int i=0;i<paramsArgsName.length;i++){
- // 参数名
- String name = paramsArgsName[i];
- // 参数值
- Object value = paramsArgsValue[i];
- buffer.append("\""+name+"\":");
- if(isPrimite(value.getClass())){
- buffer.append("\""+value+"\",");
- }else {
- buffer.append(JSON.toJSONString(value)+",");
- }
- }
- return "{"+buffer.toString().substring(0,buffer.toString().length()-1)+"}";
- }
- }
最后需要再 application.properties 文件中添加开启 aop 的配资
spring.aop.auto=true
作者: Eric.Chen
来源: https://www.cnblogs.com/lc-chenlong/p/10670343.html