PlugIn System - GUI

mavinatic

Bekanntes Mitglied
Hallo, momentan arbeite ich an einem kleinen Projekt. Es ist eher nur ein TestProgramm. Es geht um folgenes:

Ich habe ein Hauptprogramm und möchte da PlugIns einfügen. Das ganze ist visuell auf einem JDesktopPane und JInternalFrame dargestellt. Jedes Plugin soll als JInternalFrame als eigenes Fenster dargestellt werden).

Jetzt mal zum vergleich:

Wir haben das Hauptprogramm Windows, darauf laufen verschiedene Applikationen wie Taschenrechner, Texteditoren,etc. die Applikationen sind in dem Fall die Plugins. Wie realisier ich das, dass ich von außerhalb bzw während der Runtime ein PlugIn installieren möchte, dass der das als JInternalFrame, quasi als eigenes Fenster anzeigt. Ich bin momentan sehr ratlos.

Ist es überhaupt möglich? Es muss ja nicht während der Runtime compliert werden, bei einem Neustart des Programms kann ja das PlugIn neugeladen werden.

Habt ihr vorschläge wie ich das Realisieren könnte?

Grüße George!
 
Das stellt keine besondere Schwierigkeit dar und die Umsetzung steht und fällt nur mit dem Aufwand, den Du treibst. Letztlich sind ja auch z. B. Applets und Servlets nichts anderes als Plug-Ins in einer VM. Es läuft darauf hinaus, OSGI nachzuauen und ich habe sowas schon ein, zwei mal implementiert.

Du könntest z. B.:

  • Zuerst ein [c]interface[/c] spezifizieren, dass ein Plugin implementieren muss und Dich dabei an dem von Applets von Applets oder Servlets orientieren. Eine Methode z. B. verbindlich machen, die aufgerufen wird, wenn sich das Plugin initialisieren soll. Beim Aufruf könnte der "Host" ein Objekt übergeben, in dem der "Kontext" übergeben wird, aus dem heraus das Plugin mit dem Host interagieren kann.

    Wenn Du es ganz elegant machen willst, kannst Du auch Reflection verwenden; dann hast Du eine Abhängigkeit weniger.
  • Vielleich sollte das Plugin eine Methode implementieren, die eine
    Code:
    Component
    zurückliefert und die das Fenster darstellt. Besser vielleicht im "Kontext"-Methoden bieten, die das Plugin aufrufen kann, um von sich selbst aus ein Fenster oder Menüpunkte registrieren zu können.
  • Interessant wird es, da i. A. der Code nicht im Klassenpfad der VM liegt, sondern in JAR Files auf der Platte oder im Netz, und deren Position z. B. nur in einer Properties-Datei steht. Da musst Du einen eigenen Klassenlader schreiben, der schlau genug ist,
    • um einen JARInputStream vollständig zu lesen
    • alle enthaltenen Klassen und Ressourcen zu cachen und
      Code:
      defineClass()
      richtig anzuwenden
    • die Aufrufe zu findClass/findRessource richtig zu delegieren oder selbst zu verarbeiten.
    Als Konvention sollte vielleicht in jedem JAR-File genau eine Klasse das besondere Interface implementieren oder der Namenskonvention dazu genügen, damit der Klassenlader die Klasse mit dem init-Code findet. Ausserdem sollte der ClassLoader eine Methode haben, die genau eine Instanz des Plugins liefert.

    Wenn Du das richtig sauber machst, kann man sogar Klassen undeployen, wenn man alle Referenzen auf alle neu erzeugten Objekte des Klassen sauber sammelt und inklusive Klassenlader auf "null" setzt.

    Dieses Kapitel hat mir sehr geholfen, den Klassenlader zu implementieren:
    Java Class Loaders (Java Security)
 
Zuletzt bearbeitet:
Wow. Ich habe nicht viel verstanden.

Ich sage dir nur kurz warum ich es in das "Anfänger"-Forum geschrieben habe - Ich bin einer ;-). Sei mir nicht böse ich habe noch nicht so einen Fachwortschatz wie du es hast. Evtl. etwas anfängerfreundlicher schreiben. Das mit dem Interface habe ich verstanden, aber den Rest den du von mir wolltest nicht wirklich

Entschuldigung.

Gruß George
 
Ich sage dir nur kurz warum ich es in das "Anfänger"-Forum geschrieben habe - Ich bin einer ;-).
dann werde ich Dir mal Dein Selbstvertrauen zerstören ... mach was anderes mit Java um es zu lernen ... Plugin-System ist nicht wirklich trivial ... zumal Du schon alleine von der Hierarchie anders denken musst als bei einem "normalen" Programm ... dazu kommt Reflection um die anderen Klassen dynamisch aus den weiteren JARs zu laden

hand, mogel
 
Lerne Eclipse RCP, das kann was du willst.
Ansonsten ist das wirklich kein Thema für einen Anfänger.
 
Ich weiß das du meine Illusion zerstören willst! Aber ich habe schon ne "Menge" erfahrung an normalen Programmen. Habe einen Mail-Client Programmiert, Adressbücher, Server-Client-Anwendungen z.B. JavaChat... Ich habe die Erfahrung denke ich einen Schritt weiterzugehen! Ich weiß selber das das nun ein gewaltiger Sprung ist, ich will aber genau an so etwas kauen, es mangelt denk ich nicht an dem fehlenden Grundlagen.

Gib mir ne Chance auf diesem Weg die erfahrung zu machen, ich habs meinem Lehrer bewiesen, ich habs mir bewiesen dass ich zu "neuen Wissen" gut erreichbar werde(Ich eigne mir alles an auch wenns über 10 Umwege ist).

Back to the Roots ;-)
Ich kann doch einfach zB meinen Ordner "plugins" scannen alle *.java files lassen und JInternalFrame objekt übergeben lassen. Es muss ja nicht in Runtime compliert werden.


Grüße George
 
Zuletzt bearbeitet:
Ich weiß das du meine Illusion zerstören willst! Aber ich habe schon ne "Menge" erfahrung an normalen Programmen. Habe einen Mail-Client Programmiert, Adressbücher, Server-Client-Anwendungen z.B. JavaChat... Ich habe die Erfahrung denke ich einen Schritt weiterzugehen!
also bist Du doch kein Anfänger 😉 ... dann ist natürlich berechtigt die nächste Eben des Seins erreichen zu wollen 😀

dann mal ein paar Links

http://www.java-forum.org/allgemeine-java-themen/94797-java-kaputt.html ... zum Verständniss über das Verhalten des Classloaders ganz interessant

Calculon - All My Plugins ... ist zwar in VB.Net - aber Basic ist ja leicht zu lesen ... das Prinzip ist das was für Dich von Interesse sein sollte ... wenn Du das in Java umgesetzt hast, kannst Du es ja erweitern indem Du ein JPanel (oder halt Component) über das Interface durchreichst und in der GUI anzeigst

hand, mogel
 
Ich hab mal bisschen durchs internet gesurft und gebastelt...

was hälst du hier von?!

Java:
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

import Interfaces.plugin;

public class Main {
	private void scanDir() throws IOException	//Nur JavaFiles einlesen.
, InstantiationException, IllegalAccessException, ClassNotFoundException
	{
		File fDir = new File("src/plugins");
		File[] fileList = fDir.listFiles();
		
		readFile(fileList);
	}
	
	private void readFile(File[] fileList) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
	{
		File file = new File(fileList[0].getAbsolutePath());
		ClassLoader fileLoader = URLClassLoader.newInstance(new URL[] { file.toURL() });
		plugin filePlugin = (plugin) fileLoader.loadClass("plugins.HelloWorld").newInstance();
		filePlugin.run();
	}
	/**
	 * @param args
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
		// TODO Auto-generated method stub
		Main m = new Main();
		m.scanDir();
	}

}
Ist ein bisschen unübersichtlich aber Dient ja nur zum Testen!!

In Plugin Interface ist nur die Methode run();

Java:
package plugins;

import Interfaces.plugin;

public class HelloWorld implements plugin {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("hallo welt!");
	}
}

Also ganz ehrlich ich finds gut 😀 aber eine Frage habe ich noch kann ich durch das Interface, die Methode run(JInternalFrame frame) übergeben lassen oder ist das NICHT möglich, weil eclipse zeigt bei mir das als fehler an!?

Grüße und VIELEN VIELEN VIELEN DANK!
 
Mit dieser Lösung wirst Du wenig Spass haben:
  • Deine Plugins werden wohl aus mehr als einer Klasse bestehen...
  • ...und in einem JAR File ausgeliefert.
  • Der Klassenlader muss die Zielklasse finden und puffern können
  • und wenn die Plugin Klassen selbst instanziiert werden, die Aufrufe an die übergeordneten Klassenlader richtig delegieren.

Der Code ist an vielen Stellen verbesserungswürdig, aber sollte zum Experimentieren reichen und kann genau, was Du willst.

Host:
Java:
package demo.host.gui;

import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;

import demo.host.loader.JARClassLoader;

@SuppressWarnings("serial")
public class DesktopWindow extends JFrame {
	
	private static final Logger logger = Logger.getLogger(DesktopWindow.class.getSimpleName());
	
	public DesktopWindow() 
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setContentPane(new JDesktopPane());
		
		final MenuItem openMenuItem = new MenuItem("Open...");
		openMenuItem.addActionListener(new ActionListener() {			
			@Override
			public void actionPerformed(ActionEvent e) {
				loadPlugin();
			}
		});
		
		final Menu fileMenu = new Menu("File");
		fileMenu.add(openMenuItem);
		
		final MenuBar menuBar = new MenuBar();
		menuBar.add(fileMenu);
		
		setMenuBar(menuBar);
	}
	
	public void loadPlugin() {
		final JFileChooser fileChooser = new JFileChooser();
		if(fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
			try {
				registerPlugin(fileChooser.getSelectedFile());
			} catch (Throwable t) {
				logger.log(Level.SEVERE, "Could not load Plugin: "+ t.getMessage());
				t.printStackTrace();
			}
		}
	}
	
	public void registerPlugin(final File file) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {		
		final FileInputStream fis = new FileInputStream(file);
		final JARClassLoader cl = new JARClassLoader(getClass().getClassLoader());
		cl.loadJARBundle(fis);
		final JComponent internalComponent = cl.getPluginSingleton().getComponent();
		getContentPane().add(internalComponent);
		internalComponent.setSize(200,200);
		internalComponent.setVisible(true);				
	}
	
	public static void main(String[] args) {
		final DesktopWindow wnd = new DesktopWindow();
		wnd.setSize(400, 300);
		wnd.setVisible(true);
	}
}

Klassenlader (der mit beliebig vielen Klassen klarkommt und richtig delegiert)
Java:
package demo.host.loader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import demo.host.pluginapi.PluginAPI;

public class JARClassLoader extends ClassLoader {
	
	private PluginAPI pluginSingleton = null;

	private final Map<String, byte[]> classMap = new HashMap<String, byte[]>();

	public JARClassLoader(final ClassLoader parent) {
		super(parent);
	}

	private byte[] readFully(final InputStream is) throws IOException {
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
		final byte[] tmp = new byte[256];
		int n;
		while (-1 != (n = is.read(tmp))) {
			bos.write(tmp, 0, n);
		}

		return bos.toByteArray();
	}

	public void loadJARBundle(final InputStream is) throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, IOException {
		final JarInputStream jarInputStream = new JarInputStream(is);

		JarEntry entry;
		while ((entry = jarInputStream.getNextJarEntry()) != null) {

			final String name = entry.getName();

			if (name.endsWith(".class")) {
				final String className = name.replaceAll("/", "\\.").substring(0, name.length() - ".class".length());
				synchronized(classMap) {
					classMap.put(className, readFully(jarInputStream));
				}
			}
		}

		detectPluginClass();
	}

	private void detectPluginClass() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		synchronized (classMap) {
			for (Map.Entry<String, byte[]> classMapEntriy : classMap.entrySet()) {
				final Class<?> clazz = Class.forName(classMapEntriy.getKey(), true, this);
				if (PluginAPI.class.isAssignableFrom(clazz)) {
					pluginSingleton = (PluginAPI) clazz.newInstance();
				}
			}	
		}		
	}

	public PluginAPI getPluginSingleton() {
		return pluginSingleton;
	}

	@Override
	protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
		Class<?> c;

		c = findLoadedClass(name);

		if (c != null) {
			return c;
		}

		try {
			final ClassLoader parent = getParent();

			if (parent != null) {
				c = parent.loadClass(name);
			} else {
				c = findSystemClass(name);
			}
		} catch (final ClassNotFoundException e) {
			;
		}

		if (c != null) {
			return c;
		}
		
		synchronized (classMap) {
			if (classMap.containsKey(name)) {
				final byte[] classBytes = classMap.get(name);
				c = defineClass(name, classBytes, 0, classBytes.length);

				if (resolve) {
					resolveClass(c);
				}

				return c;
			}
		}
		throw new ClassNotFoundException(getClass() + ": class " + name + " not found.");
	}
}

Minimale Plugin-API
Java:
package demo.host.pluginapi;

import javax.swing.JComponent;

public interface PluginAPI {
	public JComponent getComponent();
}

Demo-Plugin aus mehr als einer Klasse. Du musst ein JAR-File bauen:
Java:
package demo.plugin;

import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JComponent;
import javax.swing.JInternalFrame;

import demo.host.pluginapi.PluginAPI;

@SuppressWarnings("serial")
public class MyPlugin extends JInternalFrame implements PluginAPI {

	private int x; 
	private int y;
	
	public MyPlugin () {
		super("I am a plugin", true, false, true);
		
		addMouseMotionListener(new MouseMotionAdapter() {	
			@Override
			public void mouseMoved(MouseEvent e) {
				x = e.getX();
				y= e.getY();			
				
				repaint();
			}		
		});
	}
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		g.drawString(getClass().getSimpleName()+" mouse @"+x+":"+y, 50, 50);
	}

	@Override
	public JComponent getComponent() {
		return this;
	}
}
 
Zuletzt bearbeitet:

Zurück
Oben