AOP 编程

news2025/1/20 1:44:23

目录

​编辑一、AOP 编程

1、AOP 概念

2、AOP 编程的开发步骤

3、切面的名词解释

二、AOP 的底层实现原理

1、核心问题

2、动态代理类的创建

(1)JDK 的动态代理创建

(2)CGlib 的动态代理

(3)总结

3、Spring 工厂如何加工原始对象

三、基于注解的 AOP 编程

1、基于注解的 AOP 编程的开发步骤

2、细节

(1)切入点复用

(2)动态代理的创建方式

四、AOP 开发中的坑

五、AOP 阶段知识总结


一、AOP 编程

1、AOP 概念

AOP (Aspect Oriented Programing)面向切面编程

以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建

面向切面编程 = Spring 动态代理开发

切面 = 切入点 + 额外功能

OOP (Object Oriented Programing)面向对象编程

以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建 

POP (Producer Oriented Programing)面向过程编程

以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建

AOP 的概念:本质就是 Spring 动态代理开发,通过代理类为原始类(目标类)增加额外功能,利于原始类的维护

注意:AOP 编程不可能代理 OOP 编程


2、AOP 编程的开发步骤

AOP 本质上就是 Spring 的动态代理开发,那么它的开发步骤和 Spring 的动态代理开发是完全一样的:

1、原始对象

2、额外功能(MethodInterceptor)

3、切入点

4、组装切面(额外功能 + 切入点)


3、切面的名词解释

切面 = 切入点 + 额外功能

为什么把这两个的组合,叫做切面呢?

在几何学上:面 = 点 + 相同的性质

切面也是由 点 来构成的,它们所具有的相同性质,就是 额外功能


二、AOP 的底层实现原理

1、核心问题

1、AOP 如何创建动态代理类?

前面我们学过,所谓的动态代理类,是依附于动态字节码技术,那么动态字节码技术到底是怎么通过编码,让我们把动态代理创建出来的呢?

2、Spring 工厂如何加工如何加工创建代理对象

Spring 是如何实现 通过原始对象的 id 值,最终获得的是代理对象 的呢?


2、动态代理类的创建

对于 Spring 来讲,在动态代理类的创建过程中有两种方式:

1、JDK 的动态代理创建

2、CGlib 的动态代理

(1)JDK 的动态代理创建

Proxy.newProxyInstance 方法参数详解 

类加载器的作用:

1、通过类加载器把对应类的字节码文件加载到 JVM 中

2、通过类加载器,创建类的 Class 对象,进而创建这个类的对象

比如:如果我们想创建一个 User 类的  user 对象

那么我们首先先要通过类加载器创建一个 User 类的 Class 对象,进而才可以通过 new User() 的方法创建 user 对象

类加载器的运行过程:

假设此时我们想创建 User 类的对象,那么第一步就得开发这个 User 类,也就是创建它的 .java 文件,之后我们会对它进行相应的编译,最后编译成 .class 文件,而 .class 文件里存放的就是它所对应的字节码文件

我们要想创建 User 类对应的对象,就得把 User 类对应的字节码加载到 JVM 虚拟机当中,这个加载实际上就是类加载器完成的(类加载器的第一个作用)

然后,我们先得创建 User 类的 Class 对象(类加载器的第二个作用), 然后我们就可以根据之前的知识,去通过 new 对象来创建 User 对象

如何获得 类加载器?

虚拟机会为每一个类的 .class 文件,自动分配与之对应的类加载器

动态代理:

动态代理,实际上也是在虚拟机当中去获得动态代理类,进而创建代理对象

但是动态代理类是没有源文件,没有字节码文件的,那么动态代理类是怎么获取这个类所对应的字节码来创建对象的呢?

动态代理,是通过动态字节码技术,来创建字节码的

我们之前学到的 Proxy.newProxyInstance (classloader , interfaces , invocationhandler) 就是动态字节码技术

生成了对应动态代理的字节码之后,就直接把字节码写在了  JVM 虚拟机里面

要想创建代理类的对象,就必须得先获得代理类的 class 对象,这个过程就需要类加载器的介入

但是此时没有 .class 文件,JVM 虚拟机就不会分配类加载器,但是我们又需要类加载器

所以我们可以借用一个 类加载器

所以这也是为什么我们在创建动态代理的时候,要指定 类加载器,这个类加载器是借用的,目的是完成动态代理类 Class 对象的创建

public class TestJDKProxy {
    public static void main(String[] args) {
        //1、创建原始对象
        UserService userService = new UserServiceImpl();

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("-------log---------");
                
                //原始对象的方法运行
                Object ret = method.invoke(userService,args);
                //          方法本身    方法属于哪个对象    方法的参数
                
                return ret;
            }
        };

        //2、JDK 创建动态代理
        UserService userService1Proxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);

        userService1Proxy.login("xiaoahei","123456");
        userService1Proxy.register(new User());
    }
}

(2)CGlib 的动态代理

我们先来看看 JDK 的动态代理:

首先,得提供我们所说的原始对象,而原始对象在 JDK 动态代理的过程中,我们必须得让它实现一个接口

当接口有了之后,我们得去实现这个接口

JDK 的动态代理中,原始对象和代理对象必须实现相同的接口

原因:

1、保证 代理类 和 原始类 方法一致,

2、代理类中可以提供新的实现:额外功能 + 对应原始方法


再来看看 CGlib 的动态代理:

假设此时有一个原始类,它没有实现任何的接口,我们想为这个没有实现任何接口的原始类,去创建它所对应的代理类,此时我们该怎么做呢?

CGlib 要求所创建的代理类,要去继承原始类

此时,也能让代理类和原始类有相同的原始方法,从而在 login 和 register 中加入额外功能

CGlib 创建动态代理的原理:通过父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时还可以在代理类中,提供新的实现(额外功能 + 原始方法)

C3Glib 的编码:

public class TestCglib extends UserService{
    public static void main(String[] args) {
        //1、创建原始对象
        UserService userService = new UserService();
        
        //2、通过 CGlib 的方法,创建动态代理对象

        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());//类加载器
        enhancer.setSuperclass(userService.getClass());//父类

        MethodInterceptor interceptor = new MethodInterceptor() {
            //等同于 invocationhandler 中的 invoke 方法
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("-----cglib log--------");
                Object ret = method.invoke(userService,args);
                
                return ret;
            }
        };
        
        enhancer.setCallback(interceptor);//额外功能
        
        UserService userService1Proxy = (UserService) enhancer.create();
        
        userService1Proxy.login("xiaohei","123456");
        userService1Proxy.register(new User());
        
    }
}

(3)总结

JDK 代理 依附 Proxy.newProxyInstance( ) ,通过接口创建代理的实现类

CGlib 代理 依附  Enhance ,通过继承父类创建的代理类


3、Spring 工厂如何加工原始对象

我们先来回顾一下 BeanPostProcessor 

在 Spring 创建一个对象的时候,比如:User ,那么创建完对象的时候,Spring 工厂可以通过 BeanPostProcessor 来对我们所创建的对象进行加工,加工完成之后,把最终加工好了的对象返回给调用者,调用者就享受到了 Spring 为我们加工的 User 对象

实际上,动态代理的创建,实际上也是通过 BeanPostProcessor  进行加工的

我们再来看看,动态代理结合了 BeanPostProcessor  之后,我们这个程序变成了什么样:

创建代理过程当中,Spring 通过 BeanPostProcessor  完成了对 UserService 这个原始对象的加工

Spring 通过 userService 获得到了代理对象,实际上这个过程中,就涉及到了对 UserServiceImpl 的一个加工过程,这个加工还是通过 BeanPostProcessor  来完成的

创建了 UserService 的原始对象,调用了 UserService 的构造方法,然后 Spring 把 userService 对象创建出来了,接下类对其进行初始化操作,再交给 after  方法进行加工,其中调用了 Proxy.newProxyInstance( ) 方法,加工成我们最终所需要的代理对象了

编码:

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("-------- new log --------");
                Object ret = method.invoke(bean,args);
                
                return ret;
            }
        };
        
        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
        
    }
}

三、基于注解的 AOP 编程

1、基于注解的 AOP 编程的开发步骤

既然是 AOP 编程,那么就应该遵从AOP 的开发步骤;

1、原始对象

2、额外功能

3、切入点

4、组装切面

我们可以把切面想象成一个类,所以在基于注解的 AOP 编程中,所对应的切面就是一个切面类

要想表达一个类是切面类,就要为其加上 @Aspect 注解

@Aspect
public class MyAspect {

    /*
    加上 @Around 就相当于 MethodInterceptor
    此时,around 方法就相当于 invoke 方法
    ProceedingJoinPoint joinPoint 就等同于 MethodInvocation invocation,代表的是原始方法
     */
    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("----aspect log----");
        Object ret = joinPoint.proceed();

        return ret;
    }
}

 

定义好了这个切面是切面类,最后用的一定是它的对象,所以后续我们还得在 Spring 的配置文件当中,通过 Spring 的工厂来创建这个类的对象

 <bean id="arround" class="aspect.MyAspect"/>

此时这个切面里面,既体现了额外功能,又体现了切入点

最后一个环节:此时我们要告诉 Spring ,我们现在要基于注解的形式,来进行 AOP 编程了,所以此时我们要增加一个新的标签:

<aop:aspectj-autoproxy />

2、细节

(1)切入点复用

    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("----aspect log----");
        Object ret = joinPoint.proceed();

        return ret;
    }
    
    @Around("execution(* login(..))")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("----aspect tx----");
        Object ret = joinPoint.proceed();

        return ret;
    }

虽然它可以达到我们想要的效果

在这个切面当中,实际上我们为我们的 login 方法加额外功能 的过程中,这个切入点表达式实际上会存在冗余的,冗余会有两个问题:

1、同样的东西,我们写了两次,会影响到开发效率

2、后续维护的过程中,两边都得变,所以不方便修改

切入点复用,就可以解决上述问题

所谓的切入点复用,就是把我们的切入点的配置,提取到一个独立的函数上

切入点复用:在切面类中,定义一个函数,该函数上面加上 @Pointcut 注解,定义切入点表达式,后面更加有利于切入点的复用 

 @Pointcut("execution(* login(..))")
    public void myPointCut(){};

    @Around(value = "myPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("----aspect log----");
        Object ret = joinPoint.proceed();

        return ret;
    }

    @Around(value = "myPointCut()")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("----aspect tx----");
        Object ret = joinPoint.proceed();

        return ret;
    }

(2)动态代理的创建方式

AOP 底层实现中有两种代理创建方式:

JDK  通过实现接口来做新的实现类的方式,来创建代理对象

CGlib 通过继承父类的方式,来做新的子类,来创建最终的代理对象的

默认情况下,AOP 编程底层应用的是 JDK 的动态代理创建方式

如果切换成 CGlib ,应该怎么办呢?

基于注解 AOP 开发

 <aop:aspectj-autoproxy  proxy-target-class="true"/>

proxy-target-class 默认情况下是 false(JDK 的动态代理),当我们改成 true 的时候,就可以更改成 CGlib 的方式了

基于传统的 AOP 开发:

    <aop:config proxy-target-class="true">
        <!--        所有的方法,都作为切入点,加入额外功能 -->
        <aop:pointcut id = "pc" expression = "execution(* proxy.UserServiceImpl.*(..))"/>

        <!--        组装:把切入点和额外功能进行整合 -->
        <aop:advisor advice-ref="arround" pointcut-ref = "pc"/>
    </aop:config>

四、AOP 开发中的坑

在 同一个业务类中 ,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能的方法(内部的方法,通过普通的方式调用,都调用的是原始方法),如果想让内层的方法也调用代理对象的方法,就要通过 ApplicationContextAware 来获得工厂,进而获得代理对象

    @Override
    public void login(String name, String password) {
        System.out.println("UserServiceImpl.login");
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");

        //调用的是原始对象的 login 方法,就只能完成核心功能
        //但是我们真正的设计目的是:想调用代理对象的 login 方法 ---> 额外功能

        this.login("xiaohei","123456");
    }

由于 Spring 工厂是重量级资源,所以一个应用中,我们只能创建一个工厂

因此此处我们不该再创建一个工厂,而是从 测试类 中获取到已经创建好了的工厂,直接使用即可

那么怎么拿到已经创建好了的工厂呢?

让当前类再实现 ApplicationContextAware 接口,并通过接口中的方法获取到 Spring 工厂 

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx ;
    
    @Override
    public void login(String name, String password) {
        System.out.println("UserServiceImpl.login");
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");

        //调用的是原始对象的 login 方法,就只能完成核心功能
        //但是我们真正的设计目的是:想调用代理对象的 login 方法 ---> 额外功能

        proxy.UserService userService = (proxy.UserService) ctx.getBean("userService");
        userService.login("xiaohei","123456");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
}

五、AOP 阶段知识总结

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[红明谷CTF 2021]write_shell %09绕过过滤空格 ``执行

目录 1.正常短标签 2.短标签配合内联执行 看看代码 <?php error_reporting(0); highlight_file(__FILE__); function check($input){if(preg_match("/| |_|php|;|~|\\^|\\|eval|{|}/i",$input)){ 过滤了 木马类型的东西// if(preg_match("/| |_||php/&quo…

设计模式5、原型模式 Prototype

解释说明&#xff1a;使用原型实例指定待创建对象的类型&#xff0c;并且通过复制这个原型阿里创建型的对象 UML 结构图&#xff1a; 抽象原型&#xff08;Prototype&#xff09;&#xff1a;规定了具体原型对象必须实现的clone()方法 具体原型&#xff08;ConcretePrototype&…

CVE-2023-5129 libwebp堆缓冲区溢出漏洞影响分析

漏洞简述 近日苹果、谷歌、Mozilla和微软等公司积极修复了libwebp组件中的缓冲区溢出漏洞&#xff0c;相关时间线如下&#xff1a; 9月7日&#xff0c;苹果发布紧急更新&#xff0c;修复了此前由多伦多大学公民实验室报告的iMessage 0-click 漏洞&#xff0c;漏洞被认为已经被…

爬取北京新发地当天货物信息并展示十五天价格变化(三)---获取物品十五天内的价格

。。。。。。。。。。。。。。。。。。。。。。 1.网页请求一下内容2.通过爬虫进行请求3.获取商品十五天详细数据并绘制折线图4.项目详细代码 1.网页请求一下内容 通过抓包我们发现一共七个参数 limit: 20 # 一页多少数据 current: …

自制代码编辑器:CASM Editor

哔哩哔哩演示视频&#xff1a;我使用python自制了一个代码编辑器——CASM Editor_哔哩哔哩_bilibili 源代码&#xff1a; import idlelib.colorizer as idc import idlelib.percolator as idp import os import sys import threading import time import tkinter as T_tk imp…

计算机视觉: 可控的高质量人体生成

背景 关于人体动作的生成范式目前主流的方向可以分为以下两种: Sequence based motion generation: 给定控制信号然后一次性生成连续的动作&#xff0c;能生成一些连续高阶语义的动作信号&#xff0c;因为其能看到整个动作信号。eg: MDM: Human Motion Diffusion Model, Teve…

机器学习(20)---神经网络详解

神经网络 一、神经网络概述1.1 神经元模型1.2 激活函数 二、感知机2.1 概述2.2 实现逻辑运算2.3 多层感知机 三、神经网络3.1 工作原理3.2 前向传播3.3 Tensorflow实战演示3.3.1 导入数据集查看3.3.2 数据预处理3.3.3 建立模型3.3.4 评估模型 四、反向传播五、例题5.1 题15.2 题…

【SQL】Mysql 时区设置解决--20230928

https://blog.csdn.net/qq_44392492/article/details/108717616 输入命令show variables like “%time_zone%”;&#xff08;注意分号结尾&#xff09;设置时区&#xff0c;输入 set global time_zone “8:00”; 回车,然后退出重启&#xff08;一定记得重启&#xff0c;不然查…

Mysql 本地计算机无法启动 mysql 服务 错误 1067:进程意外终止

有时候一段时间本地mysql不用&#xff0c;在连接本地数据库的时候&#xff0c;会报mysql无法连接出现错误提示10061错误&#xff0c; 这时候一般是本地mysql服务没有启动 去左下角搜“服务”&#xff0c;进入后选择Mysql&#xff0c;点击启动&#xff08;我的截图是已经启动好…

C#,数值计算——Ranfib的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Implements Knuths subtractive generator using only floating operations. See /// text for cautions. /// </summary> public class Ranfib { p…

通过茶叶酒水小程序商城的作用是什么?

茶叶酒水往往会在一起经营&#xff0c;同时又具备较强的送礼属性&#xff0c;需求度较高但经营商家同样不少&#xff0c;同行竞争激烈&#xff0c;加之同城生意有限、外地客户难以拓展、销售营销不足、品牌宣传效果差等痛点&#xff0c;传统酒水茶叶门店需要线上带来增长。 那…

大数据Flink(九十二):DML:集合操作

文章目录 DML:集合操作 DML:集合操作 集合操作支持 Batch\Streaming 任务。 UNION:将集合合并并且去重。

uni-app:js修改元素样式(宽度、外边距)

效果 代码 1、在<view>元素上添加一个ref属性&#xff0c;用于在JavaScript代码中获取对该元素的引用&#xff1a;<view ref"myView" id"mybox"></view> 2、获取元素引用 &#xff1a;const viewElement this.$refs.myView.$el; 3、修改…

【Flink】

事件驱动型应用 核心目标&#xff1a;数据流上的有状态计算 Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界或有界数据流进行有状态计算。 运行逻辑 状态 把流处理需要的额外数据保存成一个“状态”,然后针对这条数据进行处理,并且更新状态。这就是所谓的“…

mrctf2020_shellcode_revenge

mrctf2020_shellcode_revenge Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX disabled PIE: PIE enabled RWX: Has RWX segments64位&#xff0c;开了PIE和RELRO&#xff0c;看到RWX出来&#xff0c;就感觉是shellcode了…

如何使用PyInstaller打包Python应用(包含参数详解,spec文件详解,反编译和防止反编译)

文章目录 介绍PyInstaller安装PyInstaller参数及使用方法PyInstaller打包技巧和注意事项反编译和防止反编译介绍PyInstaller PyInstaller是一个强大的Python打包工具,它可以将Python程序打包成独立的可执行文件,方便在不同的操作系统上分发和运行。使用PyInstaller,你可以将…

react.js在visual code 下的hello World

想学习reacr.js &#xff0c;就开始做一个hello world。 我的环境是visual code &#xff0c;所以我找这个环境下的例子。参照&#xff1a; https://code.visualstudio.com/docs/nodejs/reactjs-tutorial 要学习react.js &#xff0c;还得先安装node.js&#xff0c;我在visual …

Mysql高级语句(进阶查询语句、数据库函数、连接查询)

Mysql高级语句&#xff08;进阶查询语句、MySQL数据库函数、连接查询 一、mysql查询语句1.1、 select ----显示表格中一个或数个字段的所有数据记录1.2、 distinct ----不显示重复的数据记录1.3、where ----有条件查询1.4、 and or ----且 或1.5 、in----显示已知的值的数据记录…

9_分类算法—决策树

文章目录 1 信息熵1.1 比特化&#xff08;Bits&#xff09;1.2 一般化的比特化&#xff08;Bits&#xff09;1.3 信息熵&#xff08;Entropy&#xff09;1.3.1 熵越大混乱程度越大 1.4 条件熵H&#xff08;YIX&#xff09; 2 决策树2.1 什么是决策树2.2 决策树构建过程&#xf…

MySQL 索引的作用、索引结构及执行流程介绍(索引篇 一)

索引介绍 MySQL索引&#xff08;index&#xff09;是一种用于加快数据库中数据搜索和查询的数据结构。它类似于书籍的目录&#xff0c;可以帮助数据库快速定位和访问特定数据&#xff0c;而无需扫描整个数据表。 索引的作用和缺点 1. 加快数据搜索&#xff1a;通过使用索引&…