获取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
總結
以上是生活随笔為你收集整理的获取Java接口的所有实现类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: parallels desktop虚拟机
- 下一篇: object数据类型