java 接口 实现类_获取Java接口的所有实现类

本文介绍了如何在Java中自动获取接口的所有实现类,从而实现自动化加载和解耦。作者通过探究和实践,提出了一种解决方案,包括获取当前线程的ClassLoader,遍历文件和Jar包,过滤出接口的实现类。最后,通过Spring的AppContext获取接口实现类的bean,实现了接口与调度程序的解耦。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取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)

三、实现过程

想法是很好,但实现不是那么容易,踩了很多坑。

首先是网上查资料,看看其他人都怎么做的,有没有做好的轮子。

这篇博客提供的大致思路:

1) 获取当前线程的ClassLoader

2) 通过ClassLoader获取当前工作目录,对目录下的文件进行遍历扫描。

3) 过滤出以.class为后缀的类文件,并加载类到list中

4) 对list中所有类进行校验,判断是否为指定接口的实现类,并排除自身。

5) 返回所有符合条件的类。

这个思路是对的,但是考虑不全,不能拿来工程应用,另外博文中提供的源码应该只是一个实验代码,有不少缺陷。

1)这个方没有考虑不同的文件格式。当程序打成jar包,发布运行时,上述的这种遍历file的操作 就失效了。

2)局限性。只能扫描到当前方法的同级目录及其子目录。无法覆盖整个模块。

3)遍历文件的逻辑太啰嗦,可以简化。

4)通过ClassLoader获取当前工作目录时,使用了“../bin/”这么一个固定的目录名。

Enumeration enumeration = classLoader.getResources("../bin/" + path)

事实上,不同的IDE(主要是eclipse 和 idea)项目的资源目录,在这一点上是不同的。

第二篇博客参考:

(获取全部子类或接口的全部实现)

这篇博客考虑到了在运行环境中,需要通过JarFile工具类进行单独处理。

局限性:

需要手动指定要扫描的Jar文件或目录,没有通过ClassLoader 自动获取当前运行的上下文。

此外classLoader.getResource 获得的 资源目录 是个URL对象,如何转换成JarFile对象 花费了我不少时间求索:

JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();

JarFile

jarFile = jarURLConnection.getJarFile();

综合上述思路和自己的试验研究,得出获取接口所有实现类的算法流程如下:

97d34c4727ac571be648e6446bc320ea.png

四、代码实现

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 getAllClassByInterface(Class clazz) {

ArrayList list = new ArrayList<>();

// 判断是否是一个接口

if (clazz.isInterface()) {

try {

ArrayList

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;

}

/**

*从一个指定路径下查找所有的类*

* @parampackagename*/private static ArrayList getAllClass(String

packagename) {

LOG.info("packageName to search:"+packagename);

List

classNameList =  getClassName(packagename);

ArrayList

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;

}

/**

*获取某包下所有类* @parampackageName包名* @return类的完整名称*/public static List getClassName(String

packageName) {

List 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;

}

/**

*从项目文件获取某包下所有类* @paramfilePath文件路径* @return类的完整名称*/private static List getClassNameByFile(String filePath) {

List myClassName = new ArrayList();

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 getClassNameByJar(JarFile jarFile ,String

packagePath) {

List myClassName = new ArrayList();

try {

Enumeration

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 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 processorBeanMap 为返回值,key 为beanName ,value为 bean.

接口IProcessor实现类上 要加上注解@Component

Map 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 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 getBean(Class tClass) {

if (context== null)

throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义AppContext");

try {

return context.getBean(tClass);

} catch (BeansException e) {

e.printStackTrace();

}

return null;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值