AOP日志切面

本文详细介绍了AOP(面向切面编程)在日志管理中的应用,通过Spring AOP实现切入点表达式、通知类型和切面的整合,展示了如何通过@Aspect、@Pointcut、@Before/After/Around等通知增强核心业务逻辑。
摘要由CSDN通过智能技术生成

AOP日志切面


AOP(Aspect-Oriented Programming)其实是OOP(Object-Oriented Programing) 思想的补充和完善。我们知道,OOP引进"抽象"、"封装"、"继承"、"多态"等概念,对万事万物进行抽象和封装,来建立一种对象的层次结构,它强调了一种完整事物的自上而下的关系。但是具体细粒度到每个事物内部的情况,OOP就显得无能为力了。比如日志功能。日志代码往往水平地散布在所有对象层次当中,却与它所散布到的对象的核心功能毫无关系。对于其他很多类似功能,如事务管理、权限控制等也是如此。这导致了大量代码的重复,而不利于各个模块的重用。   而AOP技术则恰恰相反,它利用一种称为"横切"的技术,能够剖解开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称为切面)。更重要的是,它又能以巧夺天功的妙手将这些剖开的切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大的方便。    AOP技术的具体实现,无非也就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。


AOP的基本概念

  1. Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。

    1.1 @Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
    1.2 @After: final增强,不管是抛出异常或者正常退出都会执行.
    1.3 @AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
    1.4 @AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
    1.5 @Around: 环绕增强,相当于MethodInterceptor.

  2. JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。

  3. Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。

  4. Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。

  5. @Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知

  6. AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。


@PointCut

这个注解包含两部分,PointCut表达式和PointCut签名。表达式是拿来确定切入点的位置的,说白了就是通过一些规则来确定,哪些方法是要增强的,也就是要拦截哪些方法。

表示式(expression)和签名(signature)

//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){} 

由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:

  • execution:用于匹配方法执行的连接点;
  • within:用于匹配指定类型内的方法执行;
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
  • @within:用于匹配所以持有指定注解类型内的方法;
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
  • @annotation:用于匹配当前执行方法持有指定注解的方法;

格式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 
  • 其中后面跟着“?”的是可选项括号中各个pattern分别表示:
  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern): 可以为*表示任何返回值, 全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern):可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
  • 参数匹配((param-pattern)):可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用"" 来表示匹配任意类型的参数,"…"表示零个或多个任意参数。
    如(String)表示匹配一个String参数的方法;(
    ,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。
  • 异常类型匹配(throws-pattern?)

eg.

  • 任意公共方法的执行:execution(public * *(…))
  • 任何一个以“set”开始的方法的执行:execution(* set*(…))
  • AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(…))
  • 定义在service包里的任意方法的执行: execution(* com.xyz.service..(…))
  • 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service….(…))
    第一个表示匹配任意的方法返回值, …(两个点)表示零个或多个,第一个…表示service包及其子包,第二个表示所有类, 第三个*表示所有方法,第二个…表示方法的任意参数个数
  • 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp…JoinPointObjP2.*(…))")
  • pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
  • pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp…*)
  • 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf)
  • 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型
  • 带有@Transactional标注的所有类的任意方法:
    @within(org.springframework.transaction.annotation.Transactional)
    @target(org.springframework.transaction.annotation.Transactional)
  • 带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)
    @within和@target针对类的注解,@annotation是针对方法的注解
  • 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
  • 参数为String类型(运行是决定)的方法: args(String)

实现

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

 

package com.yuantiaokj.demo.aop.aspect;

import com.alibaba.fastjson.JSON;
import com.yuantiaokj.common.base.SysRes;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * ************************************************************
 * Copyright © 2021 cnzz Inc.All rights reserved.  *    **
 * ************************************************************
 *
 * @program: demo
 * @description:
 * @author: cnzz
 * @create: 2021-04-29 14:50
 **/

@Aspect
@Component
@Slf4j
public class WebLogAspect {

    //@Pointcut("execution(public * com.yuantiaokj.demo.aop.controller.*.*(..))")
    //@Pointcut("@within(com.yuantiaokj.demo.aop.anotation.ControllerLog)")
    //@Pointcut("@within(org.springframework.web.bind.annotation.RestController)||@within(org.springframework.stereotype.Controller)")
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void controllerLog() {
    }

    @Pointcut("execution(public * com.yuantiaokj.showtable.dao.*.*(..))")
    public void datacentreDaoLog() {
    }

//    /**
//     * 在连接点之前的通知(返回通知和异常通知的的异常)
//     **/
//    @Before("controllerLog()")
//    public void logBeforeController(JoinPoint joinPoint) {
//        //这个RequestContextHolder是Springmvc提供来获得请求的东西
//        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
//
//        String method = request.getMethod();
//        // 记录下请求内容
//        log.info("################URL : " + request.getRequestURL().toString());
//        log.info("################HTTP_METHOD : " + request.getMethod());
//        log.info("################IP : " + request.getRemoteAddr());
//        log.info("################THE ARGS OF THE CONTROLLER : " + Arrays.toString(joinPoint.getArgs()));
//
//        //下面这个getSignature().getDeclaringTypeName()是获取包+类名的   然后后面的joinPoint.getSignature.getName()获取了方法名
//        log.info("################CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
//        //logger.info("################TARGET: " + joinPoint.getTarget());//返回的是需要加强的目标类的对象
//        //logger.info("################THIS: " + joinPoint.getThis());//返回的是经过加强后的代理类的对象
//        log.info("{} 入参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
//    }
//
//    @After("controllerLog()")
//    public void logAfterController(JoinPoint joinPoint) {
//
//        log.info("{} 出参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
//    }
//
//    @AfterReturning(returning = "ret", pointcut = "controllerLog()")
//    public void doAfterReturning(JoinPoint joinPoint, Object ret) {
//        // 处理完请求,返回内容
//        log.info("{} 出参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), JSON.toJSONString(ret));
//    }


    @Around(value = "controllerLog()")
    public Object recordLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime= System.currentTimeMillis();
        String path = proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName();
        log.info("{} 入参打印{}", path, Arrays.toString(proceedingJoinPoint.getArgs()));

        //发生异常后,没有手动捕获,抛出后由全局异常捕获统
        Object object = proceedingJoinPoint.proceed();

        log.info("{} 耗时{}ms 出参打印{}", path,System.currentTimeMillis()-startTime, object);
        return object;
    }

}

 

package com.yuantiaokj.demo.aop.anotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * ControllerLog注解
 *
 * @author cnzz
 * @date 2021-04-29
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerLog {
    String value() default "";
}

 

package com.yuantiaokj.demo.aop.controller;

import com.yuantiaokj.common.base.SysRes;
import com.yuantiaokj.common.code.PubCode;
import com.yuantiaokj.common.exception.BizException;
import com.yuantiaokj.demo.aop.bo.AopLogTestBO;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * ************************************************************
 * Copyright © 2021 cnzz Inc.All rights reserved.  *    **
 * ************************************************************
 *
 * @program: demo
 * @description: aop controler 日志测试
 * @author: cnzz
 * @create: 2021-04-29 15:36
 **/
@RestController
//@Controller
@RequestMapping("/AopLogController")
@Slf4j
@Api
//@ControllerLog
public class AopLogController {

    @PostMapping("aopLogTest")
    public SysRes aopLogTest(@RequestBody AopLogTestBO input) {
        return SysRes.success(input);
    }


    @PostMapping("aopLogError")
    public SysRes aopLogError(@RequestBody AopLogTestBO input) {
        throw new BizException(PubCode.API_99001_参数校验不通过);
    }
}

效果图


参考链接:

https://www.cnblogs.com/wangshen31/p/9379197.html

https://blog.csdn.net/reee112/article/details/84937386

https://blog.csdn.net/qq_41921994/article/details/108523818

https://www.cnblogs.com/liujiayun/p/5912628.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值