Get all Java class that implements the interface

Get all Java class that implements the interface

Preface: To see spring simplest implementation method, please see directly the seventh step.

This paper value lies  packet scanning principle explore and implement

One, background

Project development, Netty do use the server to maintain a long connection to communicate with the client (agent). Netty server according to different message types, load the corresponding Processer (message processor) processes the message. The problem arises, Processer will be expanded with the increase in message traffic types, each time increasing the need to manually Processer out a new instance, into the Map (key for the message type code, value is Processer instance), for scheduler (ProcesserManager ) according to the type of end message scheduling, obviously this is a very troublesome thing, not only trivial operations, nor with low coupling, modularity of design.

Second, Solutions

Each Processer we write is IProcessor implementation of this interface:

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);
    }
}

among them:

 

void process (BaseMsgWrapper msg) is a message processing method

The default method void getIpFromChannelContext (BaseMsgWrapper msg) to obtain the client's ip

 

If we start at the end Netty service, can get all the class that implements the interface, and then implement these classes are new out into the Map, then you can automate this work out.

The final effect is achieved as long as the message processor implements IProcessor interface will be loaded automatically invoked without the need to manually written Map. This will ProcesserManager and Processer decoupling opened.

To this end, IProcessor interface requires a method to increase

EventEnum getType ();
    the need Processer indicate their corresponding message types, this method did before, (how low the previous practice can imagine) we are all put into the Map when manually write the message into the type of

 

 

 

 

Third, the implementation process

   The idea is good, but implementation is not so easy to step on a lot of the pit.

   The first is the online information and see how other people are doing, there is no good wheels.

  

First blog reference: http://www.cnblogs.com/ClassNotFoundException/p/6831577.html

( The Java - get all the specified interface implementation class or obtain a specified class of all inherited classes )

This blog provides a general idea:

1) to obtain ClassLoader of the current thread

2) Get the current working directory by ClassLoader, on file in the directory traversal scan.

3) the class files filtered .class suffix, and loads the class into the list

4) for all classes in the list check determines whether the specified interface implementation class, and excluding itself.

5) returns all eligible class.

 

This idea is correct, but incomplete consideration, can not be used engineering applications, in addition Bowen provided the source should only be a test code, there are many defects.

1) The party does not take into account different file formats. When the program labeled jar package, release run the file traversal of this operation becomes ineffective.

2) limitations. You can only scan to the same directory and its subdirectories of the current method. You can not cover the entire module.

Logic 3) traverse the file is too much, can be simplified.

4) getting the current working directory by ClassLoader, use the "../bin/" such a fixed directory name.

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

In fact, resource directory different IDE (mainly eclipse and idea) project at this point is different.

 

The second blog Reference:

http://blog.csdn.net/littleschemer/article/details/47378455

(Get all subclasses implement all or interface)

This blog takes into account the operating environment, the need for separate treatment by JarFile tools.

limitation:

Need to manually specify to scan Jar files or directories, do not automatically get the context of the current running through the ClassLoader.

Also available is a resource directory classLoader.getResource a URL object, how to convert objects into JarFile I spent a lot of time seeking:

JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
   JarFile jarFile = jarURLConnection.getJarFile();

Taking these ideas and their experimental study, all come to get the algorithm process interface implementation class is as follows:

 

 

 

Fourth, code implementation

 

 

 

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

 

Guess you like

Origin www.cnblogs.com/wangzhen-fly/p/11002814.html