Google Guice 5:AOP
1. AOP
1.1 实际开发中面临的问题
-
在实际开发中,经常需要打印一个方法的执行时间,以确定是否存在慢操作
-
最简单的方法,直接修改已有的方法,在
finnally
语句中打印耗时@Override public Optional<Table> getTable(String databaseName, String tableName) {long startTime = System.currentTimeMillis();try {... // 省略关键代码return result;}... // 省略catch语句finally {long consumedTime = System.currentTimeMillis() - startTime;logger.info("getTable() -- databaseName: %s, tableName: %s, consumed time: %dms", databaseName, tableName, consumedTime);} }
-
随着打印耗时的需求增多,你会发现整个应用程序中存在很多打印耗时的代码
-
这些代码如果按照OOP(
Object Oriented Programming
)编程的思想,是无法抽象出来复用的,因为它与方法的执行是紧耦合的 -
而且随着需求的增多,安全检查、权限校验、审计日志等附加功能的代码,会使得整个方法越来越臃肿,以致难以阅读
-
同时,如果附加功能的逻辑发生变化,将牵一发而动全身
1.2 AOP编程
-
这时,我们希望有一种编程范例,能将这些公共的代码从方法中剥离出来,并切入到有需要的方法中
-
这种范例叫做AOP(
Aspect-Oriented Programming
,面向切面编程) -
它将问题划分成了不同的面(
Aspect
),在程序编译或运行阶段可以将这些面插入到需要的地方,以实现辅助功能与核心逻辑的解耦以及代码的复用 -
维基百科对AOP的描述如下:
- In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
在计算机领域,AOP是一种允许横切关注以增加代码模块化的编程范例 - It does so by adding behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set’”.
它可以在不修改代码本身的情况下,向已有的代码添加行为(an advice),通过 "pointcut"规范来单独指定修改哪些代码,例如:记录所有以set开头的函数的调用 - This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code core to the functionality.
这样可以允许将不属于业务核心逻辑的行为(如日志打印)添加到程序中,并不会导致功能的代码核心紊乱
- In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
-
上述的第二点,笔者觉得维基百科的描述可能存在一定问题:
- advice,通知,是对被拦截到的方法需要执行的代码(又叫增强),可以是前置、后置、最终、异常、环绕通知五类。以环绕通知为例,就是在方法执行前后需要执行的代码
- pointcut: joint point是连接点的意思,即可能需要注入切面的地方;而pointcut用于定义需要特殊处理的连接点
1.3 Srping AOP的常见术语
-
使用过Spring进行AOP编程的小伙伴,可能会注意到Aspect、Pointcut、Advice(Before、After、AfterReturning、AfterThrowing、Around)这些类似的关键字
-
在Spring AOP中,有很多术语,但最常使用的还是上面三种术语
-
下面是一段打印controller请求参数的AOP代码
@Component @Aspect // 表示这是一个切面 public class ControllerParamsLogAspect {private static final Logger logger = LoggerFactory.getLogger(ControllerParamsLogAspect.class);// 定义切点,匹配com.sunrise.controller包下所有的public方法,方法的参数任意@Pointcut("execution(public * com.sunrise.controller..*.*(..))")public void logAspect() {}// 前置通知,在执行controller方法前,打印方法入参@Before("logAspect()")public void doBefore(JoinPoint point) {// 构建打印内容JSONObject content = new JSONObject();try {... // 省略核心代码logger.error("API请求信息:{}", CommonUtils.jsonObjectToString(content));} catch (Exception exception) {... // 省略异常处理代码}} }
-
以上与AOP有关的注解,需要的maven依赖如下:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.14</version> </dependency>
-
其余术语概念可以参考:
- Spring AOP详解
- Spring 学习笔记(四):Spring AOP
-
PS: 阅读理论知识如果让你觉得迷糊,可以尝试直接看一段代码或者写一个小demo
2. AOP的实现方法
2.1 笔者的理解
-
通过对AOP基础知识的学习,在笔者心中AOP的实现方法,就应该像动态代理一样:以某种方式调用方法(如反射),并在方法的执行前后添加一些代码,从而可以在不修改已有业务逻辑的情况下增强方法
-
这是使用JDK动态代理实现的,打印方法耗时的核心代码:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime = System.currentTimeMillis();try {return method.invoke(animal, args);} finally {long diff = System.currentTimeMillis() - startTime;System.out.printf("%s() called, consumed %dms\n", method.getName(), diff);} }
-
然而,动态代理是有针对性的,AOP应该是有普适性的
- cglib动态代理虽然能代理一个类,但是后续对该类的访问,都需要转为对proxy的访问
- AOP则可以在满足特定规则的所有方法上使用,例如,只要对方法添加指定注解,Aspect则可以应用到这些方法上;又或者,所以以
set
开头的方法,都可以打印审计日志以记录数据的变化
-
让笔者不认同的是,有些博客宣称可以使用动态代理实现AOP,这明显是忽略了AOP的普适性
-
当然,这也可能是笔者对动态代理的理解不够深入 🤣 🤣 🤣
-
Guice使用拦截器机制实现AOP,笔者也比较赞同这种思想
2.2. Guice对AOP的支持
-
使用AOP实现一个打印方法耗时的功能,在目标方法执行结束后,系统会自动打印该方法的耗时
-
定义一个注解,使用该注解的方法,都将获得耗时打印的附加能力
@Target({METHOD}) @Retention(RUNTIME) public @interface PrintExecutionTime { }
-
定义一个打印方法耗时的拦截器,它实现了
org.aopalliance.intercept.MethodInterceptor
接口public class ExecutionTimePrinter implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {return invocation.proceed();} finally {long consumedTime = System.currentTimeMillis() - startTime;System.out.printf("call %s() and consumed %dms\n", invocation.getMethod().getName(), consumedTime);}} }
-
将拦截器应用到计算器的某些方法上
public class Calculator {private static final Random random = new Random();@PrintExecutionTimepublic int add(int a, int b) throws Throwable {// 随机休眠一段时间Thread.sleep(random.nextInt(2000));return a + b;}public int sub(int a, int b) {return a - b;}@PrintExecutionTimepublic int div(int a, int b) throws Throwable {// 随机休眠一段时间Thread.sleep(random.nextInt(2000));return a / b;}public long mul(int a, int b) {return a * b;} }
-
在Module中定义pointcut,即拦截器将作用到满足什么规则的方法上
public class AopModule extends AbstractModule {@Overrideprotected void configure() {// ExecutionTimePrinter将作用到任意类的、使用了@PrintExecutionTime的方法上bindInterceptor(Matchers.any(), Matchers.annotatedWith(PrintExecutionTime.class),new ExecutionTimePrinter());} }
-
从Guice中获取一个计算器实例,执行其方法以验证拦截器是否生效
public static void main(String[] args) throws Throwable {Injector injector = Guice.createInjector(new AopModule());Calculator calculator = injector.getInstance(Calculator.class);int a = 12, b = 24;System.out.printf("%d + %d = %d\n", a, b, calculator.add(a, b));System.out.printf("%d - %d = %d\n", a, b, calculator.sub(a, b));System.out.printf("%d / %d = %d\n", b, a, calculator.div(b, a));System.out.printf("%d * %d = %d\n", a, b, calculator.mul(a, b)); }
-
最终,添加了@PrintExecutionTime的
add()
和div()
方法执行结束后将打印耗时
相关文章:

Google Guice 5:AOP
1. AOP 1.1 实际开发中面临的问题 在实际开发中,经常需要打印一个方法的执行时间,以确定是否存在慢操作 最简单的方法,直接修改已有的方法,在finnally语句中打印耗时 Override public Optional<Table> getTable(String da…...

【同步、共享和内容协作软件】上海道宁与ownCloud让您的团队随时随地在任何设备上轻松处理数据
ownCloud是 一款开源文件同步、共享和 内容协作软件 可让团队随时随地 在任何设备上轻松处理数据 ownCloud开发并提供 用于内容协作的开源软件 使团队能够轻松地无缝 共享和处理文件 而无需考虑设备或位置 开发商介绍 ownCloud成立于2010年,是一个托管和同…...
Linux 文件、目录与磁盘格式
用户与用户组 用户:即某个文件的拥有者,可以管理自己账号下的文件,另有一个超级账号 root,可以统一管理全局,利用 su root 命令登录该账号。用户组:相当于群组,多个用户之间可以组成用户组&…...
锁屏面试题百日百刷-Hive篇(五)
锁屏面试题百日百刷,每个工作日坚持更新面试题。锁屏面试题app、小程序现已上线,官网地址:https://www.demosoftware.cn。已收录了每日更新的面试题的所有内容,还包含特色的解锁屏幕复习面试题、每日编程题目邮件推送等功能。让你…...

java多线程(七)线程等待与唤醒
一、wait()、notify()、notifyAll()等方法介绍 在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用࿰…...

第13天-仓储服务(仓库管理,采购管理 ,SPU规格维护)
1.仓储服务开发配置 1.1.加入到Nacos注册中心 spring:application:name: gmall-warecloud:nacos:discovery:server-addr: 192.168.139.10:8848namespace: 36854647-e68c-409b-9233-708a2d41702c1.2.配置网关路由 spring:cloud:gateway:routes:- id: ware_routeuri: lb://gmal…...
Maven 命令行及例子
基本 mvn -v - show-version -version 显示版本信息mvn -h - help 显示帮助信息mvn -e -errors控制 maven 的日志级别,产生执行错误相关消息mvn -q - quiet 控制 maven 的日志级别,仅仅显示错误mvn -o - offline 运行 offline 模式,不联网更…...

JavaScript手写题
一、防抖 function debounce(fn, delay200) {let timeout null; // 定时器控制return function(...args) {if (timeout) { // 定时器存在,表示某个动作之前触发过了clearTimeout(timeout); // 清除定时器timeout null;} else {// 对第一次输入立即执行fn.apply…...

为什么图标的宽度总是8的倍数?
对于 Windows 上的所有图标而言,它的宽度总是8的倍数,这可不是因为人们喜欢2的幂,虽然在计算机世界,你会看到很多这样的数字,例如,1024,4096等。 在 Windows 的早期阶段,大多数显卡…...

常用的xpath
一、xpath 语法 简单看一下菜鸟教程即可 1、基本语法 XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 下面列出了最有用的路径表达式: 表达式描述nodename选取此节点的所有子节点。/从根节点选取(取子节点&#…...

【035】基于java的进销库存管理系统(Vue+Springboot+Mysql)前后端分离项目,附万字课设论文
1.3 系统实现的功能 本次设计任务是要设计一个超市进销存系统,通过这个系统能够满足超市进销存系统的管理及员工的超市进销存管理功能。系统的主要功能包括:首页、个人中心、员工管理、客户管理、供应商管理、承运商管理、仓库信息管理、商品类别管理、 …...

【Spark分布式内存计算框架——Spark Streaming】7. Kafka集成方式
集成方式 Spark Streaming与Kafka集成,有两套API,原因在于Kafka Consumer API有两套, 文档:http://spark.apache.org/docs/2.4.5/streaming-kafka-integration.html。 方式一:Kafka 0.8.x版本 老的Old Kafka Consum…...
如何引入elementUI
elementUI的引入完整引入按需引入完整引入 在 main.js 中写入以下内容: import Vue from ‘vue’; import ElementUI from ‘element-ui’; import ‘element-ui/lib/theme-chalk/index.css’; import App from ‘./App.vue’; Vue.use(ElementUI); new Vue({ el: ‘…...

vue3+rust个人博客建站日记4-Vditor搞定MarkDown
即然是个人博客,那么绝对不能丢给自己一个大大的输入框敷衍了事。如果真是这样,现在就可以宣布项目到此结束了。如今没人享受用输入框写博客。作为一个有追求的程序员,作品就要紧跟潮流。 后来,Markdown 的崛起逐步改变了大家的排…...

KDZD-JC软化击穿试验仪
一、概 述 KDZD-JC智能软化击穿试验仪是根据GB/T4074.6-2008和idtIEC60851-6:2004标准而设计的一种新型漆包圆线检测仪器。主要适用于固体绝缘材料(如:塑料、橡胶、层压材料、薄膜、树脂、云母、陶瓷、玻璃、绝缘漆等绝缘材料及绝缘件)在工…...

【数据结构】单链表的C语言实现--万字详解介绍
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:数据结构 🎯长路漫漫浩浩,万事皆有期待 文章目录1.链表1.1 链表的概念…...

电子科技大学软件工程期末复习笔记(七):测试策略
目录 前言 重点一览 V模型 回归测试 单元测试 集成测试 重要概念 自顶向下的集成方法 自底向上的集成方法 SMOKE方法 系统测试 验收测试 α测试 β测试 本章小结 前言 本复习笔记基于王玉林老师的课堂PPT与复习大纲,供自己期末复习与学弟学妹参考用…...

逆向-还原代码之除法 (Interl 64)
除法和32位差不多,毕竟背后的数学公式是一样的。区别只是32位的乘法需要两个寄存器来存放大数相乘的结果,而64位的不需要,一个寄存器就能存下。所以在64位的环境下,多了右移32位这条指令,其他指令一样。 //code #incl…...

Python WebDriver自动化测试
Webdriver Selenium 是 ThroughtWorks 一个强大的基于浏览器的开源自动化测试工具,它通常用来编写 Web 应用的自动化测试。 Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是…...

2023年微信小程序获取手机号授权登录注册详细教程,包含服务端教程
前言 小程序中有很多地方都会用到用户的手机号,比如登陆注册,填写收货地址等等。有了这个组件可以快速获取微信绑定手机号码,无须用户填写。网上大多数教程还是往年的,而微信官方的api已做了修改。本篇文章将使用最新的方法获取手…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...