前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot+mybatis+dubbo+aop日志第三篇

springboot+mybatis+dubbo+aop日志第三篇

作者头像
写代码的猿
发布2019-04-11 14:52:18
5930
发布2019-04-11 14:52:18
举报
文章被收录于专栏:平凡少年平凡少年

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中切入点有助于使用特定的连接点来应用通知。请考虑以下示例: 语法 @Aspectpublic class Logging { //切所有的controller包下表所有方法 @Pointcut("execution(* com.demo.controller.*.*(..))") private void controllerPoint(){}
代码语言:javascript
复制
@Pointcut("execution(* com.demo.controller.*.*(..))")
@Pointcut("execution(* com.demo.StudentServiceImpl.getName(..))")

 @Aspect - 将类标记为包含通知方法的类。

        @Pointcut - 将函数标记为切入点

        execution( expression ) - 涵盖应用通知的方法的表达式。

  • @Before Advice @Before是一种通知类型(前置),可以确保在方法执行之前运行通知。 以下是@Before通知(advice)的语法:
代码语言:javascript
复制
@Pointcut("execution(* com.demo.controller.*.*(..))")
private void controllerPoint(){}

@Before("controllerPoint()")
public void beforeAdvice(){ 
  System.out.println("在方法执行之前输出");
}
  • @After Advice @After是一种通知类型(后置),可确保在方法执行后运行通知。 以下是@After通知类的语法:
代码语言:javascript
复制
@Pointcut("execution(* com.demo.controller.*.*(..))")p
rivate void controllerPoint(){}

@After("controllerPoint()")

public void afterAdvice(){
   System.out.println("方法执行后输出。");
}
  • @After Returning Advice @AfterReturning是一种通知类型,可确保方法执行成功后运行通知。 以下是@AfterReturning通知的语法: ps:returning - 要返回的变量的名称。
代码语言:javascript
复制
@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() );
}
  • @AfterThrowing @AfterThrowing是一种通知类型,可以确保在方法抛出异常时运行一个通知 ps:throwing - 返回的异常名称
代码语言:javascript
复制
@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); 
 }
  • @Around @Around是一种环绕通知,通过环绕通知,我们可以在一个方法内完成前置、后置、异常(@AfterThrowing)等通知所实现的功能。
代码语言:javascript
复制
@Around("controllerPoint()")

public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ 
  System.out.println("方法开始"); 
  //执行方法   
  Object result=jp.proceed(args);  
  System.out.println("方法结束");  
  return result.toString();   
} 

三 使用AOP记录每个servie方法执行日志

代码语言:javascript
复制
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 如果喜欢作者的文章,请关注“写代码的猿”订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-04-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 AOP的基本概念
  • 二 AOP常用注解
  • 三 使用AOP记录每个servie方法执行日志
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档