Java自定義注解


概念

作用

構建或者運行階段提供一些元數據,不影響正常運行邏輯,簡化開發

內置注解

Java提供了一些內置注解,並且實現了相關功能

  • @Override 檢查該方法是否是重載方法,如果發現其父類,或者是引用的接口中並沒有該方法時,會報編譯錯誤
  • @Deprecated 標記過時方法。如果使用該方法,會報編譯警告
  • @SuppressWarnings 指示編譯器去忽略注解中聲明的警告
  • @SafeVarargs 忽略任何使用參數為泛型變量的方法或構造函數調用產生的警告
  • @FunctionalInterface 標識一個匿名函數或函數式接口

元注解

Java提供了一些注解來構建自定義注解

  • @Retention 指定生命周期
    • RetentionPolicy.RUNTIME:運行時可以被反射捕獲到
    • RetentionPolicy.CLASS:注解會保留在.class字節碼文件中,這是注解的默認選項,運行中獲取不到
    • RetentionPolicy.SOURCE:只在編譯階段有用,不被保存到class文件中
  • @Target 指定注解可以加在哪里
    • ElementType.ANNOTATION_TYPE:只能用於定義其他注解
    • ElementType.CONSTRUCTOR
    • ElementType.FIELD
    • ElementType.LOCAL_VARIABLE
    • ElementType.METHOD
    • ElementType.PACKAGE
    • ElementType.PARAMETER
    • ElementType.TYPE: 可以是類、接口、枚舉或注釋
  • @Inherited 使用了注解的類的子類會繼承這個注解
  • @Documented 用於在JavaDoc中生成
  • @Repeatable 標識某注解可以在同一個聲明上使用多次

實踐

首先我們定義兩個比較常見作用域的自定義注解,在開發過程中我們一般都是定義運行時的注解,編譯時的注解一般都是實現APT,用於一些編譯時候的校驗和生成字節碼,代表的有Lombok框架。

自定義注解

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Hello {
    String value() default "";
}
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Chinese {
}

注解功能實現

JDK動態代理

現在我們有了自定義注解但是他沒有實現任何功能,就只起裝飾作用,下面我們來模擬一個場景,一個Person類有order行為,我們希望通過注解在點單前加上打招呼,Person有一個屬性name,我們希望校驗這個人名字由漢字組成

Bean
public class Person implements Action {

    @Chinese
    private String name;

    @Override
    @Hello("服務員")
    public void order() {
        System.out.println("可以給我一個漢堡包么?");
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public Person() {
    }
}
代理
public class Proxys implements InvocationHandler {

    private Object target;

    public Proxys(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (this.target instanceof Person) {
            Person person = (Person) this.target;
            // 判斷Person類,name字段有沒有加Chinese注解
            if (person.getClass()
                    .getDeclaredField("name")
                    .isAnnotationPresent(Chinese.class)) {
                // 判斷名字是不是漢字
                if (Objects.nonNull(person.getName()) &&
                        !person.getName().matches("[\\u4E00-\\u9FA5]+")) {
                    throw new IllegalArgumentException("Person Name is not chinese");
                }
            }
            Method targetMethod = person.getClass().getMethod(methodName);
            if ("order".equals(methodName)) {
                // 攔截接口實現類中order方法判斷是否有Hello注解
                if (targetMethod.isAnnotationPresent(Hello.class)) {
                    System.out.println("你好," +
                            targetMethod.getAnnotation(Hello.class).value());
                } else if (method.isAnnotationPresent(Hello.class)) { // 攔截接口中order方法判斷是否有Hello注解
                    System.out.println("你好," +
                            method.getAnnotation(Hello.class).value());
                }
                return method.invoke(this.target, args);
            }
        }
        return null;
    }

    public static Object getProxy(Object action) {
        Proxys handler = new Proxys(action);
        return Proxy.newProxyInstance(
                action.getClass().getClassLoader(),
                action.getClass().getInterfaces(),
                handler);
    }
}
測試

可以看到我們的注解起到效果了

public class Test {

    public static void main(String[] args) {
        Action person1 = (Action) Proxys.getProxy(new Person("匿名"));
        person1.order();
        Action person2 = (Action) Proxys.getProxy(new Person("Sun"));
        person2.order();
    }

    /**
     * 輸出:
     * 你好,服務員
     * 可以給我一個漢堡包么?
     * Exception in thread "main" java.lang.IllegalArgumentException: Person Name is not chinese
     * 	at reflect.annotations.Proxys.invoke(Proxys.java:32)
     * 	at com.sun.proxy.$Proxy0.order(Unknown Source)
     * 	at reflect.annotations.Test.main(Test.java:9)
     */
}

Spring AOP

目前Spring框架用的比較多,我們定義和上面一樣的hello注解

切面
@Aspect
@Component
public class HelloAspect {

    @Pointcut("@annotation(com.github.freshchen.springbootcore.annotation.Hello)")
    private void pointcut(){}

    @Before("pointcut() && @annotation(hello)")
    public void hello(Hello hello){
        System.out.println("你好," + hello.value());
    }
}
測試

同樣起到了效果,Spring真香

@SpringBootApplication
public class SpringbootCoreApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringbootCoreApplication.class, args);
        Person person = context.getBean("person", Person.class);
        person.order();
    }

    /**
     * 輸出:
     * 你好,服務員
     * 可以給我一個漢堡包么?
     */

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM