【Spring】AOP实例—日志模块的实现

AOP实例—日志模块

AOP能够使系统服务(例如:日志模块、安全模块、事务管理)模块化,并以声明的方式将它们应用到它们需要影响的组件中去。使业务组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来复杂性。


日志模块就属于一种系统服务,业务组件不需要自己编写日志逻辑,而是将日志这种渗透到整个系统的服务切面化,利用AOP编写好日志逻辑,并声明到需要记录日志的组件当中。本文以项目中比较常用的日志为例,讲解AOP及注解的部分功能 。

1.切面化日志模块

1.1导入jar包

使用AOP首先需要导入AspectJ的jar包aspectjweaver-1.9.5.jar

我用的是springboot,可以直接在pom中配置spring-boot-starter-aop,通过maven导入

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

可以看到aspectjweaver-1.9.5.jar就在spring-boot-starter-aop下。

在这里插入图片描述

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法。Spring Aop只是借助了AspectJ的AOP语法,即注解(例如@Aspect,@Pointcut)。

1.2自定义日志注解
@Target(ElementType.METHOD)	//将注解作用于方法上。(因为要在执行方法时打印日志)
@Retention(RetentionPolicy.RUNTIME)	// jvm加载后注解仍然存在,用于反射的实现
public @interface AopLog {
    // 方法所属模块
    String module() default "";
    // 方法具体内容
    String operation() default "";
}

需要什么就加什么。对于自定义注解的创建、处理及调用请参考 自定义注解

日志实体类:

public class AopLog {
    // 所属模块
    private String module;
    // 具体操作
    private String operation;
    // 执行时间
    private String execTime;

    public String getModule() {
        return module;
    }

    public void setModule(String module) {
        this.module = module;
    }

    public String getOperation() {
        return operation;
    }

    public void setOperation(String operation) {
        this.operation = operation;
    }

    public String getExecTime() {
        return execTime;
    }

    public void setExecTime(String execTime) {
        this.execTime = execTime;
    }

    @Override
    public String toString() {
        return "AopLog{" +
                "module='" + module + '\'' +
                ", operation='" + operation + '\'' +
                ", execTime='" + execTime + '\'' +
                '}';
    }
}
1.3切面声明

AOP的概念可以参考spring官网

import org.aspectj.lang.annotation.Aspect;

@Component	// 开启组建扫描
@Aspect	// 声明为切面
public class LogAspect {
    
    //声明一个Pointcut切入点
    @Pointcut("@annotation(com.zmxqq.annotation.AopLog)")//(切入点指示器+声明需要执行的方法)
    public void pointcut() {}// 切入点签名(访问修饰符+void+方法名+任何参数)
    
    // 声明一个‘切入点pointcut()’的环绕通知
    @Around("pointcut()")
    public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
        Object result;

        // ProceedingJoinPoint:正在增强的连接点,也就是正在执行的方法
        // 获取目标对象的Class对象
        Class<?> targetClass = pjp.getTarget().getClass();
        // 通过pjp获取当前执行方法的标签(包括方法名称,参数等)
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 根据方法名及参数获取指定的方法。
        Method targetMethod = getDeclaredMethod(targetClass,signature.getName(),signature.getMethod().getParameterTypes());
        // 获取当前方法的注解的对象(这里的AopLog是自定义注解)
        AopLog aopLogAnnotation = targetMethod.getAnnotation(AopLog.class);

        // 记录方法执行的时间与执行结束的时间
        long starTime = System.currentTimeMillis();
            // 调用proceed(),开始执行方法
        result = pjp.proceed();
        long endTime = System.currentTimeMillis();

        // 获取当前方法注解的参数,并存入到AopLog注解实体类中
        String module = aopLogAnnotation.module();
        String operation = aopLogAnnotation.operation();
            // 注解与实体类有一个写上全路径名避免名字冲突
        com.zmxqq.model.AopLog aopLog = new com.zmxqq.model.AopLog();
        aopLog.setModule(module);
        aopLog.setOperation(operation);
        aopLog.setExecTime((double)(endTime-starTime) + "秒");

        // 打印日志
        System.out.println(aopLog.toString());

        return result;
    }

    // 根据方法名,参数类型获取本类方法,如果没有就去父类中查找
    protected Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }
        return null;
    }
}

在controller层的方法中加上@AopLog日志注解,然后调用此方法:

@GetMapping("/Test/AopTest")
@AopLog(module = "测试模块", operation = "打印日志")
public void AopTest() {
    System.out.println("日志注解测试")
}

详细解读下都需要哪些步骤:

  1. 首先利用最开始导入的AspectJ的jar包所提供的注解@Aspect声明一个切面类,@Component注解开启组件扫描,让spring可以扫描到这个切面。

  2. 切面类内部用@Pointcut声明一个切入点(当然@Pointcut也是AspectJ包中的),格式如代码中所示,其中切入点指示器@annotation将只匹配给定注解的连接点(连接点是指目标对象的方法执行,切入点是连接点的集合),上述代码中就将匹配限制在了使用了com.zmxqq.annotation路径下的AopLog注解的连接点,即所有使用@AopLog注解的方法。[注意:切入点签名返回值类型必须是void](其他切入点指示器规则可以查看官网或我的Spring官方文档笔记

  3. 利用切入点限制了具体的方法之后,就需要对这些方法执行某些操作,也就是AOP所说的通知。通知与切入点表达式相关联,并在切入点匹配的方法执行之前、之后或周围运行。我这里用了环绕通知,其中ProceedingJoinPoint继承自JoinPoint,表示正在增强的连接点,也就是正在执行的方法。再通过反射获取正在执行的这个方法的注解与注解的参数信息,保存到数据库或打印这些信息。我这里打印出来:

    在这里插入图片描述

  4. 调用pjp.proceed();将执行当前方法并返回一个Object对象,代码中在方法执行前后记录了时间用来计算执行时间。这部分可以自由发挥。

一个简单的基于AOP的日志模块完成,此外还有① 前置通知 @Before,② 后置通知 @AfterReturning,③ 抛出异常后通知 @AfterThrowing等通知注解,可以自行扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值