概念
注解:也被称作元数据,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。(实际意义上来说,注解仅仅是一种特殊的注释而已,如果没有解析它的代码,它可能啥作用也没有)
标记注解:内部没有定义元素的注解。
单值注解 : 内部只定义一个元素,名字为value,使用时直接传值,不需要指定元素名。
语法
定义注解
自定义注解格式如下,其中@interface是用来声明一个注解,使用@interface自定义注解时将自动继承java.lang.annotation.Annotation接口,并由编译程序自动完成其他细节,通过反编译可以查看到。元注解及注解元素内容详见下文。
元注解
修饰符 @interface 注解名 {
注解元素的声明1
注解元素的声明2
...
}
自定义一个MyAnnotation注解,并声明一个名为value的注解元素:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value() default "test";
}
MyAnnocation注解反编译结果:
Compiled from "MyAnnocation.java"
public interface com.bbu.annotation.MyAnnotation extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
}
通过反编译结果可以看出,注解本质上就是一个接口,该接口默认继承Annotation接口。
元注解
Java提供了四种元注解,主要用于程序员自定义注解时使用。
@Target:表示该注解可以用于什么地方
- ElementType.TYPE:仅用于类、接口、枚举
- ElementType.FIELD:仅用于属性
- ElementType.METHOD:仅用于方法(非构造方法)
- ElementType.CONSTRUCTOR:仅用于构造方法
- ElementType.PARAMETER:仅用于方法的参数
- ElementType.LOCAL_VARIABLE:仅用于局部变量
- ElementType.ANNOTATION_TYPE:仅用于注解
- ElementType.PACKAGE:仅用于包
@Retention:表示注解的生命周期(编译,.class,运行)
- RetentionPolicy.SOURCE:注解将被编译器丢弃,仅源文件(.java)阶段有效;
- RetentionPolicy.CLASS:注解在class文件中可用,但会被VM丢弃(源文件(.java)阶段以及字节码文件阶段(.class)有效);
- RetentionPolicy.RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息(源文件、字节码以及运行时都有效)
@Documented:将此注解包含在Javadoc中。通过javadoc命令生成API文档时,注解也会生成到文档中;默认(不写@Documented时)不会把注解生成到文档中。
@Inherited:允许子类继承父类中的注解。
Java 1.8新增了一种元注解:
@Repeatable:标识注解是可重复使用的;在同一个地方使用相同的注解会报错,有了此元注解注解的注解,就可以在同一个地方使用相同的注解。
注解元素
- 所有基本类型(四类八种:byte、short、int、long、float、double、char、boolean)
- String
- Class
- enum
- Annotation
- 上述所有类型对应的数组
除以上类型外,如果使用到了其他类型,那么编译器就会报错。注意,也不允许使用任何包装类型,不过由于自动打包的存在,这算不是什么限制。
public enum Status {// 枚举
SUCCESS,
FAILURE
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
int id() default -1;// 基本类型
String value() default "";// String
Class<?> clz() default String.class;// Class
Status enu() default Status.SUCCESS;// Enum
String[] values();// 数组
// Integer id() default -1;// 报错(Invalid type Integer for the annotation attribute MyAnnocation.ids; only primitive type, String, Class, annotation, enumeration are permitted or 1-dimensional arrays thereof)
}
注解也可以做为元素的类型,也就是说注解可以嵌套:
package com.bbu.annocation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE, ElementType.FIELD})
@Inherited
public @interface Alias {
// 注解做为元素的类型
MyAnnotation annotation() default @MyAnnotation(values = { "" });
}
注解元素设定规则:
第一,只能用public或默认(default)这两个访问权修饰;
第二,如果只有一个参数成员,最好把参数名称设为"value",这样的话在注解中写值的时候可以省略value;
第三,首先,注解元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素值。其次,对于非基本类型的元素,无论是在源代码中声明,或是在注解接口中定义默认值时,都不能以null作为值,这个约束使得处理器很难表现一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个约束,只能定义一些特殊的值,例如空字符串或负数,以此表示某个元素不存在。
Java内置注解
Java SE5开始,内置了三种标准注解,定义在java.lang中:
- @Override:表示当前的方法将覆盖父类中的方法;若方法签名对不上被覆盖的方法,编译器就会发出错误提示。
- @Deprecated:如果某个元素被该注解修饰,程序员使用了该元素,编译器会发出警告信息。
- @SuppressWarnings:关闭不当的编译器警告信息。
使用方式:
@SuppressWarnings("")
@SuppressWarnings({"", “”})
@SuppressWarnings(value={"", “”})
Jdk1.8后增加了一个新的注解:
- @FunctionalInterface:用于声明一个接口为函数式接口
四中Java内置注解源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
可以看出@Override、@Deprecated和@FunctionalInterface三个注解中没有注解元素,是一个标记注解;@SuppressWarnings中的注解元素是一个String数组,比较常见的值有rawtypes(使用泛型时忽略没有指定相应的类型)、unchecked(抑制没有进行类型检查操作的警告)及all(抑制所有警告)等。
用法
注解与反射
在Class中定义了反射操作注解常用的方法:
- public < A extends Annotation> A getAnnotation(Class< A> annotationClass):返回指定的注解
- public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判定当前元素是否被指定注解修饰
- public Annotation[] getAnnotations():返回所有的注解
- public < A extends Annotation> A getDeclaredAnnotation(Class< A> annotationClass):返回本元素的指定注解
- public Annotation[] getDeclaredAnnotations():返回本元素的所有注解,不包含父类继承而来的
通过以上方法得到注解Annotation实例后,我么就可以通过注解元素方法直接获取对应注解中设置的值,如@MyAnnocation(“user_id”)注解,我们可以通过getAnnotation方法获取annotation实例,然后annotation.value()获取"user_id"这个值。
反射解析注解生成SQL案例
首先自定义一个名为MyAnnotation的注解,内部设置一个注解元素value,通过@Retention元注解设置为运行时有效,这样才能使用反射动态获取注解实例,通过@Target元注解设置类、接口、枚举及属性可以使用。
package com.bbu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解MyAnnotation
* @author code_now
*
*/
@Retention(RetentionPolicy.RUNTIME)// 设置为运行时也有效
@Target({ElementType.TYPE, ElementType.FIELD})// 设置类、接口、枚举及属性可以使用
public @interface MyAnnotation {
String value() default "";// 注解元素value
}
自定义一个User类,将对应数据库中表名及列名通过@MyAnnocation注解对应起来。
package com.bbu.model;
import com.bbu.annocation.MyAnnotation;
/**
* 自定义实体类
* @author code_now
*
*/
@MyAnnocation("t_user")
public class User {
@MyAnnocation("user_id")
private Integer userId;
@MyAnnocation("user_name")
private String userName;
@MyAnnocation("age")
private Integer age;
@MyAnnocation("home_address")
private String homeAddress;
private String desc;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(String homeAddress) {
this.homeAddress = homeAddress;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", age="
+ age + ", homeAddress=" + homeAddress + ", desc=" + desc + "]";
}
}
通过反射获取类及属性上对应的表名和列名,然后拼出最终的查询SQL。
package com.bbu.test;
import java.lang.reflect.Field;
import com.bbu.annocation.MyAnnotation;
/**
* 解析注解通过反射生成SQL语句
* @author code_now
*
*/
public class GenerateSQL {
public static void main(String[] args) throws Exception {
Class<?> clz = Class.forName("com.bbu.model.User");// 获取User类的Class对象
StringBuffer sb = new StringBuffer();// 用于拼接sql
sb.append("select ");
String temp1 = "";
Field[] declaredFields = clz.getDeclaredFields();// 获取User类中所有属性
for (Field field : declaredFields) {
field.setAccessible(true);
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);// 获取属性上对应的注解实例
if(annotation != null) {
String value = annotation.value();
sb.append(temp1+value);
temp1 = ",";
}
}
sb.append(" from ");
MyAnnotation annotation = clz.getAnnotation(MyAnnotation.class);// 获取类上对应的注解实例
if(annotation != null) {
String value = annotation.value();
sb.append(value);
}
System.out.println(sb.toString());
}
}
最终生成的SQL:
select user_id,user_name,age,home_address from t_user
参考
Java编程思想(第4版)