Java注解
1、概述
Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。
什么是metadata(元数据):
元数据从metadata一词译来,就是“关于数据的数据”的意思。
元数据的功能作用有很多,比如:你可能用过Javadoc的注释自动生成文档。这就是元数据功能的一种。总的来说,元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
1. 编写文档:通过代码里标识的元数据生成文档
2. 代码分析:通过代码里标识的元数据对代码进行分析
3. 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查
在Java中元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或针在运行时知道被运行代码的描述信息。
综上所述:
第一,元数据以标签的形式存在于Java代码中。
第二,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。
第三,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件。
第四,元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。
基本原理:
Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,annotation就像修饰符一样被使用,并应用于包、类 型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在Annotation的“name=value”结构对中。
Annotation的成员在Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认语法:允许声明任何Annotation成员的默认值:一个Annotation可以将name=value对作为没有定义默认值的Annotation成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也可以被子类覆盖。
Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。另外,尽管一些annotation通过java的反射api方法在运行时被访问,而java语言解释器在工作时忽略了这些annotation。正是由于java虚拟机忽略了Annotation,导致了annotation类型在代码中是“不起作用”的; 只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理。本文中将涵盖标准的Annotation和meta-annotation类型,陪伴这些annotation类型的工具是java编译器(当然要以某种特殊的方式处理它们)。
2、Java中的常见注解
系统内置标准注解:
@override:表示子类的方法覆盖了父类的方法
@Deprecated:表示该方法已经过时
@SuppressWarnings("deprecation"):表示忽视@Deprecation的警告
下面我们依次看看三个内置标准注解的作用和使用场景。
@Override,限定重写父类方法:
@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力。使用方法极其简单:在使用此annotation时只要在被修饰的方法前面加上@Override即可。下面的代码是一个使用@Override修饰一个企图重载父类的displayName()方法,而又存在拼写错误的实例:
Orange 类编译不会有任何问题,Apple 类在编译的时候会提示相应的错误。@Override注解只能用于方法,不能用于其他程序元素。
@Deprecated,标记已过时:
同 样Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的 “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。
值得注意,@Deprecated这个annotation类型和javadoc中的 @deprecated这个tag是有区别的:前者是java编译器识别的,而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过 时、它应当如何被禁止或者替代的描述)。
下面一段程序中使用了@Deprecated注解标示方法过期,同时在方法注释中用@deprecated tag 标示该方法已经过时,代码如下:
SuppressWarnnings,抑制编译器警告:
@SuppressWarnings 被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。有时我们无法避免某些警告,此时@SuppressWarning就要派上用场了,在调用的方法前增加@SuppressWarnings修饰,告诉编译器停止对此方法的警告。
示例代码:
annotation语法允许在annotation名后跟括号,括号中是使用逗号分割的name=value对用于为annotation的成员赋值。
SuppressWarnings注解的常见参数值的简单
1.deprecation:使用了不赞成使用的类或方法时的警告;
2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
4.path:在类路径、源文件路径等中有不存在的路径时的警告;
5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
6.finally:任何 finally 子句不能正常完成时的警告;
7.all:关于以上所有情况的警告。
3、注解的分类
一、注解按照运行机制划分:
- 源码注解:注解只在源码中存在,编译成.class文件就不存在了;
- 编译时注解:注解在源码和.class文件中都存在(如:@Override、@Deprecated、@SuppressWarings);
- 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解(如:@Autowired)。
二、按照来源分
- 来自JDK的注解;
- 来自第三方的注解(大部分);
- 我们自己定义的注解。三、元注解:注解的注解
4、元注解
概念:元注解的作用就是负责注解其他注解
Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
使用示例
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {
}
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
@Documented:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
@Inherited:
**@Inherited **元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
示例代码
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
5、自定义注解
自定义注解主要分为以下两步:
- 通过@interface关键字声明注解名称、注解成员属性等
- 使用Java内置四个元注解对自定义标注的功能和范围进行约束
自定义注解的格式: public @interface 注解名 { 定义体 }
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
注解参数的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
**Annotation类型里面的参数该怎么设定: **
- 第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
- 第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
- 第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.
- 第四,可以没有成员,此时为标识注解。
注解元素的默认值:
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。
例如:
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;
/**
* 水果供应者注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
* @return
*/
public int id() default -1;
/**
* 供应商名称
* @return
*/
public String name() default "";
/**
* 供应商地址
* @return
*/
public String address() default "";
}
6.解析注解
概念:通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。
1:RetentionPolicy.RUNTIME时,才能获取到注解,SOURCE和CLASS都获取不到注解。
2:@Inherited对implements不起作用,对extends起作用(只会继承类上面注解,和权限为public的方法的注解不包括构造函数)。
//自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
public String value();
}
//两个实验类
@MyAnnotation(value="这是class的注解")
class Apple {
@MyAnnotation(value="这是构造方法的注解")
public Apple() {
}
@MyAnnotation(value="这是含参构造方法的注解")
public Apple(int i) {
System.out.println(i);
}
@MyAnnotation(value = "这是普通 public方法1的注解")
public void displayName(){
}
@MyAnnotation(value = "这是普通 public方法2的注解")
public void displayName2(){
}
@MyAnnotation(value = "这是普通 public方法3的注解")
public void displayName3(){
}
}
class ChildApple extends Apple{
}
//测试代码:
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class AnnTest {
static void showAnnotation(String classFullName){
try {
//1通过反射获取类的注解
Class c=Class.forName(classFullName);
boolean isExist=c.isAnnotationPresent(MyAnnotation.class);
if(isExist){
MyAnnotation an=(MyAnnotation) c.getAnnotation(MyAnnotation.class);
System.out.println(an.value());
}
//2通过反射获得方法的注解
Method[] method=c.getMethods();
for (Method method2 : method) {
if(method2.isAnnotationPresent(MyAnnotation.class)){
System.out.println(method2.getAnnotation(MyAnnotation.class));
}
}
Constructor[] constructors=c.getConstructors();
for (Constructor constructor : constructors) {
if(constructor.isAnnotationPresent(MyAnnotation.class)){
System.out.println(constructor.getAnnotation(MyAnnotation.class));
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String s="zhujie.Apple";
showAnnotation(s);
System.out.println("我是分割线——————————————————");
String s1="zhujie.ChildApple";
showAnnotation(s1);
}
}
//自定义的两个注解
@Documented
@Retention(RUNTIME)
@Target(FIELD)
public @interface Column {
public String value();
}
@Documented
@Retention(RUNTIME)
@Target(TYPE)
public @interface Table {
public String value();
}
//测试代码:
public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Filter filter1=new Filter();
filter1.setID(7);
Filter filter2=new Filter();
filter2.setUserName("lucy");
Filter filter3=new Filter();
filter3.setEmail("8888@qq.com");
String sql1=Query(filter1);
String sql2=Query(filter2);
String sql3=Query(filter3);
System.out.println(sql1);
System.out.println(sql2);
System.out.println(sql3);
}
private static String Query(Filter f) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
StringBuilder stringBuilder=new StringBuilder();
//1 获取到Class
Class c =f.getClass();
//2.获取到table的名字
if(c.isAnnotationPresent(Table.class))
{
Table t=(Table)c.getAnnotation(Table.class);
String tableName=t.value();
stringBuilder.append("select * from ").append(tableName).append(" where 1=1");
//3遍历所有字段
Field[] fields=c.getDeclaredFields();
for(Field field:fields){
//4处理每个字段对应的sql
//4.1拿到字段名
if(field.isAnnotationPresent(Column.class))
{
Column column=field.getAnnotation(Column.class);
String columnName=column.value();
//4.2拿到字段地值,获得对应方法
String fieldName=field.getName();
String getedMethodName="get"
+fieldName.substring(0,1).toUpperCase()
+fieldName.substring(1);
Method gMethod=c.getMethod(getedMethodName);
//根据返回值不同的方法做不同处理
Object fieldValue;
if(getedMethodName.equals("getID")){
fieldValue=(Integer) gMethod.invoke(f);
}else {
fieldValue=(String) gMethod.invoke(f);
}
//如果循环未设置的元素(null),直接继续循环。
if(fieldValue==null){
continue;
}
//4.3拼装sql
if(fieldValue instanceof String){
stringBuilder.append(" and ").append(fieldName)
.append(" =").append("'").
append(fieldValue).append("'");
}
if(fieldValue instanceof Integer){
stringBuilder.append(" and ").append(fieldName)
.append(" =")
.append(""+fieldValue);
}
return stringBuilder.toString();
}else{
continue;
}
}
}else{
return null;
}
return null;
}
}
输出结果: