没有源码,如何修改代码逻辑?

news/2025/2/26 10:06:05/文章来源:https://www.cnblogs.com/chopper-poet/p/18734795

 拍摄于西安德福巷
 
前段时间接手了一个二次开发其他团队代码的项目,过程中发生了不少有意思的小插曲。今天正好有点空闲时间,就简单梳理一下,希望能给大家提供一些参考和帮助。
 

当我咨询对方团队:“大哥,我们这边要对你们在xxx项目上的代码进行二次开发,想了解下你们的二开机制是怎样的?”

对方回复道:“我们没有特别明确的二开机制。不过标品这边会提供jar包给你们,你们只需新建一个Spring Boot工程,然后通过依赖这个标品jar包来搭建二开工程。要是想修改业务逻辑,基于Spring的AOP机制进行操作就行,自由度还是比较高的。”

我听后有些惊讶:“原来是这样操作啊,我一开始还以为是从你们的代码仓库拉分支或者fork一份代码,然后我们在这个基础上进行修改呢。”

不得不说,基于AOP进行二次开发确实有很大的灵活性,开发者可以根据自己的需求自由发挥。但这种方式也存在明显的弊端,一旦项目出现问题,排查起来会比较困难。而且过度使用AOP,会导致业务逻辑分散在各个地方,不利于维护。回顾以往的开发经验,我使用AOP主要是进行一些横向的功能补充或扩展,比如权限控制、异常处理等方面,还真没试过用它来实现核心功能

接下来,正式进入今天的主题:在没有源码的情况下,怎样修改代码逻辑 ?

方式一:基于Spring AOP

至于AOP的基础概念我就不啰嗦了,请自行前往官网了解。通过一段简单代码来看下如何通过AOP来修改代码逻辑:

import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;@Service
public class BizService {//原有逻辑public void testBiz(){System.out.println("testBiz");}
}通过切面修改BizService的testBiz逻辑@Aspect
@Component
public class BizAspectTest {//Around切面拦截BizService的testBiz方法@Around("execution(* com.example.demo.BizService.testBiz())")public void around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("around");//继续原逻辑。如果想完全修改,也可以不继续joinPoint.proceed();}
}

  

到这里,我们成功修改了 BizService 的 testBiz 方法逻辑,却没有对 BizService 的代码做任何改动。

一切看似风平浪静,可不出意外的话,新问题马上就要接踵而至了。

方式二:类覆盖

方式一基于Spring 的AOP机制来做逻辑的更改,前提是技术框架基于Spring,且Bean的生命周期还必须交给Spring托管才可以,如果是直接new出来的Bean,AOP也无能为力,比如:

 

public class BizService2 {public void testBiz(){System.out.println("testBiz in BizService2");}
}BizService2 bizService2 = new BizService2();
bizService2.testBiz()

遇到这种情况该如何处理呢?起初,我想到的办法是在自己的二开工程里定义一个与原类同名的 BizService2 。接着,把需要修改的代码复制过来,针对要二次开发的部分进行调整。倘若我们要修改的 BizService2 位于 biz.jar 中,那么就在这个二次开发工程内,定义一个类路径与原类完全一致的 BizService2 ,如此一来,便可以按照需求自由编写代码了。其实,这里运用的是Spring Boot类加载机制的顺序原理,通过这种方式巧妙地实现了代码的定制化修改。

Spring Boot 使用 LaunchedURLClassLoader 来加载类,该类加载器在加载类时会按照一定的顺序搜索类路径。在搜索过程中,会先从 BOOT-INF/classes 目录查找所需的类,如果在该目录下找到了对应的类文件,就会直接加载该类,而不会再去 BOOT-INF/lib 目录下的 JAR 文件中查找同名类。(来源于网络)

不难发现,这种方法简单直接,弊端也很明显,会留下大量冗余代码。就算仅仅是修改一行代码,也不得不把其余代码都保留下来,这无疑增加了后续维护的难度和成本。既然这种方式存在不足,那么接下来,就让我们一起看看方法三,说不定能找到更优解。

方式三:Mojo's AspectJ Maven Plugin

这是一个Maven的插件,可以在编译期对代码进行植入,和Spring AOP的最终效果类似,Spring AOP是运行时植入,而它是编译时植入,第一次知道它的时候确实让我眼前一亮,以植入BizService2为例看看怎么操作:

 

//1.依然定义切面类,语法和使用Spring AOP时一样
@Aspect
public class BizAspect2Test {//Around切面拦截BizService的testBiz方法@Around("execution(* com.example.demo.BizService2.testBiz())")public void around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("around");joinPoint.proceed();}
}//2.配置maven插件
<build><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.15.0</version><configuration><complianceLevel>16</complianceLevel><source>16</source><showWeaveInfo>true</showWeaveInfo><!--如果要植入的类在jar包内,一定要这里指定--><weaveDependencies><weaveDependency><groupId>com.xxx</groupId><artifactId>yyy</artifactId></weaveDependency></weaveDependencies></configuration><executions><execution><configuration><skip>false</skip></configuration><goals><goal>compile</goal></goals></execution></executions></plugin></plugins>
</build>//3.引入aspectjrt
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.19</version>
</dependency>	

接下来就是使用maven正常编译,看看编译以后BizService2会发生什么变化,我们反编译BizService2看看:

import com.example.demo.BizAspect2Test;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.runtime.reflect.Factory;
public class BizService2 {private static /* synthetic */ JoinPoint.StaticPart ajc$tjp_0;static {// 1BizService2.ajc$preClinit();}public void testBiz() {// 1JoinPoint joinPoint = Factory.makeJP(ajc$tjp_0, this, this);BizService2.testBiz_aroundBody1$advice(this, joinPoint, BizAspect2Test.aspectOf(), (ProceedingJoinPoint) joinPoint);}private static /* synthetic */ void ajc$preClinit() {Factory factory = new Factory("BizService2.java", BizService2.class);ajc$tjp_0 = factory.makeSJP("method-execution", (Signature) factory.makeMethodSig("1", "testBiz", "com.example.demo.BizService2", "", "", "", "void"), 4);}private static final /* synthetic */ void testBiz_aroundBody1$advice(BizService2 ajc$this, JoinPoint thisJoinPoint, BizAspect2Test ajc$aspectInstance, ProceedingJoinPoint joinPoint) {// 13System.out.println("around");// 14ProceedingJoinPoint proceedingJoinPoint = joinPoint;System.out.println("testBiz in BizService2");}
}       

可以看到BizServer2内部已经融合了BizAspect2Test的相关逻辑,是不是很强大。最后借助豆包的回答来看看aspectjrt和Spring AOP实现原理的对比:

  • aspectjrt
    • AspectJ 采用了编译时织入(Compile-time weaving)、加载时织入(Load-time weaving)等方式。编译时织入是在 Java 源代码编译成字节码的过程中,就将切面逻辑合并到目标类的字节码中;加载时织入则是在类加载到 JVM 时,通过特殊的类加载器对字节码进行修改,插入切面逻辑。
    • 这种方式生成的代码在运行时没有额外的性能开销,因为切面逻辑已经成为目标类的一部分。
  • Spring AOP
    • Spring AOP 主要基于代理模式实现,分为 JDK 动态代理和 CGLIB 代理。当目标对象实现了接口时,使用 JDK 动态代理;当目标对象没有实现接口时,使用 CGLIB 代理。
    • 代理模式在运行时创建代理对象,通过代理对象来调用目标对象的方法,并在方法调用前后插入切面逻辑。因此,在运行时会有一定的性能开销,尤其是在频繁调用方法时。

推荐阅读

https://www.mojohaus.org/aspectj-maven-plugin/index.html

 

 

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

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

相关文章

deepseek本地部署硬件资源对比表.250226

‌DeepSeek在不同版本下的硬件资源需求对比表如下‌:版本 CPU核心数 GPU显存(GB) GPU类型 内存(GB) 存储需求(TB) 网络带宽(Mbps)1.5B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1007B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1008B 1-2 4 普通消费级(…

deepseek本地部署硬件资源对比表。250226

‌DeepSeek在不同版本下的硬件资源需求对比表如下‌:版本 CPU核心数 GPU显存(GB) GPU类型 内存(GB) 存储需求(TB) 网络带宽(Mbps)1.5B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1007B 1-2 4 普通消费级(如RTX 3090/4090) 8-16 1 50-1008B 1-2 4 普通消费级(…

用python画一个五星红旗

import turtle 设置画布和画笔 screen = turtle.Screen() 设置画布大小 screen.setup(800, 600) 设置画布背景颜色为红色,模拟五星红旗旗面颜色 screen.bgcolor("red") pen = turtle.Turtle() 设置画笔最快速度 pen.speed(0) 隐藏画笔箭头 pen.hideturtle() 定义绘制…

爬取东方财富网-parsel教学篇(正则表达式的详细使用+实例)

@目录前言导航正则表达式介绍正则表达式基本语法re库的使用常用函数案例源码运行截图共勉博客 前言 本文原本是想通过分享一个爬取东方财富网案例,来介绍parsel解析库的使用,没想到硬生生的写成了正则表达式的详细使用,想学习正则表达式的的小伙伴们可以来看下。 导航爬取小…

如何通过网管系统提升运维效率?

网络系统在企业信息化系统扮演着越来越重要的作用,网络规模不断扩大,网络结构越来越复杂,传统的运维方式已经难以满足高效、稳定运行的要求。网管系统作为IT运维的重要工具,能够帮助企业实现网络的智能化管理,显著提升运维效率。本文将探讨网管系统与IT运维的关系,并结合…

保证接口幂等性的这 7 种方案,绝了!

前言 接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景:我们在填写某些form表单时,保存按钮不小心快速点了两次,表中…

毕设的踩坑之路

main()函数 在main()函数中调用 QMessageBox 之前一定要创建 QApplication 对象, 使用 QWidget 之前要创建 QApplication 对象. 不然会程序崩溃. 下面是笔者原先的代码: 之前是因为数据库等一切正常, 所以没有触发到连接数据库失败的 QMessageBox 消息. 后来有一次连接的时候数…

表格内cron表达式转成需要的时间格式

1.表格内添加 :formatter=""<el-table-columnlabel="监视周期"align="center"prop="corn":formatter="cornFormat"/>2.方法// 频率corn转时间格式cornFormat(row, column) {let str = row.corn;// let str = row.corn…

盲派八字推理 API 数据接口

盲派八字推理 API 数据接口 AI / 八字命理 基于八字命理分析 命理分析 / 八字运势。1. 产品功能基于八字命理以及盲派八字理论解析个人运势; 提供详细的大运分析,覆盖学业、婚姻、财运、健康等领域; 分析个人运势,包括学业、婚姻、财运、健康等方面的解读; 智能解读人生关…

2.25 CW 模拟赛 T4. 博弈论

前言 本来并不打算补这个题, 但是发现想拿到更好的分数, 应该对这题有更多的分析 至于 \(\rm{T3}\) , 除了让我知道分拆数类型的状态压缩 \(\rm{dp}\) 还可以乱搞, 没有什么意义, 现在也不太可能拥有这个水平 思路题意 给定一个序列A and B\textrm{A and B}A and B 每次可以在序…

20220226

沪锌 形态待验证 胶系看之前分析即可。后市看方向 17600-18200 https://www.cnblogs.com/lyonlee/p/18729353

2. 在Linux 当中安装 Nginx(13步) 下载安装启动(详细说明+附加详细截图说明)

2. 在Linux 当中安装 Nginx(13步) 下载&安装&启动(详细说明+附加详细截图说明) @目录2. 在Linux 当中安装 Nginx(13步) 下载&安装&启动(详细说明+附加详细截图说明)1. 在 Linxu 下安装 Nginx 的详细步骤2. 最后:1. 在 Linxu 下安装 Nginx 的详细步骤 Nginx 官…