Spring Boot 学习第七天:动态代理机制与Spring AOP

1 概述

        在Java的世界中,实现AOP的主流方式是采用动态代理机制,这点对于Spring AOP也一样。代理机制的主要目的就是为其他对象提供一种dialing以控制对当前对象的访问,用于消除或缓解直接访问对象带来的问题。通过这种手段,一个对象就代表另一个对象的部分功能,我们创建包含当前对象的对象,以便向外界提供功能接口。本篇将关注目前主流的动态代理实现技术,并分析Spring AOP中的代理实现方式。

        在Spring中,采用的代理机制有两种,即JDK动态代理和CGLIB动态代理。为了介绍动态代理机制,引入一个具体的应用场景。考虑一个Account接口,它包含一个用于图片展示的open方法,代码如下:

public interface Account {void open();
}

        然后针对该接口有一个实现类RealAccount,其中的方法只用于模拟,不包括具体业务,代码如下:

public class RealAccount implements Account {private String name;public RealAccount(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic void open() {System.out.println("打开:" + name + ",账户");}
}

        现在,假设需要在执行RealAccount的open()方法的前后分别打印日志信息。接下来讨论如何分别基于JDK动态代理和CGLIB动态代理来实现这个目标。

2 JDK动态代理

        在JDK自带的动态代理中存在一个InvocationHandler接口,首先要做的就是提供一个该接口的实现类,代码如下:

public class AccountHandler implements InvocationHandler {private Object obj;public AccountHandler(Object obj) {super();this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;doBefore();result = method.invoke(obj,args);doAfter();return result;}private void doAfter() {System.out.println("开户后");}private void doBefore() {System.out.println("开户前");}}

        InvocationHandler接口中包含一个invoke()方法,必须实现这个方法。在这个方法中,通常需要调用method.invoke()方法执行原有对象的代码逻辑,然后可以在该方法前后添加相应的代理实现。在上述代码中,只是简答的打印了日志。然后编写测试类来验证执行结果,测试类代码如下:

public class Test {public static void main(String[] args) {Account account = new RealAccount("zhangsan");AccountHandler handler = new AccountHandler(account);Account proxy = (Account) Proxy.newProxyInstance(account.getClass().getClassLoader(),account.getClass().getInterfaces(),handler);proxy.open();}
}

运行结果如下:

        这里的Proxy.newProxyInstance()方法的作用就是生成代理类。当该方法被调用时,RealAccount类的实例被传入。然后当代理类的open()方法被调用时,AccountHandler中invoke方法就会被触发,从而实现代理机制。这里的类层次结构图如下:

        咨询分析上述代码结构,可以发现其遵循“设计并实现业务接口——实现Handler——创建代理类”这个流程,然后在Handler中构建具体的代理逻辑。上述流程也是代表了最基本的代理机制实现流程。联想一下,很多基于AOP机制的拦截器底层实际上就是类似的原理。

3 CGLIB动态代理

        CGLIB是一个Java字节码生成库,提供了易用的API对Java字节码进行创建和修改。现在尝试拥CGLIB来代理前面的RealAccount类,代码清单如下:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class AccountCglibProxy implements MethodInterceptor {private Enhancer enhancer = new Enhancer();public Object getProxy(Class<?> clazz){enhancer.setSuperclass(clazz);enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("之前");methodProxy.invokeSuper(o,objects);System.out.println("之后");return o;}
}

         上述代码中的Enhancer类是CGLIB中最常用的一个类,类似于前面介绍的JDK动态代理中的Proxy类。和Proxy只能代理接口不同,Enhancer既能够代理类接口,也能够代理不同类,但不能拦截final类和方法。在这里,我们实现了MethoInterceptor中的intercept()方法以提供代理逻辑。AccountCglibProxy类的使用方法也比较简单,测试代码如下:

public class CglibProxyTest {public static void main(String[] args) {AccountCglibProxy proxy = new AccountCglibProxy();RealAccount account = (RealAccount) proxy.getProxy(RealAccount.class);account.open();}
}

        作为对比,下表展示了JDK动态代理和CGLIB动态代理之间的区别。

4 ProxyFactoryBean

        JDK自带的动态代理以及基于CGLIB的动态代理在Spring框架中都得到了应用,最典型的应用场景就是实现AOP。Spring专门提供了一个ProxyFactoryBean类用于手动创建对象代理,并将创建的代理对象作为目标对象的AOP代理。

        ProxyFactoryBean提供了一组配置属性用于指定代理的执行行为,比较常见的包括proxyTargetClass和exposeProxy。如果proxyTargetClass属性为true,则仅使用CGLIB创建代理。如果该属性未设置,那么有两种情况:如果目标实现了接口,则将使用JDK创建代理;反之,将使用CGLIB创建代理。而exposeProxy属性用于设置是否将当前代理暴露给ThreadLocal。如果该属性为true,那么开发人员可以使用AopContext.currentProxy()方法来获取代理对象。

        接下来,将演示如何使用ProxyFactoryBean来创建和管理对象。继续使用前面的场景,现在为MethodBeforeAdvice接口提供了一个实现类。显然从命名上看,这个实现类是方法执行前通知的,代码清单如下:

import com.sun.istack.internal.logging.Logger;
import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class AccountTransactionInterceptor implements MethodBeforeAdvice {private static final Logger LOGGER = Logger.getLogger(AccountTransactionInterceptor.class);@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {LOGGER.info("账户交易被拦截");}
}

           接着通过Java代码创建一个通知,实现方式如下:

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;public class MyAdvisor {@Beanpublic Advisor accountServiceAdvisor(){AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(*com.jay.aop.service.AccountService.doAccountTransaction(..))");return new DefaultPointcutAdvisor(pointcut,new AccountTransactionInterceptor());}
}

        最后,创建一个ProxyFactoryBean实例,并设置相关属性,代码如下:

@Beanpublic ProxyFactoryBean accountService(){ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();proxyFactoryBean.setTarget(new AccountServiceImpl());proxyFactoryBean.addAdvisor(accountServiceAdvisor());proxyFactoryBean.setExposeProxy(true);return proxyFactoryBean;}

        注意,这里设置目标类为AccountService接口的实现类AccountServiceImpl,并把exposeProxy属性设置为true。这样,在AccountServiceImpl中就可以使用Spring AOP提供的AopContext.currentProxy方法来获取这个代理对象,实例代码如下:

public class AccountServiceImpl implements AccountService {private static  final Logger LOGGER = Logger.getLogger(AccountServiceImpl.class);@Overridepublic boolean doAccountTransaction(Account source, Account dest, int amount) throws MinimumAccountException {((AccountService)(AopContext.currentProxy())).doAccountTransaction(source,dest,amount);return true;}
}

 

              

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

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

相关文章

EEPROM与FLASH

一、EEPROM介绍 1.概念 EEPROM简介&#xff0c;EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息&#xff0c;重新编程。一般用在即插即用&…

【Bugku CTF】web解题记录

记录我在Bugku CTF靶场中做的比赛真题&#xff0c;便于自己以后的复习 1.my-first-sqli 进入此关卡&#xff0c;发现参数有username和password 我们尝试在username上注入数字型、字符型参数&#xff0c;后面发现注入字符型的单引号的有报错语句&#xff0c;我们在username上注…

【服务器08】之【游戏框架】之【加载主角】

首先简单了解一下帧率 FixedUpdate( ) > Update( ) > LateUpdate( ) 首先FixedUpdate的设置值 默认一秒运行50次 虽然默认是0.02秒&#xff0c;但FiexedUpdate并不是真的0.02秒调用一次&#xff0c;因为在脚本的生命周期内&#xff0c;FixedUpdate有一个小循环&…

大学计算机

项目一 了解计算机 1.1 了解计算机的诞生及发展阶段 1.2 认识计算机的特点、应用和分类 1&#xff0e;计算机的特点 1. 计算机的特点 2.计算机的应用 3.计算机的分类 4.数量单位 1.3 了解计算机操作系统的概念、功能与种类 1.操作系统概念 2.操作系统的作用 1&#xff0e…

主流的RAG框架

Rank1、LangChain(86k stars) https://github.com/langchain-ai/langchain/.当之无愧的霸主&#xff0c;范围很全面&#xff0c;但代码 Rank2、Quivr(33.4k stars) https://github.com/StanGirard/quivr Rank3、Llamalndex(32.1k stars) https://github.com/run-llama/llama…

MySQL数据库锁的实现原理

MySQL数据库的锁实现原理主要涉及到如何确保在多用户并发访问数据库时,保证数据的完整性和一致性。以下是MySQL数据库锁实现原理的详细解释: 锁的基本概念和目的 锁的概念:在数据库中,锁是用于管理对公共资源的并发控制的机制。当多个用户或事务试图同时访问或修改同一数…

Java零基础-集合:Set

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

感应电机转差速度估算

在感应电机矢量控制中&#xff0c;需要计算出感应电机的机械转差速度&#xff08;同步速度和转子速度之间的差&#xff09;。以下方程描述了感应电机磁场定向控制 (FOC) 中转差速度值的关系&#xff1a; 如果我们保持转子磁通恒定&#xff0c;并且 d 轴与转子磁通参考系对齐&am…

MFC时间获取与高精度计算

文章目录 MFC获取系统当前时间CTimeGetLocalTime 获取程序运行时间GetTickCount() MFC 获取系统当前时间 CTime CTime tm; tmCTime::GetCurrentTime();    int m_nYear tm.GetYear(); ///年 CString m_strTime tm.Format("%Y-%m-%d %H:%M:%S");GetLocalTime …

基于Java医院门诊互联电子病历管理信息系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

np.copy与copy.deepcopy

以下内容来源于poe的assistant&#xff1a; np.copy() 和 copy.deepcopy() 都是用于创建对象的副本,但它们之间有一些区别: 对象类型: np.copy() 主要用于创建 NumPy 数组的副本。copy.deepcopy() 可以用于创建任意 Python 对象的副本,包括列表、字典、自定义类等。 复制方式…

ubuntu 18.04 server源码编译安装freeswitch 1.10.7支持音视频通话、收发短信——筑梦之路

软件版本说明 ubuntu版本18.04&#xff1a;https://releases.ubuntu.com/18.04.6/ubuntu-18.04.6-live-server-amd64.iso freeswitch 版本1.10.7&#xff1a;https://files.freeswitch.org/freeswitch-releases/freeswitch-1.10.7.-release.tar.gz spandsp包&#xff1a;https:…

【python】在 Linux 中使用webdriver有头模式

用webdriver 模拟浏览网页时&#xff0c;可以使用无头模式&#xff0c;尤其在linux系统中&#xff0c;因为linux没有图形化界面&#xff0c;使用有头模式一般会报错。 chrome_options.add_argument("--headless") # 设置Chrome无头模式 但是有些网站的反爬措施比较…

AI与音乐:共创未来还是艺术终结?

随着人工智能技术的不断进步&#xff0c;AI在音乐创作领域的应用已经成为了一个不可忽视的现象。最近一个月&#xff0c;一系列音乐大模型的推出&#xff0c;不仅极大地降低了普通人创作音乐的门槛&#xff0c;也引发了关于音乐产业未来的广泛讨论。AI是否正在创造音乐的新纪元…

49-1 内网渗透 - Bypass UAC介绍

一、Bypass UAC 用户账户控制(UAC)是Windows操作系统的一种安全机制,旨在防止未经授权的应用程序自动安装并防止非授权修改系统设置。它确保应用程序和任务通常在非管理员账户的安全上下文中运行,除非明确需要管理员权限,用户才会被提示确认。 对于非RID 500的管理员用户(…

XML DOM 简介

XML DOM 简介 XML DOM(XML Document Object Model)是一种用于XML文档的标准编程接口。它定义了一种方式,允许程序和脚本动态地访问和更新文档的内容、结构和样式。XML DOM 将 XML 文档视为一个树形结构,其中每个节点都代表文档中的一个元素、属性、文本或其他内容。 XML …

学习笔记——路由网络基础——路由汇总(路由聚合)

九、路由汇总(路由聚合) 1、路由汇总背景 子网划分、VLSM解决了地址空间浪费的问题&#xff0c;但同时也带了新的问题&#xff0c;路由表中的路由条目数量增加。为减少路由条目数量可以使用路由汇总。 对于一个大规模的网络来说&#xff0c;路由器或其他具备路由功能的设备势…

C#的Switch语句2(case后的值与模式匹配)

文章目录 switch语法结构case具体的值枚举值字符串const关键字 如果没有匹配的值default语句不一定要在最后 模式匹配与C的差异-case穿透&#xff08;Fall-through&#xff09;下一篇文章 switch语法结构 基础的语法结构&#xff0c;在上一篇文章已经写了&#xff0c;具体请看…

git 初基本使用-----------笔记

Git命令 下载git 打开Git官网&#xff08;git-scm.com&#xff09;&#xff0c;根据自己电脑的操作系统选择相应的Git版本&#xff0c;点击“Download”。 基本的git命令使用 可以在项目文件下右击“Git Bash Here” &#xff0c;也可以命令终端下cd到指定目录执行初始化命令…

什么是流水线?

流水线&#xff08;Pipeline&#xff09;是一种提升系统效率和性能的方法&#xff0c;通过将任务分解成多个阶段&#xff08;也称为“阶段”或“段”&#xff09;&#xff0c;使得各个阶段能够并行工作。这种技术广泛应用于计算机处理器、工业生产、软件开发、数据处理等领域&a…