Java-Annotation的一種用法(消除程式碼中冗餘的if/else或switch語句)

火炎_焱燚發表於2020-11-21

Java-Annotation的一種用法(消除程式碼中冗餘的if/else或switch語句)

1.冗餘的if/else或switch

​ 有沒有朋友寫過以下的程式碼結構,大量的if/esle判斷,來選擇不同的執行方式

if(type==1001){
	return decodeMsg1001(msg);
}else if(type==1002){
	return decodeMsg1002(msg);
}
.....

​ 或者上面的程式碼也是可以轉換成相應的switch語句來執行,結構如下所示:

switch(type){
    case 1001:{
        return decodeMsg1001(msg)
    }
    case 1002:{
        return decodeMsg1002(msg);
    }
        ....
}

​ 總之,如果type的值很多的話,就會導致這段程式碼特別的長。如果想在處理訊息的函式中新增一些其他的資訊,則會導致處理訊息的函式體也會比較長。

Java語言是物件導向的,有沒有一種方法用物件導向的方式來解決這個問題。將大量的if/else語句轉換成使用介面的方式來解決。答案是有的

2.思路想法

​ 上述的問題大多數產生在,多個不同型別的訊息對應著不同的處理方法(不同訊息所對應的資料不一致)。

​ 可以將不同的型別與型別所對應的處理方法,做成一個對映表。即一種type對應於一個typeProcessor,在java中這種資料結構是Map。所以在系統初始化時,先生成這樣一種Map的對應關係。當一種型別type的訊息到來時,從這個map中查詢出所對應的處理類,然後動態產生一個介面物件,然後執行介面中的方法。那這樣就會有相應的問題:

  • 問題一:每一種型別對應一個處理類,就會有好多實體類,怎麼做好一個型別對應一個處理類
  • 問題二:如果一個一個類實體物件的手動新增,也會產生好多冗餘的程式碼。

3.訊息對應實現

3.1 利用annotation實現型別與處理類對應

​ 在java中可以利用Annotation這一特性,來實現訊息型別與實體類的對應,先建立一個annotation。

@Target(ElementType.TYPE) //TYPE(型別)是指可以用在Class,Interface,Enum和Annotation型別上.
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ProtocolCommand {

    int msgCommand() default 0 ;
}

​ 建立好相應的Annotation後,可以新增在每一個訊息處理類上,在Annotation中有一個屬性為msgCommand,是int型別的,這樣就可以做到對應。

3.2訊息處理介面

​ 為方便操作,可以建立一個訊息處理介面,對應每一種型別的訊息處理類都需要實現這一介面,這樣的好處在動態產生物件很好的做到統一處理。

public interface IUbloxMsg {

    public int decodeUbxMsg(RawData rawData);

}

​ 這裡的介面很簡單,如果想處理複雜的業務,可以自行在介面中新增相應的方法。

這裡也可以不用介面這一特性來實現功能,也可以用抽象類 來實現。每一種特性都可以實現功能。

3.3具體訊息實現

@ProtocolCommand(msgCommand=UbloxConstant.ID_NAVSOL)
public class NavSolUbloxMsgImpl implements IUbloxMsg {
    public int decodeUbxMsg(RawData rawData) {
        return 0;
    }
}

​ 可以看到在具體的訊息型別處理類上,加上我們自定義的註解,即可實現具體訊息型別與具體訊息處理類對應。

4.生成Map訊息型別對映

4.1訊息處理類掃描,得到Class集合

​ 通常情況下,為了方便管理訊息處理類應該放在一個包目錄下。所以我們需要自動掃描出這個包目錄下的所有的class,產生Class集合。然後在這個集合中查詢到有我們自定義註解的類,同時得到註解物件,獲取訊息型別,並將此訊息型別新增至Map訊息對映中。

​ 類掃描程式碼核心部分程式碼如下:

  public static ClassFilter cFilter = new ClassFilter(true);
	//傳入的引數為包名
    public static Set<Class<?>> scannClassesByPackage(String pack) throws ClassNotFoundException, IOException {
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        boolean recursive = true;
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;
        dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
            } else if ("jar".equals(protocol)) {
                JarFile jar;
                JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                jar = jarURLConnection.getJarFile();
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String name = entry.getName();
                    if (name.charAt(0) == '/') {
                        name = name.substring(1);
                    }
                    if (name.startsWith(packageDirName)) {
                        int idx = name.lastIndexOf('/');
                        if (idx != -1) {
                            packageName = name.substring(0, idx).replace('/', '.');
                        }
                        if ((idx != -1) || recursive) {
                            if (name.endsWith(".class") && !entry.isDirectory()) {
                                String className = name.substring(packageName.length() + 1, name.length() - 6);
                                classes.add(Class.forName(packageName + '.' + className));
                            }
                        }
                    }
                }
            }
        }

        return classes;
    }

    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath,
                                                        final boolean recursive, Set<Class<?>> classes) throws ClassNotFoundException {

        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] dirfiles = dir.listFiles(cFilter);
        for (File file : dirfiles) {
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
            }
        }
    }

4.2生成Map對映

​ 上述已經通過掃描得到所有的訊息處理類的集合,下面通過集合遍歷查詢,得到有自定義註解的類。並且得到註解物件獲取訊息型別,生成Map對映物件。相應的核心程式碼如下所示:

    private Map<Integer, Class<?>> msgImplClass = new HashMap<Integer, Class<?>>();
    public void initMsgClazz(String pkg )  {
        Set<Class<?>> classes = null;
        try {
            classes = ClassScannerUtils.scannClassesByPackage(pkg);
        } catch (ClassNotFoundException e) {
            throw new ProtocolMsgException("invalid package [ " +pkg+" ]",e);
        } catch (IOException e) {
            throw new ProtocolMsgException("IO error for read class  ",e);
        }
        if(classes == null ){
            throw new ProtocolMsgException("scanner class is null ");
        }
        for (Class<?> c : classes) {
           if (c.isAnnotationPresent( ProtocolCommand.class)) {
                ProtocolCommand protocolCommand=c.getAnnotation(ProtocolCommand.class);
                msgImplClass.put(protocolCommand.msgCommand(),c);
            }
        }
    }
    public Class<?> getClassByMsgCommand(Integer msgCommand){
        if(msgImplClass == null || msgImplClass.size() <=0){
            return null;
        }
        return msgImplClass.get(msgCommand);
    }

至此整個思路就完成拉

  • 用法一:TCP/IP通訊應用中,可以參考此思路進行資料訊息處理。(應該是最多的一種)
  • 用法二:其他地方,有多種訊息型別需要處理

程式碼獲取下載,可點選下方連結進行程式碼參考與下載:

下載程式碼

相關文章