热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java自定义类加载器实现插件式开发

最近接触Solr比较多,感觉Solr提供的插件式开发方式很酷,Solr对开发者提供了一个核心apijar包,开发者如果想扩展Solr某一项功能比如中文分词,只需要继承Solr提供的分词接口添加自

最近接触Solr比较多,感觉Solr提供的插件式开发方式很酷,Solr对开发者提供了一个核心api jar包,开发者如果想扩展Solr某一项功能 比如 中文分词,只需要继承Solr提供的分词接口添加自己的实现,然后把自己的分词jar包拷贝到Solr指定目录,并在solr配置文件中配置,重启即可生效。


本文会涉及到自定义类加载,所以先介绍一下java类加载器的原理和工作机制,熟悉的同学可以直接跳过。


java类加载器

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器所负责加载的类也各不相同。
JVM三种预定义类型类加载器

  1. Bootstrap ClassLoader : 将存放于\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用
  2. Extension ClassLoader : 将\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
  3. Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。


一句话总结就是:查找某个类的时候从下至上,加载某个类的时候从上至下。


自定义类加载器

因为我们需要加载指定路径下的jar文件,所以我们需要自定义类加载器来扫描指定路径下的jar包,代码如下:

package com.bytebeats.switcher.core;

import com.bytebeats.switcher.util.IoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

/**
*
* @author Ricky Fung
* @create 2016-11-12 14:27
*/
public class PluginClassLoader {

private final Logger logger = LoggerFactory.getLogger(PluginClassLoader.class);

private URLClassLoader classLoader;

public PluginClassLoader(String jarfileDir){
this(new File(jarfileDir), null);
}
public PluginClassLoader(File jarfileDir){
this(jarfileDir, null);
}
public PluginClassLoader(File jarfileDir, ClassLoader parent) {
this.classLoader = createClassLoader(jarfileDir, parent);
}

public void addToClassLoader(final String baseDir, final FileFilter filter,
boolean quiet) {

File base = new File(baseDir);

if (base != null && base.exists() && base.isDirectory()) {
File[] files = base.listFiles(filter);
if (files == null || files.length == 0) {
if (!quiet) {
logger.error("No files added to classloader from lib: "
+ baseDir + " (resolved as: "
+ base.getAbsolutePath() + ").");
}
} else {
this.classLoader = replaceClassLoader(classLoader, base, filter);
}
} else {
if (!quiet) {
logger.error("Can't find (or read) directory to add to classloader: "
+ baseDir
+ " (resolved as: "
+ base.getAbsolutePath()
+ ").");
}
}
}

private URLClassLoader replaceClassLoader(final URLClassLoader oldLoader,
final File base, final FileFilter filter) {

if (null != base && base.canRead() && base.isDirectory()) {
File[] files = base.listFiles(filter);

if (null == files || 0 == files.length){
logger.error("replaceClassLoader base dir:{} is empty", base.getAbsolutePath());
return oldLoader;
}

logger.error("replaceClassLoader base dir: {} ,size: {}", base.getAbsolutePath(), files.length);

URL[] oldElements = oldLoader.getURLs();
URL[] elements = new URL[oldElements.length + files.length];
System.arraycopy(oldElements, 0, elements, 0, oldElements.length);

for (int j = 0; j try {
URL element = files[j].toURI().normalize().toURL();
elements[oldElements.length + j] = element;

logger.info("Adding '{}' to classloader", element.toString());

} catch (MalformedURLException e) {
logger.error("load jar file error", e);
}
}
ClassLoader oldParent = oldLoader.getParent();
IoUtils.closeQuietly(oldLoader); // best effort
return URLClassLoader.newInstance(elements, oldParent);
}

return oldLoader;
}

private URLClassLoader createClassLoader(final File libDir,
ClassLoader parent) {
if (null == parent) {
parent = Thread.currentThread().getContextClassLoader();
}
return replaceClassLoader(
URLClassLoader.newInstance(new URL[0], parent), libDir, null);
}

public Class loadClass(String className) throws ClassNotFoundException{

return classLoader.loadClass(className);
}
}


为了方便使用, 提供了PluginManager

package com.bytebeats.switcher.core;

import com.bytebeats.switcher.util.StringUtils;
import java.io.File;

/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-12 14:35
*/
public class PluginManager {
private volatile static PluginManager mgr;
private PluginClassLoader pluginClassLoader;
private volatile boolean init;

private PluginManager(){

}

public static PluginManager getMgr(){
if(mgr==null){
synchronized (PluginManager.class){
if(mgr==null){
mgr = new PluginManager();
}
}
}
return mgr;
}

public T getPlugin(String className, Class required){
Class cls = null;
try {
cls = pluginClassLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("can not find class:"+className, e);
}
if(required.isAssignableFrom(cls)){
try {
return (T) cls.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("can not newInstance class:"+className, e);
}
}
throw new IllegalArgumentException("class:"+className+" not sub class of "+required);
}

public void addExternalJar(String basePath){
if (StringUtils.isEmpty(basePath)) {
throw new IllegalArgumentException("basePath can not be empty!");
}
File dir = new File(basePath);
if(!dir.exists()){
throw new IllegalArgumentException("basePath not exists:"+basePath);
}
if(!dir.isDirectory()){
throw new IllegalArgumentException("basePath must be a directory:"+basePath);
}

if(!init){
init= true;
pluginClassLoader = doInit(basePath);
}else{
pluginClassLoader.addToClassLoader(basePath, null, true);
}

}

private synchronized PluginClassLoader doInit(String basePath){
PluginClassLoader pluginClassLoader = new PluginClassLoader(basePath);
return pluginClassLoader;
}
}


插件式开发示例

下面通过一个简单例子来演示如何实现插件式开发



example-api存放核心api接口,example-plugin是插件工程 提供example-api api接口实现,example-host是主程序 它在启动的时候会去加载plugin。


example-api中的api接口如下:

package com.bytebeats.switcher.example.api;

/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-12 15:30
*/
public interface IHelloService {

String hello(String msg);
}



package com.bytebeats.switcher.example.api;

import com.bytebeats.switcher.example.domain.User;

import java.util.List;

/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-12 16:09
*/
public interface IUserService {

List getUsers();

int update(User user);

}


example-plugin中的api接口实现如下:

package com.bytebeats.example.plugin;

import com.bytebeats.switcher.example.api.IHelloService;

/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-12 16:13
*/
public class HelloServiceImpl implements IHelloService {

@Override
public String hello(String msg) {
System.out.println("hello [" + msg + "]");
return "hello [" + msg + "]";
}
}

package com.bytebeats.example.plugin;

import com.bytebeats.switcher.example.api.IUserService;
import com.bytebeats.switcher.example.domain.User;

import java.util.ArrayList;
import java.util.List;

/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2016-11-12 16:14
*/
public class UserServiceImpl implements IUserService {

@Override
public List getUsers() {
List list = new ArrayList<>();
list.add(new User("ricky", "12345"));
list.add(new User("kobe", "aaa"));
list.add(new User("jordan", "root"));
return list;
}

@Override
public int update(User user) {
System.out.println("update user = [" + user + "]");
user.setPassword("hello");
return 1;
}
}



example-host中的启动类如下:

package com.bytebeats.example.host;

import com.bytebeats.switcher.core.PluginManager;
import com.bytebeats.switcher.example.api.IHelloService;
import com.bytebeats.switcher.example.api.IUserService;
import com.bytebeats.switcher.example.domain.User;

import java.util.List;

/**
* Hello world!
*
*/
public class App {

public static void main( String[] args ) {

PluginManager pluginManager = PluginManager.getMgr();
pluginManager.addExternalJar("D:\\osgi");

IHelloService helloService = pluginManager.getPlugin("com.bytebeats.example.plugin.HelloServiceImpl", IHelloService.class);
helloService.hello("ricky");

IUserService userService = pluginManager.getPlugin("com.bytebeats.example.plugin.UserServiceImpl", IUserService.class);
List list = userService.getUsers();
System.out.println("list = [" + list + "]");

userService.update(new User("test", "test"));
}
}


注意:运行之前需要将example-plugin工程打成jar包,然后拷贝到任意路径下即可,本文是D:\osgi目录下。


代码下载

所有代码均已上传至Github:https://github.com/TiFG/cherry



推荐阅读
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • C++ 开发实战:实用技巧与经验分享
    C++ 开发实战:实用技巧与经验分享 ... [详细]
  • 在探讨Hibernate框架的高级特性时,缓存机制和懒加载策略是提升数据操作效率的关键要素。缓存策略能够显著减少数据库访问次数,从而提高应用性能,特别是在处理频繁访问的数据时。Hibernate提供了多层次的缓存支持,包括一级缓存和二级缓存,以满足不同场景下的需求。懒加载策略则通过按需加载关联对象,进一步优化了资源利用和响应时间。本文将深入分析这些机制的实现原理及其最佳实践。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 2009年12月28日,易语言公司正式推出了“易语言5.0静态编译测试版1”,这一版本标志着易语言在技术上的重要突破。与之前的4.x版本相比,5.0测试版1引入了静态编译功能,显著提升了程序的运行效率和安全性。此外,新版本还优化了代码生成机制,增强了语言的表达能力和兼容性。自发布以来,用户反馈非常积极,普遍认为新功能带来了更加流畅的开发体验。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 在ElasticStack日志监控系统中,Logstash编码插件自5.0版本起进行了重大改进。插件被独立拆分为gem包,每个插件可以单独进行更新和维护,无需依赖Logstash的整体升级。这不仅提高了系统的灵活性和可维护性,还简化了插件的管理和部署过程。本文将详细介绍这些编码插件的功能、配置方法,并通过实际生产环境中的应用案例,展示其在日志处理和监控中的高效性和可靠性。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 在处理遗留数据库的映射时,反向工程是一个重要的初始步骤。由于实体模式已经在数据库系统中存在,Hibernate 提供了自动化工具来简化这一过程,帮助开发人员快速生成持久化类和映射文件。通过反向工程,可以显著提高开发效率并减少手动配置的错误。此外,该工具还支持对现有数据库结构进行分析,自动生成符合 Hibernate 规范的配置文件,从而加速项目的启动和开发周期。 ... [详细]
  • 在尝试为 Unity 编译一个简单的 Java 库时,运行 `ant jar` 命令后遇到了 Java I/O 异常。具体错误信息为“无法启动程序 ${aAPT},错误代码 2”,这通常表示指定的文件或目录不存在。此问题可能是由于环境配置不正确或路径设置有误导致的。建议检查相关路径和环境变量,确保所有依赖项都已正确安装和配置。 ... [详细]
  • 如何在IntelliJ IDEA中生成Maven项目的所有Jar包依赖关系图
    本文详细介绍了如何在IntelliJ IDEA中生成Maven项目的完整Jar包依赖关系图。通过具体步骤和示例,帮助开发者清晰地理解并掌握这一重要功能,适合希望深入了解Maven依赖管理的读者学习参考。 ... [详细]
author-avatar
小男生2502863203
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有