Разработка на Java без всего
Снова показываю как можно вести разработку «голыми руками» — без IDE, документации и даже интернета. В этот раз с помощью «пользовательской» Ubuntu Linux и OpenJDK.
Поскольку современные разработчики постоянно жалуются на завышенные требования технических интервью вообще и на мою «дурную практику» написания кода от руки в частности — показываю на личном примере как все это работает.
Жертвам «слабой памяти» посвящается.
Заодно узнаете как можно вести разработку на Java хоть в чистом поле — в самолете, в поезде или на закрытом объекте, без подключения к интернету и документации.
Видео
В ролике показан весь процесс разработки на Java, с одним только JDK.
https://plvideo.ru/watch?v=x32Gzoe4ors0
Тестовое окружение
Для большей чистоты эксперимента был взят «Live USB»-образ Ubuntu Desktop 24.04.3 LTS
, записан на флешку, флешка вставлена в один из рабочих ноутбуков, который затем с нее был загружен.
Таким образом получилась чистая система, без средств разработки и с отключенной сетью.
Из инструментов будет лишь текстовый редактор и JDK.
Что будем писать
Самое простое что можно написать в таких полевых условиях — реверс-шелл HTTP-сервер.
На самом деле написать можно много чего, особенно если посмотреть в каталог demo
внутри OpenJDK:
Здесь и далее скриншоты из другой системы (Manjaro), чтобы не заморачиваться с их перебрасыванием из «Live USB»-системы и добавлением в статью.
Тем не менее на видео все описываемые в статье шаги и весь код вбиваются полностью вручную, на чистой системе, загруженной с Live USB.
Демо
Упомянутый выше каталог demo
содержит набор довольно серьезных примеров проектов, которых вам вполне хватит для начальной стадии изучения или в качестве основы для какого-нибудь прототипа, особенно если никаких других инструментов и интернета — нет.
Так выглядит демо-проект Notepad
, реализующий простейший текстовый редактор:
Так выглядит демо Metalworks
, с простейшей реализацией мультиоконной системы (MDI):
Напоминаю, что вся эта благодать находится внутри стандартной поставки любой версии JDK, начиная с незапамятных времен 8й версии.
Все демо-проекты содержат исходный код в архиве src.zip
и собираются без внешних зависимостей.
К сожалению каталог с демо иногда вырезается ментейнерами дистрибутивов линукса ради экономии места. И переносится в отдельный пакет, который пользователи разумеется забывают установить.
Ручная разработка
В ролике в записи показано как автор вводит и запускает в работу вот такой код:
// разумеется я не помню названий абсолютно всех // импортируемых классов, поэтому тут стоит '*' import java.io.*; import java.net.*; import java.util.concurrent.*; public class MyWebServer { static void handle(Socket s) { // метод getId() устарел, поэтому его использование в // последних версиях JDK выдает предупреждение System.out.println("Thread: %d" .formatted(Thread.currentThread().getId())); // самое сложное место, которое удалось повторить на записи // далеко не с первой попытки try(PrintWriter out = new PrintWriter(s.getOutputStream()); BufferedReader in = new BufferedReader( new InputStreamReader(s.getInputStream()));) { // поскольку используется чтение и запись строк а не байт - читаем // строку целиком, т.е. до символа \n String l = in.readLine(); // тут просто показываем в консоль System.out.println(l); // этим простым способом читаем только строку запроса, // которая идет первой, пропустив все заголовки // \r\n (пустая строка) - признак завершения запроса while (l==null || l.isEmpty() || "\r\n".equals(in.readLine())); // тут мы 'в лоб' сравниваем строку HTTP-запроса целиком // так она выглядит до работы парсера if ("GET /test HTTP/1.1".equals(l)) { // поскольку мы реагируем только на один url '/test' // формируем ниже статичный ответ String data = "Hello from alex0x08 at "+ new Date(); // так выглядят стандартные поля ответа в 'raw' виде, без обработки out.println("HTTP/1.1 200 OK"); // 'close' дает указание браузеру разорвать соединение // с сервером сразу после получения данных out.println("Connection: close"); // поскольку мы отдаем строку - ставим MIME тип 'text/plain' out.println("Content-Type: text/plain"); // опционально отдаем размер данных out.println("Content-Length: " + data.length()); // пустая строка - признак начала блока с данными out.println(); // отдаем сами данные out.println(data); } else { // во всех остальных случаях формируем ответ 404 out.println("HTTP/1.1 404 Not Found"); out.println("Connection: close"); out.println(); } // нужно обязательно вызывать поскольку PrintWriter кеширует данные out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { // в любом случае закрываем клиентский сокет try {s.close();} catch (Exception ee) {} } } // стартовый метод приложения public static void main(String[] args) throws Exception { System.out.println("Starting.."); // тоже сложное место, которое было непросто ввести по памяти ExecutorService p = Executors.newFixedThreadPool(10); // создание 'серверного' сокета, который будет прослушивать // указанный порт // поскольку хост не указан - будут прослушиваться все (0.0.0.0) ServerSocket ss = new ServerSocket(8089); // бесконечный цикл, который нужен тк метод accept() - блокирующий // и выход из него произойдет после получения входящего подключения while (true) { // получен клиентский сокет Socket s = ss.accept(); // запуск асинхронной обработки p.execute(() -> handle(s)); } } }
Комментариев в той версии этого кода, который был показан в записи разумеется нет, они были добавлены уже после — для большего понимания.
Этот код реализует простейший (но многопоточный) вебсервер на Java, который отвечает лишь на один URL /test
и отдает заранее заданную строку с датой.
Как видите даже столь небольшого кода достаточно чтобы можно было подключиться из современного браузера Chrome:
Компиляция выполняется как и в записи всего одной командой:
javac -cp . MyWebServer.java
После чего появится один единственный .class
файл c совпадающим именем, поскольку пакеты не использовались, для запуска достаточно указать в качестве classpath
текущий каталог:
java -cp . MyWebServer
Когда кончается память
Разумеется невозможно запомнить абсолютно все и рано или поздно вы столкнетесь с названием метода или класса, которые надо где-то подсмотреть.
Память все же не резиновая, пихать в нее бесконечно не получится.
Автор при записи видео столкнулся с таким в двух местах:
длинные классы-обертки над потоками (stream) сокета и сложное название статичного метода, создающего экземпляр ExecutorService.
И то и другое получилось правильно ввести далеко не с первой попытки.
Возвращаясь к ситуации когда нет доступа к интернету и полноценной среды разработки, зато на машине есть JDK — показываю что можно сделать в этом непростом случае.
подсмотреть названия системных классов и методов можно.. в самом JDK!
В последних версиях JDK появилась интересная утилита jimage
, которая находится в каталоге bin
(там же где и главные бинарники java
и javac
).
С помощью этой штуки можно легко посмотреть полные названия всех системных классов:
Правда знание полного имени класса не всегда помогает, поскольку в JDK много вложенных и системных классов, которые по идее вызывать снаружи не надо.
jimage list $JAVA_HOME/lib/modules |less
где переменная JAVA_HOME
указывает на каталог с установленной JDK:
Так вы увидите названия всех системных классов, но что делать с методами?
Вытаскиваем сигнатуры методов
Тут тоже есть решение, поскольку эта же утилита позволяет распаковывать jmod-файлы в которых находятся системные .class-файлы JDK.
jimage extract --dir=/opt/src/tmp $JAVA_HOME/lib/modules
А еще одна утилита javap
позволяет посмотреть метаданные .class-файла, в том числе сигнатуры всех методов:
cd /opt/src/tmp/jre javap java.base/java/nio/Bits.class
В виде текстового блока с подсветкой синтаксиса:
class java.nio.Bits { static final jdk.internal.misc.VM$BufferPool BUFFER_POOL; static final int JNI_COPY_TO_ARRAY_THRESHOLD; static final int JNI_COPY_FROM_ARRAY_THRESHOLD; static final boolean $assertionsDisabled; static short swap(short); static char swap(char); static int swap(int); static long swap(long); static int pageSize(); static long pageCount(long); static boolean unaligned(); static void reserveMemory(long, long); static void unreserveMemory(long, long); static {}; }
Вот этого уже с запасом хватит для полевой разработки в условиях крайнего Севера.
Если у вас есть реальный, а не нарисованный опыт разработки на Java, двух этих трюков будет достаточно для работы в поезде или самолете или на чужом копьютере — в тех местах и обстоятельствах, где нет подготовленного рабочего места.
Исходники JRE
Если вам совсем повезет, в каталоге JDK/lib
будет находиться файл src.zip
, внутри которого будут исходники всех системных классов JRE:
Внутри находится исходный код всех классов Java, используемых в JDK:
cat java.base/java/io/Bits.java |less
Так выглядит исходный код класса java.io.Bits
, который мы просматривали выше с помощью javap
:
Эпилог
Смысл такой «полевой разработки» — в первую очередь проверка реальных практических навыков, которые находятся в голове у программиста а не где‑то в интернете.
К сожалению на 2025й год можно констатировать, что такие навыки являются большой редкостью и мало кто из кандидатов, которых я когда-либо собеседовал могли осилить написание хотя‑бы трети подобного кода.
Кстати в нашем Телеграм-канале выложено первое техническое видео (запись с экрана), где впервые получилось проверить всю идею.