当前位置: 代码迷 >> 综合 >> Spring Boot学习笔记(十)AOP底层原理(JDK动态代理)以及术语、以及应用(Aspect)
  详细解决方案

Spring Boot学习笔记(十)AOP底层原理(JDK动态代理)以及术语、以及应用(Aspect)

热度:63   发布时间:2024-02-02 04:02:57.0

文章目录

  • 一、底层实现原理(JDK动态代理)
  • 二、AOP
    • 1. 术语
    • 2. 使用方法(使用Aspect注解)
      • 2.1 AOP相关注解
      • 2.2 切入点表达式
      • 2.3 实际代码

一、底层实现原理(JDK动态代理)

AOP的底层实践是使用了java.lang.reflect.ProxynewProxyInstance方法
在这里插入图片描述
有三个参数:

  • loader:class 加载器,你在某个类下使用Proxy.newProxyInstance,就使用该类的class加载器。
  • interfaces:接口数组
  • h :invocationHandler 即增强方法的处理方式

根据这几个参数,我们开始编写我们的代码。首先是接口,代码如下

public interface UserDao {public int add(int a, int b);
}

下面是接口的实现类

public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add 方法执行了");return a+b;}
}

接下来编写我们的主函数。主函数中,我们所调用的类,是由Proxy.newProxyInstance转换而来的的UserDao接口类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;public class Main {public static void main(String[] args) {Class[] interfaces = {UserDao.class};UserDaoImpl userDaoImpl = new UserDaoImpl();//创建接口实现类代理对象//此处用UserDao作为返回值的类型,是因为我们传入的interfaces就是UserDao.classUserDao dao = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new InvocationHandler() {//把想要代理的对象传递进来private Object object = userDaoImpl;//增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法之前System.out.println("方法之前执行 : " + method.getName() + "; 传递的参数:" + Arrays.toString(args) + "; object:" + object);//被增强的方法执行,填写要增强的对象、参数Object res = method.invoke(object, args);//方法之后System.out.println("方法之后执行 : " + method.getName() + "; 传递的参数:" + Arrays.toString(args) + "; object:" + object);return res;}});int res = dao.add(1, 2);System.out.println("这个是res: " + res);}
}

执行结果

方法之前执行 : add; 传递的参数:[1, 2]; object:main.UserDaoImpl@2a84aee7
add 方法执行了
方法之后执行 : add; 传递的参数:[1, 2]; object:main.UserDaoImpl@2a84aee7
这个是res: 3

可以发现,在方法执行前后,都成功输出了数据,这种特性多用于日志记录,性能统计,安全控制,事务处理,异常处理等等。

之所以要用这样的方法写日志记录等功能,是因为虽然可以将日志记录等功能写在业务代码中,但会使得业务逻辑耦合臃肿。所以此部分代码抽出。

二、AOP

1. 术语

  • 1.连接点(Join point):哪些方法可以被增强,哪些方法就被叫做连接点
  • 2.切点(Pointcut):实际被真正增强的方法,被称为切入点
  • 3.通知(增强)(Advice):是指切面在某个特殊连接点上执行的动作,执行顺序around > before > around > after > afterReturning
    1. before(前置通知): 在方法开始执行前执行
    2. after(后置通知): 在方法执行后执行
    3. afterReturning(返回后通知): 在方法返回后执行,类似于finally
    4. afterThrowing(异常通知): 在抛出异常时执行
    5. around(环绕通知): 在方法执行前和执行后都会执行
  • 4.切面(Aspect):将通知运用到切入点,叫做切面(AOP)
  • 5.引入(Introduction):引入让一个切面可以声明被通知的对象实现了任何他们没有真正实现的额外接口,而且为这些对象提供接口的实现。引入允许我们向现有的类添加新方法或属性。这个新方法和实例变量就可以被引入到现有的类中,从而可以再无需修改这些现有的类的情况下,让它们具有新的行为和状态。
  • 6.织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以织入。
    1. 编译器:切面在目标类编译时被织入。这种方式需要特殊的编译器。
    2. 类加载期:切面在目标类被引入应用之前增强该目标类的字节码。
    3. 运行期:切面在应用运行的某个时刻被织入。
  • 7.目标对象(Target object):是指被一个或多个切面通知的那个对象。也指被通知对象(“advised object”),由于Spring AOP是通过运行时代理事项的,这个目标对象往往是一个代理对象。
  • 8.AOP 代理(AOP proxy):是指通过AOP框架创建的对象,用来实现切面合约的(执行通知方法等等)。在Spring框架中,一个AOP代理是一个JDK动态代理或者是一个CGLIB代理。

2. 使用方法(使用Aspect注解)

首先,我们现创立一个SpringBoot工程,若不会可根据Spring Boot学习笔记(一)在IDEA 中创建 Hello world工程,之后在项目的pom.xml中添加以下依赖,大家可以注意到有aspectj并没有带上spring的前缀,因为这个是一个独立的库,但它是基于Spring的(此处了解一下这个点就好)。

另外,此处之所以加入alibaba的fastjson是为了后续能够更好的显示输出结果

        <dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.14</version></dependency>

2.1 AOP相关注解

  • @Pointcut 切入点:定义一个切点,主要用于填写切入点表达式,eg:@Pointcut(value = “execution(public * com.example.demo.Work.*(…))”),解析见后文
  • @Before前置通知:在切入点开始处切入内容
  • @After 后置通知:和前置通知相反,在切入点之后执行
  • @Around环绕通知:分别会在最开始、最后执行,执行的代码以proceedingJoinPoint.proceed();为界限
  • @AfterReturning:很明显,是在切入点的Return后执行
  • @AfterThrowing:这个是在切入执行报错的时候执行的

2.2 切入点表达式

作用:标明要对哪个类里面的哪个切入点进行增强
语法结构:execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
除了返回类型模式、方法名模式和参数模式为必填,修饰符模式和异常模式可选

例子1:(下面的例子中,只填写了返回类型、方法名、参数)
在这里插入图片描述
例子2: execution(public * *(..))
匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法;

例子3:execution(* *To(..))
匹配目标类所有以To为后缀的方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;

例子4:execution(* com.baobaotao.*(..))
匹配com.baobaotao包下所有类的所有方法;

例子5:execution(* com.baobaotao..*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类;

2.3 实际代码

我们有以下几个文件
在这里插入图片描述

我们先写一个简单Work类,此处需要将Work类加上注解,才可作为的切点。
这是因为 Spring AOP 实现了 AOP,要想使用spring的特性,需要把bean交给spring管理,如果只是手动new的话,只是普通对象,不是代理对象

@Component
public class Work {public void printf() {System.out.println("work 成功工作了");}
}

我们创建一个简单的Controller,用于触发Work.printf()操作

@RestController
public class demoController {@AutowiredWork work;@GetMapping("/aspectAutowired")public String aspectAutowired() {System.out.println("触发了Controll 里面的 aspectAutowired");work.printf();return "success";}@GetMapping("/aspectNew")public String aspectNew() {System.out.println("触发了Controll 里面的 aspectNew");Work work = new Work();work.printf();return "success";}
}

我们的SrpingBoot启动项不进行更改
在这里插入图片描述

接下来让我们仔细讲讲AspectLearn类
各方法参数说明:

  1. 除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。
  2. @Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。
  3. @AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。
  4. @AfterThrowing方法里,可以加throwing = “XXX”,供读取异常信息

我的工程文件路径如下所示
在这里插入图片描述
AspectLearn为我们的切面文件,目标文件是Work,则我们的切点需要这样写:@Pointcut(value = "execution(public * com.example.demo.Work.*(..))")
我们的代码如下(注释部分为point的使用方法)

package com.example.demo;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import com.alibaba.fastjson.JSON;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.aspectj.lang.ProceedingJoinPoint;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;@Component
@Aspect
public class AspectLearn {ThreadLocal<Long> startTime = new ThreadLocal<>();/*** 指定切入点匹配表达式,注意它是以方法的形式进行声明的。*/
// 此处是将Work下的所有方法设为目标对象@Pointcut(value = "execution(public * com.example.demo.Work.*(..))")public void pointCut() {}/***前置通知:在切入点之前执行的通知* @param joinPoint* @throws Throwable*/@Before("pointCut()")public void doBefore(JoinPoint joinPoint) throws Throwable {//打印请求相关参数System.out.println("========================================== Before Start ==========================================");
// startTime.set(System.currentTimeMillis());
//
// ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// HttpServletRequest request = servletRequestAttributes.getRequest();
//
// System.out.println("URL:" + request.getRequestURL().toString());
// System.out.println("HTTP_METHOD:" + request.getMethod());
//
// //header第一种格式展示
// Enumeration<String> enumeration = request.getHeaderNames();
// Map<String, String> headerMap = new HashMap<>();
// while (enumeration.hasMoreElements()) {
// String headerName = enumeration.nextElement();
// headerMap.put(headerName, request.getHeader(headerName));
// }
// String headerJsonStr = JSON.toJSONString(headerMap);
// if (headerJsonStr.length() > 0) {
// System.out.println("HTTP_HEADERS INFO IS: {}", headerJsonStr);
// }
//
// //header第二种格式展示
// System.out.println("HTTP_HEADERS: ");
// Enumeration<?> enumeration1 = request.getHeaderNames();
// while (enumeration1.hasMoreElements()) {
// String key = (String) enumeration1.nextElement();
// String value = request.getHeader(key);
// System.out.println(" {}: {}", key, value);
// }
//
// System.out.println("IP:" + request.getRemoteAddr());
//
// System.out.println("CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
// try {
// System.out.println("REQUEST BODY : [{}]", JSON.toJSONString(joinPoint.getArgs()[0]));
//// System.out.println("ARGS:{}", Arrays.toString(joinPoint.getArgs()));
// } catch (Exception e) {
// LOG.error("REQUEST BODY PARSE ERROR!");
// }
//
// HttpSession session = (HttpSession) servletRequestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
// System.out.println("SESSION ID:" + session.getId());System.out.println("========================================== Before End ==========================================");}// /**
// * 后置通知
// * @param ret
// * @throws Throwable
// */@AfterReturning("pointCut()")public void doAfterReturning() throws Throwable {System.out.println("=========================================== AfterReturning start ===========================================");
// // 处理完请求,返回内容System.out.println();
// System.out.println("RESPONSE : " + ret);
// System.out.println("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));System.out.println("=========================================== AfterReturning end ===========================================");}/*** 后置最终通知* @param joinPoint* @throws Throwable*/@After("pointCut()")public void doAfter(JoinPoint joinPoint) throws Throwable {System.out.println("=========================================== After start ===========================================");
// // 每个请求之间空一行
// String methodName = joinPoint.getSignature().getName();
// List<Object> args = Arrays.asList(joinPoint.getArgs());
// System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args);System.out.println("=========================================== After Finish =====================================");}/*** 环绕通知* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型* @param proceedingJoinPoint* @return* @throws Throwable*/@Around("pointCut()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("=========================================== Around start ===========================================");
// long startTime = System.currentTimeMillis();System.out.println("=========================================== Around start 内 proceedingJoinPoint.proceed() 之前 ===========================================");
// 目标方法执行完的返回值,即在此行代码后,是Around的后半部分Object result = proceedingJoinPoint.proceed();System.out.println("=========================================== Around start 内 proceedingJoinPoint.proceed() 之后 ===========================================");
// String resultStr = JSON.toJSONString(result);
// // 打印出参
// System.out.println("RESPONSE ARGS : {}", resultStr);
// // 执行耗时
// System.out.println("TIME-CONSUMING : {} ms", System.currentTimeMillis() - startTime);System.out.println("=========================================== Around end ===========================================");return result;}//后置异常通知@AfterThrowing("pointCut()")public void throwss(JoinPoint joinPoint){System.out.println("=========================================== AfterThrowing start ===========================================");
// String methodName = joinPoint.getSignature().getName();
// List<Object> args = Arrays.asList(joinPoint.getArgs());
// System.out.println("调用后连接点方法为:" + methodName + ",参数为:" + args);
// System.out.println("方法异常时执行.....");System.out.println("=========================================== AfterThrowing end ===========================================");}
}

正确执行时,结果如下所示:

触发了Controll 里面的 aspectAutowired
=========================================== Around start ===========================================
=========================================== Around start 内 proceedingJoinPoint.proceed() 之前 ===========================================
========================================== Before Start ==========================================
========================================== Before End ==========================================
work 成功工作了
=========================================== AfterReturning start ===========================================
=========================================== AfterReturning end ===========================================
=========================================== after start ===========================================
=========================================== After Finish =====================================
=========================================== Around start 内 proceedingJoinPoint.proceed() 之后 ===========================================
=========================================== Around end ===========================================

当Work.printf()错误执行时,结果如下所示:

触发了Controll 里面的 aspectAutowired
=========================================== Around start ===========================================
=========================================== Around start 内 proceedingJoinPoint.proceed() 之前 ===========================================
========================================== Before Start ==========================================
========================================== Before End ==========================================
=========================================== AfterThrowing start ===========================================
=========================================== AfterThrowing end ===========================================
=========================================== After Start ===========================================
=========================================== After Finish =====================================
  相关解决方案