首页 > 其他分享 >Spring AOP

Spring AOP

时间:2025-01-14 09:58:25浏览次数:3  
标签:Spring System 切面 AOP println public out

AOP,面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存、权限控制、限流等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码降低模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。

AOP专业术语

  • 切面(Aspect):切面是增强切点的结合,增强和切点共同定义了切面的全部内容。 多个切面之间的执行顺序如何控制?首先要明确,在“进入”连接点的情况下,最高优先级的增强会先执行;在“退出”连接点的情况下,最高优先级的增强会最后执行。

    1. 通常使用@Order 注解直接定义切面顺序

    2. 实现Ordered 接口重写 getOrder 方法。Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。

  • 连接点(Join point):一般指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。当然,连接点也可能是类初始化、方法执行、方法调用、字段调用或处理异常等

  • 增强(或称为通知)(Advice):在AOP术语中,切面的工作被称为增强。知实际上是程序运行时要通过Spring AOP框架来触发的代码段。

    1. 前置增强(Before):在目标方法被调用之前调用增强功能;

    2. 后置增强(After):在目标方法完成之后调用增强,此时不会关心方法的输出是什么;

    3. 返回增强(After-returning ):在目标方法成功执行之后调用增强;

    4. 异常增强(After-throwing):在目标方法抛出异常后调用增强;

    5. 环绕增强(Around):增强包裹了被增强的方法,在被增强的方法调用之前和调用之后执行自定义的逻辑

  • 切点(Pointcut):切点的定义会匹配增强所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。以AspectJ举例,说白了就可以理解为是execution表达式

  • 引入(Introduction):引入允许我们向现有类添加新方法或属性。 在AOP中表示为干什么(引入什么)

  • 目标对象(Target Object): 被一个或者多个切面(aspect)所增强(advise)的对象。它通常是一个代理对象。

  • 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在AOP中表示为怎么实现的;织入分为编译期织入、类加载期织入、运行期织入;SpringAOP是在运行期织入

Spring的AOP实现原理

而Spring的AOP的实现就是通过动态代理实现的。

如果为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。

  • 如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

  • 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。CGLIB是通过继承的方式做的动态代理,因此CGLIB存在的束:类是final的,或是方法是final的,或是方法是private,或是静态方法,也就是无法被子类实现的方法都无法使用CGLIB实现代理。

AOP的配置方式

基于XML

Spring提供了使用"aop"命名空间来定义一个切面,我们来看个例子

  • 定义目标类

public class AopDemoServiceImpl {

    public void doMethod1() {
        System.out.println("AopDemoServiceImpl.doMethod1()");
    }

    public String doMethod2() {
        System.out.println("AopDemoServiceImpl.doMethod2()");
        return "hello world";
    }

    public String doMethod3() throws Exception {
        System.out.println("AopDemoServiceImpl.doMethod3()");
        throw new Exception("some exception");
    }
}
  • 定义切面类

public class LogAspect {

    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("-----------------------");
        System.out.println("环绕通知: 进入方法");
        Object o = pjp.proceed();
        System.out.println("环绕通知: 退出方法");
        return o;
    }

    public void doBefore() {
        System.out.println("前置通知");
    }

    public void doAfterReturning(String result) {
        System.out.println("后置通知, 返回值: " + result);
    }

    public void doAfterThrowing(Exception e) {
        System.out.println("异常通知, 异常: " + e.getMessage());
    }

    public void doAfter() {
        System.out.println("最终通知");
    }

}
  • XML配置AOP

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd
">

    <context:component-scan base-package="com.seven.springframeworkaopxml" />

    <aop:aspectj-autoproxy/>

    <!-- 目标类 -->
    <bean id="demoService" class="com.seven.springframeworkaopxml.service.AopDemoServiceImpl">
        <!-- configure properties of bean here as normal -->
    </bean>

    <!-- 切面 -->
    <bean id="logAspect" class="com.seven.springframeworkaopxml.aspect.LogAspect">
        <!-- configure properties of aspect here as normal -->
    </bean>

    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="logAspect">
            <!-- 配置切入点 -->
            <aop:pointcut id="pointCutMethod" expression="execution(* com.seven.springframeworkaopxml.service.*.*(..))"/>
            <!-- 环绕通知 -->
            <aop:around method="doAround" pointcut-ref="pointCutMethod"/>
            <!-- 前置通知 -->
            <aop:before method="doBefore" pointcut-ref="pointCutMethod"/>
            <!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
            <aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="result"/>
            <!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型-->
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/>
            <!-- 最终通知 -->
            <aop:after method="doAfter" pointcut-ref="pointCutMethod"/>
        </aop:aspect>
    </aop:config>

</beans>
  • 测试类

public static void main(String[] args) {
    // create and configure beans
    ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");

    // retrieve configured instance
    AopDemoServiceImpl service = context.getBean("demoService", AopDemoServiceImpl.class);

    // use configured instance
    service.doMethod1();
    service.doMethod2();
    try {
        service.doMethod3();
    } catch (Exception e) {
        // e.printStackTrace();
    }
}

 

基于JDK动态代理

基于JDK动态代理例子源码点这里

  • 定义接口

public interface IJdkProxyService {

    void doMethod1();

    String doMethod2();

    String doMethod3() throws Exception;
}
  • 实现类

@Service
public class JdkProxyDemoServiceImpl implements IJdkProxyService {

    @Override
    public void doMethod1() {
        System.out.println("JdkProxyServiceImpl.doMethod1()");
    }

    @Override
    public String doMethod2() {
        System.out.println("JdkProxyServiceImpl.doMethod2()");
        return "hello world";
    }

    @Override
    public String doMethod3() throws Exception {
        System.out.println("JdkProxyServiceImpl.doMethod3()");
        throw new Exception("some exception");
    }
}
  • 定义切面

@EnableAspectJAutoProxy
@Component
@Aspect
public class LogAspect {

    /**
     * define point cut.
     */
    @Pointcut("execution(* com.seven.springframeworkaopannojdk.service.*.*(..))")
    private void pointCutMethod() {
    }


    /**
     * 环绕通知.
     *
     * @param pjp pjp
     * @return obj
     * @throws Throwable exception
     */
    @Around("pointCutMethod()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("-----------------------");
        System.out.println("环绕通知: 进入方法");
        Object o = pjp.proceed();
        System.out.println("环绕通知: 退出方法");
        return o;
    }

    /**
     * 前置通知.
     */
    @Before("pointCutMethod()")
    public void doBefore() {
        System.out.println("前置通知");
    }


    /**
     * 后置通知.
     *
     * @param result return val
     */
    @AfterReturning(pointcut = "pointCutMethod()", returning = "result")
    public void doAfterReturning(String result) {
        System.out.println("后置通知, 返回值: " + result);
    }

    /**
     * 异常通知.
     *
     * @param e exception
     */
    @AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        System.out.println("异常通知, 异常: " + e.getMessage());
    }

    /**
     * 最终通知.
     */
    @After("pointCutMethod()")
    public void doAfter() {
        System.out.println("最终通知");
    }

}
  • APP启动

public class App {
    public static void main(String[] args) {
        // create and configure beans
        ApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springframeworkaopannojdk");

        // retrieve configured instance
        IJdkProxyService service = context.getBean(IJdkProxyService.class);

        // use configured instance
        service.doMethod1();
        service.doMethod2();
        try {
            service.doMethod3();
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }
}
非接口使用Cglib代理

基于Cglib代理例子源码点这里

  • 类定义

@Service
public class CglibProxyDemoServiceImpl {

    public void doMethod1() {
        System.out.println("CglibProxyDemoServiceImpl.doMethod1()");
    }

    public String doMethod2() {
        System.out.println("CglibProxyDemoServiceImpl.doMethod2()");
        return "hello world";
    }

    public String doMethod3() throws Exception {
        System.out.println("CglibProxyDemoServiceImpl.doMethod3()");
        throw new Exception("some exception");
    }
}
  • 切面定义

和上面相同

  • APP启动

public class App {
    public static void main(String[] args) {
        // create and configure beans
        ApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springframeworkaopannocglib");

        // cglib proxy demo
        CglibProxyDemoServiceImpl service = context.getBean(CglibProxyDemoServiceImpl.class);
        service.doMethod1();
        service.doMethod2();
        try {
            service.doMethod3();
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }
}

AOP的应用场景

日志记录

利用 AOP 方式记录日志,只需要在Controller 的方法上使用自定义@Log 日志注解,就可以将用户操作记录到数据库。

@Log(description = "新增用户")
@PostMapping(value = "/users")
public ResponseEntity create(@Validated @RequestBody User resources){
    checkLevel(resources);
    return new ResponseEntity(userService.create(resources),HttpStatus.CREATED);
}

AOP 切面类LogAspect用来拦截带有@Log 注解的方法并处理:

@Aspect
@Component
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    // 定义切点,拦截带有 @Log 注解的方法
    @Pointcut("@annotation(com.example.annotation.Log)") // 这里需要根据你的实际包名修改
    public void logPointcut() {
    }

    // 环绕通知,用于记录日志
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      //...
    }
}
限流

利用 AOP 方式对接口进行限流,只需要在Controller 的方法上使用自定义的@RateLimit 限流注解即可。

/**
 * 该接口 60 秒内最多只能访问 10 次,保存到 redis 的键名为 limit_test,
 */
@RateLimit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
public int test() {
     return ATOMIC_INTEGER.incrementAndGet();
}

AOP 切面类RateLimitAspect用来拦截带有@RateLimit 注解的方法并处理:

@Slf4j
@Aspect
public class RateLimitAspect {
      // 拦截所有带有 @RateLimit 注解的方法
      @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
      //...
    }
}

关于限流实现这里多说一句,这里并没有自己写 Redis Lua 限流脚本,而是利用 Redisson 中的RRateLimiter 来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。

权限控制

Spring Security 使用 AOP 进行方法拦截。在实际调用 update 方法之前,Spring 会检查当前用户的权限,只有用户权限满足对应的条件才能执行。

@Log(description = "修改菜单")
@PutMapping(value = "/menus")
// 用户拥有 `admin`、`menu:edit` 权限中的任意一个就能能访问`update`方法
@PreAuthorize("hasAnyRole('admin','menu:edit')")
public ResponseEntity update(@Validated @RequestBody Menu resources){
    //...
}

标签:Spring,System,切面,AOP,println,public,out
From: https://blog.csdn.net/qq_73376107/article/details/145123337

相关文章

  • springboot同城跑腿服务小程序源码毕设+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展和人们生活节奏的加快,同城跑腿服务作为一种便捷的生活方式,逐渐成为了现代都市生活中不可或缺的一部分。传统的跑腿服务往往......
  • springboot社团管理系统源码毕设+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高校教育的不断发展,学生社团作为校园文化的重要组成部分,日益受到广泛关注。学生社团不仅为学生提供了展示自我、锻炼能力的平台,还促进了学生之间......
  • springboot毕设 高铁站失物招领平台 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高铁网络的迅速扩展和旅客出行频率的增加,高铁站已成为人们日常生活中不可或缺的交通枢纽。然而,在繁忙的旅行过程中,旅客因疏忽大意或时间紧迫,时常......
  • springboot毕设 防返贫数字化辅助系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在脱贫攻坚战取得全面胜利后,如何有效防止脱贫人口返贫成为巩固拓展脱贫攻坚成果的重要课题。传统的防返贫手段多依赖于人工管理和纸质记录,存在信息更......
  • springboot毕设 飞机订票管理系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着全球航空业的快速发展,飞机已成为人们出行的重要交通工具之一。传统的飞机订票方式,如通过电话、实体售票点等,已难以满足现代人对便捷、高效服务的......
  • springboot毕设 广西美食宣传系统的设计与实现 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景广西,这片位于中国南疆的神奇土地,以其丰富的自然资源和独特的民族文化孕育了众多令人垂涎的美食。从桂林米粉的细腻滑爽,到北海海鲜的鲜美肥嫩,再到柳州......
  • springboot毕设 二手车交易网站 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着经济的持续发展和人民生活水平的不断提升,汽车已成为许多家庭的重要交通工具。然而,随着汽车保有量的快速增长,二手车市场也迎来了前所未有的发展机......
  • 基于SpringBoot的毕业生实习跟踪及数据可视化系统
    收藏关注不迷路!!......
  • 2025毕设springboot 高校毕业班校务管理系统论文+源码
    系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和高校教育改革的不断深化,高校管理正逐步向信息化、智能化方向迈进。高校毕业班作为学校教育的关键环节,其管理工作涉及学生信息、辅导员管理、就业招聘、学校活动组织、学生成绩及请假销假等多个方面,传统的人工......
  • Spring Cloud环境搭建
    目录开发环境安装JDK介绍安装MySQL安装服务介绍服务拆分服务拆分原则1.单⼀职责原则2.服务自治3.单向依赖案例拆分数据准备订单服务商品服务开发环境安装JDK介绍Oracle从JDK9开始每半年发布⼀个新版本,新版本发布后,⽼版本就不再进⾏维护.但是会有......