在工作中,我们有时候需要将一些公共的功能封装,比如操作日志的存储,防重复提交等等。这些功能有些接口会用到,为了便于其他接口和方法的使用,做成自定义注解,侵入性更低一点。别人用的话直接注解就好。下面就来讲讲自定义注解这些事情。
一.@Target、@Retention、@Documented简介
java自定义注解的注解位于包:java.lang.annotation下。包含三个元注解@Target、@Retention、@Documented,即注解的注解。
@Target
@Target
:注解的作用目标。和枚举ElementType共同起作用
根据源码知道,可以配置多个作用目标。
@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的类型如下:
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** 类, 接口 (包括注解类型), 或 枚举 声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明(Method declaration) */
METHOD,
/** 正式的参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* 类型参数声明
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用的类型
*
* @since 1.8
*/
TYPE_USE
}
复制代码
@Retention
指示带注解类型的注解要多长时间被保留。如果没有保留注释存在 注释类型声明,保留策略默认为 {@code RetentionPolicy.CLASS}和RetentionPolicy共同起作用。
RetentionPolicy类型如下:
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
*/
SOURCE,
/**
* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
*/
CLASS,
/**
* 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
*/
RUNTIME
}
复制代码
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
**@**Documented
**@**Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。
二.编写自定义注解
1.创建自定annotation包
当然,名字自定义
2.创建自定义注解
我这里以Log为例。这里是通过新建自定义注解创建的。也可以手动创建一个class类进行修改@interface呢。
public @interface test {
}
复制代码
3.加入元注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
复制代码
4.编写AOP
@Aspect
@Component
public class SysLogAspect {
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SysLogAspect.class);
@Pointcut("@annotation(com.aldeo.common.annotation.Log)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 目标方法
Object result = point.proceed();
long time = System.currentTimeMillis() - beginTime;
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 注解上的描述
LOGGER.info(syslog.value());
}
// 保存日志
try {
saveLog(point, time);
} catch (Exception e) {
LOGGER.error("==================================> saveSysLog.around.exception: " + e.getMessage(), e);
}
return result;
}
复制代码
三.使用
在需要注解的方法上加上注解
@Log("测试自定义注解")
public String restPassword(){
return "成功";
}
复制代码
运行结果
2020-11-14 16:09:00.245 |-INFO http-nio-6089-exec-9 c.v.t.*.40 - gmzakixAkym3 : 测试自定义注解
复制代码
四.自定义注解使用场景和原理
自定义注解的原理,就是自己定义完注解。将注解加到需要注解的方法上。然后在拦截器拦截到注解的,然后进行后续的处理。
登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。反射的实现在 Java 应用层面上讲,是通过对 Class 对象的操作实现的,Class 对象为我们提供了一系列方法对类进行操作。在 JVM 这个角度来说,Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在 Class 文件中,里面包含了类、方法、字段等等相关数据。通过对 Class 数据流的处理我们即可得到字段、方法等数据。