JavaRush /Java блог /Random UA /IntelliJ Idea: Декомпіляція, Компіляція, Субституція (або...
Viacheslav
3 рівень

IntelliJ Idea: Декомпіляція, Компіляція, Субституція (або як правити чужі помилки)

Стаття з групи Random UA
"Та не винайти ти велосипед" - одне з головних правил успішної та ефективної роботи. Але що робити, коли свій велосипед винаходити не хочеться, а у чужого кермо виявилося кривим, а колеса квадратними? Даний огляд призначений для можливості короткого ознайомлення з прийомом виправлення в чужих бібліотеках "на крайній випадок" і про те, як цю справу поширити далі свого комп'ютера.
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 1

Вступ

Усі ми користуємося тими чи іншими інструментами. Але іноді інструменти підходять не до кінця або мають помилки. Завдяки особливостям мови Java ми можемо виправити поведінку інструментів там, де це потрібно. Добре, коли ми вносимо свій внесок проекти і надсилаємо пулл реквести (докладніше можна прочитати тут: « GitHub – Внесення власного вкладу до проектів »). Але приймати їх можуть не одразу, а можуть навіть не прийняти. Але для потреб проекту потрібно ось зараз. І тут, сподіваюся, ця стаття покаже доступні нам, як розробнику, гроші. Нам знадобиться виконати такі дії, про які ми говоритимемо:
  • Підготувати випробуваний додаток для прикладу (на прикладі Hibernate проекту)
  • Пошук місця, що змінюється
  • Виконання зміни
  • Розгортання репозиторію
Всі нижче вказані дії наведені для Windows, але мають аналоги під nix системи. Так що при потребі ви можете їх повторити.

Підготовка випробуваного

Отже, нам потрібний піддослідний проект. Нам ідеально підійде Hibernate, т.к. це "стильно, модно, сучасно". Не особливо вдаватися до деталей, т.к. стаття не про Hibernate Робитимемо все швидко і у справі. І будемо, як правильні розробники, використовувати систему збирання. Наприклад, нам також підійде Gradle, який для цієї статті повинен бути встановлений ( https://gradle.org/install/ ). Спочатку нам потрібно створити проект. У Maven'а для цього є архетипи , а у Gradle для цього є особливий плагін: Gradle Init . Отже, відкриваємо командний рядок будь-яким відомим вам способом. Створюємо каталог для проекту, переходимо до нього та виконуємо команду:

mkdir codegym 
cd codegym 
gradle init --type java-application
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 2
Перш ніж виконувати імпорт проекту, внесемо деякі зміни до файлу, який описує, яким чином потрібно виконувати збірку. Цей файл називається build script'ом і має ім'я build.gradle. Знаходиться він у тому каталозі, де ми виконали gradle init. Тому просто відкриваємо його (наприклад, у windows командою start build.gradle). Знаходимо там блок " dependencies ", тобто. залежності. Тут описуються всі сторонні jar, які ми будемо використовувати. Тепер слід зрозуміти, що тут описувати. Перейдемо на сайт Hibernate ( http://hibernate.org/ ). Нас цікавить Hibernate ORM. Нам потрібний останній реліз. У меню зліва є підрозділ "Releases". Вибираємо "latest stable". Промотуємо вниз і знаходимо "Core implementation (includes JPA)". Раніше потрібно було підтримувати JPA окремо, але тепер все стало простіше і достатньо лише однієї залежності. Також нам знадобиться за допомогою Hibernate працювати з базою даних. Для цього візьмемо найпростіший варіант – H2 Database . Вибір зроблений, ось наші залежності:

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 є " Getting Started Guide ", але він безглуздий і більше заважає, ніж допомагає. Тому підемо як правильні люди відразу в " User Guide ". У змісті бачимо розділ " Bootstrap ", що перекладається як "Початкове завантаження". Те що треба. Там написано багато розумних слів, але сенс у тому, що на classpath має бути каталог META-INF, а там файл persistence.xml. На classpath за стандартом потрапляє каталог "resources". Тому створюємо вказаний каталог:mkdir src\main\resources\META-INF Створюємо там файл persistence.xml та відкриваємо його. Там же в документації є приклад "Example 268. META-INF/persistence.xml configuration file" з якого ми візьмемо вміст і вставимо у файл persistence.xml. Далі запускаємо IDE та імпортуємо до неї наш створений проект. Тепер нам треба щось зберігати у базу. Це щось називається сутністю. Сутності представляють щось із так званої доменної моделі. І в змісті, про диво, бачимо « 2. Domain Model ». Спускаємося нижче за текстом і бачимо у розділі "2.1. Mapping types" простий приклад сутності. Забираємо його до себе, трохи скоротивши:
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вкажемо свій клас entity.Contact. Добре, залишилося запуститися. Повертаємося в розділ « Bootstrap ». Так як у нас немає сервера додатків, який нам надасть особливе EE оточення (тобто оточення, яке реалізує для нас певну поведінку системи), ми працюємо в SE оточенні. Для нього нам підійде лише приклад "Example 269. Application bootstrapped 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);
    }
}
Ура, наш випробуваний готовий. Цю частину не хотів опускати , т.к. для наступних розділів бажано розуміти те, як з'явився наш випробуваний.

Пошук змінної поведінки

Давайте станемо місце ініціалізації поля count типу BigInteger і поставимо там точки зупинки ( BreakPoint ). Вставши на потрібному рядку це можна зробити за допомогою Ctrl+F8 або через меню Run -> Toggle Line Breakpoint. Після чого запускаємо наш main метод у дебазі (Run -> Debug):
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 3
Трохи незграбний приклад, але, скажімо, ми хочемо змінити кількість query spaces при старті. Як бачимо, наш sqlQuery це NativeQueryImpl. Натискаємо Ctrl+N, пишемо назву класу, переходимо до нього. Щоб при перехід у клас нас перекидало те місце, де лежить цей клас включив автоскрол:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 4
Відразу зауважимо, що Idea не знає зараз, де можна знайти вихідний код програми (вихідники, тобто). Тому вона милостиво декомпілювала для нас із class файлу вміст:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 5
Зауважимо також, що у заголовку вікна IntelliJ Idea пишеться, де Gradle зберігає для нас артефакт. Тепер отримаємо в Idea шлях, де лежить наш артефакт:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 6
Перейдемо в цей каталог у командному рядку за допомогою команди cd шлях до каталогу. Відразу зроблю зауваження: якщо є можливість зібрати проект із вихідних, краще збирати з вихідних. Наприклад, вихідний код Hibernate доступний на офіційному сайті. Краще забрати його для потрібної версії і зробити всі зміни там і зібратися за допомогою скриптів збирання, які вказані у проекті. Я ж наводжу у статті найжахливіший варіант – є jar, а вихідного коду немає. І зауваження номер 2: Gradle може отримати вихідний код за допомогою плагінів. Докладніше див. « How to download javadocs and sources for jar using Gradle .

Виконання зміни

Нам потрібно відтворити структуру каталогів відповідно до того, в якому пакеті лежить клас, що змінюється. У цьому випадку: mkdir org\hibernate\query\internal, після чого створюємо в цьому каталозі файл NativeQueryImpl.java. Тепер відкриваємо даний файл і копіюємо туди весь вміст класу з IDE (те саме, яке для нас декомпілювала Idea). Змінюємо потрібні рядки. Наприклад:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 7
Тепер компілюємо файл. Виконуємо: javac org\hibernate\query\internal\NativeQueryImpl.java. Потрібно ж, не можна просто взяти і скомпілювати без помилок. Набули купу помилок Cannot Find Symbol, т.к. змінюваний клас зав'язаний інші класи, які зазвичай IntelliJ Idea за нас додає в classpath. Чи відчуваєте всю корисність наших IDE? =) Що ж, давайте додамо самі, ми теж можемо. Скопіюємо окремо в блокнот шляху:
  • [1] - hibernate-core-5.2.17.Final.jar
  • [2] - hibernate-jpa-2.1-api-1.0.0.Final.jar
Як ми робабо: У вигляді "Project" в "External libraries" знаходимо потрібний jar і натискаємо Ctrl+Shift+C. Тепер сформуємо та виконаємо таку команду: javac -cp [1];[2] org\hibernate\query\internal\NativeQueryImpl.java В результаті поряд з java файлом з'являться нові class файли, які потрібно оновити у jar файлі:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 8
Ура тепер можна виконати jar update. Можемо керуватися офіційними матеріалами : jar uf hibernate-core-5.2.17.Final.jar org\hibernate\query\internal\*.class Відкрита IntelliJ Idea, швидше за все, не дасть змінювати файли. Тому до виконання jar update, швидше за все, доведеться закрити Idea, а після оновлення – відкрити. Після цього можна знову відкриваємо IDE, знову виконуємо dubug. Break Points не скидаються між перезапусками IDE. Тому виконання програми зупиниться там, де й раніше. Вуаля, ми бачимо, як працюють наші зміни:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 9
Чудово. Але тут постає питання – завдяки чому? Просто завдяки тому, що коли gradle збирає проект, він аналізує блок dependencies та repositories. У гредла є якийсь build cache, який лежить у певному місці (див. « How to set gradle cache location? ». Якщо в кеші немає залежності, то Gradle її скачає з репозиторію. Оскільки ми змінювали jar у самому кеші, то Gradle думає, що в кеші бібліотека є і нічого не викачує, але будь-яке очищення кешу призведе до того, що наші зміни пропадуть, плюс, ніхто, крім нас, не може просто взяти і отримати їх, скільки незручностей, чи не так? Хм, скачує з репозиторію?Отже, нам потрібен наш репозиторій, з преферансом та поетесами, про це наступний етап.

Розгортання репозиторію

Для розгортання свого репозиторію існують різні безкоштовні рішення: одне з них Artifactory , а інше – Apache Archive . Артифактори виглядає модно, стильно, сучасно, але з ним у мене виникли труднощі, ніяких не хотів правильно розміщувати артефакти і генерував помилкові метадані. Тому, несподівано для себе, у мене запрацював апачевський варіант. Він виявився не таким гарним, зате працює надійно. На сторінці завантаження шукаємо Standalone версію, розпаковуємо. У них є свій " Quick Start ". Після запуску слід дочекатися, коли за адресаою http://127.0.0.1:8080/#repositorylist. Після цього вибираємо "Upload Artifact":
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 10
Натискаємо "Start Upload", а після "Save Files". Після цього з'явиться зелене повідомлення про успішність і артефакт стане доступним у розділі "Browse". Так треба зробити для jar та для pom файлів:
IntelliJ Idea : Декомпіляція, Компіляція, Субституція (або як правити чужі помилки) - 11
Це з тим, що у pom файлі прописані додаткові залежності хибернейта. А нам залишилося лише 1 крок – вказати репозиторій у нашому білд скрипті:

repositories {
    jcenter()
    maven {
        url "http://127.0.0.1:8080/repository/internal/"
    }
}
І, відповідно, версія нашого хібернейту стане: compile 'org.hibernate:hibernate-core:5.2.17.Final-CODEGYM'. Ось і все тепер наш проект використовує виправлений нами варіант, а не початковий.

Висновок

От наче й ознайомабось. Сподіваюся, було цікаво. Подібні "трюки" робляться рідко, але якщо раптом ваші бізнес вимоги поставлять умови, які не можуть задовольнити бібліотеки, що використовуються - ви знаєте що робити. І так, ось кілька прикладів, що може так виправлятися:
  • Є такий веб-сервер Undertow. До деякого часу був баг, який при використанні проксі не давав дізнатися про IP кінцевого користувача.
  • До певного часу WildFly JPA певним чином обробляв один не врахований специфікацією момент, через що сипалися Exception. І це не налаштовувалося.
#Viacheslav
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ