https://www.cnblogs.com/wangzhen-fly/p/11002814.html

前言:想看基于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;

    }

}
 

【转载】获取Java接口的所有实现类的更多相关文章

  1. 获取Java接口的所有实现类

    获取Java接口的所有实现类 前言:想看基于spring 的最简单实现方法,请直接看 第七步. 本文价值在于 包扫描的原理探究和实现 一.背景 项目开发中,使用Netty做服务端,保持长连接与客户端( ...

  2. Java -- 获取指定接口的所有实现类或获取指定类的所有继承类

    Class : ClassUtil package pri.lime.main; import java.io.File; import java.io.IOException; import jav ...

  3. 简述java接口和C++虚类的相同和不同之处

    C++虚类相当于java中的抽象类,与接口的不同处是: 1.一个子类只能继承一个抽象类(虚类),但能实现多个接口 2.一个抽象类可以有构造方法,接口没有构造方法 3.一个抽象类中的方法不一定是抽象方法 ...

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

    var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes().Where(t => t ...

  5. 获取同一接口多个实现类的bean

    @Service("taskExecutorFactory") public class TaskExecutorFactory implements ApplicationCon ...

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

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  7. java接口和抽象类

    关于接口 1.创建一个接口,需要使用interface关键字. 2.实现一个接口,需要使用implements关键字. 3.接口的成员属性都是静态常量(默认public static final). ...

  8. Java 接口和抽象类差别

    原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想非常大程度上取决于它的总体架构,而这个总体架构事实上 ...

  9. 我对面向对象设计的理解——Java接口和Java抽象类

    在没有好好地研习面向对象设计的设计模式之前,我对Java接口和Java抽象类的认识还是很模糊,很不可理解. 刚学Java语言时,就很难理解为什么要有接口这个概念,虽说是可以实现所谓的多继承,可一个只有 ...

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

    原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是 ...

随机推荐

  1. NET Core 基础 - 删除字符串最后一个字符的七大类N种实现方式

    今天想通过和大家分享如何删除字符串最后一个字符的N种实现方法,来回顾一些基础知识点. 01.第一类.字符串方式 这类方法是通过string类型自身方法直接实现. 1.Substring方法 相信大多数 ...

  2. 玩玩虚拟化-KVM

    1.讲在前面(玩这个的心历路程) 最近一段时间想玩一些集群之类的东西,学习搞一下K8s,集群啥的,但是我没有多台服务器,如果购买云服务器成本太高,后来想到了买台台式机弄点虚拟机来玩,于是我就在某鱼上淘 ...

  3. threejs 浏览器窗口resize变化 自适应 html 全屏

    全屏:画布全屏和body页面全屏: // 导入 threejs import * as THREE from "three"; import { OrbitControls } f ...

  4. kotlin函数和Lambda表达式——>函数

    函数: 1.函数声明 kotlin中的函数使用fun关键字声明: fun double(x: Int): Int { return 2 * x } 2.函数用法 调用函数使用传统的方法: val re ...

  5. KubeKey v3.1 发布:快速自定义离线安装包

    日前,KubeKey v3.1 正式发布.该版本主要对离线场景部署.离线包制作以及向 Kubernetes v1.24+ 升级进行了优化. KubeKey 简介 KubeKey 是 KubeSpher ...

  6. 线上debug&gateway自定义路由规则

    如何进行线上debug. 如何在gateway自定义路由规则去进行请求分发,让请求打到集群模式下我们想要的节点. 1.配置remote debug 1.在启动参数配置参数: -Xdebug -Xrun ...

  7. style="word-break: break-all;" 用于 对应 td 文本内容过长自适应换行适用

    style="word-break: break-all;" 用于 对应 td 文本内容过长自适应换行适用 <td style="word-break: break ...

  8. Redis数据结构:List类型全面解析

    文章目录 一.List数据类型 1.1 简介 1.2 应用场景 1.3 底层结构 二.数据结构 2.1 压缩列表ZipList 2.2 双向链表LinkedList(后续已废弃) 2.3 快速链表Qu ...

  9. CAD Build 隐私说明

    该App不会收集任何用户隐私数据.

  10. 配置NVIDIA Container Runtime和容器运行GPUStack教程

    GPUStack 是一个设计用于运行大模型的开源 GPU 集群管理器,提供私有部署的大模型服务,支持大语言模型.Embedding 文本嵌入模型.Reranker 重排序模型.Vision 多模态模型 ...