获取Java接口的所有实现类
获取Java接口的所有实现类
前言:想看基于spring 的最简单实现方法,请直接看 第七步。
本文价值在于 包扫描的原理探究和实现
一、背景
项目开发中,使用Netty做服务端,保持长连接与客户端(agent)通讯。Netty服务端需要根据不同消息类型,加载对应的Processer(消息处理器)对消息进行处理。问题就出现了,Processer会随着消息业务类型增多进行扩展,每一次增加Processer都需要手动new出来一个实例,放到Map里(key为消息类型码,value为Processer实例),供调度程序(ProcesserManager)根据端消息类型调度,显然这是件很麻烦的一件事,不仅操作琐碎,也不符合低耦合、模块化的设计思想。
二、解决思路
我们所写的每一个Processer都是IProcessor这个接口的实现:
public interface IProcessor { void process(BaseMsgWrapper msg) throws Exception; EventEnum getType(); default String getIpFromChannelContext(ChannelHandlerContext ctx){String[] ipPort = ctx.channel().remoteAddress().toString().split(":");return ipPort[0].substring(1);} }
其中:
void process(BaseMsgWrapper msg) 为消息处理方法
void getIpFromChannelContext (BaseMsgWrapper msg) 为获取客户端ip的默认方法
假如我们在Netty服务端启动时,能获取该接口的所有实现类,然后把这些实现类分别new出来,放到Map中,那么这个工作就可以自动化掉了。
最终实现的效果就是 消息处理器只要 implements IProcessor接口,就会被自动加载调用,而不再需要手动写到Map中。这样就将ProcesserManager 与 Processer解耦开了。
为此,IProcessor接口需要增加一个方法
EventEnum getType();
即需要Processer表明自己对应的消息类型,没这个方法之前,我们都是在put进Map的时候,手动把消息类型写进去的(可以想象之前的做法多么的low)
三、实现过程
想法是很好,但实现不是那么容易,踩了很多坑。
首先是网上查资料,看看其他人都怎么做的,有没有做好的轮子。
第一篇博客参考:http://www.cnblogs.com/ClassNotFoundException/p/6831577.html
(Java -- 获取指定接口的所有实现类或获取指定类的所有继承类)
这篇博客提供的大致思路:
1) 获取当前线程的ClassLoader
2) 通过ClassLoader获取当前工作目录,对目录下的文件进行遍历扫描。
3) 过滤出以.class为后缀的类文件,并加载类到list中
4) 对list中所有类进行校验,判断是否为指定接口的实现类,并排除自身。
5) 返回所有符合条件的类。
这个思路是对的,但是考虑不全,不能拿来工程应用,另外博文中提供的源码应该只是一个实验代码,有不少缺陷。
1)这个方没有考虑不同的文件格式。当程序打成jar包,发布运行时,上述的这种遍历file的操作 就失效了。
2)局限性。只能扫描到当前方法的同级目录及其子目录。无法覆盖整个模块。
3)遍历文件的逻辑太啰嗦,可以简化。
4)通过ClassLoader获取当前工作目录时,使用了“../bin/”这么一个固定的目录名。
Enumeration<URL> enumeration = classLoader.getResources("../bin/" + path)
事实上,不同的IDE(主要是eclipse 和 idea)项目的资源目录,在这一点上是不同的。
第二篇博客参考:
http://blog.csdn.net/littleschemer/article/details/47378455
(获取全部子类或接口的全部实现)
这篇博客考虑到了在运行环境中,需要通过JarFile工具类进行单独处理。
局限性:
需要手动指定要扫描的Jar文件或目录,没有通过ClassLoader 自动获取当前运行的上下文。
此外classLoader.getResource 获得的 资源目录 是个URL对象,如何转换成JarFile对象 花费了我不少时间求索:
JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
综合上述思路和自己的试验研究,得出获取接口所有实现类的算法流程如下:
四、代码实现
package com.hikvision.hummer.pandora.gateway.proc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 获取接口的所有实现类 理论上也可以用来获取类的所有子类
* 查询路径有限制,只局限于接口所在模块下,比如pandora-gateway,而非整个pandora(会递归搜索该文件夹下所以的实现类)
* 路径中不可含中文,否则会异常。若要支持中文路径,需对该模块代码中url.getPath() 返回值进行urldecode.
* Created by wangzhen3 on 2017/6/23.
*/
public class ClassUtil {
private static final Logger LOG = LoggerFactory.getLogger(ClassUtil.class);
public static ArrayList<Class> getAllClassByInterface(Class clazz) {
ArrayList<Class> list = new ArrayList<>();
// 判断是否是一个接口
if (clazz.isInterface()) {
try {
ArrayList<Class> allClass = getAllClass(clazz.getPackage().getName());
/**
* 循环判断路径下的所有类是否实现了指定的接口 并且排除接口类自己
*/
for (int i = 0; i < allClass.size(); i++) {
/**
* 判断是不是同一个接口
*/
// isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class
// 参数所表示的类或接口是否相同,或是否是其超类或超接口
if (clazz.isAssignableFrom(allClass.get(i))) {
if (!clazz.equals(allClass.get(i))) {
// 自身并不加进去
list.add(allClass.get(i));
}
}
}
} catch (Exception e) {
LOG.error("出现异常{}",e.getMessage());
throw new RuntimeException("出现异常"+e.getMessage());
}
}
LOG.info("class list size :"+list.size());
return list;
}
/**
* 从一个指定路径下查找所有的类
*
* @param packagename
*/
private static ArrayList<Class> getAllClass(String packagename) {
LOG.info("packageName to search:"+packagename);
List<String> classNameList = getClassName(packagename);
ArrayList<Class> list = new ArrayList<>();
for(String className : classNameList){
try {
list.add(Class.forName(className));
} catch (ClassNotFoundException e) {
LOG.error("load class from name failed:"+className+e.getMessage());
throw new RuntimeException("load class from name failed:"+className+e.getMessage());
}
}
LOG.info("find list size :"+list.size());
return list;
}
/**
* 获取某包下所有类
* @param packageName 包名
* @return 类的完整名称
*/
public static List<String> getClassName(String packageName) {
List<String> fileNames = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String packagePath = packageName.replace(".", "/");
URL url = loader.getResource(packagePath);
if (url != null) {
String type = url.getProtocol();
LOG.debug("file type : " + type);
if (type.equals("file")) {
String fileSearchPath = url.getPath();
LOG.debug("fileSearchPath: "+fileSearchPath);
fileSearchPath = fileSearchPath.substring(0,fileSearchPath.indexOf("/classes"));
LOG.debug("fileSearchPath: "+fileSearchPath);
fileNames = getClassNameByFile(fileSearchPath);
} else if (type.equals("jar")) {
try{
JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
fileNames = getClassNameByJar(jarFile,packagePath);
}catch (java.io.IOException e){
throw new RuntimeException("open Package URL failed:"+e.getMessage());
}
}else{
throw new RuntimeException("file system not support! cannot load MsgProcessor!");
}
}
return fileNames;
}
/**
* 从项目文件获取某包下所有类
* @param filePath 文件路径
* @return 类的完整名称
*/
private static List<String> getClassNameByFile(String filePath) {
List<String> myClassName = new ArrayList<String>();
File file = new File(filePath);
File[] childFiles = file.listFiles();
for (File childFile : childFiles) {
if (childFile.isDirectory()) {
myClassName.addAll(getClassNameByFile(childFile.getPath()));
} else {
String childFilePath = childFile.getPath();
if (childFilePath.endsWith(".class")) {
childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));
childFilePath = childFilePath.replace("\\", ".");
myClassName.add(childFilePath);
}
}
}
return myClassName;
}
/**
* 从jar获取某包下所有类
* @return 类的完整名称
*/
private static List<String> getClassNameByJar(JarFile jarFile ,String packagePath) {
List<String> myClassName = new ArrayList<String>();
try {
Enumeration<JarEntry> entrys = jarFile.entries();
while (entrys.hasMoreElements()) {
JarEntry jarEntry = entrys.nextElement();
String entryName = jarEntry.getName();
//LOG.info("entrys jarfile:"+entryName);
if (entryName.endsWith(".class")) {
entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
myClassName.add(entryName);
//LOG.debug("Find Class :"+entryName);
}
}
} catch (Exception e) {
LOG.error("发生异常:"+e.getMessage());
throw new RuntimeException("发生异常:"+e.getMessage());
}
return myClassName;
}
}
五、项目应用
接口IProcessor
*/
public interface IProcessor {
void process(BaseMsgWrapper msg) throws Exception;
EventEnum getType();
default String getIpFromChannelContext(ChannelHandlerContext ctx){
String[] ipPort = ctx.channel().remoteAddress().toString().split(":");
return ipPort[0].substring(1);
}
}
接口实现类HeartBeatMsgProcessor, 主要 加了注解@Component
@Component
public class HeartBeatMsgProcessor implements IProcessor {
private static final HikGaLogger logger = HikGaLoggerFactory.getLogger(HeartBeatMsgProcessor.class);
@Override
public EventEnum getType(){
return EventEnum.HEART_BEAT;
}
private BaseMsg bmsg = new BaseMsg( "requestId-null", EventEnum.HEART_BEAT.getEventType(),1L, Constants.ASYN_INVOKE,
"pong", "uuid-null", Constants.ZH_CN);
@Override
public void process(BaseMsgWrapper msg) throws Exception {
Assert.notNull(msg);
logger.debug("ping from [{}]", msg.getCtx().channel().remoteAddress().toString());
msg.getCtx().writeAndFlush(bmsg);
}
}
调用ClassUtil 获取接口的所有类,并根据查找到的类从spring容器中取出bean.
private ProcessorManager(){List<Class> classList = ClassUtil.getAllClassByInterface(IProcessor.class);LOG.info("processor num :"+classList.size());for(Class classItem : classList){IProcessor msgProcessor = null;try{msgProcessor = (IProcessor) AppContext.getBean(classItem);processorMap.put(msgProcessor.getType(),msgProcessor);}catch (Exception e){LOG.error("加载脚本处理器:[{}]失败:[{}]!",classItem.getName(),e.getMessage());throw new RuntimeException("加载脚本处理器"+classItem.getName()+"失败");}LOG.info("加载脚本处理器成功:[{}] MsgType:[{}] ", classItem.getName(), msgProcessor.getType());}LOG.info("脚本处理器加载完成!"); }
代码中AppContext是对springContext 的封装,实现了ApplicationContextAware接口,目的是从Spring容器取出指定类的实例。代码见附录1.
六、更进一步
本文通过研究Java接口实现类的自动扫描加载,达成接口与调度程序的解耦,拓展了Java接口在代码解耦方面的应用价值。
虽然是获取接口所有实现类,但对获取类的所有子类,同样适用。
另外基于此功能,可以通过反射分析扫描上来的Class, 获知哪些类使用了自定义注解,然后应用注解处理器,完成注解处理器的自动执行。
七、最简单的实现方法
ApplicationContext 的 getBeansOfType 方法已经封装了该实现,可以直接调用。
AppContext 见附录1
IProcessor 为接口。Map<String, IProcessor> processorBeanMap 为返回值,key 为beanName ,value为 bean.
接口IProcessor实现类上 要加上注解@Component
Map<String, IProcessor> processorBeanMap = null;
try {
processorBeanMap = AppContext.getContext().getBeansOfType(IProcessor.class);
}catch (BeansException e){
throw new RuntimeException("加载脚本处理器Bean失败!");
}
调用AppContext 时,AppContex 必须已经被容器优先注入,否则可能会出现applicaitonContext未注入的报错。
可以在调用类上,加上注解 @DependsOn("appContext") 来控制appContext 优先加载。
附录1 AppContext
package com.hikvision.hummer.pandora.common.context; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;@Component public class AppContext implements ApplicationContextAware { private static ApplicationContext context = null;/*** 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量*/public void setApplicationContext(ApplicationContext context) {AppContext.setContext(context);} /*** 取得存储在静态变量中的ApplicationContext.*/public static ApplicationContext getContext() {if (context == null)throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义AppContext");return context;}/*** 存储静态变量中的ApplicationContext.*/public static void setContext(ApplicationContext context) {AppContext.context = context;}/*** 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型*/@SuppressWarnings("unchecked")public static <T> T getBean(String name) {if (context == null)throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义AppContext");try {return (T) context.getBean(name);} catch (BeansException e) {e.printStackTrace();}return (T) null;} /*** 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型*/@SuppressWarnings("unchecked")public static <T> T getBean(Class<T> tClass) {if (context == null)throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义AppContext");try {return context.getBean(tClass);} catch (BeansException e) {e.printStackTrace();}return null;} }
转载于:https://www.cnblogs.com/wangzhen-fly/p/11002814.html
获取Java接口的所有实现类相关推荐
- excel字段自动java类,Java 接口自动化系列--工具类之Excel测试数据解析封装
在进行数据解析时,先来看看excel测试数据格式,这里采用接口和测试数据分离的方式,即分为两个sheet页签分别存放接口信息,用例信息 excel封装成对象步骤 1.导入easypoi的坐标 2.加载 ...
- java中调用_如何获取Java中的调用方类
小编典典 你可以生成堆栈跟踪并使用StackTraceElements中的信息. 例如,实用程序类可以为你返回调用类名称: public class KDebug { public static St ...
- java 抽象接口类,Java接口(interface)和Java抽象类(abstract class)的区别(详诉版)
1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架, 当代表业务逻辑的高层抽象层结构合理时,你底层的具体实现需要考虑的就仅仅是一些算法 ...
- Java接口和Java抽象类
Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方,才是它们的最佳位置呢?把它们比较一下,你就可以发现了. 1.Java接口和Java抽象类最大的一个区别,就在于Java ...
- java 接口与抽象类的区别
1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法 ...
- Java接口和Java抽象类的认识
在没有好好地研习面向对象设计的设计模式之前,我对Java接口和Java抽象类的认识还是很模糊,很不可理解. 刚学Java语言时,就很难理解为什么要有接口这个概念,虽说是可以实现所谓的多继承,可一个只有 ...
- 反射-获取java私有内部类反射类型、私有字段
获取JAVA私有内部类反射类型 方式一 Class.forName("外部类完整路径$内部私有类类名"); 方式二 通过获取对应私有内部类的字段而获取 完整的类名 Class.fo ...
- Java接口和Java抽象类(转,原文已被删除)
1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法 ...
- java反射获取所有接口实现类
需求描述 项目业务前后端交互需要的一些特定值需要用枚举类型列举出来,并且每个枚举值对应的外部使用值不是固定的, 类似于 APPLE("apple","苹果") ...
最新文章
- 2015大型互联网公司校招都开始了,薪资你准备好了嘛?
- 语音识别|基于CNN+DFSMN(简化版:标量+无步长因子)的声学模型实现及代码开源(keras)
- Python数据可视化:幂律分布
- python时间模块datetime模块
- 小程序 md5 32位加密
- 章节七、4-Sets
- CentOS7通过yum安装MySQL5.7
- 方立勋_30天掌握JavaWeb_Servlet事件监听器
- 【渝粤题库】陕西师范大学151203 初级会计学作业(笔试题型)
- mkpasswd命令使用方法
- c语言函数修改指针本身,C语言函数内部改变指针本身
- php 查询 判断 语句,关于php的判断语句
- 数据结构常见算法机试题
- 腾讯面试题:如何实现一个类似新浪微博的短链接服务!
- 设计并实现一个员工(Employee)类(C++)
- angular页面间传递参数
- 【雅思大作文考官范文】——第四篇: 'power of advertising' essay
- c 自动打印的服务器,C-Lodop云打印服务器 x64
- 小数化分数 (思维)
- 十个著名思维实验的思考
热门文章
- linux逻辑分区最小值,linux 逻辑卷管理 调整分区大小
- python 分析html_用python的BeautifulSoup分析html
- limit实现原理 mysql_值得一生典藏:MySQL的事务实现原理
- 使用H5实现机器人脸
- python打印进度条starting...done_python打印进度条-tqdm
- Requests 2.18.1文档
- python打包成.exe程序
- 计算商品价格找零(Python)
- 频率概率与贝叶斯概率
- scikit-learn学习笔记(三)Generalized Linear Models ( 广义线性模型 )