揭示 Spring AOP 的内部工作原理

揭示 spring aop 的内部工作原理

在这篇文章中,我们将揭秘 spring 中面向方面编程 (aop) 的内部机制。重点将放在理解 aop 如何实现日志记录等功能,这些功能通常被认为是一种“魔法”。通过浏览核心 java 实现,我们将了解 java 的反射、代理模式和注释,而不是任何真正神奇的东西。

先决条件

java 核心代理 api反射api注释api

这些都是 java.lang.reflect、java.lang.annotation 和 javassist.util.proxy 包的一部分。

核心机制

spring aop 的核心是代理对象、方法拦截器和反射的概念。此模式中的关键角色是 methodhandler(或调用处理程序)。该处理程序通过拦截方法调用来控制代理对象的行为。当在代理上调用方法时,它会通过处理程序传递,其中可以通过反射对注释进行内省。根据应用的注释,可以在异常之前、之后或异常时执行必要的逻辑(例如日志记录)。

分解它

代理对象:这些是动态创建的对象,代表您的实际业务对象,通过方法处理程序路由方法调用。调用处理程序:这就是拦截的神奇之处。使用反射,处理程序可以检查目标方法上存在的注释并相应地改变行为。自定义注释:您可以定义自定义注释,它们用作触发日志记录、安全检查或事务管理等附加功能的标记。

示例: 假设我们想在某些方法执行之前和之后添加日志记录。我们可以使用 @beforemethod 和 @aftermethod 注释方法,而不是到处硬编码日志记录。我们的处理程序检查此注释的方法并动态添加适当的日志记录逻辑。

下面是我们示例中控制器和服务的类。

workercontroller.java

package edu.pk.poc.aop.controller;import edu.pk.poc.aop.annotation.aftermethod;import edu.pk.poc.aop.annotation.all;import edu.pk.poc.aop.annotation.beforemethod;import edu.pk.poc.aop.helper.proxyfactory;import edu.pk.poc.aop.service.worker;import edu.pk.poc.aop.service.workerservice;import edu.pk.poc.aop.service.workerserviceimpl;public class workercontroller {    workerservice workerservice = proxyfactory.createproxy(workerserviceimpl.class);    /**     * this method 1s annotated with @beforemethod and @aftermethod, so the log statements     * will be generated before and after method call.     */    @beforemethod    @aftermethod    public void engagefulltimeworker() throws exception {        worker fulltimeworker = new worker();        fulltimeworker.setname("fulltime-worker");        fulltimeworker.setparttime(false);        fulltimeworker.setduration(9);        workerservice.dowork(fulltimeworker);    }    /**     * this method is annotated with @all, so the log statements will be generated before and after method call     * along with exception if raised.     */    @all    public void engageparttimeworker() throws exception {        worker parttimeworker = new worker();        parttimeworker.setname("parttime-worker");        parttimeworker.setparttime(true);        parttimeworker.setduration(4);        workerservice.dowork(parttimeworker);    }}

workerserviceimpl.java

package edu.pk.poc.aop.service;import edu.pk.poc.aop.annotation.aftermethod;public class workerserviceimpl implements workerservice {    /**     * here this method is annotated with only @aftermethod, so only log statement     * will be generated after method call     */    @aftermethod    @override    public void dowork(worker worker) throws exception {        if (worker.isparttime()) {            throw new exception("part time workers are not permitted to work.");        }        system.out.print("a full time worker is working for " + worker.getduration() + " hours :: ");        for (int i = 1; i < worker.getduration(); i++) {            system.out.print("* ");        }        system.out.println();    }}

main.java 测试类

Trae国内版 Trae国内版

国内首款AI原生IDE,专为中国开发者打造

Trae国内版 815 查看详情 Trae国内版

package edu.pk.poc.aop.test;import edu.pk.poc.aop.controller.workercontroller;import edu.pk.poc.aop.helper.proxyfactory;import edu.pk.util.logger;public class main {    public static void main(string[] args) {        workercontroller controller = proxyfactory.createproxy(workercontroller.class);        logger logger = new logger();        try {            system.out.println("testing @beforemethod and @aftermethod");            system.out.println("-----------------------------------------");            controller.engagefulltimeworker();            system.out.println("testing @all");            system.out.println("-----------------------------------------");            controller.engageparttimeworker();        } catch (exception e) {            logger.error("exception caught in main class");        }    }}

输出

testing @beforemethod and @aftermethod----------------------------------------->>> entering into edu.pk.poc.aop.controller.workercontroller.engagefulltimeworker()a full time worker is working for 9 hours :: * * * * * * * * >>> exiting from edu.pk.poc.aop.service.workerserviceimpl.dowork()>>> exiting from edu.pk.poc.aop.controller.workercontroller.engagefulltimeworker()testing @all----------------------------------------->>> entering into edu.pk.poc.aop.controller.workercontroller.engageparttimeworker()>>> exception in edu.pk.poc.aop.controller.workercontroller.engageparttimeworker()exception caught in main class

它是如何运作的

当在代理对象上调用方法时,该调用会被处理程序拦截,处理程序使用反射来检查目标方法上的所有注释。根据这些注释,处理程序决定是否记录方法进入/退出、记录异常或完全跳过记录。

以下是可视化它的方法:

执行前:记录方法条目。执行后:记录方法退出或成功。全部:记录方法条目、方法条目以及引发的异常。 这种动态行为表明 spring aop 利用了核心 java api,而不是使用一些神奇的技巧。

定义注释

package edu.pk.poc.aop.annotation;import java.lang.annotation.elementtype;import java.lang.annotation.retention;import java.lang.annotation.retentionpolicy;import java.lang.annotation.target;@retention(retentionpolicy.runtime)@target(elementtype.method)public @interface aftermethod {}
package edu.pk.poc.aop.annotation;import java.lang.annotation.elementtype;import java.lang.annotation.retention;import java.lang.annotation.retentionpolicy;import java.lang.annotation.target;@retention(retentionpolicy.runtime)@target(elementtype.method)public @interface beforemethod {}
package edu.pk.poc.aop.annotation;import java.lang.annotation.elementtype;import java.lang.annotation.retention;import java.lang.annotation.retentionpolicy;import java.lang.annotation.target;@retention(retentionpolicy.runtime)@target(elementtype.method)public @interface all {}

定义代理工厂

package edu.pk.poc.aop.helper;/** * the {@code proxyfactory} class is responsible for creating proxy objects using the javassist library. * it allows for dynamic generation of proxies for classes or interfaces, with support for method interception. */public class proxyfactory {    /**     * a javassist proxyfactory instance used to generate proxy classes.     */    private static final javassist.util.proxy.proxyfactory factory = new javassist.util.proxy.proxyfactory();    /**     * creates a proxy object for the given class or interface.     * if the class is an interface, the proxy implements the interface.     * if it's a concrete class, the proxy extends the class.     *     * @param    the type of the class or interface for which the proxy is to be created     * @param klass the {@code class} object representing the class or interface to proxy     * @return a proxy instance of the specified class or interface, or {@code null} if proxy creation fails     */    public static  t createproxy(class klass) {        if (klass.isinterface())            factory.setinterfaces(new class[]{klass});        else            factory.setsuperclass(klass);        try {            return (t) factory.create(new class[0], new object[0], new aoploggingmethodhandler());        } catch (exception e) {            system.err.println(e.getmessage());        }        return null;    }}

定义 methodhandler

package edu.pk.poc.aop.helper;import edu.pk.poc.aop.annotation.AfterMethod;import edu.pk.poc.aop.annotation.All;import edu.pk.poc.aop.annotation.BeforeMethod;import edu.pk.poc.aop.annotation.OnException;import java.lang.annotation.Annotation;import java.lang.reflect.Method;import edu.pk.util.Logger;import javassist.util.proxy.MethodHandler;public class AOPLoggingMethodHandler implements MethodHandler {    private static final Logger logger = new Logger();    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {        if (proceed != null) { // Concrete Method            Object result = null;            String className = resolveClassName(self);            try {                if (isAnnotationPresent(thisMethod, BeforeMethod.class) || isAnnotationPresent(thisMethod, All.class)) {                    logger.info(">>> Entering into " + className + "." + thisMethod.getName() + "()");                }                result = proceed.invoke(self, args);                if (isAnnotationPresent(thisMethod, AfterMethod.class) || isAnnotationPresent(thisMethod, All.class)) {                    logger.info(">>> Exiting from " + className + "." + thisMethod.getName() + "()");                }            } catch (Throwable t) {                if (isAnnotationPresent(thisMethod, OnException.class) || isAnnotationPresent(thisMethod, All.class)) {                    logger.error(">>> Exception in " + className + "." + thisMethod.getName() + "()");                }                throw t;            }            return result;        }        throw new RuntimeException("Method is Abstract");    }    private boolean isAnnotationPresent(Method method, Class klass) {        Annotation[] declaredAnnotationsByType = method.getAnnotationsByType(klass);        return declaredAnnotationsByType != null && declaredAnnotationsByType.length > 0;    }    private String resolveClassName(Object self) {        String className = self.getClass().getName();        if (className.contains("_$$")) {            className = className.substring(0, className.indexOf("_$$"));        }        return className;    }}

结论

spring aop 是一个用于横切关注点的强大工具,但它并没有做任何革命性的事情。它建立在反射和代理等核心 java 概念之上,这些概念在语言本身中可用。通过理解这一点,您可以更好地理解 spring 如何简化这些底层机制以方便开发人员。

以上就是揭示 Spring AOP 的内部工作原理的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/479790.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月8日 10:47:48
下一篇 2025年11月8日 10:49:04

相关推荐

  • 解决 Vaadin 8 中大文件音频播放与定位时出现的 IOException

    在 Vaadin 8 应用中处理大型音频文件(超过 7 MB)时,用户在尝试进行音频定位(seek)操作时可能会遭遇 `java.io.IOException: A connection established by software on your host computer has been d…

    2025年12月23日
    000
  • 获得 Java 认证:实践测试的作用

    在竞争激烈的 IT 行业,Java 认证是展现您在业界最流行编程语言之一专业技能的有效途径。备考过程虽然充满挑战,但巧妙运用 Java 认证练习测试能显著提升您的成功率。本文将深入探讨练习测试(包括“Java 认证练习测试题库”)如何助您顺利通过认证考试。 为什么需要 Java 认证? Java 认…

    2025年12月19日
    000
  • XML处理库有哪些推荐?

    答案是lxml和ElementTree为Python中处理XML的高效常用库。ElementTree作为标准库简洁易用,适合中小文件;lxml基于C实现,性能更强,支持XPath/XSLT,适合大型文件或高性能需求;对于大型XML文件,应优先选择流式解析如SAX、StAX或lxml的iterpars…

    2025年12月17日
    000
  • XML流式解析的优势是什么?

    流式解析能高效处理超大XML文件,因它边读边处理,内存占用低。SAX事件驱动、性能高但状态管理复杂;StAX拉模式灵活可控,适合复杂逻辑。挑战包括上下文维护、错误恢复难、验证集成和无随机访问,需用栈管理、索引或混合模式应对。 XML流式解析的优势在于它能够以极低的内存消耗处理任意大小的XML文档,尤…

    2025年12月17日
    000
  • XML如何表示化学结构?

    CML(化学标记语言)是基于XML的化学信息表示标准,通过atomArray和bondArray等标签描述分子结构,如水分子的原子坐标与单键连接;其优势在于通用性与可扩展性,支持跨平台数据交换,常用工具包括Open Babel、JChem和RDKit;尽管如此,XML因冗长性导致文件大、解析慢,对大…

    2025年12月17日
    000
  • SOAP消息示例代码?各语言实现示例?

    SOAP是一种基于XML的协议,用于在分布式系统中交换结构化信息,其消息由Envelope、Header、Body和Fault四部分组成,广泛应用于企业级应用集成。尽管因XML冗余和WSDL复杂性导致新项目更倾向使用REST,但SOAP在安全性、可靠性和事务支持方面仍具优势,适用于金融、医疗等高要求…

    2025年12月17日
    000
  • SOAP服务依赖管理?如何管理库版本?

    答案:SOAP服务依赖管理需借助Maven/Gradle工具,通过版本锁定、依赖排除和BOM统一版本,解决XML解析、HTTP库冲突等问题,结合依赖树分析、父POM统一管理和自动化测试,实现升级时的平滑过渡与系统稳定性。 SOAP服务依赖管理和库版本控制的核心,在于采用Maven或Gradle这类构…

    2025年12月17日
    000
  • SOAP客户端代码生成?工具如何使用?

    答案:SOAP客户端代码生成通过WSDL文件自动生成调用服务所需的代理类,简化开发。开发者只需输入WSDL,工具如Java的wsimport、.NET的svcutil或Python的zeep便解析WSDL并生成封装了SOAP通信细节的代码,使远程调用像本地方法一样简单,提升效率、类型安全和可维护性,…

    2025年12月17日
    000
  • SOAP头自定义?如何添加业务头信息?

    答案:SOAP头可自定义添加认证、事务ID等元数据,通过命名空间在Header中定义结构,Java用SOAPHandler实现客户端添加与服务端解析,需结合TLS和WS-Security保障安全。 SOAP头自定义,说白了,就是在标准的SOAP消息体(Body)之外,添加一些额外的、业务相关的元数据…

    2025年12月17日
    000
  • XSLT扩展函数如何自定义使用?

    XSLT扩展函数通过集成外部编程语言(如Java)弥补了XSLT内置功能的不足,允许执行复杂逻辑、文件操作、数据库访问等。其实现需三步:编写外部代码(如Java静态方法)、在XSLT中声明命名空间(如xmlns:my-ext=”java:com.example.StringUtils&#…

    2025年12月17日
    000
  • XML如何验证Schema规范?

    xml验证schema规范的实现步骤包括:1.准备xsd文件定义xml结构和数据类型;2.使用支持schema验证的解析器如java的jaxp、python的lxml或c#的xmlreader;3.加载xml文档并执行验证;4.处理验证结果,捕获错误信息。xml schema相较于dtd具有xml语…

    2025年12月17日
    000
  • 利用 Java 代码实现 PDF 转 XML

    使用 Java 代码将 PDF 转换为 XML 的步骤:选择 PDF 解析库,例如 PDFBox 或 PDFTron。创建 PDFReader 对象解析 PDF 文档。使用 PDFReader 提取 PDF 文本。选择 XML 解析器,例如 JAXP 或 DOM。创建 XMLDocument 表示 …

    2025年12月17日
    000
  • 探索Go语言在Java虚拟机(JVM)平台上的实现

    本文旨在探讨将Go语言引入Java虚拟机(JVM)平台的可能性与挑战,以期结合JVM卓越的性能与生态系统,以及Go语言高效的开发效率和并发模型。我们将分析现有探索项目(如JGo)的工作原理,并权衡技术实现中的利弊,为开发者提供一个全面的视角。 引言:融合两大技术栈的愿景 在现代软件开发领域,java…

    2025年12月15日
    000
  • 使用 Go 程序在 Android 上访问互联网

    本文档旨在指导开发者如何在 Android 平台上运行的 Go 程序中实现互联网访问。由于 Android 系统与传统的 Linux 环境存在差异,直接使用 Linux 下的代码可能会遇到问题。本文将分析问题的根源,并提供解决方案,帮助开发者在 Android 环境下成功实现网络请求。 问题分析 在…

    2025年12月15日
    000
  • Python操作HBase:为什么需要Thrift?

    Python操作HBase:绕不开的Thrift? 很多Python教程都采用Python -> Thrift -> HBase的模式操作HBase,这让人不禁疑问:为什么非要Thrift做中间层?Python可以直接连接HBase吗?如果可以,有什么缺点?Java、Go、Node.js…

    2025年12月13日
    000
  • Python操作HBase为什么要使用Thrift?

    详解Python操作HBase为何选择Thrift 许多Python开发者在与HBase交互时,普遍采用Python -> Thrift -> HBase的模式。本文将深入探讨原因,并分析绕过Thrift直接连接HBase的可行性及潜在问题。 HBase底层基于Java开发,原生仅提供J…

    2025年12月13日
    000
  • Python操作HBase:为什么需要Thrift作为中间层?

    Python连接HBase:Thrift为何不可或缺? 许多Python HBase教程都采用Python -> Thrift -> HBase的架构。这不禁让人疑问:Thrift作为中间层,究竟有何必要?Python难道无法直接连接HBase吗? 答案是:HBase主要提供Java A…

    2025年12月13日
    000
  • Python操作HBase为何需要Thrift中间层?

    Python操作HBase:Thrift中间层的必要性 许多Python开发者在学习HBase操作时,会遇到Python -> Thrift -> HBase的访问模式。这引发了一个疑问:为什么需要Thrift这个中间层?Python能否直接连接HBase? HBase核心是用Java编…

    2025年12月13日
    100
  • Java中如何生成XML 详解DOM方式创建XML文档

    使用dom方式创建xml文档的步骤如下:1. 创建documentbuilderfactory对象;2. 创建documentbuilder对象;3. 创建document对象;4. 创建根元素并添加到document对象;5. 创建子元素和文本节点;6. 将元素逐级添加到dom树;7. 使用tra…

    2025年12月5日 java
    000
  • Java中如何开发CAD插件?AutoCAD API

    java不是auto#%#$#%@%@%$#%$#%#%#$%@_b5fde512c++76571c8afd6a6089eaaf42a插件开发的原生语言,但可通过桥接技术实现。1. 使用.net桥接(如ikvm.net或jnbridgepro)可将java代码转换为.net组件或实现java与.ne…

    2025年12月2日 java
    000

发表回复

登录后才能评论
关注微信