除了Java提供的注解之外,我们也可以根据自己的实际需要,订制自己的注解。
与注解相关的基础内容都在java.lang.annotation下面,结构如下
基础接口:Annotation
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
两个枚举:ElementType,RetentionPolicy
public enum RetentionPolicy {
SOURCE, // 代码级注解,一旦生成了.class文件之后丢弃。常运用于代码检查类注解
CLASS, // class级注解,同时是默认注解等级。到了JVM加载时丢弃。常用于编译时的预处理,生成一些辅助代码
RUNTIME // 运行时注解,生命周期最长的注解等级。如果需要获取运行时动态的注解信息,需要用到该等级
}
public enum ElementType {
TYPE, // 作用于:类、接口(包括注解类型)、枚举
FIELD, // 作用于:成员变量(枚举常量)
METHOD, // 作用于:方法
PARAMETER, // 作用于:方法入参
CONSTRUCTOR, // 作用于:构造函数
LOCAL_VARIABLE, // 作用于:局部变量
ANNOTATION_TYPE, // 作用于:注解类型
PACKAGE, // 作用于:包
TYPE_PARAMETER, // 作用于:类型变量(1.8新增)
TYPE_USE // 作用于:应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)(1.8新增)
}
6个已有元注解:Documented,Inherited,Native,Repeatable,Retention,Target
// 是一个标记注解,表示可以被JavaDoc文档化
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
// 是一个标记注解,用来指定该注解可以被继承。如果注释类型用于注释类以外的任何内容,则此元注释类型无效。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
// 本地方法栈相关,对象Target是FIELD成员变量
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
// 1.8新增注解,运行重复标记。直观的感受就是Repeatable修饰的自定义注解,在同一个地方可以多次声明
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}
// 标明注解的类型,具体参见RetentionPolicy枚举定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
// 标明注解的作用范围,具体参见ElementType枚举定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
三个异常:AnnotationFormatError,AnnotationTypeMismatchException,IncompleteAnnotationException。需要注意的是AnnotationFormatError继承于Error,而AnnotationTypeMismatchException和IncompleteAnnotationException继承的是RuntimeException。
// 当注释分析器试图从类文件读取注释并确定注释出现异常时,抛出该错误。
public class AnnotationFormatError extends Error {
private static final long serialVersionUID = -4256701562333669892L;
public AnnotationFormatError(String message) {
super(message);
}
public AnnotationFormatError(String message, Throwable cause) {
super(message, cause);
}
public AnnotationFormatError(Throwable cause) {
super(cause);
}
}
// 若某个注释的类型在对该注释进行编译(或序列化)后发生了更改,而程序试图访问该注释的元素时,抛出此异常。
public class AnnotationTypeMismatchException extends RuntimeException {
private static final long serialVersionUID = 8125925355765570191L;
private final Method element;
private final String foundType;
public AnnotationTypeMismatchException(Method element, String foundType) {
super("Incorrectly typed data found for annotation element " + element
+ " (Found data of type " + foundType + ")");
this.element = element;
this.foundType = foundType;
}
public Method element() {
return this.element;
}
public String foundType() {
return this.foundType;
}
}
// 若某个注释在编译(或序列化)后将某个注释类型添加到其类型定义中,而程序试图该注释类型的元素时,抛出此异常。如果新元素有默认值,则不抛出此异常。
public class IncompleteAnnotationException extends RuntimeException {
private static final long serialVersionUID = 8445097402741811912L;
private Class<? extends Annotation> annotationType;
private String elementName;
public IncompleteAnnotationException(
Class<? extends Annotation> annotationType,
String elementName) {
super(annotationType.getName() + " missing element " +
elementName.toString());
this.annotationType = annotationType;
this.elementName = elementName;
}
public Class<? extends Annotation> annotationType() {
return annotationType;
}
public String elementName() {
return elementName;
}
}
基本看完这些源码之后,对自定义注解就足够创建了。其中最重要的两个标注就是@Retention和@Target,指定注解的生命周期,以及作用范围。其他的就自定义属性,运用时获取就行。尝尝用于用注解声明缩减共通代码、AOP切片编程等。
我们需要注意的是,如何定义自定义注解只是一个基础,并不是定义了自定义注解、并且在相应的位置加上了注解声明,这个注解想要实现的功效就可以发挥作用。我们更应该把注意力放在如果通过这些自定义注解实现功能的逻辑上。
打个比方
//自定义限制注解,加载controller 方法上 判断是否重复提交
@Retention(RUNTIME)
@Target(METHOD)
public @interface ReSubmitLimit {
long seconds() default 2; //指定时间内,不能重复提交,默认2秒
}
是不是定义了这个防止重复提交的注解,同时在Controller上声明了这个防二次提交注解就可以生效了。但其实它的核心功能是在拦截器里面发挥作用的。
@Component
public class ResubmitLimitInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(handler instanceof HandlerMethod) {
//String accessToken = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
SysUser sysUser = UserUtils.getCurrentMember();
if(null == sysUser || null == sysUser.getId()) {
return true;
}
String sysUserId = sysUser.getId().toString(); //获取当前登录用户
if (StringUtils.isBlank(sysUserId)){
return true;
}
HandlerMethod handlerMethod = (HandlerMethod)handler;
//请求的方法是否带有ReSubmitLimit注解
ReSubmitLimit reSubmitLimit = handlerMethod.getMethodAnnotation(ReSubmitLimit.class);
if(reSubmitLimit == null) {
return true;
}
String url = request.getRequestURI();
Long sencods = reSubmitLimit.seconds();
String key = "RESUBMIT:"+sysUserId+":"+url;
boolean exists = this.redisService.exists(key);
if(!exists) {
logger.debug(" normal submit method : {}", key);
this.redisService.set(key,1,sencods);
return true;
}else {
logger.debug(" re submit method : {}", key);
render(response, CodeMsg.ACCESS_LIMIT_REACHED);
this.redisService.set(key,1,sencods);
return false;
}
}
return true;
}
//返回前台
private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(Result.error(cm));
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}
本文技术菜鸟个人学习使用,如有不正欢迎指出修正。xuweijsnj