Java 注解
一、什么是 java 注解
注解,顾名思义,注解, 就是对某一事物进行添加注释说明,会存放一些信息,这些信息可能对以后某个时段来说是很有用处的。
Java 注解又叫 java 标注,java 提供了一套机制,使得我们可以对方法、类、参数、包、域以及变量等添加标准 (即附上某些信息)。且在以后某个时段通过反射将标注的信息提取出来以供使用。
二、自定义 Java 标注
1 为什么要自定义注解
Java 从 1.5 版本以后默认内置三个标注:
Ø @Override: 只能用在方法之上的,用来告诉别人这一个方法是改写父类的。
Ø @Deprecated: 用来表示某个类的属性或方法已经过时,不想别人再用时, 在属性和方法上用 @Deprecated 修饰; 编译的时候会用产生警告信息, 可以设定在程序里的所有的元素上.
Ø @SuppressWarnings: 这一个类型可以来暂时把一些警告信息消息关闭.
但是,仅仅这三个标注是不能满足我们开发时一些需求的。所以 java 允许我们自定义注解来使用。
2 如何自定义注解
java 用 @interface xxx{ } 定义一个注解 @xxx,一个注解是一个类
自定义步骤大致分为两步:
1, 通过 @interface 关键字声明注解名称,以及注解的成员属性或者叫做注解的参数。
2, 使用 java 内置的四个元注解对这个自定义标注的功能和范围进行一些限制
问题来了,什么是元注解?
3 什么是元注解
元注解,就是定义注解的注解,也就是说这些元注解是的作用就是专门用来约束其它注解的注解。请区别上面那三个注解,他们也是通过元注解定义而来的。
元注解有哪些呢,主要有四个 @Target,@Retention,@Documented,@Inherited?
1. * 元注解有:@Target,@Retention,@Documented,@Inherited
2. *
3. * @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
4. * ElemenetType.CONSTRUCTOR 构造器声明
5. * ElemenetType.FIELD 域声明(包括 enum 实例)
6. * ElemenetType.LOCAL_VARIABLE 局部变量声明
7. * ElemenetType.METHOD 方法声明
8. * ElemenetType.PACKAGE 包声明
9. * ElemenetType.PARAMETER 参数声明
10. * ElemenetType.TYPE 类,接口(包括注解类型)或 enum 声明
11. *
12. * @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
13. * RetentionPolicy.SOURCE 注解仅存在于源码中, 注解将被编译器丢弃. 即不会留在 class(字节码文件) 文件中
14. * RetentionPolicy.CLASS 注解在 class 文件中可用,但会被 VM 丢弃,即在运行时无法获得
15. * RetentionPolicy.RUNTIME 注解会在 class 字节码文件中存在,VM 在运行期也保留注释,因此在运行时可以通过反射机制读取注解的信息。
16. *
17. * @Documented 默认情况下,注解不会在 javadoc 中记录,但是可以通过这个注解来表明这个注解需要包含在 javadoc 中
18. *
19. * @Inherited 允许子类继承父类中的注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。
4 自定义及使用注解示例
自定义注解的语法要求:
1 @Target({ElementType.METHOD,ElementType.TYPE})
2 @Retention(RetentionPolicy.RUNTIME)
3 @Inherited
4 @Documented
5 ublic @interface Description {
String desc();
String author();
int age() default 18;
}
首先我们要明确这不是一个接口,它是使用 @interface 关键字定义的一个注解。
然后我们看下面的几个方法,String desc(); 虽然它很类似于接口里面的方法,其实它在注解里面只是一个成员变量(成员以无参无异常的方式声明),int age() default 18;(成员变量可以用 default 指定一个默认值的)。
最后我们要知道:
①. 成员类型是受限制的,合法的类型包括基本的数据类型以及 String,Class,Annotation,Enumeration 等。
②. 如果注解只有一个成员,则成员名必须取名为 value(),在使用时可以忽略成员名和赋值号(=)。
③. 注解类可以没有成员,没有成员的注解称为标识注解。
使用自定义注解:
使用注解的语法:
@< 注解名 >(< 成员名 1>=< 成员值 1>,< 成员名 2>=< 成员值 2>,…)
案例:
1 @Description(desc="i am Color",author="boy",age=18)
2 public String Color() {
3 return "red";
4 }
这里的 Description 是我们刚才在自定义注解语法要求里面定义的注解噢,然后我们可以给它的每一个成员变量赋值,注意数据类型。值得注意的是,因为我们前面定义的作用域是在方法和类接口上,所以这个注解在 Color() 方法上使用是没问题的。
解析注解
概念:
通过反射获取类 、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。
Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。相应地,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素。
实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时 Annotation 信息的能力。当一个 Annotation 类型被定义为运行时的 Annotation 后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Field、Method、Package、Class 和 Constructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的如下七个方法来访问 Annotation 信息:
<T extends Annotation> T getAnnotation(Class<T> annotationClass) :返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null;
Annotation[] getDeclaredAnnotation(Class<T>):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null;与此接口中的其他方法不同,该方法将忽略继承的注解;
Annotation[] getAnnotations():返回该程序元素上存在的所有注解;
Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
Annotation[] getAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解;
Annotation[] getDeclaredAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解;
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false;
自定义一个类级别的标注 Description
package lighter.javaeye.com;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)// 这个标注应用于类
@Retention(RetentionPolicy.RUNTIME)// 标注会一直保留到运行时
@Documented// 将此注解包含在 javadoc 中
public @interface Description {
String value();
}
首先,一个注解一般需要 2 个元注解修饰:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
具体作用上面已解释。
所有的注解都会有一个类似于“func”的部分。这个可以理解为注解的参数。
再定义个方法级别的注解 Name
package lighter.javaeye.com;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 注意这里的 @Target 与 @Description 里的不同, 参数成员也不同
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Name {
String originate();
String community();
}
然后使用以上两个注解
package lighter.javaeye.com;
@Description(value="test description")
public class DescTest{
@Name(originate="panda",community="javatest")
public String getName()
{
return null;
}
@Name(originate="tiger",community="spider")
public String getName2()
{
return "this is a case~”
}
}
说明:其中标注“@Description(value="test description")”,可以写成“@Description("test description") ”,结果也是一样的。因为 Description 标注定义的时候其参数 (或者说属性) 为 value。而 value 比较特殊,它在被指定参数的时候可以不用显示的写出来。当然如果定义的时候参数名不是 value 而是其它的比如 des,那么使用注解的时候,必须显示写出参数名,然后再赋值:@Description(Des=”xxx”)
提取出注解的信息
package lighter.javaeye.com;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
public class TestAnnotation {
/**
* author lighter
* 说明: 具体关天 Annotation 的 API 的用法请参见 javaDoc 文档
*/
public static void main(String[] args) throws Exception {
String CLASS_NAME = "lighter.javaeye.com.JavaTest";
Class test = Class.forName(CLASS_NAME);
Method[] method = test.getMethods();
boolean flag = test.isAnnotationPresent(Description.class);
if(flag)
{
Description des = (Description)test.getAnnotation(Description.class);
System.out.println("描述:"+des.value());
System.out.println("-----------------");
}
// 把 JavaEyer 这一类有利用到 @Name 的全部方法保存到 Set 中去
Set<Method> set = new HashSet<Method>();
for(int i=0;i<method.length;i++)
{
boolean otherFlag = method[i].isAnnotationPresent(Name.class);
if(otherFlag) set.add(method[i]);
}
for(Method m: set)
{
Name name = m.getAnnotation(Name.class);
System.out.println(name.originate());
System.out.println("the community:"+name.community());
}
}
}
注意事项:
所有的 Annotation 会自动继承 java.lang.annotation 这一个接口, 所以不能再去继承别的类或是接口.
最重要的一点,Annotation 类型里面的参数该怎么设定:
第一, 只能用 public 或默认 (default) 这两个访问权修饰. 例如,String value(); 这里把方法设为 defaul 默认类型.
第二, 参数成员只能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型和 String,Enum,Class,annotations 等数据类型, 以及这一些类型的数组. 例如,String value(); 这里的参数成员就为 String.
注解用例
注解的功能很强大,Spring 和 Hebernate 这些框架在日志和有效性中大量使用了注解功能。注解可以应用在使用标记接口的地方。不同的是标记接口用来定义完整的类,但你可以为单个的方法定义注释,例如是否将一个方法暴露为服务。
在最新的 servlet3.0 中引入了很多新的注解,尤其是和 servlet 安全相关的注解。
HandlesTypes –该注解用来表示一组传递给 ServletContainerInitializer 的应用类。
HttpConstraint – 该注解代表所有 HTTP 方法的应用请求的安全约束,和 ServletSecurity 注释中定义的 HttpMethodConstraint 安全约束不同。
HttpMethodConstraint – 指明不同类型请求的安全约束,和 ServletSecurity 注解中描述 HTTP 协议方法类型的注释不同。
MultipartConfig –该注解标注在 Servlet 上面,表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。
ServletSecurity 该注解标注在 Servlet 继承类上面,强制该 HTTP 协议请求遵循安全约束。
WebFilter – 该注解用来声明一个 Server 过滤器;
WebInitParam – 该注解用来声明 Servlet 或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。
WebListener –该注解为 Web 应用程序上下文中不同类型的事件声明监听器。
WebServlet –该注解用来声明一个 Servlet 的配置。