什么是AOP?如何使用AOP写一个方便的自定义日志功能?

先赞再看,良好习惯,希望对大家有点帮助,小白猿-【左小涩】

前言

又是一年疫情,让大家在本就不平缓的生活里,变得更加坎坎坷坷。好好保护自己,保护身边的陌生人,大家记得戴口罩呀!生活再苦,也别忘记微笑。

正文

一、首先说说什么是AOP?

AOP:Aspect Oriented Programming,面向切面编程
通过预编译,在服务运行期间动态代理实现功能的一种方式,也是一种维护。
在Spring中,也是一个非常重要的功能。

AOP 利用一种称为横切的技术,剖开对象的封装,并将影响多个类的公共行为封装到一个可重用模块,组成一个切面,即 Aspect

切面就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块间的耦合度,利于可操作性可维护性

实现AOP的方式,一共有两种:

1、采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;
2、 采用静态织入的方式,引入特定的语法创建"切面",从而使得编译器可以在编译期间织入有关"切面"的代码。

AOP相关名词及解释

切面(Aspect)、连接点(Join point)、通知(Advice)、切点(Pointcut)、引入(Introduction)、目标对象(Target Object)、AOP代理(AOP Proxy)、织入(Weaving)

切面(Aspect)
横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能另外横切多个对象。

连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。

通知(Advice)
在特定的连接点,AOP框架执行的动作。Spring AOP 提供了5种类型的通知:

1、前置通知(Before):在目标方法被调用之前调用通知功能。
2、后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
3、后置返回通知(After-returning):在目标方法成功执行之后调用通知。
4、后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
5、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

切点(Pointcut)
指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。

引入(Introduction)
添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。

AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving)
织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

二、使用动态代理(自定义注解)的方式,完成日志记录

话不多说,直接上干货!

1、引入maven

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

2、在这个配置中,就可以指定注解的参数,一般来说日志我只记录增删改操作,所以我用了logType,来区分是否是访问操作。

/**
 * 日志 自定义注解
 */
@Target({
    
     ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLogAop {
    
    

    //日志名称
    String logName() default "";

    //日志类型 0页面跳转 1操作
    String logType() default "1";

}

3、这里就是记录日志的具体方法,方法最后面,调用相应的数据库保存接口。

为了复用切点,建议使用@Pointcut来定义切点,然后在 @Around、@After等注解中引用定义好的切点

获取IP地址的这部分,可能会存在一些问题,这里只用HttpServletRequest,写简单的获取IP地址的方式。

@Aspect
@Component
public class SystemLogAspect {
    
    

    /**
     * 切点
     */
    @Pointcut("@annotation(SystemLogAop完整路径)")
    public void logPointcut(){
    
    }

    /**
     * 环绕触发
     */
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        HttpServletRequest request = sra.getRequest();

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

        //操作人person
        map.put("userName","-");

        //请求IP
        String requestIp = request.getRemoteAddr();
        if(EmptyUtil.isEmpty(requestIp)){
    
    
            requestIp = "-";
        }
        map.put("ip",requestIp);

        //请求时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String requestTime = simpleDateFormat.format(new Date());
        map.put("createTime",requestTime);

        //获取日志名字
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SystemLogAop systemLogAop = method.getAnnotation(SystemLogAop.class);
        String logName = systemLogAop.logName();
        map.put("logName",logName);

        //获取日志类型
        String logType = systemLogAop.logType();
        map.put("logType",logType);

        //获取请求地址
        String pathName = request.getRequestURI();
        if(EmptyUtil.isEmpty(pathName)){
    
    
            pathName = "-";
        }
        map.put("pathName",pathName);

        //获取输入参数
        Map argsMap = request.getParameterMap();
        map.put("args",argsMap);

        //执行完方法的返回值
        Object result = joinPoint.proceed();
        map.put("result",result);

        //保存日志
        //保存数据库的操作。。。

        return result;
    }

}

4、下面就是如何使用,只用作参考,具体写在要记录的方法上,我一般是写在Controller中。
在这里插入图片描述
在这里插入图片描述

总结

使用自定义注解方式的日志功能,大致就是这样了,也可以使用XML的方式来完成AOP的配置,这里就不写这种方式了,个人比较喜欢用注解,省事为主,能干活就行!哈。

如果有错误,请大家指出,我会进行修改。

我是左小涩,一个独自在大城市努力的年轻人。
对您有帮助的话,希望献上您的【三连】呦!

猜你喜欢

转载自blog.csdn.net/wushuanghao123/article/details/113173566