Spring 实现AOP常见的两种方式(注解或者自定义注解)

第一种

导入AOP相关坐标(依赖冲突解决办法,将依赖中版本号删除,springboot会自动匹配合适的版本 )

<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <!--aspect: 切面, AOP;aspect-java -> aspectj-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名。类/接口名.方法名(参数)异常名

execution(public User com.itheima.service.UserService.findById(int))

**- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点

  • 访问修饰符:public,private等,可以省略
  • 返回值
  • 包名
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略**

**- AOP切入点表达式

  • 可以使用通配符描述切入点,快速描述
  • :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现*
execution(public * com.itheima.*.UserService.find*(*))

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法。
… : 多个连续的任意符号,可以独立出现,常用语简化包名与参数的书写(参数一般使用…)

execution(public User com..UserService.findById(..))

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+:专用于匹配子类类型

execution(* *..*Service+.*(..))
总结:
切入点表达式标准格式:动作关键字(访问修饰符 返回值  包名./接口名.方法名(参数)异常名)
* execution(* com.itheima.service.*Service.*(..))
切入点表达式描述通配符
作用:用于快速描述,范围描述
* : 匹配任意符号(常用)
.. : 匹配多个连续的任意符号(常用)
+ : 匹配子类类型

自定义注解的三个属性
1、@Target ElementType是一个枚举类型,它规范了注解的使用位置;

public enum ElementType {
    /** 类, 接口 (包括注解类型), 或 枚举 声明 */
    TYPE,
 
    /** 字段声明(包括枚举常量) */
    FIELD,
 
    /** 方法声明(Method declaration) */
    METHOD,
 
    /** 正式的参数声明 */
    PARAMETER,
 
    /** 构造函数声明 */
    CONSTRUCTOR,
 
    /** 局部变量声明 */
    LOCAL_VARIABLE,
 
    /** 注解类型声明 */
    ANNOTATION_TYPE,
 
    /** 包声明 */
    PACKAGE,
 
    /**
     * 类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * 使用的类型
     *
     * @since 1.8
     */
    TYPE_USE
}

2、@Retention
RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它确定了注解的存活时间

public enum RetentionPolicy {
    /**
     * 注解只在源代码级别保留,编译时被忽略
     */
    SOURCE,
    /**
     * 注解将被编译器在类文件中记录
     * 但在运行时不需要JVM保留。这是默认的
     * 行为.
     */
    CLASS,
    /**
     *注解将被编译器记录在类文件中
     *在运行时保留VM,因此可以反读。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

3、@Documented

这个注解表明将会被javadoc记录,如果类型声明被这个注解了,它将成为公共API的一部分。

第二步:在启动类上开启aop注解开发
Spring 实现AOP常见的两种方式(注解或者自定义注解)_第1张图片
第三步: 定义一个切面类

package com.test.easycodeauto.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,@Pointcut注解要求配置在方法上方  监控业务层所有的方法
    @Pointcut("execution(* com.test.easycodeauto.service.*Service.*(..))")
    private void pt(){}

    @Pointcut("execution(* com.test.easycodeauto.service.StudentService.queryById(..))")
    private void pt1(){}

    @Pointcut("execution(* com.test.easycodeauto.controller.StudentController.check*(..))")
    private void pt2() {
    }

    @Around("pt2()")
    public Object aroundCheck(ProceedingJoinPoint jpj) throws Throwable {
        // 去除web层的参数空格
        Object[] args = jpj.getArgs();
        System.out.println(Arrays.toString(args));
        for (int i = 0; i < args.length; i++) {
            if(args[i].getClass().equals(String.class)) {
                args[i] = args[i].toString().trim();
            }
        }
        Object proceed = jpj.proceed(args);
        return proceed;
    }

    /**
     * 通过 JoinPoint 来拿参数,around通知 使用ProceedingJoinPint
     */
//    @Before("pt1()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice...");
    }
//    @After("pt1()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("after advide ...");
    }
//    @Around("pt1()")
    public Object around(ProceedingJoinPoint jpj) throws Throwable {
        Object[] args = jpj.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = "02";
        Object proceed = jpj.proceed(args);
        System.out.println("around advide ...");
        return proceed;

    }
//    @AfterReturning("pt1()")
    public void afterReturning() {

    }
//    @AfterThrowing("pt1()")
    public void afterThrowing() {

    }

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Around("pt()")
    public void method(ProceedingJoinPoint pjp) throws Throwable {
        // 获取执行签名信息
        Signature signature = pjp.getSignature();
        // 通过签名获取执行操作类型(接口名)
        String className = signature.getDeclaringTypeName();
        // 通过签名获取执行操作类型(方法名)
        String methodName = signature.getName();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("测试业务侧接口"+className +"."+ methodName +"万次执行效率:   "+ (end - start) + "ms");
    }
}

第二种:基于自定义注解开发

第一种方式:
自定义注解

package com.test.easycodeauto.customaop.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAop {
    String value() default "";
}

定义一个切面类

package com.test.easycodeauto.customaop.aop;

import com.test.easycodeauto.customaop.domain.Dept;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;

@Component
@Aspect
@Slf4j
public class TestAopAspect {
    @Pointcut("@annotation(com.test.easycodeauto.customaop.aop.TestAop)")
    public void annotationPointcut(){}

    /**
     * 前置增强
     * @param jp
     */
//    @Before("annotationPointcut()")
    public void beforePointCut(JoinPoint jp) {
        // 此处进入方法前,可以实现一些业务逻辑
        log.info("before...");
    }

    /**
     * 后置增强
     * @param jp
     */
//    @After("annotationPointcut()")
    public void afterPointCut(JoinPoint jp) {
        // 此处进入方法,可以实现一些业务逻辑
        log.info("after...");
    }
    @Around("annotationPointcut()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        log.info("around...");
        // 获取这个方法的注解的方法实例
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();

        // 获取这个注解的信息
        TestAop testAop = method.getAnnotation(TestAop.class);
        // 输出这个方法上这个注解的属性信息
        log.info(testAop.toString());
        // 获取传入的参数
        Object[] args = jp.getArgs();
        // 获取传入的参数名称,是有序的数组对象
        String[] argNames = signature.getParameterNames();
        // 这个把参数名称和参数匹对
        HashMap<String,Object> params = new HashMap<>();
        for (int i = 0; i < argNames.length; i++) {
            params.put(argNames[i],args[i]);
        }
        //通过使坏来改变参数一的值,即name的值
        args[0] = "666";
        // 通过反射的方式获取属性信息

        Object proceed = jp.proceed(args);
        // 通过使坏来改变运行结果
        proceed = "Helo Wo";
        return proceed;
    }
}

使用方式:

package com.test.easycodeauto.customaop.service.impl;

import com.test.easycodeauto.customaop.aop.TestAop;
import com.test.easycodeauto.customaop.domain.Dept;
import com.test.easycodeauto.customaop.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class DeptServiceImpl implements DeptService {
    @Override
    @TestAop(value="TEST_AOP")
    public String testAop(String name, Dept dept) {
        log.info(name);
        log.info(dept.toString());
        log.info("testAop");
        return "testAop";
    }
}

第二种方式:
自定义一个注解:

package com.test.easycodeauto.customaop.aop;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyOperationLog {
    // 方法名称
    String methodName() default "";
    // 当前操作人
    String currentUser() default "";
    // 操作
    String operate() default "";
}

定义一个切面类:

package com.test.easycodeauto.customaop.aop;

import com.test.easycodeauto.customaop.domain.MyOperationLogVo;
import com.test.easycodeauto.customaop.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Component
@Aspect
public class MyLogAspect {

    @Autowired
    private LogService logService;

    // 定义一个切入点
    @Pointcut("@annotation(com.test.easycodeauto.customaop.aop.MyOperationLog)")
    public void pt(){}
    // 定义一个通知
    @Before("pt()")
    public void deBefore(JoinPoint joinPoint) {
        log.info("before...");
        // 获取自定义注解@MyOperationLog
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (null == method) {
            return;
        }
        MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);

        // 获取签名
        String signa = joinPoint.getSignature().toString();
        // 获取方法名
        String methodName = signa.substring(signa.lastIndexOf(".") + 1, signa.indexOf("("));
        String methodName1 = joinPoint.getSignature().getName();//获取目标方法的名称;
        // 获取方法的execution
        String longTemp = joinPoint.getStaticPart().toLongString();
        //获取目标类的名称
        String classType = joinPoint.getTarget().getClass().getName();
        try{
            Class<?> clazz = Class.forName(classType);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method1 : methods) {
                if(method1.isAnnotationPresent(MyOperationLog.class)&& method1.getName().equals(methodName)) {
                    // 封装对象,可以进行一些业务逻辑
                    // 解析
                    MyOperationLogVo myOperationLogVos = parseAnnotation(method1);
                    // 日志添加
                    logService.addLog(myOperationLogVos);
                }
            }
        }catch (Exception e) {
           e.printStackTrace();
        }
    }

    private MyOperationLogVo parseAnnotation(Method method) {
        MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);
        if (null == myOperationLog) {
            return null;
        }
        MyOperationLogVo myOperationLogVo = new MyOperationLogVo();
        myOperationLogVo.setMethodName(myOperationLog.methodName());
        myOperationLogVo.setCurrentUser(myOperationLog.currentUser());
        myOperationLogVo.setOperate(myOperationLog.operate());
        return myOperationLogVo;
    }
}

使用方式:

 @RequestMapping("/controller")
 @MyOperationLog(methodName = "testLog",currentUser = "admin",operate = "查询")
    public String testLog() {
        return "log";
    }

Spring 实现AOP常见的两种方式(注解或者自定义注解)_第2张图片

你可能感兴趣的:(spring,java,spring,boot)