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,一经查实,立即删除!

相关文章

【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…

感应电机转差速度估算

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

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

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

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:…

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

九、路由汇总(路由聚合) 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到指定目录执行初始化命令…

6.20作业

1.已知网址www.hqyj.com截取出网址的每一个部分(要求&#xff0c;该网址不能存入文件中) echo www.hqyj.com | cut -d "." -f "1,2,3" 2.整理思维导图 3.将配置桥接网络的过程整理成文档&#xff0c;发csdn

条码工具 Dynamic Web TWAIN HTML5 版本的工作原理

Dynamic Web TWAIN 是一个专为Web应用程序设计的TWAIN扫描识别控件。你只需在TWAIN接口写几行代码&#xff0c;就可以用兼容TWAIN的扫描仪扫描文档或从数码相机/采集卡中获取图像。然后用户可以编辑图像并将图像保存为多种格式&#xff0c;用户可保存图像到远程数据库或者Share…

轻松搞定Python List 列表必备方法大全!

更多Python学习内容&#xff1a;ipengtao.com 在 Python 编程中&#xff0c;list&#xff08;列表&#xff09;是一种非常重要且常用的数据结构。列表可以存储不同类型的元素&#xff0c;并且提供了许多方便的内置方法来操作和处理这些元素。本文将详细介绍 Python 中 list 列表…

【笔记】HashMap的头插死循环问题

HashMap头插死循环是指在JDK1.7中&#xff0c;多线程环境下&#xff0c;HashMap进行扩容时由于多个线程一起执行扩容&#xff0c;可能会导致某一结点被错误插入头部并形成一个循环链表。 发生死循环的源码如下&#xff1a; // hashmap由数组链表构成 void transfer(Entry[] ne…

Android 13 修改系统导航默认值

Android 13 原生系统上&#xff0c;设置-系统-手势-系统导航 菜单&#xff0c;可以修改系统导航方式。 手势导航&#xff1a; 三按钮导航&#xff1a; adb 获取当前导航方式&#xff0c;手势导航 是 2 &#xff0c;三按钮导航是 0 。 settings get secure navigation_mode 修…

电脑网络动态IP配置:步骤详解与实用指南

在构建和管理电脑网络时&#xff0c;IP地址的配置是一个关键步骤。IP地址是设备在网络中的唯一标识符&#xff0c;它决定了设备如何与其他设备进行通信。有两种主要的IP地址类型&#xff1a;静态IP和动态IP。静态IP是手动配置的&#xff0c;而动态IP则是由网络中的DHCP服务器自…

Android C++系列:内存知识整理

1. 控制C的内存分配 在嵌入式系统中使用C的一个常见问题是内存分配&#xff0c;即对new 和 delete 操作符的失控。 具有讽刺意味的是&#xff0c;问题的根源却是C对内存的管理非常的容易而且安全。具体地说&#xff0c;当一个对象被消除时&#xff0c;它的析构函数能够安全的释…

202406最新manjaro安装sogou输入法解决方案(采用aur本地package+sogou deb包解决方案)

本地执行安装方法 1.拉取源码 git clone https://gitee.com/liushuai05/fcitx-sogoupinyin.git cd fcitx-sogoupinyin 2.获取sogou下载地址并替换到源码中 - 下载地址&#xff1a;https://pinyin.sogou.com/linux/ - 点击立即下载->x86_64->下载&#xff0c;然后右键复…

LoRaWAN在嵌入式网络通信中的应用:打造高效远程监控系统(附代码示例)

引言 随着物联网&#xff08;IoT&#xff09;技术的发展&#xff0c;远程监控系统在各个领域的应用越来越广泛。LoRaWAN&#xff08;Long Range Wide Area Network&#xff09;作为一种低功耗广域网通信协议&#xff0c;因其长距离传输、低功耗和高可靠性等特点&#xff0c;成为…

407串口01发送

实验一&#xff1a; 工程。 链接&#xff1a;https://pan.baidu.com/s/1g8DV4yZWOix0BbcZ08LYDQ?pwd2176 提取码&#xff1a;2176串口1的使用。发送功能。 单片机发送信息到电脑。 通过串口进行通信。 首先单片机这边。 单片机这边&#xff0c;需要对单片机的串口模块进行使…

zctf2016_note2-堆利用-unlink

一、题目 题目:zctf2016_note2 题目描述&#xff1a;二、WriteUp &#xff08;一&#xff09;题目环境 glibc版本2.23 通过patchelf进行修改&#xff08;二&#xff09;信息收集 $ checksec note2Arch: amd64-64-littleRELRO: Partial RELRO # (.plt.got)段依…