注解
元注解
- 元注解就是标记其他注解的注解
- @Target 用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型,其定义如下,也代表可能的取值范围
- 当注解未指定Target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开
- @Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)
- SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
- CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
- RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
- @Documented 被修饰的注解会生成到javadoc中
- @Inherited 可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解
注解与反射机制
返回值 | 方法名称 | 说明 |
---|---|---|
getAnnotation(Class annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 | |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
运行时注解处理器
了解完注解与反射的相关API后,现在通过一个实例(该例子是博主改编自《Tinking in Java》)来演示利用运行时注解来组装数据库SQL的构建语句的过程
/**
* 表注解
*/
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface DBTable {
String name() default "";
}
/**
* 注解Integer类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//该字段对应数据库表列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
}
/**
* Created by wuzejian on 2017/5/18.
* 注解String类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
//对应数据库表的列名
String name() default "";
//列类型分配的长度,如varchar(30)的30
int value() default 0;
Constraints constraint() default @Constraints;
}
/**
* Created by wuzejian on 2017/5/18.
* 约束注解
*/
@Target(ElementType.FIELD)//只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
//判断是否作为主键约束
boolean primaryKey() default false;
//判断是否允许为null
boolean allowNull() default false;
//判断是否唯一
boolean unique() default false;
}
/**
* Created by wuzejian on 2017/5/18.
* 数据库表Member对应实例类bean
*/
@DBTable(name = "MEMBER")
public class Member {
//主键ID
@SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true))
private String id;
@SQLString(name = "NAME" , value = 30)
private String name;
@SQLInteger(name = "AGE")
private int age;
@SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true))
private String description;//个人描述
//省略set get.....
}
上述定义4个注解,分别是@DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上)并在Member类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表MEMBER的构建语句,在这里有点需要注意的是,我们使用了嵌套注解@Constraints,该注解主要用于判断字段是否为null或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为@Retention(RetentionPolicy.RUNTIME),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是Java自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理SQL的注解,其处理器必须由我们自己编写了.
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zejian on 2017/5/13.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
* 运行时注解处理器,构造表创建语句
*/
public class TableCreator {
public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
//如果没有表注解,直接返回
if(dbTable == null) {
System.out.println(
"No DBTable annotations in class " + className);
return null;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if(tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
//通过Class类API获取到所有成员字段
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
//获取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column
//判断注解类型
if(anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
//获取字段对应列名称,如果没有就是使用字段名称替代
if(sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
//构建语句
columnDefs.add(columnName + " INT" +
getConstraints(sInt.constraint()));
}
//判断String类型
if(anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if(sString.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + " VARCHAR(" +
sString.value() + ")" +
getConstraints(sString.constraint()));
}
}
//数据库表构建语句
StringBuilder createCommand = new StringBuilder(
"CREATE TABLE " + tableName + "(");
for(String columnDef : columnDefs)
createCommand.append("\n " + columnDef + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(
0, createCommand.length() - 1) + ");";
return tableCreate;
}
/**
* 判断该字段是否有其他约束
* @param con
* @return
*/
private static String getConstraints(Constraints con) {
String constraints = "";
if(!con.allowNull())
constraints += " NOT NULL";
if(con.primaryKey())
constraints += " PRIMARY KEY";
if(con.unique())
constraints += " UNIQUE";
return constraints;
}
public static void main(String[] args) throws Exception {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
/**
* 输出结果:
Table Creation SQL for com.zejian.annotationdemo.Member is :
CREATE TABLE MEMBER(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT NOT NULL,
DESCRIPTION VARCHAR(150)
);
*/
}
}
Java注解相关面试题
什么是注解?他们的典型用例是什么?
注解是绑定到程序源代码元素的元数据,对运行代码的操作没有影响。
他们的典型用例是:
- 编译器的信息 - 使用注解,编译器可以检测错误或抑制警告
- 编译时和部署时处理 - 软件工具可以处理注解并生成代码,配置文件等。
- 运行时处理 - 可以在运行时检查注解以自定义程序的行为
描述标准库中一些有用的注解。
java.lang和java.lang.annotation包中有几个注解,更常见的包括但不限于此:
- @Override -标记方法是否覆盖超类中声明的元素。如果它无法正确覆盖该方法,编译器将发出错误
- @Deprecated - 表示该元素已弃用且不应使用。如果程序使用标有此批注的方法,类或字段,编译器将发出警告
- @SuppressWarnings - 告诉编译器禁止特定警告。在与泛型出现之前编写的遗留代码接口时最常用的
- @FunctionalInterface - 在Java 8中引入,表明类型声明是一个功能接口,可以使用Lambda Expression提供其实现
可以从注解方法声明返回哪些对象类型?
返回类型必须是基本类型,String,Class,Enum或数组类型之一。否则,编译器将抛出错误。
这是一个成功遵循此原则的示例代码:
enum Complexity {
LOW, HIGH
}
public @interface ComplexAnnotation {
Class<? extends Object> value();
int[] types();
Complexity complexity();
}
下一个示例将无法编译,因为Object不是有效的返回类型:
public @interface FailingAnnotation {
Object complexity();
}
哪些程序元素可以注解?
注解可以应用于整个源代码的多个位置。它们可以应用于类,构造函数和字段的声明:
@SimpleAnnotation
public class Apply {
@SimpleAnnotation
private String aField;
@SimpleAnnotation
public Apply() {
// ...
}
}
方法及其参数:
@SimpleAnnotation
public void aMethod(@SimpleAnnotation String param) {
// ...
}
局部变量,包括循环和资源变量:
@SimpleAnnotation
int i = 10;
for (@SimpleAnnotation int j = 0; j < i; j++) {
// ...
}
try (@SimpleAnnotation FileWriter writer = getWriter()) {
// ...
} catch (Exception ex) {
// ...
}
其他注解类型:
@SimpleAnnotation
public @interface ComplexAnnotation {
// ...
}
甚至包,通过package-info.java文件:
@PackageAnnotation
package com.baeldung.interview.annotations;
从Java 8开始,它们也可以应用于类型的使用。为此,注解必须指定值为ElementType.USE的@Target注解:
@Target(ElementType.TYPE_USE)
public @interface SimpleAnnotation {
// ...
}
现在,注解可以应用于类实例创建:
new @SimpleAnnotation Apply();
类型转换:
aString = (@SimpleAnnotation String) something;
接口中:
public class SimpleList<T>
implements @SimpleAnnotation List<@SimpleAnnotation T> {
// ...
}
抛出异常上:
void aMethod() throws @SimpleAnnotation Exception {
// ...
}
有没有办法限制可以应用注解的元素?
有,@ Target注解可用于此目的。如果我们尝试在不适用的上下文中使用注解,编译器将发出错误。
以下是仅将@SimpleAnnotation批注的用法限制为字段声明的示例:
@Target(ElementType.FIELD)
public @interface SimpleAnnotation {
// ...
}
如果我们想让它适用于更多的上下文,我们可以传递多个常量:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
我们甚至可以制作一个注解,因此它不能用于注解任何东西。当声明的类型仅用作复杂注解中的成员类型时,这可能会派上用场:
@Target({})
public @interface NoTargetAnnotation {
// ...
}
下面的代码会编译吗?
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.FIELD })
public @interface TestAnnotation {
int[] value() default {};
}
不能。如果在@Target注解中多次出现相同的枚举常量,那么这是一个编译时错误。
删除重复常量将使代码成功编译:
@Target({ ElementType.FIELD, ElementType.TYPE})