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 框架。

自定义注解#

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

注解功能实现#

JDK 动态代理#

现在我们有了自定义注解但是他没有实现任何功能,就只起装饰作用,下面我们来模拟一个场景,一个 Person 类有 order 行为,我们希望通过注解在点单前加上打招呼,Person 有一个属性 name,我们希望校验这个人名字由汉字组成

Bean
Copy
public class Person implements Action {
<span class="hljs-meta">@Chinese</span>
<span class="hljs-keyword">private</span> String name;

<span class="hljs-meta">@Override</span>
<span class="hljs-meta">@Hello("服务员")</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">order</span><span class="hljs-params">()</span> {
    System.out.println(<span class="hljs-string">"可以给我一个汉堡包么?"</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">Person</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">Person</span><span class="hljs-params">()</span> {
}

}

代理
Copy
public class Proxys implements InvocationHandler {
<span class="hljs-keyword">private</span> Object target;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">Proxys</span><span class="hljs-params">(Object target)</span> {
    <span class="hljs-built_in">this</span>.target = target;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable {
    <span class="hljs-type">String</span> <span class="hljs-variable">methodName</span> <span class="hljs-operator">=</span> method.getName();
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.target <span class="hljs-keyword">instanceof</span> Person) {
        <span class="hljs-type">Person</span> <span class="hljs-variable">person</span> <span class="hljs-operator">=</span> (Person) <span class="hljs-built_in">this</span>.target;
        <span class="hljs-comment">// 判断Person类,name字段有没有加Chinese注解</span>
        <span class="hljs-keyword">if</span> (person.getClass()
                .getDeclaredField(<span class="hljs-string">"name"</span>)
                .isAnnotationPresent(Chinese.class)) {
            <span class="hljs-comment">// 判断名字是不是汉字</span>
            <span class="hljs-keyword">if</span> (Objects.nonNull(person.getName()) &amp;&amp;
                    !person.getName().matches(<span class="hljs-string">"[\\u4E00-\\u9FA5]+"</span>)) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(<span class="hljs-string">"Person Name is not chinese"</span>);
            }
        }
        <span class="hljs-type">Method</span> <span class="hljs-variable">targetMethod</span> <span class="hljs-operator">=</span> person.getClass().getMethod(methodName);
        <span class="hljs-keyword">if</span> (<span class="hljs-string">"order"</span>.equals(methodName)) {
            <span class="hljs-comment">// 拦截接口实现类中order方法判断是否有Hello注解</span>
            <span class="hljs-keyword">if</span> (targetMethod.isAnnotationPresent(Hello.class)) {
                System.out.println(<span class="hljs-string">"你好,"</span> +
                        targetMethod.getAnnotation(Hello.class).value());
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.isAnnotationPresent(Hello.class)) { <span class="hljs-comment">// 拦截接口中order方法判断是否有Hello注解</span>
                System.out.println(<span class="hljs-string">"你好,"</span> +
                        method.getAnnotation(Hello.class).value());
            }
            <span class="hljs-keyword">return</span> method.invoke(<span class="hljs-built_in">this</span>.target, args);
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">getProxy</span><span class="hljs-params">(Object action)</span> {
    <span class="hljs-type">Proxys</span> <span class="hljs-variable">handler</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Proxys</span>(action);
    <span class="hljs-keyword">return</span> Proxy.newProxyInstance(
            action.getClass().getClassLoader(),
            action.getClass().getInterfaces(),
            handler);
}

}

测试

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

Copy
public class Test {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    <span class="hljs-type">Action</span> <span class="hljs-variable">person1</span> <span class="hljs-operator">=</span> (Action) Proxys.getProxy(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">"匿名"</span>));
    person1.order();
    <span class="hljs-type">Action</span> <span class="hljs-variable">person2</span> <span class="hljs-operator">=</span> (Action) Proxys.getProxy(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">"Sun"</span>));
    person2.order();
}

<span class="hljs-comment">/**
 * 输出:
 * 你好,服务员
 * 可以给我一个汉堡包么?
 * 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)
 */</span>

}

Spring AOP#

目前 Spring 框架用的比较多,我们定义和上面一样的 hello 注解

切面
Copy
@Aspect @Component public class HelloAspect {
<span class="hljs-meta">@Pointcut("@annotation(com.github.freshchen.springbootcore.annotation.Hello)")</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">pointcut</span><span class="hljs-params">()</span>{}

<span class="hljs-meta">@Before("pointcut() &amp;&amp; @annotation(hello)")</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">hello</span><span class="hljs-params">(Hello hello)</span>{
    System.out.println(<span class="hljs-string">"你好,"</span> + hello.value());
}

}

测试

同样起到了效果,Spring 真香

Copy
@SpringBootApplication public class SpringbootCoreApplication {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    <span class="hljs-type">ApplicationContext</span> <span class="hljs-variable">context</span> <span class="hljs-operator">=</span> SpringApplication.run(SpringbootCoreApplication.class, args);
    <span class="hljs-type">Person</span> <span class="hljs-variable">person</span> <span class="hljs-operator">=</span> context.getBean(<span class="hljs-string">"person"</span>, Person.class);
    person.order();
}

<span class="hljs-comment">/**
 * 输出:
 * 你好,服务员
 * 可以给我一个汉堡包么?
 */</span>

}