Spring Aop的学习(一):Spring Aop的简单入门

1. 什么是AOP

AOP(Aspect Oriented Programming):面向切面编程,是OOP(面向对象编程)的一个延续,其和OOP一样,也是一种编程思想。不过AOP是一种横向开发模式。

 

2. AOP的作用及应用场景

  • 作用
    AOP的主要作用就是减少代码量,提高代码的可重用性,有利于未来的可操作性与可维护性。
    主要操作就是将所有模块中共同拥有的代码,单独抽取出来,放在一块地方,在主代码运行之前或之后,或主程序运行的其他时间点执行这块代码。也可以理解成把这些单独抽出来的代码封装成一个个单独的方法,但是这些方法的执行不需要我们在程序中进行显示的调用,而是通过动态代理(jdk动态代理和cglib动态代理)的方式来帮助我们执行这些方法。
  • 应用场景
    AOP的主要应用场景是一些相似性代码比较高的场景,比如:权限认证,日志,事务等。

3. AOP的术语

1.连接点(Joinepoint):是程序执行的某个特定位置,如类开始初始化前、类初始化后、类的某个方法调用前/后、方法抛出异常后。一个类或者一段程序代码拥有一些具有边界性质的特定点,这些特定点就称为 “连接点” 。这边有个注意的点就是spring的链接点只支持方法,也就是只能是方法在调用前,调用后,抛出异常后。
2.切点(Pointcut):切点就是能够定位到特定连接点的点。也就是能够通过切点知道程序在哪个连接点之前或之后执行。
3.增强(Advice):增强说白了就是你想要在切点上执行的代码逻辑,也就是在连接点之前或之后执行的那段代码。
4.目标对象(Target):就是你要插入代码的类,也就是连接点所在的类。
5.引介(Introduction):是一种特殊的增强,为类添加一些属性和方法。
6.织入(Weaving):是一个过程,就是将增强添加到目标类具体连接点上的过程。
7.代理(Proxy):就是融合了目标类和增强逻辑后生成的那个对象
8.切面(Aspect):由切点和增强组成,包含了切点的定义与增强的代码逻辑的定义

4. 如何使用AOP

使用AOP的方式主要有两种,一种是基于xml文件进行开发,另一种是基于注解进行开发,这边主要是讲的基于注解的进行开发的方式。

4.1. 引入依赖

        <!--AOP的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

4.2. 注解介绍

@Aspect:声明一个切面,标注在类,接口或枚举上。

 @Pointcut:声明切入点,即切入到哪些目标类的目标方法,标注在方法上。

 

value的值是指定切入点表达式,常用的主要有两种表达式,一种是execution(),另一种是annotation():

  • execution()表达式
    语法:execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
    参数部分允许使用通配符:
    * 匹配任意字符,但只能匹配一个元素。
    .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用。
    + 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
  • annotation()表达式
    此方式是针对某个注解来定义切面,一般对于自定义注解常使用annotation()表达式,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")

@Before:前置通知,在切入点执行之前执行,也就是在执行切入点之前执行这个方法中的代码,但是如果这个方法中抛出了异常那么就不会再执行切入点的方法。和@Pointcut一样作用在方法上。

 其中value的值可以是指定切入点表达式,也可以是由@Pointcut注解标注的方法,如果是指定切入点表达式的话这边是和@Pointcut注解一样的。
@After:后置通知,在执行切入点方法之后执行,也就是执行了切入点的方法之后,会再执行@After注解标注的方法。其同样是作用在方法上的注解:

 其中value的值可以是指定切入点表达式,也可以是由@Pointcut注解标注的方法,如果是指定切入点表达式的话这边是和@Pointcut注解一样的。这个的value与@Before注解的value值一样。
@AfterReturning:返回通知,在切入点返回结果之后执行,这个结果是在@After注解之后执行的,其同样是作用在方法上:

 其中value的属性与@Before和@After注解的value一样。pointcut属性则是绑定同通知的契入点表达式,需要注意的是它的优先级高于value,也就是pointcut与value都填写的话会优先以pointcut的为准。
@AfterThrowing:异常通知,这个则是在抛出异常之后进行执行,另外,需要注意的是如果目标方法自己try-catch了异常,而没有继续往外抛出,则不会进入此回调函数。其同样是作用在方法上:

 他的属性值和@AfterReturning的属性值基本上一样。同样是pointcut的优先级高于value的
@Around:环绕通知,这个则是在切入点执行之前或执行之后都分别执行一些代码,还可以控制目标方法的执行。它是早于前置通知,晚于返回通知。其同样是作用在方法上的注解:

 

其中value的属性与@Before注解和@After注解的value属性一样。需要注意的是环绕通知标注的方法有一个必须的参数ProceedingJoinPoint,这个参数主要是用来执行目标方法,以及获取目标方法的一些信息。

4.3. 定义切点、切面以及通知

package com.mcj.music.aspect;

import com.mcj.music.utils.AspectUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

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

/**
 * @author mcj
 * @date 2022/10/23 9:54
 * @description 日志的切面类,为controller类中的每个方法添加日志
 */
@Aspect                // 定义切面
@Component
@Slf4j
public class LogAspect {

    /**
     * execution函数用于匹配方法执行的连接点,语法为:
     * execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))
     * 参数部分允许使用通配符:
     * *  匹配任意字符,但只能匹配一个元素
     * .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
     * +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
     * 此处表示返回类型、参数不限,只要是com.mcj.music.controller包中的任何类中的任何方法
     */
    @Pointcut("execution(* com.mcj.music.controller.*.*(..))")      // 定义切点
    public void logPointcut(){}

    /**
     * 环绕通知,在执行每个controller类中的方法之后打印相关的日志
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("logPointcut()")                                       // 进行环绕通知
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String paramNameAndValue = AspectUtils.getParamNameAndValue(joinPoint);
        String methodName = AspectUtils.getMethod(joinPoint);
        Object proceed = joinPoint.proceed();
        log.info("当前请求方法:{},当前请求参数名与参数:{},当前请求结果:{}", methodName, paramNameAndValue, proceed);
        return proceed;
    }

    @Before("logPointcut()")
    public void doBefore() {
        System.out.println("这是前置通知");
        System.out.println("--------------------------------------------");
    }

    @After("logPointcut()")
    public void doAfter() {
        System.out.println("这是后置通知");
        System.out.println("--------------------------------------------");
    }

    @AfterReturning("logPointcut()")
    public void doAfterReturning() {
        System.out.println("这是返回通知");
        System.out.println("--------------------------------------------");
    }

    @AfterThrowing("logPointcut()")
    public void doAfterThrowing() {
        System.out.println("这是异常通知");
        System.out.println("--------------------------------------------");
    }

}

AspectUtils工具类

package com.mcj.music.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;

import java.util.HashMap;
import java.util.Map;

/**
 * @author mcj
 * @date 2022/10/23 10:15
 * @description
 */
@Slf4j
public class AspectUtils {

    /**
     * 获取当前切点的参数名称与值
     * @param joinPoint
     * @return
     */
    public static String getParamNameAndValue(ProceedingJoinPoint joinPoint) {
        Map<String, Object> paramNameAndValueMap = new HashMap<>(8);
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < parameterNames.length; i++) {
            paramNameAndValueMap.put(parameterNames[i], args[i]);
        }
        return JSONObject.toJSONString(paramNameAndValueMap);
    }

    /**
     * 获取当前切点的全限定方法名
     * @param joinPoint
     * @return
     */
    public static String getMethod(ProceedingJoinPoint joinPoint) {
        // 获取当前切点的全限定类型
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        // 获取当前切点的方法名
        String name = joinPoint.getSignature().getName();
        return declaringTypeName + "." + name;
    }


}

执行结果:

 由于方法在执行的过程中出现异常,根本没有返回值,所以也不会有返回通知了。

 

5. 总结

spring AOP的简单入门使用并不是很难,基本上最主要的就是切面与切点的定义,至于它的几种通知类型,最主要的就是@Around环绕通知

 

 

 

 

转: https://www.cnblogs.com/mcj123/p/16819370.html

 

posted @ 2023-04-20 10:49  与f  阅读(85)  评论(0编辑  收藏  举报