【JavaEE】AOP实现原理

概述

  • Spring AOP 是基于动态代理来实现AOP的, 此处主要介绍代理模式和Spring AOP的源码剖析

一.代理模式

  • 代理模式是一种常用的设计模式,它允许为其他对象提供代理,以控制对这个对象的访问。这种结构在不改变原始类的基础上,通过引入代理类来扩展功能,广泛应用于各种编程场景和框架中。

  • 使用代理前:
    在这里插入图片描述

  • 使用代理后:
    在这里插入图片描述

  • 在生活当中也有很多代理模式的例子:

    • 艺人经纪人: 广告商找艺人拍广告, 需要经过经纪人,由经纪人来和艺人进行沟通.
    • 房屋中介: 房屋进行租赁时, 卖方会把房屋授权给中介, 由中介来代理看房, 房屋咨询等服务.
    • 经销商: 厂商不直接对外销售产品, 由经销商负责代理销售.
    • 秘书/助理: 合作伙伴找老板谈合作, 需要先经过秘书/助理预约.
  • 代理模式的主要角色

    • Subject: 业务接口类. 可以是抽象类或者接口(不⼀定有)
    • RealSubject: 业务实现类. 具体的业务执行, 也就是被代理对象.
    • Proxy: 代理类. RealSubject的代理.

    比如房屋租赁
    Subject 就是提前定义了房东做的事情, 交给中介代理, 也是中介要做的事情
    RealSubject: 房东
    Proxy: 中介

  • 根据代理的创建时期, 代理模式分为静态代理动态代理.

    • 静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译, 在程序运行前代理类的.class 文件就已经存在了.
    • 动态代理: 在程序运行时, 运用反射机制动态创建而成
    • 更形象的描述就是就是好比你去买房, 静态代理没买房前就已经分配好销售了(可以是根据一些因素进行划分), 动态代理就是买房的时候随机给你发分配销售

二. 静态代理

  • 静态代理是指在程序运行前就已经存在代理类的字节码文件,由程序员手动编写或特定工具自动生成源代码实现的代理方式。

代码实现

我们通过代码来加深对静态代理的理解. 以房租租赁为例:

  1. 定义接口(定义房东要做的事情, 也是中介需要做的事情)
public interface houseSubject {void rentHouse();void saleHouse();
}
  1. 实现接口(房东出租房子)
public class RealHouseSubject implements houseSubject{@Overridepublic void rentHouse() {System.out.println("我是房东,我出租房子");}@Overridepublic void saleHouse() {System.out.println("我是房东,我出售房子");}
}
  1. 代理(中介, 帮房东出租房子)
public class HouseProxy implements houseSubject{private RealHouseSubject target;public HouseProxy(RealHouseSubject realHouseSubject) {this.target = realHouseSubject;}@Overridepublic void rentHouse() {//出租前System.out.println("我是中介,我开始代理");//出租房子target.rentHouse();//出租后System.out.println("我是中介,结束代理");}@Overridepublic void saleHouse() {//出售前System.out.println("我是中介,我开始代理");//出售房子target.saleHouse();//出售后System.out.println("我是中介,结束代理");}
}
  1. 使用静态代理
public class Main {public static void main(String[] args) {//创建代理类HouseProxy houseProxy = new HouseProxy(new RealHouseSubject());//通过代理类访问目标方法houseProxy.rentHouse();}
}
  • 运行结果:
    在这里插入图片描述

上面这个代理实现方式就是静态代理(仿佛啥也没干). 从上述程序可以看出, 虽然静态代理也完成了对目标对象的代理, 但是由于代码都写死了, 对目标对象的每个方法的增强都是手动完成的,非常不灵活. 所以日常开发几乎看不到静态代理的场景

接下来新增需求: 中介又新增了其他业务, 我们修改接口(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy). 同样的, 如果有新增接口(Subject)和业务实现类(RealSubject), 也需要对每一个业务实现类新增代理类(Proxy).

三.动态代理.

  • 动态代理是指我们不需要针对每个目标对象都单独创建一个代理对象, 而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现. 也就是说动态代理在程序运行时, 根据需要动态创建生成.

  • 在Java中实现动态代理的方式主要有两种,一种是JDK动态代理,一种是CGLib. 接下来就主要介绍这两种方式.

3.1 JDK动态代理.

  • JDK动态代理的实现步骤:
      1. 定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
      1. 自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法)并自定义一些处理逻辑.
      1. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
        interfaces,InvocationHandler h) 方法创建代理对象

JDK动态代理的代码实现

1. 实现 InvocationHandler 接口
public class JDKInvocationHandler implements InvocationHandler {private Object target;//目标对象public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("我是代理,开始代理");//通过反射调用目标函数Object result = method.invoke(target, args);System.out.println("我是代理,结束代理");return result;}
}
2. 创建一个代理对象并使用
public class Main {public static void main(String[] args) {/***public static Object newProxyInstance(ClassLoader loader,*                                           Class<?>[] interfaces,*                                           InvocationHandler h) {* 加载代理类的classloader* 要实现的接口,* 代理要做的事情,InvocationHandler这个接口*///真实的目标对象RealHouseSubject targer = new RealHouseSubject();//动态生成代理对象houseSubject proxy = (houseSubject)Proxy.newProxyInstance(targer.getClass().getClassLoader(),new Class[]{houseSubject.class},new JDKInvocationHandler(targer));proxy.rentHouse();proxy.saleHouse();

JDK动态代理过程中涉及方法的参数讲解

  1. InvocationHandler接口
    InvocationHandler 接口是Java动态代理的关键接口之一, 它定义了一个单一方法 invoke() , 用于处理被代理对象的方法调用.
public interface InvocationHandler {/*** 参数说明* proxy:代理对象* method:代理对象需要实现的⽅法,即其中需要重写的⽅法* args:method所对应⽅法的参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过实现 InvocationHandler 接口, 可以对被代理对象的方法进行功能增强.

  1. Proxy
    Proxy 类中使用频率最高的方法是: newProxyInstance() , 这个方法主要用来生成一个代理对象.
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{//...代码省略}
  • 这个方法一共有 3 个参数:
    • Loader: 类加载器, 用于加载代理对象.
    • interfaces : 被代理类实现的一些接口(这个参数的定义, 也决定了JDK动态代理只能代理实现了接口的一些类)
    • h : 实现了 InvocationHandler 接口的对象.

3.2 CGLib动态代理

  • CGLib动态代理的实现步骤:
    • 定义一个类(被代理类).
    • 自定义 MethodInterceptor 并重写 intercept 方法, intercept 用于增强目标
      方法,和 JDK 动态代理中的 invoke 方法类似.
    • 通过 Enhancer 类的 create()创建代理类.

CGLib动态代理的代码实现

1. 自定义 MethodInterceptor(方法拦截器)实现MethodInterceptor接口.
public class CGLibMethodInterceptor implements MethodInterceptor {private Object target;public CGLibMethodInterceptor(Object target) {this.target = target;}/*** 调用代理对象的方法*/@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("我是中介开始代理~~");Object result = method.invoke(target, args);//proxy.invokeSuper(obj, args);System.out.println("我是中介,结束代理~~");return result;}
}
2.创建代理类, 并使用
  • 目标对象,用来代理接口
//        目标对象 代理接口HouseSubject targer = new RealHouseSubject();houseSubject houseSubject = (houseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));houseSubject.rentHouse();houseSubject.saleHouse();
  • 目标对象,用来代理类.
		HouseSubject targer = new RealHouseSubject();
//目标对象 代理类RealHouseSubject realHouseSubject = (RealHouseSubject) Enhancer.create(targer.getClass(), new CGLibMethodInterceptor(targer));realHouseSubject.rentHouse();realHouseSubject.saleHouse();

CGLib动态代理过程中涉及方法的参数讲解.

  1. MethodInterceptor
    MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似, 它只定义了一个方法 intercept() , 用于增强目标方法.
public interface MethodInterceptor extends Callback {/*** 参数说明:* o: 被代理的对象* method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)* objects: ⽅法⼊参* methodProxy: ⽤于调⽤原始⽅法*/Object intercept(Object o,Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}
  1. Enhancer.create()
    Enhancer.create() 用来生成一个代理对象
public static Object create(Class type, Callback callback) {//...代码省略
}
  • 参数说明:
    • type: 被代理类的类型(类或接口)
    • callback: 自定义方法拦截器 MethodInterceptor

四.Spring AOP源码剖析

  • Spring AOP 主要基于两种方式实现的: JDK 及 CGLIB 的方式
  • Spring 源码过于复杂, 此处只摘出一些主要内容, 以了解为主.
    Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成. 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中
	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);}//创建代理对象ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);/*** 检查proxyTargetClass属性值,spring默认为false* proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理* 如果代理对象为类, 设置为true, 使⽤cglib代理*/if (proxyFactory.isProxyTargetClass()) {//是否有设置cglib代理if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {//设置proxyTargetClass为true,使⽤cglib代理for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {/*** 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理* 否则CGLIB代理(设置ProxyTargetClass为true )* 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其设为true*/if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = smartClassLoader.getOriginalClassLoader();}return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));}
  • 代理工厂有一个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置
    在这里插入图片描述
  • 可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置

注意:
Spring Boot 2.X开始, 默认使用CGLIB代理. 可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理. SpringBoot设置 @EnableAspectJAutoProxy 无效, 因为Spring Boot 默认使用AopAutoConfiguration进行装配

@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(DemoApplication.class, args);/*** HouseProxy houseProxy = context.getBean(HouseProxy.class);* 设置spring.aop.proxy-target-class=true cglib代理, 运⾏成功* 设置spring.aop.proxy-target-class=false jdk代理, 运⾏失败, 不能代理类* 因为 HouseProxy 是⼀个类, ⽽不是接⼝, 需要修改为* HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject")* */HouseProxy houseProxy = context.getBean(HouseProxy.class);//HouseSubject houseProxy = (HouseSubject) context.getBean("realHouseSubject");//正确运⾏System.out.println(houseProxy.getClass().toString());}}

使用context.getBean() 需要添加注解,使HouseProxy,RealHouseSubject被Spring管理测试AOP代理, 需要把这些类交给AOP管理(自定义注解或使用@Aspect)

我看点进去看代理工厂的代码

public class ProxyFactory extends ProxyCreatorSupport {//...代码省略//获取代理public Object getProxy(@Nullable ClassLoader classLoader) {//分两步 先createAopProxy,后getProxyreturn createAopProxy().getProxy(classLoader);}protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this);}//...代码省略
}

createAopProxy的实现在 DefaultAopProxyFactory中

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {//...代码省略@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {/*** 根据proxyTargetClass判断* 如果⽬标类是接⼝, 使⽤JDK动态代理* 否则使⽤cglib动态代理*/if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || 
hasNoUserSuppliedProxyInterfaces(config))) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy 
creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}//...代码省略
}

接下来就是创建代理了
JDK动态代理

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, 
Serializable {//...代码省略@Overridepublic Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + 
this.advised.getTargetSource());}return Proxy.newProxyInstance(determineClassLoader(classLoader), 
this.proxiedInterfaces, this);}//...代码省略
}

CGLIB动态代理

class CglibAopProxy implements AopProxy, Serializable {//...代码省略@Overridepublic Object getProxy(@Nullable ClassLoader classLoader) {//...代码省略// Configure CGLIB Enhancer...Enhancer enhancer = createEnhancer();// Generate the proxy class and create a proxy instance.return createProxyClassAndInstance(enhancer, callbacks);}//...代码省略}

五.总结

  1. 什么是AOP?
  2. Spring AOP的实现方式有哪些?
    • 基于注解
    • 基于XML
    • 基于代理
  3. Spring AOP的实现原理.?
  4. Spring 使用哪种代理方式?
  5. JDK和CGLib的区别?

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

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

相关文章

MongoDB教程(一):Linux系统安装mongoDB详细教程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、Ubuntu…

应急响应总结

应急响应 日志 windows IIS 6.0 及更早版本&#xff1a; C:\WINDOWS\system32\LogFiles\W3SVC[SiteID]\ IIS 7.0 及更高版本&#xff1a; C:\inetpub\logs\LogFiles\W3SVC[SiteID]\ Apache HTTP Server C:\Program Files (x86)\Apache Group\Apache2\logs\ 或者 C:\Prog…

STFT:解决音频-视频零样本学习 (ZSL) 中的挑战

传统的监督学习方法需要大量的标记训练实例来进行训练,视听零样本学习的任务是利用音频和视频模态对对象或场景进行分类&#xff0c;即使在没有可用标记数据的情况下。为了解决传统监督方法的限制&#xff0c;提出了广义零样本学习&#xff08;Generalized Zero-Shot Learning,…

Golang操作ES全系列(olivere curl操作es)

Golang操作ES全系列&#xff08;olivere & curl操作es&#xff09; &#x1f680;全部代码&#xff08;欢迎&#x1f44f;&#x1f3fb;star&#xff09;&#xff1a; https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-es 1 olivere 创建clie…

html表格账号密码备忘录:表格内容将通过JavaScript动态生成。点击查看密码10秒关闭

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>账号密码备忘录</title><style>body {background: #2c3e50;text-shadow: 1px 1px 1px #100000;}/* 首页样式开始 */.home_page {color: …

《Linux系统编程篇》Visual Studio Code配置下载,中文配置,连接远程ssh ——基础篇

引言 vscode绝对值得推荐&#xff0c;非常好用&#xff0c;如果你能体会其中的奥妙的话。 工欲善其事&#xff0c;必先利其器 ——孔子 文章目录 引言下载VS Code配置VS Code中文扩展连接服务器 连接服务器测试确定服务器的IP地址VS code 配置ssh信息选择连接到主机选择这个添…

韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)

1 面向对象 字符设备驱动程序抽象出一个 file_operations 结构体&#xff1b; 我们写的程序针对硬件部分抽象出 led_operations 结构体。 2 分层 上下分层&#xff0c;比如我们前面写的 LED 驱动程序就分为 2 层&#xff1a; ① 上层实现硬件无关的操作&#xff0c;比如注册…

防御第二次作业完成接口配置实验

一、实验括扑图 二、实验要求 1.防火墙向下使用子接口分别对应生产区和办公区 2.所有分区设备可以ping通网关 三、实验思路 1、配置各设备的IP地址 2、划分VLAN及VLAN的相关配置 3、配置路由及安全策略 四、实验步骤 1、配置PC跟Client还有server配置&#xff0…

【C++】初始化列表”存在的意义“和“与构造函数体内定义的区别“

构造函数是为了方便类的初始化而存在&#xff0c;而初始化时会遇到const成员变量、引用成员变量等&#xff0c;这些变量不允许函数内赋值&#xff0c;必须要在初始化时进行赋值&#xff0c;所以就有了初始化列表&#xff0c;初始化列表只能存在于类的构造函数中&#xff0c;用于…

Spring Boot快速上手

一&#xff0c;什么是spring 首先登陆Spring官网&#xff0c;看一下官网如何形容的&#xff0c; 可以看出Spring是为了使java程序更加快速&#xff0c;方便&#xff0c;安全所做出的java框架。 1.Spring Boot Spring Boot的诞生就是为了简化Spring的开发&#xff0c;也就是更…

gfast前端UI:基于Vue3与vue-next-admin适配手机、平板、pc 的后台开源模板

摘要 随着现代软件开发的高效化需求&#xff0c;一个能够快速适应不同设备、简化开发过程的前端模板变得至关重要。gfast前端UI&#xff0c;基于Vue3.x和vue-next-admin&#xff0c;致力于提供这样一个解决方案。本文将深入探讨gfast前端UI的技术栈、设计原则以及它如何适配手机…

【VS2019】安装下载库HtmlAgilityPack,可解析 HTML (图文详情)

目录 0.背景 1.环境 2.详细步骤 0.背景 项目需要&#xff0c;搭建WCF服务&#xff0c;需求是输入一个string类型字符串&#xff08;网页代码&#xff0c;如<html><body><p>Hello, <b>World</b>!</p></body></html>&#xf…

刷题之单词规律同构字符串(leetcode)

同构字符串 单词规律 两个都是映射关系&#xff0c;用两张哈希表记录互相映射就可以了 同构字符串&#xff1a; class Solution { public:bool isIsomorphic(string s, string t) {//用两张哈希表做映射if(s.size()!t.size()){return false;}unordered_map<char,char&…

清华计算几何-ConvexHull(凸包)-极点InTriangle/ToLeft Test

ConvexHull(凸包)的基本概念 给定一个点集, 求出最外围的点所形成的几何, 就是凸包。如下所示 凸包在计算几何是一个非常基础和核心的一个概念, 很多几何计算算法都围绕凸包展开。 极点和非极点 如上图所示, 蓝图圈圈住的点都是极端点, 极端点具备一个重要的特性: 极点(ext…

YOLOv10改进 | 特殊场景检测篇 | 单阶段盲真实图像去噪网络RIDNet辅助YOLOv10图像去噪(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是单阶段盲真实图像去噪网络RIDNet&#xff0c;RIDNet&#xff08;Real Image Denoising with Feature Attention&#xff09;是一个用于真实图像去噪的卷积神经网络&#xff08;CNN&#xff09;&#xff0c;旨在解决现有去噪方法在处理…

c# 容器变换

List<Tuple<int, double, bool>> 变为List<Tuple<int, bool>>集合 如果您有一个List<Tuple<int, double, bool>>并且您想要将其转换为一个List<Tuple<int, bool>>集合&#xff0c;忽略double值&#xff0c;您可以使用LINQ的S…

卷积神经网络-猫狗识别实战

课程来自bilibiliMomodel平台 全长只有两个小时&#xff0c;理论部分讲得很粗糙 1 人的视觉和计算机视觉 人的大脑&#xff1a;神经元细胞&#xff0c;轴突发送信号&#xff0c;树突接收信号&#xff0c;互相连接&#xff0c;连接的强度和状态会随着新的经历刺激而变化。 用…

server nat表和会话表的作用及NAT地址转换详细

本章节主要讲nat技术的基础 -会话表的建立也是看5元组 -状态检测技术的回包一样也看5元组&#xff0c;但是状态检测技术会看的除开5元组还有更多东西 老哥&#xff0c;你真的应该好好注意一个东西&#xff1a;我们的会话表只是为了后续包的转发&#xff0c;会话表是记录的首…

【机器学习】和【人工智能】在航空航天中的应用

作者主页: 知孤云出岫 目录 引言机器学习和人工智能在航空航天中的应用1. 预测性维护2. 飞行路径优化3. 自动驾驶飞行器 未来展望1. 增强人机协作2. 更智能的空中交通管理3. 高效的航空制造 结论参考文献 引言 随着科技的迅猛发展&#xff0c;机器学习和人工智能&#xff08;…

【python报错已解决】 “Invalid Array Index“

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法&#xff1a;2.1 方法一&#xff1a;检查索引范…