03_03_初识SpringAOP和应用

一、SpringAOP的初识与原理

1、概述

  • AOP:面向切面编程
  • OOP:面向对象编程
  • 面相切面编程:是基于OOP基础之上的新编程思想,OOP面向的主要是对象是类,而AOP面向的主要对象是切面,它在处理日志、安全管理、事务管理、权限控制等方面具有非常重要的作用。是一种非侵入式(不改变原来的代码)的代码增强的开发工具,可以减
    少重复代码的编写,降低模块间的耦合度,并有利于提高程序的可拓展性可维护性
  • AOP是Spring中的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC进行补充。
  • Spring AOP 是基于动态代理实现的,如果被代理的对象已经实现了某个接口,则可以使用JDK动态代理(核心是invocationHandler接口和Proxy类)的方式来创建代理对象;如果被代理对象没有实现某个接口,可以使用Cglib(核心是MethodInterceptor接口和Enhancer类),基于继承的方式,生成一个被代理对象的子类作为代理;
  • 简而言之,在程序运行期间,在不修改原有代码的情况下,增强跟主业务没有关系的公共功能代码到之前写好的方法中的指定位置, 这种编程的方式叫AOP;

2、代理模式

  • AOP的底层是通过代理模式来实现。
  • 代理模式是一种设计模式,其主要目的是为其他对象提供一种代理,以控制对该对象的访问。这意味着用户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  • 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
  • 总的来说,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能。
  • 代理模型分为静态代理和动态代理(JDK动态代理CGLB动态代理)两种。

2.1 静态代理

  • 静态代理就是需要手动去为每一个被代理对象去创建一个代理类。
  • 例如,有一个游戏代练的示例:
    /**
     * 游戏接口(公共接口)
     */
    public interface IGamePlayer {
    
        //登录游戏
        void start();
    
        //玩游戏
        void play();
    }
    
    /**
     * 菜鸟游戏玩家(目标对象-被代理对象)
     */
    public class GamePlayer implements IGamePlayer {
    
    	//玩家名称
        private String name;
    	//构造赋值
        public GamePlayer(String name){
            this.name = name;
        }
    
        @Override
        public void start() {
            System.out.println("正在登录游戏........");
            System.out.println(name + ":开始了游戏");
        }
    
        @Override
        public void play() {
            System.out.println(name + "技术太low,游戏失败!");
        }
    }
    
        @Test
        public void testProxy(){
            GamePlayer player = new GamePlayer("菜鸟");
            player.start();
            player.play();
        }
    

菜鸟游戏把把输

  • 现在,因为菜鸟玩家的技术能力太低,玩游戏把把都输,他需要一个代练帮他玩游戏提升等级,我们可以帮他创建一个代理对象帮他代玩。
    /**
     * 代练游戏玩家(静态代理类)
     */
    public class ProxyGamePlayer implements IGamePlayer{
        //代练
        private String proxyName;
        //菜鸟玩家
        private GamePlayer gamePlayer;
    
        public ProxyGamePlayer(String name){
            this.proxyName = name;
            this.gamePlayer = new GamePlayer(name);
        }
    
        @Override
        public void start() {
            //代练以菜鸟的身份去玩游戏
            System.out.println("拿到"+proxyName+"的游戏账号密码");
            gamePlayer.start();
        }
    
        @Override
        public void play() {
            //代练帮菜鸟玩游戏
            System.out.println("代练技术太强,赢得了游戏!");
        }
    }
    
     @Test
        public void testProxy(){
            IGamePlayer gamePlayer = new ProxyGamePlayer("菜鸟");
            gamePlayer.start();
            gamePlayer.play();
        }
    

代练赢

  • 静态代理的弊端
    • 需要为每一个被代理的类创建一个代理类,虽然这种方式可以实现,但是成本太高。

2.2 动态代理

2.2.1 JDK动态代理
  • 核心关键

    • 一个类:Proxy
      • 概述:Proxy是所有代理类的基类;
      • 作用:创建代理对象,newProxyInstance();
    • 一个接口:InvocationHandler
      • 概述:实现动态织入效果的关键接口;
      • 作用:通过反射机制,执行invoke()方法来实现动态织入的效果;
  • 还是以游戏代练为例,实现Jdk动态代理:

    • 创建一个为被代理对象(目标对象)创建代理类的工具类
    public class GamePlayerProxy {
    
        /**
         * 被代理对象(目标对象)
         */
        private Object targetObj;
    
    
        /**
         * 有参构造,避免目标对象为null
         * @param targetObj
         */
        public GamePlayerProxy(Object targetObj){
            this.targetObj = targetObj;
        }
    
        /**
         * 获取代理类对象
         */
        public Object getProxyObject(){
            Object proxyObject = null;
    
            /*
             *  ClassLoader loader 类加载器,通常指被代理类的接口的类加载器
             *  Class<?> [] interfaces  类型,通常指被代理类的接口的类型
             *  InvacationHandler handler 委托执行的代理类,具体功能增强的逻辑在这里实现
             */
    
            ClassLoader loader = targetObj.getClass().getClassLoader();
            Class<?>[] interfaces = targetObj.getClass().getInterfaces();
    
            proxyObject = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
    		 //重写invoke()实现动态织入效果
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                    Object invoke = "";
    
                    if (method.getName().equals("play")){
                        //逻辑替换
                        System.out.println("代练技术太强,已成功帮菜鸟赢得了游戏!");
                    }else {
                        //逻辑增强
                        //执行被代理的方法
                        /*
                         * Object targetObj 被代理的对象
                         * Object... args 被代理的方法的参数
                         */
                        invoke = method.invoke(targetObj, args);
                    }
                    return invoke;
                }
            });
    
            //返回创建好的代理类对象
            return proxyObject;
        }
    
    • 使用代理类对象来代玩游戏:
      	@Test
        public void testJdkProxy(){
            //被代理(目标)对象
            IGamePlayer gamePlayer = new GamePlayer("菜鸟");
            //获取代理对象(代理对象不能转换为目标对象)
            GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
            IGamePlayer player = (IGamePlayer) gamePlayerProxy.getProxyObject();
            //代理玩游戏
            player.start();
            player.play();
        }
    

    jdk动态代理实现

2.2.2 CGLB动态代理
  • 后期补充…

二、SpringAOP的应用

  • 通过上述的例子,已经实现了代打游戏的目的,但是这种动态代理的实现方式调用的是JDK的基本实现,如果需要被代理的目标对象没有实现任何接口,那么是无法为它创建代理对象的,这也是致命的缺陷。而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

1、AOP的核心概念及术语

概念图

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。
  • 连接点(Join point): 在程序执行过程中某个特定的点。在Spring AOP中,一个连接点总
    是代表一个方法的执行。
  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。
  • 切点(Pointcut): 匹配连接点的断言。
  • 引入(Introduction): 声明额外的方法或者某个类型的字段。
  • 目标对象(Target object): 被一个或者多个切面所通知的对象。
  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象的过程。这个过程可以在编译时、类加载时、运行时完成。

2、AOP的通知类型

  • 前置通知
    • 语法:@Before(value=“execution()”)
    • 执行时机:在切入点表达式中指定的方法执行之前执行(无论是否发生异常,都会执行);
  • 后置通知
    • 语法:@After(value=“execution()”)
    • 执行时机:在切入点表达式中指定的方法执行之后执行(无论是否发生异常,都会执行);
  • 返回通知
    • 语法:@AfterReturning(pointcut = “execution()”,returning = “result”)
    • 执行时机:在切入点表达式中指定的方法返回结果时执行(如果发生异常,则不执行);
  • 异常通知
    • 语法:@AfterThrowing(pointcut = “execution()”,throwing = “e”)
    • 执行时机:在切入点表达式中指定的方法发生异常时执行;
  • 环绕通知
    • 语法:@Around(value = “pointCut()”)
    • 作用:环绕通知可以对前置通知、后置通知、返回通知、异常通知进行整合使用;

3、AOP的应用场景

  • 日志管理
  • 权限认证
  • 安全检查
  • 事务控制

4、AOP的相关配置

4.1 添加pom依赖

 	<!--Aop的AspectJ框架的jar包 -->
     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
         <version>1.9.5</version>
     </dependency>
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-aspects</artifactId>
         <version>5.3.28</version>
     </dependency>

4.2 将目标类加入IOC容器

/**
* 计算器接口(公共接口)
*/
public interface CalculatorService {

    int add(int a,int b);
    int sub(int a,int b);
    int mul(int a,int b);
    int div(int a,int b);

}
/**
*计算器实现类对象(目标对象)
*/
@Service
public class CalculatorServiceImpl implements CalculatorService {

    @Override
    public int add(int a, int b) {
        System.out.println("正在执行加法......");
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("正在执行减法......");
        return a-b;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println("正在执行乘法......");
        return a*b;
    }

    @Override
    public int div(int a, int b) {
        System.out.println("正在执行除法......");
        return a/b;
    }
}

4.3 声明切面类并加入IOC容器

@Component   //标识该类是一个组件类(保证这个切面在IOC容器中)
@Aspect      //标识该类是一个切面
public class LogUtil {

    //可以采用引用切点的方式,让其他通知共同使用
    @Pointcut(value = "execution(* org.example.mvc.impl.*.*(..))")
    public void pointCut(){}

    //前置通知
    @Before(value = "execution(public int org.example.mvc..CalculatorServiceImpl.*(int, int))")
    public void beforeMethod(JoinPoint joinPoint){
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("【前置通知】计算器  "+methodName+"  方法,执行参数为:"+ Arrays.toString(args));
    }

    //后置通知
//    @After("pointCut() && @annotation(logger)")
    @After(value = "pointCut()")
    public  void afterMethod(JoinPoint joinPoint){
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        System.out.println("【后置通知】计算器  "+methodName+" 方法,执行完毕");
    }

    //后置返回通知
    @AfterReturning(pointcut = "pointCut()",returning = "result")
    //注意:returning属性中的返回结果名称要与入参中的参数名一致
    public void afterReturn(JoinPoint joinPoint,Object result){
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        System.out.println("【后置返回通知】计算器 "+methodName+" 方法,执行结果为:"+ result);
    }

    //后置异常通知
    @AfterThrowing(pointcut = "pointCut()",throwing = "ex")
    //注意:throwing属性中的异常名称要与入参中的异常参数名一致
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        StringWriter stringWriter = new StringWriter();
        ex.printStackTrace(new PrintWriter(stringWriter,true));
        System.out.println("【后置异常通知】计算器 "+methodName+" 方法,执行时出现异常:" + stringWriter.getBuffer().toString());
    }

    //环绕通知
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result = null;
        try {
            //前置通知
            System.out.println("【环绕-前置通知】计算器 "+methodName+" 方法,执行参数为:"+ Arrays.toString(args));

            //触发目标对象中的目标方法
            result = joinPoint.proceed();

            //返回通知
            System.out.println("【环绕-返回通知】计算器 "+methodName+" 方法,执行结果为:"+ result);

        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            System.out.println("【环绕-异常通知】计算器 "+methodName+" 方法,执行时出现异常:" + throwable.getMessage());

        } finally {
            //后置通知
            System.out.println("【环绕-后置通知】计算器 "+methodName+" 方法,执行完毕");
        }
        return result;
      }
    }

切点表达式

  • 切点表达式语法:

    • execution(访问修饰符 方法返回值类型 包名.类名.方法名(参数...))
      • (1)访问修饰符:可写可不写;
      • (2)方法返回值类型:如果是JDK自带的类型,则直接可以用类型名,例如(Int)。如果不是,则需要写上自定义类型的全局限定名,如果返回值的类型不同,则可以用通用符 * 代表任何类型;
      • (3)包名:可以写具体的包名,也可以用通用符*占位代表任何包名,..代表子孙包。例如:cn.、cn.trs.、cn.trs.service…*
      • (4)类的全局限定名:可以写具体的类名,也可以使用通配符*代表任何类的名字;
      • (5)方法名:可以写具体的方法名,也可以使用通配符*代表任何方法的名字,也可以模糊匹配 *Add => userAdd()、roleAdd()
      • (6)参数:如果是JDK自带类型,可以不用写类型的全局限定名,否则,需要写上自定义类型的全局限定名。如果需要匹配任意参数可以写:..代替
  • 合并切点表达式:

    • 可以使用 &&|| !等符号进行合并操作,也可以通过名字来指向切点表达式。
     //&&:两个表达式同时
    execution( public int cn.trs.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
    //||:任意满足一个表达式即可
    execution( public int cn.trs.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
    //!:只要不是这个位置都可以进行切入
    //&&:两个表达式同时
    execution( public int cn.trs.inter.MyCalculator.*(..))
    

4.4 开启组件扫描和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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

        <!--开启组件扫描-->
        <context:component-scan base-package="org.example"></context:component-scan>
        <!--开启AspectJ的注解对AOP的支持-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 在Spring容器中,如果有接口,那么会使用jdk自带的动态代理,如果没有接口,那么会使用cglib的动态代理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/676284.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第二十六章CSS3续~

3.CSS3渐变属性 CSS3渐变(gradients)可以在两个或多个指定的颜色之间显示平稳的过渡。 以前&#xff0c;我们必须使用图像来实现这些效果。但是&#xff0c;通过使用CSS3渐变(gradients)&#xff0c;可以减少下载的事件和宽带的使用。由于渐变(gradient)是由浏览器生成的&…

MyBatis学习(二)--MyBatis获取参数值的两种方式

1、搭建新的module:mybatis_parameter MyBatis获取参数值的两种方式&#xff1a;${}和#{} ${}的本质就是字符串拼接&#xff0c;采用sql拼接&#xff0c;无法防止sql注入 #{}的本质就是占位符赋值 &#xff0c;采用预编译 防止sql注入 不同参数使用案例 2、单个字面量类型…

深度学习-06-手动进行反向传播

深度学习-06-手动进行反向传播 本文是《深度学习入门2-自製框架》 的学习笔记&#xff0c;记录自己学习心得&#xff0c;以及对重点知识的理解。如果内容对你有帮助&#xff0c;请支持正版&#xff0c;去购买正版书籍&#xff0c;支持正版书籍不仅是尊重作者的辛勤劳动&#xf…

O2O : Finetuning Offline World Models in the Real World

CoRL 2023 Oral paper code Intro 算法基于TD-MPC&#xff0c;利用离线数据训练世界模型&#xff0c;然后在线融合基于集成Q的不确定性估计实现Planning。得到的在线数据将联合离线数据共同训练目标策略。 Method TD-MPC TD-MPC由五部分构成: 状态特征提取 z h θ ( s ) …

Amazon Q Developer 实战:从新代码生成到遗留代码优化(下)

简述 本文是使用 Amazon Q Developer 探索如何在 Visual Studio Code 集成编程环境&#xff08;IDE&#xff09;&#xff0c;从新代码生成到遗留代码优化的续集。在上一篇博客《Amazon Q Developer 实战&#xff1a;从新代码生成到遗留代码优化&#xff08;上&#xff09;》中…

java基础篇(1)

JDK是什么?有哪些内容组成?JDK是Java开发工具包 JVM虚拟机: Java程序运行的地方 核心类库: Java已经写好的东西&#xff0c;我们可以直接用开发工具: javac、java、jdb、jhat.. JRE是什么?有哪些内容组成? JRE是Java运行环境 JVM、核心类库、运行工具 JDK&#xff0c;JRE&…

Linux网络编程:传输层协议|UDP|TCP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

Java1.8基于BS版 vue+ uniapp+ springboot专业团队自主研发的一套上门家政APP系统成品源码,支持商用(后台端介绍)

Java1.8基于BS版 vue uniapp springboot专业团队自主研发的一套上门家政APP系统成品源码&#xff0c;支持商用&#xff08;后台端介绍&#xff09; 家政服务后台端 家政服务后台端是一个专为家政服务行业设计的管理系统&#xff0c;用于处理业务运营、用户端管理、师傅端调度、…

Spring boot 随笔 1 DatasourceInitializer

0. 为啥感觉升级了 win11 之后&#xff0c;电脑像是刚买回来的&#xff0c;很快 这篇加餐完全是一个意外&#xff1a;时隔两年半&#xff0c;再看 Springboot-quartz-starter 集成实现的时候&#xff0c;不知道为啥我的h2 在应用启动的时候&#xff0c;不能自动创建quartz相关…

FL Studio怎么给钢琴加延音 FL Studio怎么用钢琴做伴奏

在使用钢琴音色进行音乐创作的时候&#xff0c;可以对钢琴进行延音处理&#xff0c;这样处理的音色给人的感觉会更加的饱满丰富&#xff0c;同时&#xff0c;给钢琴加了延音之后&#xff0c;钢琴的声音时值也会相应的变长&#xff0c;听起来更加的柔和。今天就和大家讲一讲&…

STM32使用HAL库UART接收不定长数据-1

使用STM32的HAL库实现UART串口不定长数据的接收 使用STM32的UART接收数据的时候&#xff0c;经常会遇到接收长度不固定的数据&#xff0c;比如一帧数据可能是10个字节&#xff0c;也可能是12个字节。这种数据称为不定长数据。 现有的很多通信协议是不定长的&#xff0c;比如mo…

vue3_组件间通信方式

目录 一、父子通信 1.父传子&#xff08; defineProps&#xff09; 2.父传子&#xff08;useAttrs&#xff09; 3.子传父&#xff08;ref&#xff0c;defineExpose &#xff09; 4.子传父&#xff08;defineEmits&#xff09; 5.子传父&#xff08;v-model&#xff09; …

数据库 mysql 的彻底卸载

MySQL卸载步骤如下&#xff1a; &#xff08;1&#xff09;按 winr 快捷键&#xff0c;在弹出的窗口输入 services.msc&#xff0c;打开服务列表。 &#xff08;2&#xff09;在服务列表中&#xff0c; 找到 mysql 开头的所有服务&#xff0c; 右键停止&#xff0c;终止对应的…

【问题随记】tightvnc 连接后灰屏

问题描述 刚刚入手了官方发的 OrangePi AI Pro&#xff0c;想用 tight vnc 来连接开发板&#xff0c;就不用连接屏幕那么麻烦了。结果连接后&#xff0c;没能显示 OrangePi AI Pro 桌面。 问题解决 看一下现有的桌面环境。 apt list --installed | grep desktop从中可以看到…

游戏找不到d3dcompiler43.dll怎么办,分享5种有效的解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是找不到某个文件。其中&#xff0c;找不到d3dcompiler43.dll是一个常见的问题。这个问题通常出现在运行某些游戏或应用程序时&#xff0c;由于缺少了d3dcompiler43.dll文件&#xff0c;导致程…

【PTA】7-3 拯救007(C++)代码实现 易错点反思

题目见下: 输入样例 14 20 25 -15 -25 28 8 49 29 15 -35 -2 5 28 27 -29 -8 -28 -20 -35 -25 -20 -13 29 -30 15 -35 40 12 12 //输入上述数据后输出“Yes” AC代码如下: #include<bits/stdc++.h> using namespace std; #define sz 100 typedef struct node{int …

基于javacv ffmpeg 使用原生ffmpeg命令

基于javacv ffmpeg 使用原生ffmpeg命令 1. ffmpeg2. ffprobe 相关阅读&#xff1a; javacv ffmpeg使用笔记 测试过程中&#xff0c;发现ffmpeg-6.0-1.5.9-linux-x86_64.jar 存在问题&#xff08;ffmpeg原生命令执行失败&#xff09;&#xff0c;降级到ffmpeg-5.1.2-1.5.8-linux…

Mixly 开启WIFI AP UDP收发数据

一、开发环境 软件&#xff1a;Mixly 2.0在线版 硬件&#xff1a;ESP32-C3&#xff08;立创实战派&#xff09; 固件&#xff1a;ESP32C3 Generic(UART) 测试工工具&#xff1a;NetAssist V5.0.1 二、实现功能 ESP32开启WIFI AP&#xff0c;打印接入点IP地址&#xff0c;允许…

直播预告|手把手教你玩转 Milvus Lite !

Milvus Lite&#xff08;https://milvus.io/docs/milvus_lite.md&#xff09;是一个轻量级向量数据库&#xff0c;支持本地运行&#xff0c;可用于搭建 Python 应用&#xff0c;由 Zilliz 基于全球最受欢迎的开源向量数据库 Milvus&#xff08;https://milvus.io/intro&#xf…

Pandas读取文本文件为多列

要使用Pandas将文本文件读取为多列数据&#xff0c;你可以使用pandas.read_csv()函数&#xff0c;并通过指定适当的分隔符来确保正确解析文件中的数据并将其分隔到多个列中。 假设你有一个以逗号分隔的文本文件&#xff08;CSV格式&#xff09;&#xff0c;每一行包含多个值&a…