`
ydbc
  • 浏览: 778736 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Java URLClassLoader实现插件功能开发

 
阅读更多

插件(Plugin)是什么不用多说,用过Eclipse就知道Eclipse有很多插件。但本文的内容不是Eclipse插件开发。

插件是根据软件提供的接口编写出来的程序,很多软件都支持插件,例如Eclipse、Photoshop、VisualStudio。插件可以动态给软件添加一些功能,也可以随时删除,这样的好处是任何人都可以给这个软件进行功能上的扩展,而不用去改软件本身的代码。

一、适用场景

比如需要开发一个系统,用来将一些有数据推送给客户,至于是什么数据不是重点。有三个客户:A客户需要把数据组织成一个xml格式的文件,通过FTP上传到客户服务器上;B客户需要把数据组织成一个json,通过HTTP请求提交;C客户希望生成一个Excel文件再通过E-mail发送...以后可能还会有更多的客户,也还会有更多操蛋的需求。

对于这样一个系统的开发,如果使用普通的方式开发,那么每增加一个客户就要修改一次系统代码,在代码中增加一个针对某个客户的功能,很不灵活。如果再减少一个客户,那么其对应的代码也就没有用了,是不是要删除掉又成了问题。

以上只是一个例子,在实际开发中经常会有类似的情形,此时使用插件化的方式会更灵活。

可以把数据的获取和整理这块和客户无关的逻辑放在主程序中,而主程序提供一个客户推送的接口,接口定义一个未实现的抽象方法“推送数据”,这个方法由各个客户对应的插件来实现。这样新增一个客户需求,不需要修改主程序的代码,只需要实现这个接口就行,插件写好打成jar包放在指定目录下,再配置一下,主程序就可以使用这个插件了。当不需要这个插件,也可以通过配置来去掉它。

二、主程序配置插件

上面说到主程序可以通过配置来动态添加和删除插件,配置的方式一般有两种:XML或数据库,二者选其一即可。

方法1:XML

主程序可以通过一个xml配置文件,动态配置插件。

<?xml version="1.0" encoding="UTF-8"?>
<plugins>
	<plugin>
		<name>A客户插件</name>
		<jar>D:/plugin/a-plugin.jar</jar>
		<class>com.xxg.aplugin.APlugin</class>
	</plugin>
	<plugin>
		<name>B客户插件</name>
		<jar>D:/plugin/b-plugin.jar</jar>
		<class>com.xxg.bplugin.BPlugin</class>
	</plugin>
	<plugin>
		<name>C客户插件</name>
		<jar>D:/plugin/c-plugin.jar</jar>
		<class>com.xxg.cplugin.CPlugin</class>
	</plugin>
</plugins>

主程序通过解析这个XML来调用插件,<plugin>元素即一个插件,可以通过添加和删除<plugin>元素来动态的添加和删除插件。<name>是插件名称,<jar>是插件jar文件所在的路径,<class>是插件实现主程序接口的类。

方法2:数据库

如果使用数据库来配置插件,需要一个插件表:

插件表(plugin_info):

id

int

主键

name

varchar

插件名称

jar

varchar

插件jar文件路径

class

varchar

实现主程序接口的类的包名加类名



两种方法的区别:

两种方式从功能上来说是一样的。使用数据库方式的好处是可以很方遍的再开发一个管理界面来管理,不好的地方就是依赖数据库。我更推荐XML的方式,不依赖数据库。

三、主程序

下面是以XML作为插件配置方式的调用插件的主程序。

主程序需要提供一个接口来提供给插件开发者来实现:

package com.xxg.main;

public interface PluginService
{
	public void service();
}

上面是一个接口,包含一个未实现的方法service(),这个方法即和客户相关的逻辑,由插件来实现。

插件封装类:

package com.xxg.main;

public class Plugin
{
	private String name;
	
	private String jar;
	
	private String className;
	
	// setter、getter省略…
}

解析XML获取所有插件信息(这里用到DOM4J):

package com.xxg.main;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class XMLParser
{
	public static List<Plugin> getPluginList() throws DocumentException
	{
		List<Plugin> list = new ArrayList<Plugin>();
		
		SAXReader saxReader =new SAXReader();
		Document document = saxReader.read(new File("plugin.xml"));
		Element root = document.getRootElement();
		List<?> plugins = root.elements("plugin");
		for(Object pluginObj : plugins)
		{
			Element pluginEle = (Element)pluginObj;
			Plugin plugin = new Plugin();
			plugin.setName(pluginEle.elementText("name"));
			plugin.setJar(pluginEle.elementText("jar"));
			plugin.setClassName(pluginEle.elementText("class"));
			list.add(plugin);
		}
		
		return list;
	}
}

使用URLClassLoader动态加载jar文件,实例化插件中的对象:

package com.xxg.main;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class PluginManager
{
	private URLClassLoader urlClassLoader;

	public PluginManager(List<Plugin> plugins) throws MalformedURLException
	{
		init(plugins);
	}
	
	private void init(List<Plugin> plugins) throws MalformedURLException
	{
		int size = plugins.size();
		URL[] urls = new URL[size];
		
		for(int i = 0; i < size; i++)
		{
			Plugin plugin = plugins.get(i);
			String filePath = plugin.getJar();

			urls[i] = new URL("file:" + filePath);
		}
		
		// 将jar文件组成数组,来创建一个URLClassLoader
		urlClassLoader = new URLClassLoader(urls);
	}
	
	public PluginService getInstance(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException
	{
		// 插件实例化对象,插件都是实现PluginService接口
		Class<?> clazz = urlClassLoader.loadClass(className);
		Object instance = clazz.newInstance();

		return (PluginService)instance;
	}
}

main函数依次调用插件逻辑:

package com.xxg.main;

import java.util.List;

public class Main
{
	public static void main(String[] args)
	{
		try
		{
			List<Plugin> pluginList = XMLParser.getPluginList();
			PluginManager pluginManager = new PluginManager(pluginList);
			for(Plugin plugin : pluginList)
			{
				PluginService pluginService = pluginManager.getInstance(plugin.getClassName());
				System.out.println("开始执行[" + plugin.getName() + "]插件...");
				// 调用插件
				pluginService.service();
				System.out.println("[" + plugin.getName() + "]插件执行完成");
			}
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
}

四、插件开发

插件开发很简单,只需要把主程序的jar包引入到项目中,再实现主程序提供的接口就行:

package com.xxg.aplugin;

import com.xxg.main.PluginService;

public class APlugin implements PluginService
{
	@Override
	public void service()
	{
		System.out.println("A客户插件正在执行~");
	}
}

service()方法应该实现客户相关的逻辑,即实现插件的功能。这里就用一句System.out.println来代替。

插件实现完成后,打个jar包,注意不要把主程序的部分也打到jar里。

再实现其他插件,插件实现完成后,配置主程序的plugin.xml。

五、执行结果

配置好plugin.xml,插件jar放到配置的路径下:


运行主程序main方法:

开始执行[A客户插件]插件...

A客户插件正在执行~

[A客户插件]插件执行完成

开始执行[B客户插件]插件...

B客户插件正在执行~

[B客户插件]插件执行完成

开始执行[C客户插件]插件...

C客户插件正在执行~

[C客户插件]插件执行完成

六、service()参数、返回值

如果逻辑需要的话,service()可以添加参数和返回值。例如主程序需要传入数据给插件,可以加入参数,插件需要返回结果给主程序,可以加入返回值。

例如传给插件一些插件需要的配置项。在上面的场景中,各个客户的需求不同。A需要FTP上传,那么需要FTP服务器的地址、端口号、用户名、密码配置项;B需要HTTP请求,那么需要请求地址配置项;C需要发送邮件,那么需要e-mail地址配置项。

这些配置项可以统一配置在XML或数据库中。

XML:

每个插件元素中加入<properties>元素来配置。

<?xml version="1.0" encoding="UTF-8"?>
<plugins>
	<plugin>
		<name>A客户插件</name>
		<jar>D:/plugin/a-plugin.jar</jar>
		<class>com.xxg.aplugin.APlugin</class>
		<properties>
			<property name="FTP_IP">192.168.7.1</property>
			<property name="FTP_PORT">21</property>
			<property name="FTP_USERNAME">XXG</property>
			<property name="FTP_PASSWORD">123456</property>
		</properties>
	</plugin>
	<plugin>
		<name>B客户插件</name>
		<jar>D:/plugin/b-plugin.jar</jar>
		<class>com.xxg.bplugin.BPlugin</class>
		<properties>
			<property name="URL">http://www.xxg.com/api</property>
		</properties>
	</plugin>
	<plugin>
		<name>C客户插件</name>
		<jar>D:/plugin/c-plugin.jar</jar>
		<class>com.xxg.cplugin.CPlugin</class>
		<properties>
			<property name="EMAIL">xxg@xxg.com</property>
		</properties>
	</plugin>
</plugins>

数据库:

再加一个插件配置表(plugin_config_info):

id

int

主键

plugin_id

int

外键,关联插件表id

key

varchar

配置项键

value

varchar

配置项值


主程序定义接口,加入一个Map<String,String>参数来传入这些配置:

package com.xxg.main;

import java.util.Map;

public interface PluginService
{
	public void service(Map<String,String> configs);
}

在插件中,可以获取这些配置:

package com.xxg.aplugin;

import java.util.Map;

import com.xxg.main.PluginService;

public class APlugin implements PluginService
{
	@Override
	public void service(Map<String, String> configs)
	{
		String ftpIp = configs.get("FTP_IP");
		String ftpPort = configs.get("FTP_PORT");
		String ftpUsername = configs.get("FTP_USERNAME");
		String ftpPassword = configs.get("FTP_PASSWORD");
		
		// ...
	}
}

插件获取的配置项首先应该判断是否为null,防止忘了添加一些配置项。


作者:叉叉哥 转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/9239743


分享到:
评论

相关推荐

    Java URLClassLoader动态加载jar包1

    这个功能在开发插件系统、热部署或者处理多种版本库的场景中非常有用。从JDK 1.2开始,`java.net.URLClassLoader`就被引入,以支持从网络或者其他支持URL的来源加载类。 `URLClassLoader`的主要工作原理是通过URL...

    java动态加载插件化编程详解

    java动态加载插件化编程是指在Java程序中动态加载插件,以实现插件化功能。以下是本文中介绍的知识点: 1. 插件化编程:插件化编程是一种软件设计模式,它允许开发者在不修改原有代码的情况下添加新功能。插件化...

    URLClassLoader初体验

    `URLClassLoader`在实际开发中有很多用途,比如在插件系统、动态部署或测试框架(如JUnit)中,它允许我们灵活地加载和卸载类,而不影响主应用程序的其他部分。此外,理解`URLClassLoader`的工作原理对于理解Java的...

    JAVA项目开发案例全程实录(第2版)jar包资源下载

    5. **动态加载**:Java允许在运行时动态加载jar包,这在插件系统或需要按需加载功能的场景中非常常见。Java的`URLClassLoader`类提供了这种能力。 6. **混淆与反混淆**:对于发布的产品,开发者可能需要对jar包进行...

    通过自定义Gradle插件修改编译后的class文件

    在Java开发中,Gradle是一种广泛应用的构建自动化工具,它允许开发者通过编写Groovy或Kotlin DSL脚本来管理项目的构建过程。自定义Gradle插件是Gradle的强大特性之一,可以扩展其功能以满足特定项目需求。本篇将详细...

    动态编译、加载java类

    这种能力对于开发灵活性高、可扩展性强的应用程序非常有用,比如在服务器端处理动态生成的代码、插件系统或者在运行时根据用户需求定制功能等场景。下面我们将详细探讨动态编译和加载Java类的相关知识点。 1. **...

    java8 动态加载示例

    动态加载在Java中的一个重要应用就是实现插件系统。通过动态加载,插件可以被独立地开发、测试和部署,而不必修改主程序的代码。这不仅提高了软件的可维护性,也增强了系统的灵活性。 此外,动态加载还常见于热部署...

    java reflection demo

    在Java中,我们通常通过`URLClassLoader`或`ClassLoader`的子类来实现动态加载外部JAR包。例如,假设我们有一个名为`PrintInterface.jar`的库,其中包含一个实现了特定接口的类。我们可以通过以下方式加载这个JAR并...

    JAVA文件中调用已编译的.CLASS的类.doc

    Java允许我们通过自定义类加载器或者使用内置的`ClassLoader`来实现这一功能。以下是一份详细的步骤和知识点解释: 1. **创建Java接口和实现**: - 首先,定义一个公共接口,例如`ActionInterface`,它包含一个...

    Java类加载器(ClassLoader)1

    Java类加载器(ClassLoader)是...理解类加载器的工作机制对于进行JVM优化、插件开发、安全控制等高级Java应用至关重要。通过自定义类加载器,开发者可以控制类的加载过程,满足特定需求,比如模块化系统、热部署等。

    java8 动态加载jar包至系统的classpath的例子

    在Java编程中,类加载机制是整个Java平台的核心机制之一。类加载器负责从文件系统或网络中加载Class文件,Class文件...在实际开发中,建议深入理解Java类加载机制,并仔细测试动态加载功能,以确保应用的稳定性和性能。

    Java反射机制测试Demo

    总结来说,Java反射机制提供了一种强大的手段,让我们可以在运行时动态地获取类的信息并操作对象,这对于实现元编程、插件化、代码自省等高级功能具有重要作用。在学习和使用时,我们需要理解其原理,合理运用,避免...

    URLClass工程之间反射机制的实现

    在实际开发中,反射和`URLClassLoader`结合使用可以解决许多问题,比如插件系统、热部署等。然而,需要注意的是,反射操作通常比直接调用方法慢,因此应当谨慎使用,避免过度依赖反射,尤其是在性能敏感的场景下。 ...

    动态加载jar

    开发者可以编写独立的插件模块,以JAR的形式存在,主应用在运行时根据需求加载相应的插件,实现功能扩展。 2. **热修复**:在应用出现严重问题时,可以通过动态加载修复模块的JAR文件,对出现问题的部分进行修复,...

    jar 包 内文件读取 文件内容

    - 开发环境中,像Eclipse和IntelliJ IDEA这样的IDE提供了方便的功能来查看和编辑JAR文件的内容,这对调试和开发非常有用。 8. **安全性和权限**: - 访问JAR文件时要考虑安全性,确保拥有正确的权限。在Java安全...

    springboot动态加载jar包到容器中,并映射为接口

    在实际的开发过程中,我们经常会遇到这样的需求:系统需要根据不同的业务场景来动态地加载不同的功能模块或者组件。这些功能模块可能以独立的 Jar 匼形式存在。Spring Boot 作为一款轻量级的 Spring 开发框架,提供...

    classloader-study.zip

    在Java编程语言中,`ClassLoader`是一个至关重要的组件,它负责加载类到...通过研究"**classloader-study.zip**"的内容,你将有机会提升对Java动态加载和卸载类的理解,这对于开发更高级的Java应用程序和框架至关重要。

    关于Android中自定义ClassLoader耗时问题的追查

    Android中类加载器有BootClassLoader,URLClassLoader, PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最终继承自java.lang.ClassLoader 最近在优化西瓜视频客户端冷启动速度时,发现在关闭插件 ...

    浅谈Android Classloader动态加载分析

    总之,Android ClassLoader机制是Android系统运行和应用程序扩展的关键部分,它提供了动态加载类的能力,使得热更新、插件化等高级功能得以实现。理解ClassLoader的工作原理对于Android开发人员来说至关重要,尤其是...

Global site tag (gtag.js) - Google Analytics