什么是注解?
对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
注解的原理:
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
元注解:
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
1.)Retention: 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.)Target : 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
● ElementType.METHOD:用于描述方法
● ElementType.PARAMETER:用于描述参数
● ElementType.FIELD:成员变量、对象、属性(包括enum实例)
● ElementType.CONSTRUCTOR:用于描述构造器
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.PACKAGE:用于描述包
元素类型 | 注解适用场合 |
---|---|
TYPE | 类(包括enum)和接口 |
METHOD | 方法 |
PARAMETER | 方法或构造器参数 |
FIELD | 成员域(即成员变量) |
CONSTRUCTOR | 构造器 |
LOCAL_VARIABLE | 局部变量 |
PACKAGE | 包 |
ANNOTATION_TYPE | 注解类型声明 |
3.)Documented:一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
4.)Inherited :定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
常见标准的Annotation:
1.)Override
java.lang.Override是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。
2.)Deprecated
Deprecated也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
3.)SuppressWarnings
SuppressWarning不是一个标记类型注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
@SuppressWarnings("unchecked")
自定义注解:
自定义注解类编写的一些规则:
1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2. 参数成员只能用public或默认(default)这两个访问权修饰
3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
5. 注解也可以没有定义成员, 不过这样注解就没啥用了
PS:自定义注解需要使用到元注解
注解的基础
1.注解的定义:Java文件叫做Annotation,用@interface表示。
2.元注解:@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。
3.注解的保留策略:
@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
4.注解的作用目标:
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) // 包
5.注解包含在javadoc中:
@Documented
6.注解可以被继承:
@Inherited
7.注解解析器:用来解析自定义注解。
自定义注解实例:
通过注解进行赋值(结合了工厂方法模式)
1.自定义注解
package annotation;
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;
/**
* Init.java
*
* @author IT唐伯虎 2014年7月10日
*/
@Documented
@Inherited
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Init
{
public String value() default "";
}
2.在数据模型使用注解
package model;
3.用“构造工厂”充当“注解解析器”
import annotation.Init;
/**
* User.java
*
* @author IT唐伯虎 2014年7月10日
*/
public class User
{
private String name;
private String age;
public String getName()
{
return name;
}
@Init(value = "liang")
public void setName(String name)
{
this.name = name;
}
public String getAge()
{
return age;
}
@Init(value = "23")
public void setAge(String age)
{
this.age = age;
}
}package factory;
import java.lang.reflect.Method;
import annotation.Init;
import model.User;
/**
* UserFactory.java
*
* @author IT唐伯虎 2014年7月10日
*/
public class UserFactory
{
public static User create()
{
User user = new User();
// 获取User类中所有的方法(getDeclaredMethods也行)
Method[] methods = User.class.getMethods();
try
{
for (Method method : methods)
{
// 如果此方法有注解,就把注解里面的数据赋值到user对象
if (method.isAnnotationPresent(Init.class))
{
Init init = method.getAnnotation(Init.class);
method.invoke(user, init.value());
}
}
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
return user;
}
}
4.运行的代码
package app;
import java.lang.reflect.InvocationTargetException;
import factory.UserFactory;
import model.User;
/**
* Test.java
*
* @author IT唐伯虎 2014年7月10日
*/
public class Test
{
public static void main(String[] args) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
User user = UserFactory.create();
System.out.println(user.getName());
System.out.println(user.getAge());
}
}
5.运行结果
liang
23
三、通过注解进行校验
1.自定义注解
package annotation;
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;
/**
* Validate.java
*
* @author IT唐伯虎 2014年7月11日
*/
@Documented
@Inherited
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate
{
public int min() default 1;
public int max() default 10;
public boolean isNotNull() default true;
}
2.在数据模型使用注解
package model;
import annotation.Validate;
/**
* User.java
*
* @author IT唐伯虎 2014年7月11日
*/
public class User
{
@Validate(min = 2, max = 5)
private String name;
@Validate(isNotNull = false)
private String age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAge()
{
return age;
}
public void setAge(String age)
{
this.age = age;
}
}
3.注解解析器
package check;
import java.lang.reflect.Field;
import annotation.Validate;
import model.User;
/**
* UserCheck.java
*
* @author IT唐伯虎 2014年7月11日
*/
public class UserCheck
{
public static boolean check(User user)
{
if (user == null)
{
System.out.println("!!校验对象为空!!");
return false;
}
// 获取User类的所有属性(如果使用getFields,就无法获取到private的属性)
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields)
{
// 如果属性有注解,就进行校验
if (field.isAnnotationPresent(Validate.class))
{
Validate validate = field.getAnnotation(Validate.class);
if (field.getName().equals("age"))
{
if (user.getAge() == null)
{
if (validate.isNotNull())
{
System.out.println("!!年龄可空校验不通过:不可为空!!");
return false;
}
else
{
System.out.println("年龄可空校验通过:可以为空");
continue;
}
}
else
{
System.out.println("年龄可空校验通过");
}
if (user.getAge().length() < validate.min())
{
System.out.println("!!年龄最小长度校验不通过!!");
return false;
}
else
{
System.out.println("年龄最小长度校验通过");
}
if (user.getAge().length() > validate.max())
{
System.out.println("!!年龄最大长度校验不通过!!");
return false;
}
else
{
System.out.println("年龄最大长度校验通过");
}
}
if (field.getName().equals("name"))
{
if (user.getName() == null)
{
if (validate.isNotNull())
{
System.out.println("!!名字可空校验不通过:不可为空!!");
return false;
}
else
{
System.out.println("名字可空校验通过:可以为空");
continue;
}
}
else
{
System.out.println("名字可空校验通过");
}
if (user.getName().length() < validate.min())
{
System.out.println("!!名字最小长度校验不通过!!");
return false;
}
else
{
System.out.println("名字最小长度校验通过");
}
if (user.getName().length() > validate.max())
{
System.out.println("!!名字最大长度校验不通过!!");
return false;
}
else
{
System.out.println("名字最大长度校验通过");
}
}
}
}
return true;
}
}
4.运行的代码
package app;
import check.UserCheck;
import model.User;
/**
* Test.java
*
* @author IT唐伯虎 2014年7月11日
*/
public class Test
{
public static void main(String[] args)
{
User user = new User();
user.setName("liang");
user.setAge("1");
System.out.println(UserCheck.check(user));
}
}
5.运行结果
名字可空校验通过
名字最小长度校验通过
名字最大长度校验通过
年龄可空校验通过
年龄最小长度校验通过
年龄最大长度校验通过
true