基于AOP实现日志切面功能

  • Post author:
  • Post category:其他



基础概念:

  • Target(目标对象):代理的目标对象。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Joinpoint(连接点):所谓的连接点是指那些被拦截到的点。在spring中,这些点指的就是方法。通俗来讲就是

    可以被增强的方法叫做连接点

  • Pointcut(切入点):所谓切入点就是指我们要对拿些连接点进行拦截的定义。

    切点只是连接的一部分,真正被增强的方法

  • Advice(通知/增强):所谓通知是指拦截到Joinpoint只后所要做的事情就是通知。

    封装增强业务逻辑的方法
  • Aspect(切面):是指切点和通知的结合。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态织入方式,而AspectJ采用编译期织入和类装载期织入。

    将切点和通知进行结合的过程。

使用AOP可以实现事物的控制,权限控制,日志切面等,其中spring中事物就是基于AOP的,下面通过日志切面的DEMO来介绍具体的实现。


获取DEMO

项目运行结果:

在这里插入图片描述

在这里插入图片描述

项目基础:

在这里插入图片描述


涉及到的数据库sql:

CREATE TABLE `sys_log` (
  `id` varchar(32) NOT NULL,
  `log_content` varchar(1000) DEFAULT NULL COMMENT '日志内容',
  `ip` varchar(100) DEFAULT NULL COMMENT 'IP',
  `method` varchar(500) DEFAULT NULL COMMENT '请求java方法',
  `request_url` varchar(255) DEFAULT NULL COMMENT '请求路径',
  `request_param` longtext COMMENT '请求参数',
  `request_type` varchar(10) DEFAULT NULL COMMENT '请求类型',
  `cost_time` bigint(20) DEFAULT NULL COMMENT '耗时',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DAO层涉及到的sql语句
    <insert id="saveSysLog" parameterType="com.study.logaop.entity.SysLog">
        INSERT INTO sys_log(
          id,
          log_content,
          ip,
          method,
          request_url,
          request_param,
          request_type,
          cost_time
        )VALUES (#{id},#{logContent},#{ip},#{method},#{requestUrl},#{requestParam}
            ,#{requestType},#{costTime})
    </insert>


pom.xml:

<dependencies>
        <!--集成springmvc框架并实现自动配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 数据库驱动 https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <!-- mybatisPLUS-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--简化实体类,用@Data代替getset方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <!--spring切面aop依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- json -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

基于元注解的AOP所以先创建自定义注解@AutoLog

@Target(ElementType.METHOD) //作用于方法上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
@Documented
public @interface AutoLog {

    /**
     * 日志内容
     * @return
     */
    String value() default "";

}


自定义切面处理类,用于方法的增强,使用的是@Around 环绕通知

/**
 * 切面处理类
 */
@Aspect
@Component
public class AutoLogAspect {

    @Autowired
    private SysLogService sysLogService;

    //方法上面有 @AutoLog 这个注解时生效
    @Pointcut("@annotation(com.study.logaop.aspect.annotion.AutoLog)")
    private void logPointCut(){

    }
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行增强方法 ,joinPoint.proceed()执行结果(proceed )得到的是 方法上面有 @AutoLog 这个注解的方法的返回值
        Object proceed = joinPoint.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        saveSysLog(joinPoint,time);
        return proceed;
    }
    //保存日志操作
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) throws NoSuchMethodException, ClassNotFoundException {
        SysLog sysLog = new SysLog();

        //获取request
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();


        AutoLog annotation = signature.getMethod().getAnnotation(AutoLog.class);
        //获取日志信息 value
        String value = annotation.value();
        //获取Ip
        String ip = IpUtils.getIpAddr(request);
        //获取方法 (这里格式为 类名.方法名())
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        //请求路径 URL
        String requestURI = request.getRequestURI();
        //请求参数
        Map fieldsName = AopUtils.getFieldsName(joinPoint);
        //请求方式 类型 GET POST ..
        String method = request.getMethod();
        sysLog.setId(IdWorker.getIdStr());
        sysLog.setLogContent(value);
        sysLog.setIp(ip);

        sysLog.setRequestUrl(requestURI);
        sysLog.setRequestParam(JSON.toJSONString(fieldsName));
        sysLog.setRequestType(method);
        sysLog.setCostTime(time);
        sysLogService.saveSysLog(sysLog);
    }
}

**涉及到的一些工具类**

  • AopUtils
/**
 * 切面相关工具类
 */
@Slf4j
public class AopUtils {

    /**
     * 获取AOP代理的方法的参数名称和参数值
     *
     * @param cls
     * @param clazzName
     * @param methodName
     * @param args
     * @return
     * @throws NotFoundException
     */
    public static StringBuffer getNameAndArgs(Class<?> cls, String clazzName, String methodName, Object[] args) {

        StringBuffer sb = new StringBuffer();

        try {
            Map<String, Object> nameAndArgs = new HashMap<>();

            ClassPool pool = ClassPool.getDefault();
            ClassClassPath classPath = new ClassClassPath(cls);
            pool.insertClassPath(classPath);

            CtClass cc = pool.get(clazzName);
            CtMethod cm = cc.getDeclaredMethod(methodName);
            MethodInfo methodInfo = cm.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) {
                // exception
            }
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            for (int i = 0; i < cm.getParameterTypes().length; i++) {
                // paramNames即参数名
                nameAndArgs.put(attr.variableName(i + pos), args[i]);
            }

            // nameAndArgs的两种类型,用实体类接收的类似这样:
            // reqParams=com.whoareyou.fido.rest.User@616b9c0e
            // 用Map<String,Object>接收的是这样:menuNo=56473283,遍历这个map区分两种不同,使用不同的取值方式。
            // 根据获取到的值所属的不同类型通过两种不同的方法获取参数
            boolean flag = false;
            if (nameAndArgs != null && nameAndArgs.size() > 0) {
                for (Map.Entry<String, Object> entry : nameAndArgs.entrySet()) {
                    if (entry.getValue() instanceof String) {
                        flag = true;
                        break;
                    }
                }
            }
            sb = new StringBuffer();
            if (flag) {
                // 从Map中获取
                sb.append(JSON.toJSONString(nameAndArgs));
            } else {
                if (args != null) {
                    for (Object object : args) {
                        if (object != null) {
                            if (object instanceof MultipartFile || object instanceof ServletRequest
                                    || object instanceof ServletResponse) {
                                continue;
                            }
                            sb.append(JSON.toJSONString(object));
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return sb;
    }

    /**
     * 获取AOP代理的方法的参数名称和参数值
     *
     * @param cls
     * @param clazzName
     * @param methodName
     * @param args
     * @return
     */
    public static Map<String, Object> getNameAndArgsMap(Class<?> cls, String clazzName, String methodName, Object[] args) {

        Map<String, Object> nameAndArgs = new HashMap<>();

        try {

            ClassPool pool = ClassPool.getDefault();
            ClassClassPath classPath = new ClassClassPath(cls);
            pool.insertClassPath(classPath);

            CtClass cc = pool.get(clazzName);
            CtMethod cm = cc.getDeclaredMethod(methodName);
            MethodInfo methodInfo = cm.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) {
                // exception
            }
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            for (int i = 0; i < cm.getParameterTypes().length; i++) {
                String variableName = attr.variableName(i + pos);
                System.out.println("参数名" + attr.variableName(i + pos));
                // paramNames即参数名
                nameAndArgs.put(attr.variableName(i + pos), args[i]);
            }

        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return nameAndArgs;
    }

    /**
     * 获取参数名和值
     *
     * @param joinPoint
     * @return
     */
    public static Map getFieldsName(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NoSuchMethodException {
        // 参数值
        Object[] args = joinPoint.getArgs();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();

        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }
  • IpUtils 获取请求的IP信息工具类
/**
 * IP相关工具类
 */
@Slf4j
public class IpUtils {

    /**
     * 获取当前网络ip
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress = inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割  //"***.***.***.***".length() = 15
        if (ipAddress != null && ipAddress.length() > 15) {
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }

    /**
     * 获取真实IP
     *
     * @param request
     * @return
     */
    public static String getRealIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        return checkIp(ip) ? ip : (
                checkIp(ip = request.getHeader("Proxy-Client-IP")) ? ip : (
                        checkIp(ip = request.getHeader("WL-Proxy-Client-IP")) ? ip :
                                request.getRemoteAddr()));
    }

    /**
     * 校验IP
     *
     * @param ip
     * @return
     */
    private static boolean checkIp(String ip) {
        return !StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip);
    }


    /**
     * 获取操作系统,浏览器及浏览器版本信息
     *
     * @param request
     * @return
     */
    public static Map<String, String> getOsAndBrowserInfo(HttpServletRequest request) {
        String browserDetails = request.getHeader("User-Agent");
        String userAgent = browserDetails;
        String user = userAgent.toLowerCase();

        String os = "";
        String browser = "";

        //=================OS Info=======================
        if (userAgent.toLowerCase().indexOf("windows") >= 0) {
            os = "Windows";
        } else if (userAgent.toLowerCase().indexOf("mac") >= 0) {
            os = "Mac";
        } else if (userAgent.toLowerCase().indexOf("x11") >= 0) {
            os = "Unix";
        } else if (userAgent.toLowerCase().indexOf("android") >= 0) {
            os = "Android";
        } else if (userAgent.toLowerCase().indexOf("iphone") >= 0) {
            os = "IPhone";
        } else {
            os = "UnKnown, More-Info: " + userAgent;
        }

        //===============Browser===========================
        try {
            if (user.contains("edge")) {
                browser = (userAgent.substring(userAgent.indexOf("Edge")).split(" ")[0]).replace("/", "-");
            } else if (user.contains("msie")) {
                String substring = userAgent.substring(userAgent.indexOf("MSIE")).split(";")[0];
                browser = substring.split(" ")[0].replace("MSIE", "IE") + "-" + substring.split(" ")[1];
            } else if (user.contains("safari") && user.contains("version")) {
                browser = (userAgent.substring(userAgent.indexOf("Safari")).split(" ")[0]).split("/")[0]
                        + "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
            } else if (user.contains("opr") || user.contains("opera")) {
                if (user.contains("opera")) {
                    browser = (userAgent.substring(userAgent.indexOf("Opera")).split(" ")[0]).split("/")[0]
                            + "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
                } else if (user.contains("opr")) {
                    browser = ((userAgent.substring(userAgent.indexOf("OPR")).split(" ")[0]).replace("/", "-"))
                            .replace("OPR", "Opera");
                }
            } else if (user.contains("chrome")) {
                browser = (userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0]).replace("/", "-");
            } else if ((user.indexOf("mozilla/7.0") > -1) || (user.indexOf("netscape6") != -1) ||
                    (user.indexOf("mozilla/4.7") != -1) || (user.indexOf("mozilla/4.78") != -1) ||
                    (user.indexOf("mozilla/4.08") != -1) || (user.indexOf("mozilla/3") != -1)) {
                browser = "Netscape-?";

            } else if (user.contains("firefox")) {
                browser = (userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0]).replace("/", "-");
            } else if (user.contains("rv")) {
                String IEVersion = (userAgent.substring(userAgent.indexOf("rv")).split(" ")[0]).replace("rv:", "-");
                browser = "IE" + IEVersion.substring(0, IEVersion.length() - 1);
            } else {
                browser = "UnKnown";
            }
        } catch (Exception e) {
            log.error("获取浏览器版本失败");
            log.error(e.getMessage());
            browser = "UnKnown";
        }

        Map<String, String> result = new HashMap<>(2);
        result.put("OS", os);
        result.put("BROWSER", browser);
        return result;
    }

    /**
     * 判断是否是内网IP
     *
     * @param ip
     * @return
     */
    public static boolean isInner(String ip) {
        String reg = "(10|172|192)\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})";
        Pattern p = Pattern.compile(reg);
        Matcher matcher = p.matcher(ip);
        return matcher.find();
    }

    /**
     * 获取IP
     *
     * @return
     */
    public static String getHostIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
        }
        return "127.0.0.1";
    }

    /**
     * 获取主机名
     *
     * @return
     */
    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
        }
        return "未知";
    }
}
  • SpringContextUtils 获取spring容器的一些信息工具类
 * @Component
public class SpringContextUtils implements ApplicationContextAware {

	/**
	 * 上下文对象实例
	 */
	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringContextUtils.applicationContext = applicationContext;
	}

	/**
	 * 获取applicationContext
	 *
	 * @return
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	  * 获取HttpServletRequest
	 */
	public static HttpServletRequest getHttpServletRequest() {
		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}

	public static String getDomain(){
		HttpServletRequest request = getHttpServletRequest();
		StringBuffer url = request.getRequestURL();
		return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
	}

	public static String getOrigin(){
		HttpServletRequest request = getHttpServletRequest();
		return request.getHeader("Origin");
	}

	/**
	 * 通过name获取 Bean.
	 *
	 * @param name
	 * @return
	 */
	public static Object getBean(String name) {
		return getApplicationContext().getBean(name);
	}

	/**
	 * 通过class获取Bean.
	 *
	 * @param clazz
	 * @param       <T>
	 * @return
	 */
	public static <T> T getBean(Class<T> clazz) {
		return getApplicationContext().getBean(clazz);
	}

	/**
	 * 通过name,以及Clazz返回指定的Bean
	 *
	 * @param name
	 * @param clazz
	 * @param       <T>
	 * @return
	 */
	public static <T> T getBean(String name, Class<T> clazz) {
		return getApplicationContext().getBean(name, clazz);
	}
}



版权声明:本文为qq_578978992原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。