通过java注解的第一节学习,我们大致对java的内置注解有了一定的认识,本篇文章主要学习自定义注解。
上节: java学习之路01-java注解(Annotation)01-java内置注解.
注解的组成
通过上一节的学习,我们可以知道每个注解必备的两个组成:注解的作用范围(@Target
)和注解的保留策略(@Retention
)
通过查看元注解的源码可以看出每个注解都有1个保留策略和1到多个作用范围构成
@Targe
源码,其value对应的枚举为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();
}
public enum ElementType {
/** 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
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention
源码,其value对应的枚举为RetentionPolicy
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
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,
/**
* 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
*/
RUNTIME
}
这两个元注解对应枚举的含义上节元注解学习中已经学过,这里不再累赘
使用自定义注解
AnnotatedElement
在使用自定义注解前,我们先了解一下java.lang.reflect.AnnotatedElement
这个接口。
先看一下该接口的方法,注解的处理是通过java反射来处理的:
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
指定类型的注解出现在当前对象上,则返回true,否则将返回false。
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
指定类型的注解出现在当前对象上,则返回对应的注解,否则将返回null。
Annotation[] getAnnotations();
返回直接出现在当前对象上的注解(不包括继承的注解),没有则返回空数组。
常用的Class, Method, Field等都实现了该接口,也就是说我们可以通过java 的反射拿到Class, Method, Field类等,就可以处理到注解在上面的注解了。
注意:只有注解的保留策略为RetentionPolicy.RUNTIME
时,才可以通过反射获取注解并且做响应的处理。
基本用法
写两个个简单的demo学习下通过反射获取注解:
//定义自定义注解
//使用范围为类,方法,构造器
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
//保留策略为运行期间
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String value() default "1";
}
@TestAnnotation(value = "3")
class Test {
//如果注解的成员只有一个value,则可以省略前面的"参数名="
@TestAnnotation("2")
public Test() {
}
//如果注解的成员有默认值,则可以不用复制
@TestAnnotation
//保留策略为源码级别的注解
@Override
public int hashCode() {
return 0;
}
public static void main(String[] args) throws NoSuchMethodException {
//通过获取指定注解的方式获取类上的注解
TestAnnotation annotationClass = Test.class.getAnnotation(TestAnnotation.class);
System.out.println(annotationClass.value());
//通过获取注解数组的方式获取构造器上的注解
Annotation[] annotationConstructors = Test.class.getConstructor().getAnnotations();
for (Annotation annotation : annotationConstructors) {
System.out.println(((TestAnnotation) annotation).value());
}
//通过获取注解数组的方式获取方法上的注解
Annotation[] annotationMethods = Test.class.getMethod("hashCode").getAnnotations();
if (annotationMethods.length > 1) {
for (Annotation annotation : annotationMethods) {
System.out.println(annotation);
}
} else {
System.out.println(((TestAnnotation) annotationMethods[0]).value());
}
}
}
运行结果:
//定义自定义注解
//使用范围为类
@Target({ElementType.TYPE})
//保留策略为运行期间
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String a() default "a";
String b() default "b";
}
@TestAnnotation(a = "aa")
class Test {
public static void main(String[] args) throws NoSuchMethodException {
//通过获取指定注解的方式获取类上的注解
TestAnnotation annotationClass = Test.class.getAnnotation(TestAnnotation.class);
System.out.println(annotationClass.a());
System.out.println(annotationClass.b());
}
}
运行结果:
应用场景
在日常项目开发中,一般自定义注解配合AOP或者拦截器使用。
比如cookie拦截,接口权限控制,日志打印等,都可以通过自定义注解实现。
举个栗子:
- cookie拦截:
定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginAnnotation {
}
实现拦截器
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o){
//获取method
HandlerMethod handlerMethod = (HandlerMethod)o;
//获取method上的loginAnnotation注解
LoginAnnotation loginAnnotation = handlerMethod.getMethod().getAnnotation(LoginAnnotation.class);
if(loginAnnotation != null) {
//如果有loginAnnotation注解,则说明需要cookie拦截
if(//todo...cookie存在){
//允许访问接口
return true;
}else {
//未登录 则不允许访问
return false;
}
}
//没有loginAnnotation注解,则说明不需要cookie拦截(用户不需要登陆)
return true;
}
在登陆,注册等不需要登陆的url上,不加该注解,则不会进入校验cookie阶段;如果修改个人信息,查看信息等需要登陆的url上,加该注解,则会检验cookie,验证是否登陆。
- 接口权限控制
定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JurisdictionAnnotation {
//定义接口权限code
String jurisdictionCode() default "0";
}
定义权限级别枚举
enum JurisdictionEnum {
USER(0, "普通用户"),
ADMIN(1, "管理员"),
;
@Getter
private Integer code;
@Getter
private String desc;
JurisdictionEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
实现拦截器
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o){
//获取method
HandlerMethod handlerMethod = (HandlerMethod)o;
//获取method上的loginAnnotation注解
JurisdictionAnnotation jurisdictionAnnotation = handlerMethod.getMethod().getAnnotation(JurisdictionAnnotation.class);
if(jurisdictionAnnotation != null) {
//如果有jurisdictionAnnotation注解,则说明需要判断权限
//找出该接口需要的权限级别
String code = jurisdictionAnnotation.jurisdictionCode();
if(//todo...判断当前用户的权限级别是否与接口的权限级别一致){
//允许访问接口
return true;
}else {
//权限不足 则不允许访问
return false;
}
}
//没有jurisdictionAnnotation注解,则说明不需要权限控制
return true;
}
- 日志打印
定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
//定义打印日志的一些参数 比如日志级别,打印到指定目录等
String logLevel() default "info";
String logFileName() default "service";
}
声明切面
@Aspect
class Test {
//该切点是切在LogAnnotation注解上的
@Pointcut("@annotation(com.demo.testannotation.LogAnnotation)")
public void logAspect() {
}
@Around("logAspect()")
public void logAround(ProceedingJoinPoint joinPoint) {
//todo...根据具体日志参数打印入参
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//todo...根据具体日志参数打印出参
}
}