獲取Java接口的全部實現類

獲取Java接口的全部實現類

前言:想看基於spring 的最簡單實現方法,請直接看 第七步。

本文價值在於 包掃描的原理探究和實現html

1、背景

項目開發中,使用Netty作服務端,保持長鏈接與客戶端(agent)通信。Netty服務端須要根據不一樣消息類型,加載對應的Processer(消息處理器)對消息進行處理。問題就出現了,Processer會隨着消息業務類型增多進行擴展,每一次增長Processer都須要手動new出來一個實例,放到Map裏(key爲消息類型碼,value爲Processer實例),供調度程序(ProcesserManager)根據端消息類型調度,顯然這是件很麻煩的一件事,不只操做瑣碎,也不符合低耦合、模塊化的設計思想。java

2、解決思路

咱們所寫的每個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);
    }
}

其中:spring

 

void process(BaseMsgWrapper msg)  爲消息處理方法app

void getIpFromChannelContext (BaseMsgWrapper msg)  爲獲取客戶端ip的默認方法eclipse

 

假如咱們在Netty服務端啓動時,能獲取該接口的全部實現類,而後把這些實現類分別new出來,放到Map中,那麼這個工做就能夠自動化掉了。ide

最終實現的效果就是 消息處理器只要 implements IProcessor接口,就會被自動加載調用,而再也不須要手動寫到Map中。這樣就將ProcesserManager 與 Processer解耦開了。模塊化

爲此,IProcessor接口須要增長一個方法函數

EventEnum getType();
    即須要Processer代表本身對應的消息類型,沒這個方法以前,咱們都是在put進Map的時候,手動把消息類型寫進去的(能夠想象以前的作法多麼的low)工具

 

 

 

 

3、實現過程

   想法是很好,但實現不是那麼容易,踩了不少坑。

   首先是網上查資料,看看其餘人都怎麼作的,有沒有作好的輪子。

  

第一篇博客參考: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();

綜合上述思路和本身的試驗研究,得出獲取接口全部實現類的算法流程以下:

 

 

 

4、代碼實現

 

 

 

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

}

 

 

 

5、項目應用

接口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.

 

6、更進一步

本文經過研究Java接口實現類的自動掃描加載,達成接口與調度程序的解耦,拓展了Java接口在代碼解耦方面的應用價值。

雖然是獲取接口全部實現類,但對獲取類的全部子類,一樣適用。

另外基於此功能,能夠經過反射分析掃描上來的Class, 獲知哪些類使用了自定義註解,而後應用註解處理器,完成註解處理器的自動執行。

 

7、最簡單的實現方法

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;     } }
相關文章
相關標籤/搜索