SpringBoot中AOP的应用记录

简介: 版权声明:本文为 testcs_dn(微wx笑) 原创文章,非商用自由转载-保持署名-注明出处,谢谢。 https://blog.csdn.net/testcs_dn/article/details/80325552 ...
版权声明:本文为 testcs_dn(微wx笑) 原创文章,非商用自由转载-保持署名-注明出处,谢谢。 https://blog.csdn.net/testcs_dn/article/details/80325552

AOP目的: 
面向切面编程(aspect-oriented programming,AOP)主要实现的目的是针对业务处理过程中的切面进行提取,诸如日志、事务管理和安全这样的系统服务,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


Spring AOP术语:

1.连接点(Joinpoint) 
程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入通知。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。连接点是在应用执行过程中能够插入切面的一个点。


2.切点(Pointcut) 
AOP通过“切点”定位特定的连接点。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。


3.通知(Advice) 
切面的工作被称为通知。是织入到目标类连接点上的一段程序代码。 
Spring切面可以应用5种类型的通知: 
- 前置通知(Before):在目标方法被调用之前调用通知功能; 
-后置通知(After):在目标方法完成之后调用通知,此时不会关心方 法的输出是什么; 
-返回通知(After-returning):在目标方法成功执行之后调用通知; 
-异常通知(After-throwing):在目标方法抛出异常后调用通知; 
-环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调 用之前和调用之后执行自定义的行为。


4.引介(Introduction) 
引入允许我们向现有的类添加新方法或属性,是一种特殊的通知。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

5.切面(Aspect) 
切面由切点和通知(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义。


6.织入(Weaving) 
织入是把切面应用到目标对象并创建新的代理对象的过程。 
AOP有三种织入的方式: 
a、编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。 
b、类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增 
强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。 
c、运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring 

AOP就是以这种方式织入切面的。

AOP织入的目标/对象:

这个是一个需要注意的问题,并不是任何类都会被织入增强的;

只有使用 @RestController 注解和 @Controller 注解标记的类才会被织入增强。

有些例子给出了接口的定义,有些例子给出Services的定义,然而那都不是AOP的菜啊!反而误导读者。

需要做哪些配置?

对不起!真的不需要配置!

有的文章指出要使用 @EnableAspectJAutoProxy 注解,然而亲测真的不需要!

示例代码:

package com.wanyu.configuration;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AdviceTest {
	
    @Pointcut("execution(public * com.wanyu.fams.controller.*.*(..))")
    public void pointcut() {
		// 仅用于定义 Pointcut
    }
	
    /**
     * 可以在执行方法之前和之后改变参数和返回值
     * @param joinPoint用于获取目标方法相关信息的参数
     * @return 最终的返回值
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object processTx(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around增强:执行方法之前,模拟开始事物");
        Object[] args = joinPoint.getArgs();
        if(args != null && args.length > 0 && args[0].getClass() == String.class) {
            args[0] = "增加的前缀" + args[0];
        }
        Object rvt = joinPoint.proceed();
        System.out.println("Around增强:执行方法之后,模拟结束事物");
        if(rvt != null && rvt instanceof Integer) {
            rvt = (Integer)rvt * (Integer)rvt;
        }
        return rvt;
    }

    /**
     * 可以在执行方法之前对目标方法的参数进行判断
     * 通过抛出一个异常来阻断目标方法的访问
     * @param joinPoint
     */
    @Before("pointcut()")
    public void authority(JoinPoint joinPoint) {
        System.out.println("Before增强:模拟权限检查");
        System.out.println("Before增强:被织入增强处理的目标目标方法为:" + joinPoint.getSignature().getName());
        System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(joinPoint.getArgs()));
        joinPoint.getArgs()[0] = "除了Around其他的都是是不可以修改目标方法的参数的";
        System.out.println("joinPoint.getArgs()[0]:"+joinPoint.getArgs()[0]);
        System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(joinPoint.getArgs()));
        System.out.println("Before增强:被织入增强处理的目标对象为:" + joinPoint.getTarget());
    }

    /**
     * 可以在执行方法之后对目标方法的参数进行判断
     * @param joinPoint
     */
    @After("pointcut()")
    public void release(JoinPoint joinPoint) {
        System.out.println("After增强:模拟方法结束后的释放资源");
        System.out.println("After增强:被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        System.out.println("After增强:目标方法的参数为:" + Arrays.toString(joinPoint.getArgs()));
        System.out.println("After增强:被织入增强处理的目标对象为" + joinPoint.getTarget());
    }
    /**
     * 与After的区别在于AfterReturning只有在方法执行成功的之后才会被织入,如果After和
     * AfterReturning同时存在于一个文件中,谁写在前面谁先运行
     * @param joinPoint
     * @param rvt方法的返回值
     */
    @AfterReturning(pointcut="execution(public * com.wanyu.fams..*.*(..))", returning="rvt")
    public void log(JoinPoint joinPoint, Object rvt) {
        System.out.println("AfterReturning增强:获取目标方法的返回值:" + rvt);
        System.out.println("AfterReturning增强:模拟日志功能");
        System.out.println("AfterReturning增强:获织入增强的目标方法为:" + joinPoint.getSignature().getName());
        System.out.println("AfterReturning增强:目标方法的参数为:" + Arrays.toString(joinPoint.getArgs()));
        System.out.println("AfterReturning增强:被织入增强处理的目标对象为:" + joinPoint.getTarget());
    }
}

可以看到 pointcut 的定义:

@Pointcut("execution(public * com.wanyu.fams.controller.*.*(..))")

与引用:

@After("pointcut()")

也可以单独的定义切入点

@AfterReturning(pointcut="execution(public * com.wanyu.fams..*.*(..))", returning="rvt")


控制台输出

项目启动后可能看到以下输出:

2018-05-15 16:53:27.276  WARN 9587 --- [           main] o.s.aop.framework.CglibAopProxy          : Unable to proxy interface-implementing method [public final void org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationException] because it is marked as final: Consider using interface-based JDK proxies instead!
2018-05-15 16:53:27.277  INFO 9587 --- [           main] o.s.aop.framework.CglibAopProxy          : Final method [public final void org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationException] cannot get proxied via CGLIB: Calls to this method will NOT be routed to the target instance and might lead to NPEs against uninitialized fields in the proxy instance.
2018-05-15 16:53:27.299  WARN 9587 --- [           main] o.s.aop.framework.CglibAopProxy          : Unable to proxy interface-implementing method [public final void org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationException] because it is marked as final: Consider using interface-based JDK proxies instead!
2018-05-15 16:53:27.299  INFO 9587 --- [           main] o.s.aop.framework.CglibAopProxy          : Final method [public final void org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationException] cannot get proxied via CGLIB: Calls to this method will NOT be routed to the target instance and might lead to NPEs against uninitialized fields in the proxy instance.
2018-05-15 16:53:27.318  WARN 9587 --- [           main] o.s.aop.framework.CglibAopProxy          : Unable to proxy interface-implementing method [public final void org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationException] because it is marked as final: Consider using interface-based JDK proxies instead!

访问网页,可能看到以下输出:

Around增强:执行方法之后,模拟结束事物
After增强:模拟方法结束后的释放资源
After增强:被织入增强处理的目标方法为:welcome
After增强:目标方法的参数为:[除了Around其他的都是是不可以修改目标方法的参数的]
After增强:被织入增强处理的目标对象为com.wanyu.fams.controller.AdminController@ef6be2
AfterReturning增强:获取目标方法的返回值:admin/welcome
AfterReturning增强:模拟日志功能
AfterReturning增强:获织入增强的目标方法为:welcome
AfterReturning增强:目标方法的参数为:[除了Around其他的都是是不可以修改目标方法的参数的]
AfterReturning增强:被织入增强处理的目标对象为:com.wanyu.fams.controller.AdminController@ef6be2

织入时机

从控制台输出来看,它是在 Filter init 之后,

在拦截器注册之前织入的。

实现AOP的切面主要有以下几个要素:

  • 使用@Aspect注解将一个java类定义为切面类
  • 使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
  • 根据需要在切入点不同位置的切入内容,5种类型的通知 
    • 使用@Before在切入点开始处切入内容
    • 使用@After在切入点结尾处切入内容
    • 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
    • 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
    • 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑


目录
相关文章
|
3月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
212 1
|
7天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
36 8
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
141 62
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
85 5
|
2月前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
88 8
|
2月前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
65 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
148 2
|
2月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
77 5
|
2月前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
59 1
|
2月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
59 0