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
出处:https://www.cnblogs.com/lc-chenlong
如果喜欢作者的文章,请关注“写代码的猿”订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载