Как стать автором
Поиск
Написать публикацию
Обновить

Как запихнуть большую java-программу в один class-файл

Встала задача написать программу так, чтобы весь ее код содержался в одном файле. Точнее даже не в одном файле, а в одном классе.
Самое очевидное решение — это сделать один большой класс и много внутренних статических классов внутри него.
Но сделать один класс можно только в том случае, если приложение относительно небольшое, и, что самое важное, если в нем не приходится использовать сторонние библиотеки.
В противном случае такое решение не подойдет. Но, есть варианты…


Идея заключается в том, чтобы разрабатывать приложение как обычно, а потом хитро собрать его в один class-файл, написав собственный класслоадер для своего приложения.

Тут самое время привести исходник, ибо из него сразу станет понятно почти все :)
  1. package com.example;
  2.  
  3. import java.util.Hashtable;
  4. import java.util.Map;
  5.  
  6. public class ApplicationLoader extends ClassLoader {
  7.  
  8.     private static final Map<String, String> classes = new Hashtable<String, String>(4);
  9.  
  10.     static {
  11.        
  12.         // Обратите внимание как читаются первые символы байткода в hex :)
  13.  
  14.         classes.put("com.example.classes.Application", "cafebabe0000003200260a0009001907001a0a000200190a0002001b0a000" +
  15.                 "2001c0a0002001d07001e0a0007001907001f0100063c696e69743e010003282956010004436f646501000f4c696e654e756" +
  16.                 "d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100214c636f6d2f6578616d706" +
  17.                 "c652f636c61737365732f4170706c69636174696f6e3b0100056672616d6501001f4c636f6d2f6578616d706c652f636c617" +
  18.                 "37365732f4d61696e4672616d653b0100046d61696e010016285b4c6a6176612f6c616e672f537472696e673b29560100046" +
  19.                 "17267730100135b4c6a6176612f6c616e672f537472696e673b01000a536f7572636546696c650100104170706c696361746" +
  20.                 "96f6e2e6a6176610c000a000b01001d636f6d2f6578616d706c652f636c61737365732f4d61696e4672616d650c002000210" +
  21.                 "c002200230c0024002501001f636f6d2f6578616d706c652f636c61737365732f4170706c69636174696f6e0100106a61766" +
  22.                 "12f6c616e672f4f626a65637401000773657453697a65010005284949295601000a73657456697369626c65010004285a295" +
  23.                 "601001873657444656661756c74436c6f73654f7065726174696f6e010004284929560021000700090000000000020001000" +
  24.                 "a000b0001000c0000006900030002000000212ab70001bb000259b700034c2b11012c11012cb600042b04b600052b06b6000" +
  25.                 "6b100000002000d0000001a00060000000700040008000c00090016000a001b000b0020000c000e000000160002000000210" +
  26.                 "00f00100000000c00150011001200010009001300140001000c000000370002000100000009bb000759b7000857b10000000" +
  27.                 "2000d0000000a00020000000f00080010000e0000000c00010000000900150016000000010017000000020018");
  28.  
  29.         classes.put("com.example.classes.MainFrame", "cafebabe0000003200200a000700130a000600140700150a000300130a00160" +
  30.                 "0170700180700190100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c6501001" +
  31.                 "24c6f63616c5661726961626c655461626c650100047468697301001f4c636f6d2f6578616d706c652f636c61737365732f4" +
  32.                 "d61696e4672616d653b01000a457863657074696f6e7307001a01000a536f7572636546696c6501000e4d61696e4672616d6" +
  33.                 "52e6a6176610c000800090c001b001c01001e636f6d2f6578616d706c652f636c61737365732f4d6167696350616e656c070" +
  34.                 "01d0c001e001f01001d636f6d2f6578616d706c652f636c61737365732f4d61696e4672616d650100126a617661782f73776" +
  35.                 "96e672f4a4672616d6501001a6a6176612f6177742f486561646c657373457863657074696f6e01000e676574436f6e74656" +
  36.                 "e7450616e6501001628294c6a6176612f6177742f436f6e7461696e65723b0100126a6176612f6177742f436f6e7461696e6" +
  37.                 "57201000361646401002a284c6a6176612f6177742f436f6d706f6e656e743b294c6a6176612f6177742f436f6d706f6e656" +
  38.                 "e743b0021000600070000000000010001000800090002000a0000004600030001000000142ab700012ab60002bb000359b70" +
  39.                 "004b6000557b100000002000b0000000e000300000008000400090013000a000c0000000c000100000014000d000e0000000" +
  40.                 "f000000040001001000010011000000020012");
  41.  
  42.         classes.put("com.example.classes.MagicPanel", "cafebabe00000032003307001c0a0001001d0a000c001e07001f0a00200021" +
  43.                 "0a000400220700230a000700240a000400250a000b00260700270700280100063c696e69743e010003282956010004436f64" +
  44.                 "6501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010001620100154c6a61" +
  45.                 "7661782f7377696e672f4a427574746f6e3b0100016901000149010004746869730100204c636f6d2f6578616d706c652f63" +
  46.                 "6c61737365732f4d6167696350616e656c3b01000d537461636b4d61705461626c6507002701000a536f7572636546696c65" +
  47.                 "01000f4d6167696350616e656c2e6a6176610100136a6176612f6177742f477269644c61796f75740c000d00290c000d002a" +
  48.                 "0100136a617661782f7377696e672f4a427574746f6e07002b0c002c002d0c000d002e010027636f6d2f6578616d706c652f" +
  49.                 "636c61737365732f4d61676963427574746f6e4c697374656e65720c000d000e0c002f00300c0031003201001e636f6d2f65" +
  50.                 "78616d706c652f636c61737365732f4d6167696350616e656c0100126a617661782f7377696e672f4a50616e656c01000728" +
  51.                 "49494949295601001b284c6a6176612f6177742f4c61796f75744d616e616765723b29560100106a6176612f6c616e672f53" +
  52.                 "7472696e6701000776616c75654f660100152849294c6a6176612f6c616e672f537472696e673b010015284c6a6176612f6c" +
  53.                 "616e672f537472696e673b2956010011616464416374696f6e4c697374656e6572010022284c6a6176612f6177742f657665" +
  54.                 "6e742f416374696f6e4c697374656e65723b295601000361646401002a284c6a6176612f6177742f436f6d706f6e656e743b" +
  55.                 "294c6a6176612f6177742f436f6d706f6e656e743b0021000b000c0000000000010001000d000e0001000f000000a9000700" +
  56.                 "030000003d2abb0001590606100a100ab70002b70003033c1b1009a20026bb0004591bb80005b700064d2cbb000759b70008" +
  57.                 "b600092a2cb6000a57840101a7ffdab10000000300100000001e000700000008001100090019000a0025000b0030000c0036" +
  58.                 "0009003c000e001100000020000300250011001200130002001300290014001500010000003d001600170000001800000010" +
  59.                 "0002ff00130002070019010000fa00280001001a00000002001b");
  60.  
  61.         classes.put("com.example.classes.MagicButtonListener", "cafebabe0000003200320a000a001b0a001c001d0500000000000" +
  62.                 "000fa0a001e001f0700200a002100220a000600230700240700250700260100063c696e69743e010003282956010004436f6" +
  63.                 "46501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c650100047468697301002" +
  64.                 "94c636f6d2f6578616d706c652f636c61737365732f4d61676963427574746f6e4c697374656e65723b01000f616374696f6" +
  65.                 "e506572666f726d656401001f284c6a6176612f6177742f6576656e742f416374696f6e4576656e743b29560100016501001" +
  66.                 "c4c6a6176612f6177742f6576656e742f416374696f6e4576656e743b01000472616e640100014601000a536f75726365466" +
  67.                 "96c650100184d61676963427574746f6e4c697374656e65722e6a6176610c000c000d0700270c0028002907002a0c002b002" +
  68.                 "c0100136a617661782f7377696e672f4a427574746f6e07002d0c002e002f0c00300031010027636f6d2f6578616d706c652" +
  69.                 "f636c61737365732f4d61676963427574746f6e4c697374656e65720100106a6176612f6c616e672f4f626a65637401001d6" +
  70.                 "a6176612f6177742f6576656e742f416374696f6e4c697374656e65720100106a6176612f6c616e672f53797374656d01001" +
  71.                 "163757272656e7454696d654d696c6c697301000328294a01001a6a6176612f6177742f6576656e742f416374696f6e45766" +
  72.                 "56e74010009676574536f7572636501001428294c6a6176612f6c616e672f4f626a6563743b01000e6a6176612f6177742f4" +
  73.                 "36f6c6f7201000b676574485342436f6c6f7201001528464646294c6a6176612f6177742f436f6c6f723b01000d736574426" +
  74.                 "1636b67726f756e64010013284c6a6176612f6177742f436f6c6f723b295600210009000a0001000b000000020001000c000" +
  75.                 "d0001000e0000002f00010001000000052ab70001b100000002000f0000000600010000000800100000000c0001000000050" +
  76.                 "011001200000001001300140001000e00000060000400030000001ab800021400037189452bb60005c00006242424b80007b" +
  77.                 "60008b100000002000f0000000e00030000000a0009000b0019000c00100000002000030000001a0011001200000000001a0" +
  78.                 "01500160001000900110017001800020001001900000002001a");
  79.     }
  80.  
  81.     private static final String HEXINDEX = "0123456789abcdef          ABCDEF";
  82.  
  83.     public static byte[] hexToByte(CharSequence charSequence) {
  84.         int length = charSequence.length() / 2;
  85.         byte[] data = new byte[length];
  86.         int j = 0;
  87.         for (int i = 0; i < length; i++) {
  88.             char c = charSequence.charAt(j++);
  89.             int n = HEXINDEX.indexOf(c);
  90.             int b = (n & 0xf) << 4;
  91.             c = charSequence.charAt(j++);
  92.             n = HEXINDEX.indexOf(c);
  93.             b += n & 0xf;
  94.             data[i] = (byte) b;
  95.         }
  96.         return data;
  97.     }
  98.  
  99.     @Override
  100.     public Class<?> loadClass(String name) throws ClassNotFoundException {
  101.         if (classes.containsKey(name)) {
  102.             byte[] bytes = hexToByte(classes.get(name));
  103.             return defineClass(name, bytes, 0, bytes.length);
  104.         } else {
  105.             return findSystemClass(name);
  106.         }
  107.     }
  108.  
  109.     public static void main(String[] args) throws Throwable {
  110.         ClassLoader loader = new ApplicationLoader();
  111.         Class<?> application = loader.loadClass("com.example.classes.Application");
  112.         application.newInstance();
  113.     }    
  114. }





Как можно догадаться, переменная classes содержит в себе hex-представления уже скомпилированных java-файлов — при этом это могут быть как наши, собственноручно написанные классы, так и выдернутые из каких-нибудь библиотек.

Эти четыре класса сгенерированы из следующего кода:

  1. /* Application.java */
  2.  
  3. package com.example.classes;
  4.  
  5. import javax.swing.*;
  6.  
  7. public class Application {
  8.  
  9.     public Application() {
  10.         MainFrame frame = new MainFrame();
  11.         frame.setSize(300, 300);
  12.         frame.setVisible(true);
  13.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14.     }
  15.  
  16.     public static void main(String args[]) {
  17.         new Application();
  18.     }
  19. }



  1. /* MagicButtonListener.java */
  2.  
  3. package com.example.classes;
  4.  
  5. import javax.swing.*;
  6. import java.awt.*;
  7. import java.awt.event.ActionEvent;
  8. import java.awt.event.ActionListener;
  9.  
  10. public class MagicButtonListener implements ActionListener {
  11.     public void actionPerformed(ActionEvent e) {
  12.         float rand = System.currentTimeMillis() % 250;
  13.         ((JButton)e.getSource()).setBackground(Color.getHSBColor(rand, rand, rand));
  14.     }
  15. }



  1. /* MagicPanel.java */
  2.  
  3. package com.example.classes;
  4.  
  5. import javax.swing.*;
  6. import java.awt.*;
  7.  
  8. public class MagicPanel extends JPanel {
  9.     public MagicPanel() {
  10.         super(new GridLayout(3, 3, 10, 10));
  11.         for (int i = 0; i < 9; ++i) {
  12.             final JButton b = new JButton(String.valueOf(i));
  13.             b.addActionListener(new MagicButtonListener());
  14.             this.add(b);
  15.         }
  16.     }
  17. }



  1. /* MainFrame.java */
  2.  
  3. package com.example.classes;
  4.  
  5. import javax.swing.*;
  6. import java.awt.*;
  7.  
  8. public class MainFrame extends JFrame {
  9.  
  10.     public MainFrame() throws HeadlessException {
  11.         this.getContentPane().add(new MagicPanel());
  12.     }
  13. }




Когда мы запускаем наш класслоадер он пытается создать класс Application, при этом классы в первую очередь подгружаются из массива с нашими классами, и только в том случае, если нужный класс не был найден, делается попытка загрузить его стандартными класслоадером.

Вот собственно и все. Реальное применение такому решению найти сложно, но можно :) Например, можно в таком виде грузить файлы на Robocode, или заливать хитрые хранимые процедуры в oracle, или еще что-то в таком духе. А еще, мне кажется, если немного подумать, то можно сделать неплохой обфускатор.
______________________
Текст подготовлен в Хабра Редакторе от © SoftCoder.ru
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.