自定义注解那些事(二)
一、元注解
从JDK 1.5开始, Java增加meta-annotation(元注解)支持,所谓元注解就是可以用于注解注解类型的注解。JDK 1.5中提供了4个标准的元注解如下所示,此处不再详述。
- @Target
- @Retention
- @Documented
- @Inherited : 表示被注解的注解可被子类继承(不包括接口实现类)。
二、可重复注解
从JDK 1.8开始,增加了
@Repeatable
元注解,用于支持可重复注解的使用。以Spring框架中的@PropertySource
和@PropertySources
注解为例
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.io.support.PropertySourceFactory;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}
三、注解属性别名
@AliasFor
(注解属性别名)是Spring框架特有的东西,位于spring-core包,用于指定注解属性之间的别名关系。
- 注解中的
value
属性在注解目标对象时,仅需要填写value属性时,可以不加value="xxx"
,但是自定义的属性名不能这样,因此我们可以将自定义的属性名与value属性建立别名关系,简化代码。例如@AliasFor("path")
、@AliasFor("value")
@AliasFor
还可以与元注解属性建立别名关系,达到属性覆盖的目的(请看下一节)。例如@AliasFor(annotation = RequestMapping.class)
,此处是与@RequestMapping
元注解的中同名属性建立别名关系,其实你完全可以用不同的属性名与元注解中的属性建立别名关系,例如建立path1
属性与@RequestMapping
元注解的中path
属性的别名关系。
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class, value = "path")
String[] path1() default {};
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
* @since 4.3.5
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
四、注解属性覆盖
上一章讲到,Spring提到的常用的
AnnotationUtils
和AnnotatedElementUtils
注解工具类,它们功能基本相同,一般情况下使用AnnotationUtils
即可,但是如果涉及注解属性覆盖则需要使用AnnotatedElementUtils
。我们知道,使用Spring的强大注解工具类
AnnotationUtils
和AnnotatedElementUtils
,我们可以获取到父类注解、父类字段注解、父类方法注解、接口注解、接口方法注解以及元注解的属性值,那我们是否可以在注解里定义元注解里的同名注解对元注解的值进行覆盖哪,使用Spring注解工具类AnnotatedElementUtils
我们同样可以做到。实际下,我们常用的@GetMapping
、@PostMapping
注解就是这么干的,它覆盖了@RequestMapping
里的同名属性,通常我们会结合@AliasFor
注解属性别名对元注解属性进行覆盖,一般也建议这么做(直观),我们甚至可以通过结合@AliasFor
注解属性别名覆盖不同名的注解属性值。
1.获取属性值
被
@RequestMapping
元注解标注的注解的目标对象(Class或Method)上即使未增加@RequestMapping
注解,也可以通过Spring注解工具类获取到@RequestMapping
注解及属性值,不过要使用AnnotatedElementUtils
注解工具类才能获取到覆盖后的属性值。
// 可获取到被@GetMapping注解覆盖的属性值
final RequestMapping annotation1 = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
System.out.println(annotation1);
// 获取不到被@GetMapping注解覆盖的属性值,仅能获取@RequestMapping自身的属性值
final RequestMapping annotation2 = AnnotationUtils.findAnnotation(method, RequestMapping.class);
System.out.println(annotation1);
2.完整示例
package com.example.demo.annotation;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface MyMethodRequestMapping {
/**
* 与RequestMapping注解中的method属性建立别名关系
*
* @return 请求方式
*/
@AliasFor(annotation = RequestMapping.class, value = "method")
RequestMethod[] method1() default {};
}
package com.example.demo.service;
public interface TestService {
void test();
}
package com.example.demo.service.impl;
import com.example.demo.annotation.MyMethodRequestMapping;
import com.example.demo.enumeration.Operate;
import com.example.demo.service.TestService;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
@Service
public class TestServiceImpl implements TestService {
@Override
@MyMethodRequestMapping(method1 = {RequestMethod.GET, RequestMethod.POST})
public void test() {
System.out.println("this is TestServiceImpl");
}
}
package com.example.demo.controller;
import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.LogPoint;
import com.example.demo.service.TestService;
import com.example.demo.vo.ResultBody;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.lang.reflect.Method;
@RestController
public class HelloController{
@Resource
private TestService testService;
@GetMapping("test")
public ResultBody test() throws NoSuchMethodException {
final Class<? extends TestService> aClass = testService.getClass();
System.out.println(aClass);
final Method method = aClass.getMethod("test");
final RequestMapping annotation1 = method.getAnnotation(RequestMapping.class);
System.out.println(annotation1);
final RequestMapping annotation2 = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
System.out.println(annotation2);
final RequestMapping annotation3 = AnnotationUtils.findAnnotation(method, RequestMapping.class);
System.out.println(annotation3);
testService.test();
return new ResultBody(aClass);
}
}