Давайте посмотрим, как такое приложение может быть организовано:
Тут Plugin API Library - библиотека, включающая в себя набор интерфейсов, определяющих методы, котрорые ядро приложения будет использовать для доступа к функционалу плагина. В нашем, простейшем, случае она содержит только один интерфейс:
package net.multipi.jd.api;
import javax.swing.JButton;
public interface Plugin {
public JButton getButton();
}
Эту библиотеку мы включим во все плагины и поставим разработчикам плагинов только одно условие: их библиотека должна содержать класс, реализующий наш интерфейс. Ни название класса ни имя пакета значения не имеют. Также нам безразлична внутренняя структура плагина: он может иметь сколько угодно классов и пакетов.Сделаем простую Plugin Library: она по клику на кнопку выведет на экран MessageBox. Опять только один класс:
package net.multipi.jd.test;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import net.multipi.jd.api.Plugin;
public class PluginImpl implements Plugin {
@Override
public JButton getButton() {
JButton button = new JButton("Test");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(null, "Message from plugin");
}
});
return button;
}
}
А теперь самое интересное: как плагины "включаются" в приложение? Для загрузки плагинов мы в модуле Application core используем такой код:
package net.multipi.jd.plugin;
import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import net.multipi.jd.api.Plugin;
public class PluginFactory {
public static ArrayList<Plugin> getPlugins() {
ArrayList<Plugin> rez = new ArrayList<Plugin>();
File pluginDir = new File("plugins");
File[] jars = pluginDir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".jar");
}
});
for (int i = 0; i < jars.length; i++) {
try {
URL jarURL = jars[i].toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{jarURL});
JarFile jf = new JarFile(jars[i]);
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
String e = entries.nextElement().getName();
if (!e.endsWith(".class")) continue;
e = e.replaceAll("/", ".");
e = e.replaceAll(".class", "");
Class<?> plugCan = classLoader.loadClass(e);
Class<?>[] interfaces = plugCan.getInterfaces();
for (Class interf : interfaces) {
if (interf.getName().endsWith(".Plugin")) {
Class c = classLoader.loadClass(plugCan.getName());
Object inst = c.newInstance();
rez.add((Plugin)inst);
}
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
return rez;
}
}
Рассмотрим подробнее, что тут происходит.Во-первых, мы вычитываем список .jar файлов из директории "plugins" в массив
File[] jars.
Во вторых, для каждого найденного файла мы создаём отдельный URLClassLoader. Зачем? А представьте себе, что разработчики, не зная о существовании друг друга, случайно использовали одинаковые имена пакетов и классов в своих плагинах. Если все плагины будут загружены одним ClassLoader-ом, неизбежен конфликт. Разные ClassLoader-ы - наша защита от таких коллизий. В-третьих, нам нужно найти стартовый класс плагина, но сначала найдём все классы в jar. Для этого мы используем класс JarFile, позволяющий получить список содержимого дл каждой из библиотек. Все найденные файлы с расширением .class мы загружаем нашим UrlClassLoader-ом и получаем для каждого из них список реализуемых интерфейсов. Класс, который реализует интерфейс Plugin и будет нашим искомым "стартовым" классом. Получаем его инстанс и добавляем в список.
Теперь, когда плагины загружены осталось вызвать у каждого метод для получения кнопки (объявленный в API-интерфейсе) и отрисовать форму приложения:
package net.multipi.jd; import java.awt.FlowLayout; import java.awt.Toolkit; import java.util.ArrayList; import javax.swing.UIManager; import net.multipi.jd.api.Plugin; import net.multipi.jd.plugin.PluginFactory; public class MainJFrame extends javax.swing.JFrame { public MainJFrame() { try { javax.swing.UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { ex.printStackTrace(System.err); } setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setSize(400, 300); getContentPane().setLayout(new FlowLayout()); createDesktop(); } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new MainJFrame().setVisible(true); } }); } private void createDesktop() { ArrayList<Plugin> plugins = PluginFactory.getPlugins(); for (Plugin p : plugins) { this.getContentPane().add(p.getButton()); } } }
В результате получаем:
Спасибо. Но хотелось бы увидеть более реалистичный пример применения плагинов. Как именно они могут использоваться в реальном приложении?
ОтветитьУдалитьпросто и понятно, спасибо.
ОтветитьУдалитьне-а, не работает.
ОтветитьУдалитьjava.lang.ClassCastException: net.multipi.jd.test.PluginImpl cannot be cast to net.multipi.jd.api.Plugin