AOP 编程

目录

​编辑一、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() {@Overridepublic 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 方法@Overridepublic 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {InvocationHandler handler = new InvocationHandler() {@Overridepublic 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 来获得工厂,进而获得代理对象

    @Overridepublic void login(String name, String password) {System.out.println("UserServiceImpl.login");}@Overridepublic 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 ;@Overridepublic void login(String name, String password) {System.out.println("UserServiceImpl.login");}@Overridepublic void register(User user) {System.out.println("UserServiceImpl.register");//调用的是原始对象的 login 方法,就只能完成核心功能//但是我们真正的设计目的是:想调用代理对象的 login 方法 ---> 额外功能proxy.UserService userService = (proxy.UserService) ctx.getBean("userService");userService.login("xiaohei","123456");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.ctx = applicationContext;}
}

五、AOP 阶段知识总结

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

支持笔记本电脑直插直充,TOWE 65W智能快充PDU超级插座

电源插排在我们的生活中是必不可少的电器配件。今天&#xff0c;我们日常生活中所使用的电子设备越来越多&#xff0c;无论是手机、平板、笔记本电脑还是各种家用电器&#xff0c;都需要电源来驱动。虽然相对于其他电器来说&#xff0c;插排结构比较简单&#xff0c;但现代家庭…

设计模式5、原型模式 Prototype

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

【我的创作纪念日】使用pix2pixgan实现barts2020数据集的处理(完整版本)

使用pix2pixgan &#xff08;pytorch)实现T1 -> T2的基本代码 使用 https://github.com/eriklindernoren/PyTorch-GAN/ 这里面的pix2pixgan代码进行实现。 进去之后我们需要重新处理数据集&#xff0c;并且源代码里面先训练的生成器&#xff0c;后训练鉴别器。 一般情况下…

计算机图像处理-均值滤波

均值滤波 线性滤波器的原始数据与滤波结果是一种算术运算&#xff0c;即用加减乘除等运算实现&#xff0c;如均值滤波器&#xff08;模板内像素灰度值的平均值&#xff09;、高斯滤波器&#xff08;高斯加权平均值&#xff09;等。由于线性滤波器是算术运算&#xff0c;有固定…

Android Jetpack Compose之确定重组范围并优化重组

1.概述 Compose的重组是智能的&#xff0c;Composable函数在进行重组时会尽可能的跳过不必要的重组&#xff0c;只对需要变化的UI进行重组。那Compose是如何认定UI需要变化呢&#xff1f;或者换句话说Compose是如何确定重组的范围呢。如果重组随意的发生&#xff0c;那么对UI的…

LabVIEW开发实时自动化多物镜云计算全玻片成像装置

LabVIEW开发实时自动化多物镜云计算全玻片成像装置 数字病理学领域正在迅速发展&#xff0c;这主要是由于计算机处理能力、数据传输速度、软件创新和云存储解决方案方面的技术进步。因此&#xff0c;病理科室不仅将数字成像用于图像存档等简单任务&#xff0c;还用于远程病理学…

一些 dp 题

CYEZ 弹珠 key&#xff1a;思维题&#xff0c;完全背包。 先每个组分一个小球。等价于 n − k n-k n−k 拆分为任意个 [ 1 , k ] [1,k] [1,k] 的数的方案数。 本质是根据面积的转换&#xff0c;直观解释&#xff1a; 完全背包即可。代码。 No Bug No Game key&#xff1…

无人直播间

失败&#xff01;&#xff01; 采用 ffmpeg 技术进行推流 推流代码&#xff1a; 【需要将rtmp替换为你的推流地址】 ffmpeg -re -stream_loop -1 -i "rain.mp4" -c copy -f flv ""推流地址获取 以哔哩哔哩为例 点击下方链接 开播设置 - 个人中心 - …

Java编程技巧:文件上传、下载、预览

目录 1、上传文件1.1、代码1.2、postman测试截图 2、下载resources目录中的模板文件2.1、项目结构2.2、代码2.3、使用场景 3、预览文件3.1、项目结构3.2、代码3.3、使用场景 1、上传文件 1.1、代码 PostMapping("/uploadFile") public String uploadFile(Multipart…

麒麟信安服务器操作系统V3.5.2重磅发布!

9月25日&#xff0c;麒麟信安基于openEuler 22.03 LTS SP1版本的商业发行版——麒麟信安服务器操作系统V3.5.2正式发布。 麒麟信安服务器操作系统V3定位于电力、金融、政务、能源、国防、工业等领域信息系统建设&#xff0c;以安全、稳定、高效为突破点&#xff0c;满足重要行…

试图一文彻底讲清 “精准测试”

在软件测试中&#xff0c;我们常常碰到两个基本问题&#xff08;困难&#xff09;&#xff1a; 很难保障无漏测&#xff1a;我们做了大量测试&#xff0c;但不清楚测得怎样&#xff0c;对软件上线后会不会出问题&#xff0c;没有信心&#xff1b; 选择待执行的测试用例&#…

阿里云七代云服务器实例、倚天云服务器及通用算力型和经济型实例规格介绍

在目前阿里云的云服务器产品中&#xff0c;既有五代六代实例规格&#xff0c;也有七代和八代倚天云服务器&#xff0c;同时还有通用算力型及经济型这些刚推出不久的新品云服务器实例&#xff0c;其中第五代实例规格目前不在是主推的实例规格了&#xff0c;现在主售的实例规格是…

MySQL架构 InnoDB存储引擎

1. 什么是Mysql&#xff1f; 我们在开发的时候&#xff0c;我们都需要对业务数据进行存储&#xff0c;这个时候&#xff0c;你们就会用到MySQL、Oracal等数据库。 MySQL它是一个关系型数据库&#xff0c;这种关系型数据库就有Oracal、 MySQL&#xff0c;以及最近很火的PgSQL等。…

Springcloud实战之自研分布式id生成器

一&#xff0c;背景 日常开发中&#xff0c;我们需要对系统中的各种数据使用 ID 唯一表示&#xff0c;比如用户 ID 对应且仅对应一个人&#xff0c;商品 ID 对应且仅对应一件商品&#xff0c;订单 ID 对应且仅对应 一个订单。我们现实生活中也有各种 ID &#xff0c;比如身…

分享78个Python源代码总有一个是你想要的

分享78个Python源代码总有一个是你想要的 源码下载链接&#xff1a;https://pan.baidu.com/s/1ZhXDsVuYsZpOUQIUjHU2ww?pwd8888 提取码&#xff1a;8888 下面是文件的名字。 12个python项目源码 Apache Superset数据探查与可视化平台v2.0.1 API Star工具箱v0.7.2 Archery…

Springcloud:二、Eureka介绍+上手(搭建EurekaServer注册中心+服务注册+服务拉取)

Eureka介绍 Eureka上手 搭建EurekaServer注册中心 在cloud-demo这个maven项目下创建eureka-server模块 引入依赖 在eureka-server模块的pom文件中新增如下代码 <dependencies><dependency><groupId>org.springframework.cloud</groupId><artif…

安卓:解决AndroidStudio导出Unity的Apk(APP)出现2个显示图标

用AndroidStudio打开该项目 实现只保留1个app图标 AndroidManifest.xml的改法如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android" package"com.fru…

uniapp iOS离线打包——原生工程配置

uniapp iOS离线打包&#xff0c;如何配置项目工程&#xff1f; 文章目录 uniapp iOS离线打包&#xff0c;如何配置项目工程&#xff1f;工程配置效果图DebugRelease 配置工程配置 Appkey应用图标模块及三方SDK配置未配置模块错误配置模块TIP: App iOS 离线打包 前提&#xff1a…

EasyX趣味化编程note2,绘制基本图形

创意化编程&#xff0c;让编程更有趣 今天介绍的仍为比较简单的效果&#xff0c;由浅入深来进行学习 介绍每个函数都会附上代码和运行结果&#xff0c;感兴趣的大家可以复制粘贴运行一下看看效果&#xff0c;也可以自己进行改动&#xff0c;非常好玩且加深印象。 上节课的知识…

Java 18的未来:新特性和编程实践

文章目录 引言新特性预览1. 基于值的类的进一步改进2. 模式匹配的增强3. 新的垃圾回收器4. 扩展的模块系统5. 更强大的异步编程 编程实践示例1&#xff1a;基于值的类示例2&#xff1a;模式匹配的增强示例3&#xff1a;新的垃圾回收器 结论 &#x1f389;欢迎来到Java学习路线专…