首先一句话结论:注解就是一种通过在类、方法、或者属性等上使用类似@xxx的方式进行“打标签”,然后可以通过反射机制对标签的内容进行解析并进行相应处理的手段。
注解是java中的一个重要知识点,从java5后开始引入,尤其在spring框架中大量使用。比较常用的有@controller、@service等等各种,本文将从注解的实现原理出发,通过一些demo代码的实现,进行分析。
直接上代码,看看spring中@Service注解的定义就知道了:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { String value() default ""; } 复制代码
可以看到注解的定义和接口定义很像,但是多了@字符,注解的定义上有以下约定: 1.只能定义属性名,不能定义方法 2.属性的可见性只有public和default,不写则默认后者 3.属性的类型只能支持:基本数据类型、string、class、enum、Annotation类型及以上类型的数组 4.可以加上defult关键字指明默认值,当某字段不指明默认值时,必须在进行注解标注的时候进行此字段值的指定。 5.当使用value作为属性名称时,可以不显式指定value=“xxx”,如可以直接使用@Service("xxxService")
所谓元注解就是java中默认实现的专门对注解进行注解的注解。元注解的总数就5个,下面我们以上面讲到的@Service注解为例子各个击破: 1.@Target 此注解用于表示当前注解的使用范围,@Target({ElementType.TYPE})就代表着@Service这个注解是专门用来注解到类、接口、或者枚举类型上面的,当在方法上面加这个注解时,就会报错。可以看到注解位置是一个枚举类型,完整定义如下
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 } 复制代码
2.@Retention 此注解用于表示当前注解的生命周期,说人话就是这个注解作用会保留到什么时候,如@Retention(RetentionPolicy.RUNTIME)就表示在程序运行期间依然有效,此时就可以通过反射拿到注解的信息,完整的枚举定义如下
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 } 复制代码
3.@Documented 当被此注解所注解时,使用javadoc工具生成文档就会带有注解信息。 4.@Inherited 此注解与继承有关,当A注解添加此注解后,将A注解添加到某类上,此类的子类就会继承A注解。
@Inherited public @interface A{ } @A public class Parent{} public class Son entends Parant{}//Son类继承了父类的A注解 复制代码
5.@Repeatable 此注解顾名思义是拥有可以重复注解的能力。想象这样一个场景,我们需要定时执行某个任务,需要在每周一和周三执行,并且这个时间是可以灵活调整的,此时这个元注解就能派上用场:
@Repeatable(Schedules.class) public @interface Schedule { String date(); } public @interface Schedules { Schedule[] value(); } @Schedule(date = "周一") @Schedule(date = "周三") public class Executor { } 复制代码
注意看到此元注解后面括号里内容,在这指定的类叫做容器注解,意思是保存这多个注解的容器,故我们创建一个@Schedules注解作为@Schedule的容器注解,容器注解必须含有一个名字为value,返回类型为需放入此容器的注解数组的属性。
下面我们以web项目中非常常见的鉴权场景为例自己实现一个自定义注解。 首先我们定义系统的使用人员身份,有超级管理员、管理员、访客三种身份。
public enum IdentityEnums { SUPER_ADMIN, ADMIN, VISIVOR } 复制代码
接下来我们定义一个权限注解:
@Target({ElementType.TYPE,ElementType.METHOD}) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Authorization { IdentityEnums[] value(); } 复制代码
然后使用拦截器的方式,对所有页面进行统一的鉴权管理,此处只展示一些关键代码:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { IdentityEnums user = getIdentityFromRequset(request);//这里从request里获取账号信息并判断身份,自己实现 Authorization auth =((HandlerMethod) handler).getMethodAnnotation(Authorization.class);//获取方法上面的注解 if (!Arrays.asList(auth.value()).contains(user)){ return false; } } return true; } } 复制代码
最后在spring配置文件中对拦截器进行配置开启拦截器:
<!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> <mvc:mapping path="/**" /> <!-- 拦截器类 --> <bean class="com.xx.xx.AuthInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> 复制代码
在实际使用中,我们将在方法上面添加此自定义注解,当身份权限符合时,才能对页面进行访问,使用方式如下:
@ResponseBody @RequestMapping(value = "/management") @Authorization({IdentityEnums.ADMIN,IdentityEnums.SUPER_ADMIN}) public String management(HttpServletRequest request, HttpServletResponse response) { log.info("has permission!"); } 复制代码