Встала задача написать программу так, чтобы весь ее код содержался в одном файле. Точнее даже не в одном файле, а в одном классе.
Самое очевидное решение — это сделать один большой класс и много внутренних статических классов внутри него.
Но сделать один класс можно только в том случае, если приложение относительно небольшое, и, что самое важное, если в нем не приходится использовать сторонние библиотеки.
В противном случае такое решение не подойдет. Но, есть варианты…
Идея заключается в том, чтобы разрабатывать приложение как обычно, а потом хитро собрать его в один class-файл, написав собственный класслоадер для своего приложения.
Тут самое время привести исходник, ибо из него сразу станет понятно почти все :)
Как можно догадаться, переменная classes содержит в себе hex-представления уже скомпилированных java-файлов — при этом это могут быть как наши, собственноручно написанные классы, так и выдернутые из каких-нибудь библиотек.
Эти четыре класса сгенерированы из следующего кода:
Когда мы запускаем наш класслоадер он пытается создать класс Application, при этом классы в первую очередь подгружаются из массива с нашими классами, и только в том случае, если нужный класс не был найден, делается попытка загрузить его стандартными класслоадером.
Вот собственно и все. Реальное применение такому решению найти сложно, но можно :) Например, можно в таком виде грузить файлы на Robocode, или заливать хитрые хранимые процедуры в oracle, или еще что-то в таком духе. А еще, мне кажется, если немного подумать, то можно сделать неплохой обфускатор.
______________________
Самое очевидное решение — это сделать один большой класс и много внутренних статических классов внутри него.
Но сделать один класс можно только в том случае, если приложение относительно небольшое, и, что самое важное, если в нем не приходится использовать сторонние библиотеки.
В противном случае такое решение не подойдет. Но, есть варианты…
Идея заключается в том, чтобы разрабатывать приложение как обычно, а потом хитро собрать его в один class-файл, написав собственный класслоадер для своего приложения.
Тут самое время привести исходник, ибо из него сразу станет понятно почти все :)
- package com.example;
- import java.util.Hashtable;
- import java.util.Map;
- public class ApplicationLoader extends ClassLoader {
- private static final Map<String, String> classes = new Hashtable<String, String>(4);
- static {
- // Обратите внимание как читаются первые символы байткода в hex :)
- classes.put("com.example.classes.Application", "cafebabe0000003200260a0009001907001a0a000200190a0002001b0a000" +
- "2001c0a0002001d07001e0a0007001907001f0100063c696e69743e010003282956010004436f646501000f4c696e654e756" +
- "d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100214c636f6d2f6578616d706" +
- "c652f636c61737365732f4170706c69636174696f6e3b0100056672616d6501001f4c636f6d2f6578616d706c652f636c617" +
- "37365732f4d61696e4672616d653b0100046d61696e010016285b4c6a6176612f6c616e672f537472696e673b29560100046" +
- "17267730100135b4c6a6176612f6c616e672f537472696e673b01000a536f7572636546696c650100104170706c696361746" +
- "96f6e2e6a6176610c000a000b01001d636f6d2f6578616d706c652f636c61737365732f4d61696e4672616d650c002000210" +
- "c002200230c0024002501001f636f6d2f6578616d706c652f636c61737365732f4170706c69636174696f6e0100106a61766" +
- "12f6c616e672f4f626a65637401000773657453697a65010005284949295601000a73657456697369626c65010004285a295" +
- "601001873657444656661756c74436c6f73654f7065726174696f6e010004284929560021000700090000000000020001000" +
- "a000b0001000c0000006900030002000000212ab70001bb000259b700034c2b11012c11012cb600042b04b600052b06b6000" +
- "6b100000002000d0000001a00060000000700040008000c00090016000a001b000b0020000c000e000000160002000000210" +
- "00f00100000000c00150011001200010009001300140001000c000000370002000100000009bb000759b7000857b10000000" +
- "2000d0000000a00020000000f00080010000e0000000c00010000000900150016000000010017000000020018");
- classes.put("com.example.classes.MainFrame", "cafebabe0000003200200a000700130a000600140700150a000300130a00160" +
- "0170700180700190100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c6501001" +
- "24c6f63616c5661726961626c655461626c650100047468697301001f4c636f6d2f6578616d706c652f636c61737365732f4" +
- "d61696e4672616d653b01000a457863657074696f6e7307001a01000a536f7572636546696c6501000e4d61696e4672616d6" +
- "52e6a6176610c000800090c001b001c01001e636f6d2f6578616d706c652f636c61737365732f4d6167696350616e656c070" +
- "01d0c001e001f01001d636f6d2f6578616d706c652f636c61737365732f4d61696e4672616d650100126a617661782f73776" +
- "96e672f4a4672616d6501001a6a6176612f6177742f486561646c657373457863657074696f6e01000e676574436f6e74656" +
- "e7450616e6501001628294c6a6176612f6177742f436f6e7461696e65723b0100126a6176612f6177742f436f6e7461696e6" +
- "57201000361646401002a284c6a6176612f6177742f436f6d706f6e656e743b294c6a6176612f6177742f436f6d706f6e656" +
- "e743b0021000600070000000000010001000800090002000a0000004600030001000000142ab700012ab60002bb000359b70" +
- "004b6000557b100000002000b0000000e000300000008000400090013000a000c0000000c000100000014000d000e0000000" +
- "f000000040001001000010011000000020012");
- classes.put("com.example.classes.MagicPanel", "cafebabe00000032003307001c0a0001001d0a000c001e07001f0a00200021" +
- "0a000400220700230a000700240a000400250a000b00260700270700280100063c696e69743e010003282956010004436f64" +
- "6501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010001620100154c6a61" +
- "7661782f7377696e672f4a427574746f6e3b0100016901000149010004746869730100204c636f6d2f6578616d706c652f63" +
- "6c61737365732f4d6167696350616e656c3b01000d537461636b4d61705461626c6507002701000a536f7572636546696c65" +
- "01000f4d6167696350616e656c2e6a6176610100136a6176612f6177742f477269644c61796f75740c000d00290c000d002a" +
- "0100136a617661782f7377696e672f4a427574746f6e07002b0c002c002d0c000d002e010027636f6d2f6578616d706c652f" +
- "636c61737365732f4d61676963427574746f6e4c697374656e65720c000d000e0c002f00300c0031003201001e636f6d2f65" +
- "78616d706c652f636c61737365732f4d6167696350616e656c0100126a617661782f7377696e672f4a50616e656c01000728" +
- "49494949295601001b284c6a6176612f6177742f4c61796f75744d616e616765723b29560100106a6176612f6c616e672f53" +
- "7472696e6701000776616c75654f660100152849294c6a6176612f6c616e672f537472696e673b010015284c6a6176612f6c" +
- "616e672f537472696e673b2956010011616464416374696f6e4c697374656e6572010022284c6a6176612f6177742f657665" +
- "6e742f416374696f6e4c697374656e65723b295601000361646401002a284c6a6176612f6177742f436f6d706f6e656e743b" +
- "294c6a6176612f6177742f436f6d706f6e656e743b0021000b000c0000000000010001000d000e0001000f000000a9000700" +
- "030000003d2abb0001590606100a100ab70002b70003033c1b1009a20026bb0004591bb80005b700064d2cbb000759b70008" +
- "b600092a2cb6000a57840101a7ffdab10000000300100000001e000700000008001100090019000a0025000b0030000c0036" +
- "0009003c000e001100000020000300250011001200130002001300290014001500010000003d001600170000001800000010" +
- "0002ff00130002070019010000fa00280001001a00000002001b");
- classes.put("com.example.classes.MagicButtonListener", "cafebabe0000003200320a000a001b0a001c001d0500000000000" +
- "000fa0a001e001f0700200a002100220a000600230700240700250700260100063c696e69743e010003282956010004436f6" +
- "46501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c650100047468697301002" +
- "94c636f6d2f6578616d706c652f636c61737365732f4d61676963427574746f6e4c697374656e65723b01000f616374696f6" +
- "e506572666f726d656401001f284c6a6176612f6177742f6576656e742f416374696f6e4576656e743b29560100016501001" +
- "c4c6a6176612f6177742f6576656e742f416374696f6e4576656e743b01000472616e640100014601000a536f75726365466" +
- "96c650100184d61676963427574746f6e4c697374656e65722e6a6176610c000c000d0700270c0028002907002a0c002b002" +
- "c0100136a617661782f7377696e672f4a427574746f6e07002d0c002e002f0c00300031010027636f6d2f6578616d706c652" +
- "f636c61737365732f4d61676963427574746f6e4c697374656e65720100106a6176612f6c616e672f4f626a65637401001d6" +
- "a6176612f6177742f6576656e742f416374696f6e4c697374656e65720100106a6176612f6c616e672f53797374656d01001" +
- "163757272656e7454696d654d696c6c697301000328294a01001a6a6176612f6177742f6576656e742f416374696f6e45766" +
- "56e74010009676574536f7572636501001428294c6a6176612f6c616e672f4f626a6563743b01000e6a6176612f6177742f4" +
- "36f6c6f7201000b676574485342436f6c6f7201001528464646294c6a6176612f6177742f436f6c6f723b01000d736574426" +
- "1636b67726f756e64010013284c6a6176612f6177742f436f6c6f723b295600210009000a0001000b000000020001000c000" +
- "d0001000e0000002f00010001000000052ab70001b100000002000f0000000600010000000800100000000c0001000000050" +
- "011001200000001001300140001000e00000060000400030000001ab800021400037189452bb60005c00006242424b80007b" +
- "60008b100000002000f0000000e00030000000a0009000b0019000c00100000002000030000001a0011001200000000001a0" +
- "01500160001000900110017001800020001001900000002001a");
- }
- private static final String HEXINDEX = "0123456789abcdef ABCDEF";
- public static byte[] hexToByte(CharSequence charSequence) {
- int length = charSequence.length() / 2;
- byte[] data = new byte[length];
- int j = 0;
- for (int i = 0; i < length; i++) {
- char c = charSequence.charAt(j++);
- int n = HEXINDEX.indexOf(c);
- int b = (n & 0xf) << 4;
- c = charSequence.charAt(j++);
- n = HEXINDEX.indexOf(c);
- b += n & 0xf;
- data[i] = (byte) b;
- }
- return data;
- }
- @Override
- public Class<?> loadClass(String name) throws ClassNotFoundException {
- if (classes.containsKey(name)) {
- byte[] bytes = hexToByte(classes.get(name));
- return defineClass(name, bytes, 0, bytes.length);
- } else {
- return findSystemClass(name);
- }
- }
- public static void main(String[] args) throws Throwable {
- ClassLoader loader = new ApplicationLoader();
- Class<?> application = loader.loadClass("com.example.classes.Application");
- application.newInstance();
- }
- }
Как можно догадаться, переменная classes содержит в себе hex-представления уже скомпилированных java-файлов — при этом это могут быть как наши, собственноручно написанные классы, так и выдернутые из каких-нибудь библиотек.
Эти четыре класса сгенерированы из следующего кода:
- /* Application.java */
- package com.example.classes;
- import javax.swing.*;
- public class Application {
- public Application() {
- MainFrame frame = new MainFrame();
- frame.setSize(300, 300);
- frame.setVisible(true);
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- }
- public static void main(String args[]) {
- new Application();
- }
- }
- /* MagicButtonListener.java */
- package com.example.classes;
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- public class MagicButtonListener implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- float rand = System.currentTimeMillis() % 250;
- ((JButton)e.getSource()).setBackground(Color.getHSBColor(rand, rand, rand));
- }
- }
- /* MagicPanel.java */
- package com.example.classes;
- import javax.swing.*;
- import java.awt.*;
- public class MagicPanel extends JPanel {
- public MagicPanel() {
- super(new GridLayout(3, 3, 10, 10));
- for (int i = 0; i < 9; ++i) {
- final JButton b = new JButton(String.valueOf(i));
- b.addActionListener(new MagicButtonListener());
- this.add(b);
- }
- }
- }
- /* MainFrame.java */
- package com.example.classes;
- import javax.swing.*;
- import java.awt.*;
- public class MainFrame extends JFrame {
- public MainFrame() throws HeadlessException {
- this.getContentPane().add(new MagicPanel());
- }
- }
Когда мы запускаем наш класслоадер он пытается создать класс Application, при этом классы в первую очередь подгружаются из массива с нашими классами, и только в том случае, если нужный класс не был найден, делается попытка загрузить его стандартными класслоадером.
Вот собственно и все. Реальное применение такому решению найти сложно, но можно :) Например, можно в таком виде грузить файлы на Robocode, или заливать хитрые хранимые процедуры в oracle, или еще что-то в таком духе. А еще, мне кажется, если немного подумать, то можно сделать неплохой обфускатор.
______________________