天天看點

關于Java注解的那些事

學習Java的注解有幾個好處:首先是能夠讀懂别人的代碼,特别是架構相關的代碼;其次是能夠讓程式設計更加簡潔、代碼更清晰;最後讓代碼看起來高大上(高逼格^^);

1.常見注解與分類

即使你對Java注解有多麼不熟悉,下面Java自帶的注解幾個你也一定遇到過:

  • @Override:覆寫父類方法
  • @Deprecated:表示該方法已過時,不建議使用,隻是為了前向相容
  • @SuppressWarning:忽略警告

如果是經常用架構類的同學,那麼對第三方注解應該也是很熟悉的:

  • 比如Spring: @Autowired、@Service、@Repository
  • 比如Mybatis: @InsertProvider、@UpdateProvider、@Options

Java注解通常可以有不同次元分類,比如基于注解生效的時間進行分類,或者基于注解來源進行分類(Java自帶注解、第三方注解等)。如果基于注解生效的時間對注解進行分類,可以分為下面三類:

  • 源碼注解:注解隻在源碼中存在,編譯成.class檔案後就不存在,比如@Override、@Deprecated等。
  • 編譯時注解:注解在源碼和.class檔案中都存在,
  • 運作時注解:通常采用反射機制來實作注解處理器,在程式運作階段起作用,能夠影響代碼運作邏輯的注解,比如:@Autowired、@Service等。

2.自定義注解

自定義Java注解時需要遵循一定的文法要求:

  • 使用@interface關鍵字定義注解
  • 成員以無參無異常的方式說明

注意:

  • 可以用default為成員制定一個預設值
  • 成員類型是受限的,合法的類型包括原始類型、String、Class等
  • 如果注解隻有一個成員,則成員名必須取名為value(),在使用時可以忽略成員名和指派号(=)
  • 注解類可以沒有成員,沒有成員的注解稱為辨別注解

自定義注解時涉及到的元注解(解釋注解的注解)

  • @Target:聲明該注解的作用域,包括method、field、package、type(類或者接口)。
  • @Retention:聲明注解的運作生命周期,包括runtime(運作時注解)、source(源碼注解)、class(編譯注解)
  • @Inherited:聲明子類是否能繼承使用了該注解的父類的注解,該元注解隻對普通類有效,對于接口繼承無效。
  • @Documented:生成javadoc時會包含注解的資訊

使用注解的文法:@<注解名>(<成員名1>=<成員值1>,<成員名2>=<成員值2>)

3.自定義運作時注解

運作時注解時通過反射擷取類、方法或成員上的運作時注解資訊,進而實作動态控制程式運作的邏輯。

自定義注解類:

package com.yongfxu.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
//@Retention(RetentionPolicy.SOURCE)
//@Retention(RetentionPolicy.CLASS)
@Inherited // 是否允許子類繼承使用了該注解的父類的注解,對接口繼承無效,對普通類繼承有效
@Documented // 是否生成javadoc
public @interface Description {
    
    String desc();
    
    String author();
    
    int age() default 18;
}
           

定義一個接口,聲明要實作的方法:

package com.yongfxu.annotation;

public interface Person {
    
    String name();
    
    String sing();
    
    int age();
}
           

聲明一個實作類,實作上述Person接口的方法,并增加注解:

package com.yongfxu.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@Description(desc = "descClass", author = "authorClass")
public class Child implements Person {
    @Override
    @Description(desc = "descMethodName", author = "authorMethodName")
    public String name() {
        return "hahaha";
    }
    
    @Override
    @Description(desc = "descMethodSing", author = "authorMethodSing")
    public String sing() {
        return null;
    }
    
    @Override
    @Description(desc = "descMethodAge", author = "authorMethodAge")
    public int age() {
        return 0;
    }
    
    public static void main(String[] args) {
        /*
         * 使用類加載器的反射解析注解的方式,該注解必須是@Retention(RetentionPolicy.RUNTIME),
         * 否則執行不出效果,因為類加載就是運作時執行的
         */
        try {
            // 1.使用類加載器加載類
            Class aClass = Class.forName("com.yongfxu.annotation.Child");
            // 2.找到類上的注解
            boolean isExist = aClass.isAnnotationPresent(Description.class);
            if (isExist) {
                // 3.拿到注解執行個體
                Description description = (Description) aClass.getAnnotation(Description.class);
                System.out.println(description.author());
            }
            
            // 4.找到方法上的注解
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                boolean isMExist = method.isAnnotationPresent(Description.class);
                if (isMExist) {
                    Description description = (Description) method.getAnnotation(Description.class);
                    System.out.println(description.author());
                }
            }
            
            // 5.另外一種解析方法
            for (Method method : methods) {
                Annotation[] as = method.getAnnotations();
                for (Annotation annotation : as) {
                    if (annotation instanceof Description) {
                        Description description = (Description) annotation;
                        System.out.println(description.author());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}           

執行上述類的main方法進行測試,得到下面的結果:

authorClass
authorMethodName
authorMethodSing
authorMethodAge
authorMethodName
authorMethodSing
authorMethodAge           

4.Java注解的應用

下面根據所學的注解,實作一個持久層架構的注解,用于代替Hibernate,核心代碼就是通過注解來實作。下面直接上代碼。

聲明一個Table的注解類,用于标注資料庫的表名:

package com.yongfxu.mock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    
    String value();
}
           

聲明一個Column的注解類,用于标注待查詢的字段名:

package com.yongfxu.mock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    
    String value();
}
           

聲明一個基礎Bean對象,用于承載資料庫擷取到的資料。為該類加上上述兩注解:

package com.yongfxu.mock;

@Table("user")
public class User {
    
    @Column("user_name")
    private String userName;
    
    @Column("city")
    private  String city;
    
    @Column("profession")
    private String profession;
    
    @Column("age")
    private int age;
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getProfession() {
        return profession;
    }
    
    public void setProfession(String profession) {
        this.profession = profession;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}
           

最後,聲明一個Filter類,用于實作注解的解析流程:

package com.yongfxu.mock;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Filter {
    
    public static void main(String[] args) {
        // 根據使用者名和年齡生成sql查詢語句
        User user1 = new User();
        user1.setUserName("yongfxu");
        user1.setAge(36);
        
        // 根據使用者名和其中一個崗位生成sql查詢語句
        User user2 = new User();
        user2.setUserName("yongfxu");
        user2.setProfession("programmer, manager, engineer");
        
        System.out.println("sql1: " + query(user1));
        System.out.println("sql2: " + query(user2));
    }
    
    private static String query(User user) {
        // 1.擷取對象的class
        Class userClass = user.getClass();
        // 2.驗證注解是否存在
        boolean isExist = userClass.isAnnotationPresent(Table.class);
        if (!isExist) {
            return null;
        }
        
        StringBuffer stringBuffer = new StringBuffer("select * from ");
        // 3.擷取注解Table中的内容
        Table table = (Table) userClass.getAnnotation(Table.class);
        String tableName = table.value();
        stringBuffer.append(tableName).append(" where 1 = 1");
        
        Field[] fields = userClass.getDeclaredFields();
        // 4.處理每個字段對應的SQL
        for (Field field : fields) {
            // 4.1 拿到字段的名字
            boolean isFieldExist = field.isAnnotationPresent(Column.class);
            if (!isFieldExist) {
                continue;
            }
            Column column = field.getAnnotation(Column.class);
            String columnName = column.value();
            // 4.2 拿到字段的值
            String fieldName = field.getName();
            String methodName = new StringBuffer("get").append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1)).toString();
            Object fieldValue = null;
            try {
                Method method = userClass.getMethod(methodName);
                fieldValue = method.invoke(user);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
                // 字段值為null或者int為0
                continue;
            }
            
            // 4.3 拼接SQl
            stringBuffer.append(" and ");
            if (fieldValue instanceof Integer) {
                stringBuffer.append(columnName).append(" = ").append(fieldValue);
            } else if (fieldValue instanceof String) {
                if (((String) fieldValue).contains(",")) {
                    String[] strArray = ((String) fieldValue).split(",");
                    stringBuffer.append(columnName).append(" in (");
                    for (String string : strArray) {
                        stringBuffer.append("'").append(string.trim()).append("',");
                    }
                    stringBuffer.deleteCharAt(stringBuffer.length() - 1);
                    stringBuffer.append(")");
                } else {
                    stringBuffer.append(columnName).append(" = ").append("'").append(fieldValue).append("'");
                }
            }
        }
        return stringBuffer.toString();
    }
}
           

至此,已經完成注解的實作。執行Filter類的main函數進行測試,得到下述結論:

sql1: select * from user where 1 = 1 and user_name = 'yongfxu' and age = 36
sql2: select * from user where 1 = 1 and user_name = 'yongfxu' and profession in ('programmer','manager','engineer')

程序已結束,退出代碼0