JavaRush /Java 博客 /Random-ZH /IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)
Viacheslav
第 3 级

IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)

已在 Random-ZH 群组中发布
“不要重新发明轮子”是成功高效工作的主要规则之一。但是,当你不想重新发明自己的轮子,但别人的方向盘是弯的,而轮子是方的时,该怎么办呢?这篇评论的目的是尽可能简要地介绍“作为最后手段”修复其他人的库的技术,以及如何将这个问题扩展到你的计算机之外。
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误) - 1

介绍

我们都使用一种或另一种工具。但有时这些工具并不完全适合或存在错误。由于 Java 语言的特性,我们可以在需要的地方纠正工具的行为。当我们为项目做出贡献并发送拉取请求时,这很好(您可以在此处阅读更多内容:“ GitHub - 为项目做出贡献”)。但他们可能不会立即被接受,或者甚至可能不被接受。但出于项目的需要,现在是有必要的。我希望本文能够向我们展示作为开发人员可以使用的工具。我们需要执行我们将要讨论的以下步骤:
  • 准备一个测试应用程序例如(使用Hibernate项目的例子)
  • 寻找可变动的地点
  • 做出改变
  • 部署存储库
以下所有步骤均适用于 Windows 操作系统,但也有适用于 nix 系统的类似步骤。因此,如有必要,您可以重复这些操作。

科目准备

所以,我们需要一个测试项目。Hibernate 对我们来说是理想的选择,因为... 它是“时尚、时尚、现代”。我不会讲太多细节,因为…… 这篇文章不是关于 Hibernate 的。我们将迅速、切中要害地做每件事。我们,像适当的开发人员一样,将使用构建系统。例如Gradle也适合我们,本文必须安装它(https://gradle.org/install/)。首先,我们需要创建一个项目。Maven 有这方面的原型,Gradle 有一个专门的插件:Gradle Init。因此,请以您已知的任何方式打开命令行。为项目创建一个目录,进入该目录并执行命令:

mkdir javarush 
cd javarush 
gradle init --type java-application
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 2
在导入项目之前,让我们对描述如何构建的文件进行一些更改。该文件称为构建脚本,名为 build.gradle。它位于我们执行gradle init的目录中。因此,我们只需打开它(例如,在 Windows 中使用 start build.gradle 命令)。我们在那里找到“依赖项”块,即 依赖关系。这里描述了我们将使用的所有第三方 jar。现在我们需要了解这里要描述什么。让我们访问 Hibernate 网站 ( http://hibernate.org/ )。我们对Hibernate ORM感兴趣。我们需要最新版本。在左侧菜单中有一个“发布”小节。选择“最新稳定版”。向下滚动并找到“核心实现(包括 JPA)”。以前需要单独连接JPA支持,但现在一切都变得简单了,只需要一个依赖就足够了。我们还需要使用 Hibernate 来处理数据库。为此,我们采用最简单的选项 - H2 数据库。选择已做出,这是我们的依赖项:

dependencies {
    // Базовая зависимость для Hibernate (новые версии включают и JPA)
    compile 'org.hibernate:hibernate-core:5.2.17.Final'
    // База данных, к которой мы будем подключаться
    compile 'com.h2database:h2:1.4.197'
    // Use JUnit test framework
    testCompile 'junit:junit:4.12'
}
太好了,下一步是什么?我们需要配置 Hibernate。Hibernate 有一个“入门指南”,但它很愚蠢,而且更多的是阻碍而不是帮助。因此,让我们像合适的人一样直接进入“用户指南”。在目录中,我们看到“ Bootstrap ”部分,翻译为“Bootstrapping”。正是您所需要的。那里写了很多聪明的话,但要点是类路径上应该有一个 META-INF 目录,并且应该有一个 persistence.xml 文件。根据标准,类路径包含“resources”目录。因此,我们创建指定的目录:mkdir src\main\resources\META-INF 在那里创建 persistence.xml 文件并打开它。文档中有一个示例“示例 268. META-INF/persistence.xml 配置文件”,我们将从中获取内容并将其插入到 persistence.xml 文件中。接下来,启动 IDE 并将我们创建的项目导入其中。现在我们需要将一些内容保存到数据库中。这就是所谓的实体。实体代表所谓的领域模型中的某些东西。在目录中,你瞧,我们看到“ 2.领域模型”。我们继续阅读正文,在“2.1.映射类型”一章中看到一个简单的实体示例。让我们把它简化一下:
package entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity(name = "Contact")
public class Contact {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    public Contact(String name) {
        this.name = name;
    }
}
现在我们有一个代表实体的类。让我们回到 persistence.xml 并更正那里的一处:在指示的地方,class我们将指示我们的 class entity.Contact。太棒了,剩下的就是启动了。让我们回到Bootstrap章节。由于我们没有应用服务器为我们提供特殊的EE环境(即为我们实现某些系统行为的环境),所以我们在SE环境中工作。为此,只有示例“示例 269. 应用程序引导的 EntityManagerFactory”适合我们。例如,让我们这样做:
public class App {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("CRM");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Contact contact = new Contact("Vasya");
        em.persist(contact);
        em.getTransaction().commit();
        Query sqlQuery = em.createNativeQuery("select count(*) from contact");
        BigInteger count = (BigInteger) sqlQuery.getSingleResult();
        emf.close();
        System.out.println("Entiries count: " + count);
    }
}
万岁,我们的主题已经准备好了。我不想省略这部分,因为...... 在接下来的章节中,建议您了解我们的主题是如何产生的。

寻找可修改的行为

让我们代替 BigInteger 类型的 count 字段的初始化并在那里设置断点 ( BreakPoint )。插入到所需的行后,可以使用 Ctrl+F8 或通过菜单“运行”->“切换行断点”来完成。然后我们在调试中运行 main 方法(运行 -> 调试):
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 3
这是一个有点笨拙的例子,但假设我们想在启动时更改查询空间的数量。正如我们所看到的,我们的sqlQuery是NativeQueryImpl。单击Ctrl+N,输入班级名称,然后转到该班级。这样当我们去某个班级的时候,就会转移到这个班级所在的地方,并开启自动滚动:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 4
让我们立即注意,Idea 目前不知道在哪里可以找到程序的源代码(即源代码)。于是,她好心地帮我们反编译了类文件中的内容:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 5
另请注意,在 IntelliJ Idea 窗口的标题中,写着 Gradle 为我们保存工件的位置。现在,让我们在 Idea 中获取我们的工件所在的路径:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 6
让我们使用命令在命令行上进入该目录cd way to каталогу。我会立即记下:如果可以从源代码构建项目,那么最好从源代码构建。例如,Hibernate源代码可以在官方网站上找到。最好选择所需的版本并进行所有更改并使用项目中指定的构建脚本进行组装。我在文章中提出了最可怕的选择 - 有一个 jar,但没有源代码。注意事项 2:Gradle 可以使用插件获取源代码。有关详细信息,请参阅如何使用 Gradle 下载 javadoc 和 jar 源

做出改变

我们需要根据我们要更改的类所在的包重新创建目录结构。在本例中:mkdir org\hibernate\query\internal,之后我们在此目录中创建一个文件NativeQueryImpl.java。现在我们打开这个文件,并从 IDE 中复制该类的所有内容(与 Idea 为我们反编译的内容相同)。更改必要的行。例如:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 7
现在,让我们编译该文件。我们的确是:javac org\hibernate\query\internal\NativeQueryImpl.java。哇,你不能直接拿走它并毫无错误地编译它。我们收到了一堆“找不到符号”错误,因为...... 可变类与其他类相关联,IntelliJ Idea 通常将其添加到我们的类路径中。您感受到我们 IDE 的全部用处了吗?=) 好吧,我们自己添加吧,我们也可以做到。让我们复制以下路径:
  • [1] - hibernate-core-5.2.17.Final.jar
  • [2] - hibernate-jpa-2.1-api-1.0.0.Final.jar
就像我们所做的那样:在“外部库”的“项目”视图中,我们找到所需的 jar 并单击Ctrl+Shift+C。现在让我们创建并执行以下命令: javac -cp [1];[2] org\hibernate\query\internal\NativeQueryImpl.java 结果,java文件旁边会出现新的类文件,需要在jar文件中更新:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误) - 8
万岁,现在您可以执行 jar 更新了。我们可以参考官方资料jar uf hibernate-core-5.2.17.Final.jar org\hibernate\query\internal\*.class Open IntelliJ Idea 很可能不允许您更改文件。因此,在执行jar更新之前,您很可能必须关闭Idea,并在更新之后打开它。之后,您可以重新打开 IDE 并再次运行 dubug。IDE 重新启动之间不会重置断点。因此,程序执行将停止在之前的位置。瞧,我们看看我们的改变是如何运作的:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误)- 9
伟大的。但这里出现了一个问题——因为什么?原因很简单,当 gradle 构建项目时,它会分析依赖项和存储库块。Gradle有一定的构建缓存,它位于某个位置(参见“如何设置gradle缓存位置? ”如果缓存中没有依赖项,那么Gradle会从存储库中下载它。由于我们更改了缓存本身,那么 Gradle 会认为库在缓存中,不会抽出任何东西。但是任何清除缓存都会导致我们的更改丢失。而且,除了我们之外,没有人可以直接去获取它们。多么不便是吗?该怎么办。嗯,从存储库下载?所以我们需要我们的存储库,其中包含首选项和女诗人。这是下一步。

部署存储库

有不同的免费解决方案可用于部署存储库:其中之一是 Artifactory,另一个是Apache Archive。Artifactory 看起来时尚、时尚、现代,但我在使用它时遇到了困难,我不想正确放置工件并生成错误的 Maven 元数据。因此,出乎我意料的是,Apache 版本对我有用。事实证明它并不那么漂亮,但工作可靠。在下载页面上,找到独立版本并解压。他们有自己的“快速入门”。启动后,需要等待地址http://127.0.0.1:8080/#repositorylist。之后,选择“上传工件”:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误) - 10
单击“开始上传”,然后单击“保存文件”。此后,将出现绿色成功消息,并且该工件将在“浏览”部分中可用。应该对 jar 和 pom 文件执行此操作:
IntelliJ Idea:反编译、编译、替换(或者如何纠正别人的错误) - 11
这是因为 pom 文件中指定了额外的 hibernate 依赖项。我们只剩下 1 步了 - 在构建脚本中指定存储库:

repositories {
    jcenter()
    maven {
        url "http://127.0.0.1:8080/repository/internal/"
    }
}
因此,我们的 hibernate 版本将变为:compile 'org.hibernate:hibernate-core:5.2.17.Final-JAVARUSH'。就这样,现在我们的项目使用了我们更正后的版本,而不是原来的版本。

结论

看来我们是熟人了。我希望这很有趣。这种“技巧”很少被采用,但如果您的业务需求突然设置了您使用的库无法满足的条件,您就知道该怎么做。是的,这里有几个可以通过这种方式纠正的示例:
  • 有一个名为 Undertow 的 Web 服务器。直到一段时间,出现了一个错误,当使用代理时,我们无法找到最终用户的 IP。
  • 目前,WildFly JPA 以某种方式处理,规范未考虑到这一点,因此抛出了异常。而且它是不可配置的。
#维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION