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

时间: 2024-10-13 12:24:07

获取Java接口的所有实现类的相关文章

Java 接口自动化系列--实体类之entity封装

实体包entity下面有4个类,主要存放java对象,每个类必须有私有属性,空参构造,get set方法 具体excel中数据信息见---工具类excel文章 1.API类 解析excel中接口信息的表头信息,需要定义参数变量,get set方法 @Excel是使用了easypoi对Excel进行读写,需要导入依赖 <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-annotat

使用上下文获取java接口实例,对象等

package com.tythin.tyboot.rest.thread; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component

C# 获取指定接口的所有实现类

var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(InterfaceName)))) .ToArray();

Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了Android系统进程间通信机制Binder的原理,并且深入分析了系统提供的Binder运行库和驱动程序的 源代码.细心的读者会发现,这几篇文章分析的Binder接口都是基于C/C++语言来实现的,但是我们在编写应用程序都是基于Java语言的,那么,我 们如何使用Java语言来使用系统的Binder机

Java 接口和抽象类区别

1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点. 既然面向对象设计的重点在于

Java 接口和抽象类--缺省模式

一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点. 既然面向对象设计的重点在于抽象,那J

Java 接口和抽象类差别

原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想非常大程度上取决于它的总体架构,而这个总体架构事实上就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的详细实现须要考虑的就不过一些算法和一些详细的业务实现了.当你须要再开发还有一个相近的项目时,你曾经的抽象层说不定还能够再次利用 .面对对象的设计,复用的重点事实上应该是抽象层的复用,而不是详细某一个代码块的复用. 说

java 接口与抽象类的区别

1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点. 既然面向对象设计的重点在于

Java 接口和抽象类区别(写的很好,转了)

原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽