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通訊應用中,可以參考此思路進行資料訊息處理。(應該是最多的一種)
- 用法二:其他地方,有多種訊息型別需要處理
程式碼獲取下載,可點選下方連結進行程式碼參考與下載: