1.先看一个dome
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAutoRecord {
// 强制记录方法功能(使用注解必须填写该属性)
String methodDesc();
// 带有默认值,非必填
String defValue() default "";
}
对于上述dome中,有以下几个属性。
-
注解定义用
@interface
关键字修饰 -
@Target
注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的 -
@Retention
注解,翻译为持久力、保持力。即用来修饰自定义注解的生命周期 -
@Documented
注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中 -
@Inherited
注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解
2.属性解释
-
用
@interface
关键字修饰,表明这是一个注解,可将注解挂到别的头上,至于挂到哪要看@Target注解了。 -
@Target
注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
从源码中看出 该注解传入 ElementType[] ,是一个数组,可以传多个,如:
@Target({ElementType.METHOD,ElementType.TYPE})
上面是修饰的注解表明该注解可以用在方法上,也可以用在类上。具体属性看源码
/** Class, interface (including annotation type), or enum declaration */ /**类、接口(包括注释类型)或枚举声明*/ TYPE, /** Field declaration (includes enum constants) */ /**字段声明(包括枚举常量)*/ FIELD, /** Method declaration */ /**方法声明*/ METHOD, /** Formal parameter declaration */ /**形式参数声明*/ PARAMETER, /** Constructor declaration */ /**构造函数声明*/ CONSTRUCTOR, /** Local variable declaration */ /**局部变量声明*/ LOCAL_VARIABLE, /** Annotation type declaration */ /**注释类型声明*/ ANNOTATION_TYPE, /** Package declaration */ /**包装声明*/ PACKAGE, /** * Type parameter declaration * * @since 1.8 * 类型参数声明,1.8以后才有 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 字体的使用 */ TYPE_USE
-
@Retention
注解,可以理解为生效时期Retention 源码
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
Retention 源码中属性值为RetentionPolicy对象,所以不能像Target一样配置多个,下面看一下RetentionPolicy属性的源码:
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. * 编译器将丢弃注释。 * 这个属性的表明只会将注解信息保存到程序源码中,在经过编译器编译后就会将信息抛弃,不会保存到.class文件中,也不会被加载到Jvm中。基本就是看着玩呢,标记一下 */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. * 这个属性的表明只会将注解信息保存到程序源码中,也会保存到.class文件中,但不会加载到jvm中。标记一下,读取class时候可以读取到。 */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement * 这个属性的表明只会将注解信息保存到程序源码中,也会保存到.class文件中,也会加载到jvm中。在程序中可以读取到哪些类或方法标记了该注解,便于我们后续操作 */ RUNTIME }
测试这三个属性,创建三个注解,Retention分别标记为RUNTIME,SOURCE,CLASS
@Retention(RetentionPolicy.RUNTIME) public @interface LogAutoRecord { // 省略 1 @Retention(RetentionPolicy.CLASS) public @interface LogAutoRecordTestClass { // 省略2 @Retention(RetentionPolicy.SOURCE) public @interface LogAutoRecordTestSource { // 省略 3
controller
// 我在controller中使用注解,方法如下: @GetMapping("/getUserByCommon") @LogAutoRecord(methodDesc = "根据统一请求查询用户信息") public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO){ String userById = userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); } @GetMapping("/getUserByCommonTestSource") @LogAutoRecordTestSource(methodDesc = "根据统一请求查询用户信息,测试注解属性source") public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO){ String userById = userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); } @GetMapping("/getUserByCommonTestClass") @LogAutoRecordTestClass(methodDesc = "根据统一请求查询用户信息,测试注解属性Class") public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO){ String userById = userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); }
编译后的class文件
// 编译后的.class 文件 @GetMapping({"/getUserByCommon"}) @LogAutoRecord( methodDesc = "根据统一请求查询用户信息" ) public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO) { String userById = this.userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); } @GetMapping({"/getUserByCommonTestSource"}) public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO) { String userById = this.userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); } @GetMapping({"/getUserByCommonTestClass"}) @LogAutoRecordTestClass( methodDesc = "根据统一请求查询用户信息,测试注解属性Class" ) public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO) { String userById = this.userService.getUserById(userRequestDTO.getUserId()); return CommonResponse.succeed(userById); }
根据编译后的文件可以看出source标记的编译后就不见了,class的还在。
-
@Documented
注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中 -
@Inherited
注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
3. 获取注解
-
给出指定包
public class GetAnnotation { public static void getAnnotation() { // 要扫描的包 String packageName = "com.zhoust.fastdome.business.controller"; Reflections f = new Reflections(packageName); // 获取扫描到的标记注解的集合 Set<Class<?>> set = f.getTypesAnnotatedWith(LogAutoRecord.class); for (Class<?> c : set) { // 循环获取标记的注解 LogAutoRecord annotation = c.getAnnotation(LogAutoRecord.class); // 打印注解中的内容 System.out.println(annotation.methodDesc()); } } }
使用Reflections反射需要导入反射包,或者自己使用java自带的反射包
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> </dependency>
-
aop切面 中获取注解的对象
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String methodName = method.getName(); LogAutoRecord annotation = method.getAnnotation(LogAutoRecord.class); String methodDesc = "记录日志"; if(null != annotation){ methodDesc = annotation.methodDesc(); }
活到老学到老