中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

獲取JAVA接口的所有實現類

發布時間:2020-05-21 15:09:27 來源:網絡 閱讀:330 作者:ckllf 欄目:編程語言

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

  一、背景

  項目開發中,使用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)

  三、實現過程

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

  首先是網上查資料,看看其他人都怎么做的,有沒有做好的輪子。

  (Java – 獲取指定接口的所有實現類或獲取指定類的所有繼承類)

  這篇博客提供的大致思路:

  1) 獲取當前線程的ClassLoader

  2) 通過ClassLoader獲取當前工作目錄,對目錄下的文件進行遍歷掃描。

  3) 過濾出以.class為后綴的類文件,并加載類到list中

  4) 對list中所有類進行校驗,判斷是否為指定接口的實現類,并排除自身。

  5) 返回所有符合條件的類。

  這個思路是對的,但是考慮不全,不能拿來工程應用,另外博文中提供的源碼應該只是一個實驗代碼,有不少缺陷。

  1)這個方沒有考慮不同的文件格式。當程序打成jar包,發布運行時,上述的這種遍歷file的操作 就失效了。

  2)局限性。只能掃描到當前方法的同級目錄及其子目錄。無法覆蓋整個模塊。

  3)遍歷文件的邏輯太啰嗦,可以簡化。

  4)通過ClassLoader獲取當前工作目錄時,使用了“…/bin/”這么一個固定的目錄名。

  Enumeration enumeration = classLoader.getResources("…/bin/" + path)

  事實上,不同的IDE(主要是eclipse 和 idea)項目的資源目錄,在這一點上是不同的。

  (獲取全部子類或接口的全部實現)

  這篇博客考慮到了在運行環境中,需要通過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 getAllClassByInterface(Class clazz) {

  ArrayList list = new ArrayList<>();

  // 判斷是否是一個接口

  if (clazz.isInterface()) {

  try {

  ArrayList 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 getAllClass(String packagename) {

  LOG.info(“packageName to search:”+packagename);

  List classNameList = getClassName(packagename);

  ArrayList 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 getClassName(String packageName) {

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

  }

  /**

  從項目文件獲取某包下所有類

  @return 類的完整名稱

  */

  private static List getClassNameByFile(String filePath) {

  List myClassName = new ArrayList();

  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 類的完整名稱

  */鄭州人流醫院哪家好 http://www.89906662.com/

  private static List getClassNameByJar(JarFile jarFile ,String packagePath) {

  List myClassName = new ArrayList();

  try {

  Enumeration 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 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 processorBeanMap 為返回值,key 為beanName ,value為 bean.

  接口IProcessor實現類上 要加上注解@Component

  Map processorBeanMap = null;

  try {

  processorBeanMap = AppContext.getContext().getBeansOfType(IProcessor.class);

  }catch (BeansException e){

  throw new RuntimeException(“加載腳本處理器Bean失敗!”);

  }

  調用AppContext 時,AppContex 必須已經被容器優先注入,否則可能會出現applicaitonContext未注入的報錯。

  可以在調用類上,加上注解 @DependsOn(“appContext”) 來控制appContext 優先加載。

  八.借助ServiceLoader類

  ServiceLoader是JDK自帶的一個類加載器,位于java.util包當中,作為 A simple service-provider loading facility. 具體使用方式如下:

  1.在META-INF/services/目錄下用你的接口全路徑名稱命名一個文件(不加后綴),然后在該文件中一行一個添加你的接口實現類的全路徑名。

  2.通過load方法來加載出所有的接口實現類

  附錄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 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 getBean(Class tClass) {

  if (context == null)

  throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義AppContext");

  try {

  return context.getBean(tClass);

  } catch (BeansException e) {

  e.printStackTrace();

  }

  return null;

  }

  }


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

博野县| 玛沁县| 彰化市| 昂仁县| 鄂托克前旗| 泗阳县| 沧州市| 綦江县| 宿松县| 合水县| 灵宝市| 伊金霍洛旗| 石渠县| 桦川县| 吉木萨尔县| 荥阳市| 伽师县| 北辰区| 英山县| 田东县| 仁怀市| 南投市| 措美县| 肥乡县| 赤城县| 苏尼特左旗| 金山区| 秭归县| 丹阳市| 洪洞县| 石首市| 临颍县| 武川县| 昌宁县| 莱州市| 台湾省| 望城县| 突泉县| 天水市| 兴仁县| 交城县|