玩转java_玩转Java注解

前言

在我们平常的代码开发过程中,遇见过无数的注解,大多数注解都是我们使用的框架所给我们集成好了的,相信也很少有人使用自己编写的注解,我也如此,但是只有当你了解了注解背后的秘密后,一定会对它有不同的看法。

注解,也被称为元数据,可以为我们在代码中添加信息提供一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据

注解的优点

使用注解有许多的优点:

注解能使编译器来测试和验证格式,存储有关程序的额外信息。

注解可以用来生成描述符文件,有助于减轻编写“样板”代码的负担、

使用注解可以将这些元数据保存在Java源代码中。并利用annotation API为我们的注解构造处理工具。

注解提供编译器类型检查以及更加干净易读的便利。

Java中的注解

目前Java提供三种内置注解:

@Override,表示当前的方法定义将覆盖超类中的方法。

@Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。

@SuppressWarnings,关闭不当的编译器警告信息。

除此之外,Java还另外提供了四种元注解,专门负责新注解的创建。可理解为注解的注解。

@Targer:表示该注解可以用于什么地方。

参数

说明

CONSTRUCTOR

构造器的声明

FIELD

域声明(包括enum实例)

LOCAL_VARIABLE

局部变量声明

METHOD

方法声明

PACKAGE

包声明

PARAMETER

参数声明

TYPE

类、接口(包括注解类型)或enum声明

参数

说明

SOURCE

注解将被编译器丢弃

CLASS

注解在class文件中可用,但会被JVM丢弃

RUNTIME

JVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息

@Documented:将此注解包含在Javadoc中

@Inherited:允许子类继承父类中的注解

如何定义注解

注解的定义很像接口的定义,并且与其他任何Java接口一样,注解也将会编译成class文件。

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{

}

除了@符号外,注解的定义很像一个空的接口。在定义一个注解的时候,会需要使用到我们上面的元注解,如@Target和@Retention。像我们这里定义的注解称为标记注解,因为在注解内没有任何元素。上面这个注解的使用方式:@Test。

下面我们来看一下hibernate中的@Table注解:

下面我们来看一下hibernate中的@Table注解:

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface Table {

String name() default "";

String catalog() default "";

String schema() default "";

UniqueConstraint[] uniqueConstraints() default {};

Index[] indexes() default {};

}

从@Target中可以看出这个注解是应用于类、接口上的,并且是在运行期保存注解信息。该注解中有5个元素,default为默认值。注解的元素在使用时表现为名-值对的形式,例如我们可以使用@Table(name="myTable")的方式设置该实体类对应的数据库表名为myTable。

注解处理器

当我们编写好我们的注解后如果没有用来读取注解的工具的话,那么注解对于我们来说也就没有太大意义了。在Java SE5扩展了反射机制的API,以帮助程序员构造这类工具。

我们首先定义一个简单的注解:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Person{

String name() default "I don't have name";

int age() default 21;

}

我们将该注解用于一个实体类中:

public class Hello{

@Person(name = "My name is xiaoming")

public String zhy(){

return "xiaoming";

}

@Person(name = "My name is xiaohong", age = 19)

public String xyx(){

return "xiaohong";

}

}

接下来我们编写注解处理器,通过反射机制来查找注解中的信息。

public class HelloTest {

public static void helloTest(List ages, Class> cl){

Method[] methods = cl.getDeclaredMethods();

for(Method method : methods){

Person person = method.getAnnotation(Person.class);

if(person != null){

System.out.println("My name is " + person.name() + " and I'm " + person.age());

ages.remove(new Integer(person.age()));

}

}

for(int i : ages){

System.out.print("Missing age is " + i);

}

}

public static void main(String[] args) {

List list = new ArrayList<>();

Collections.addAll(list, 20, 21, 22);

helloTest(list, Hello.class);

}

}

输出结果如下:

My name is xiaohong and I'm 19

My name is xiaoming and I'm 21

Missing age is 20

在这个注解处理器程序中,我们用到了两个反射的方法:getDeclaredMethods()和getAnnotation(),这两个都是AnnotatedElement接口(Class、Method和Field等类都实现了该接口)。getAnnotation()方法返回指定类型的注解对象,在这里就是Person。如果被注解的方法上没有该类型的注解,则返回null值。然后我们通过调用name()和age()方法从Person对象中提取元素的值。

案例驱动

下面我们写一个注解小例子,它将读取一个实体类,检查其上的数据库注解,并生成用来创建数据库的SQL命令:

下面是我们要使用到的注解:

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

//@DBTable注解

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface DBTable {

String name() default "";

}

//@Constraints注解

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Constraints {

boolean primaryKey() default false;

boolean allowNull() default true;

}

//@SQLInteger注解

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface SQLInteger {

int value() default 10;

String name() default "";

Constraints constraints() default @Constraints;

}

//@SQLString注解

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface SQLString {

int value() default 255;

String name() default "";

Constraints constraints() default @Constraints;

}

注解应用到的实体类:

@DBTable(name = "user")

public class User {

@SQLInteger(constraints = @Constraints(primaryKey = true), name = "id")

private int id;

@SQLString(30)

private String username;

@SQLInteger

private Integer age;

}

最后就是注解处理器:

import java.lang.annotation.Annotation;

import java.lang.reflect.Field;

import java.util.ArrayList;

import java.util.List;

public class TableCreator {

public static void main(String[] args) throws ClassNotFoundException {

Class> userClass = Class.forName("User");

DBTable dbTable = userClass.getAnnotation(DBTable.class);

if(dbTable == null){

System.out.println("No DBTable annotations in class");

}

String tableName = dbTable.name();

if(tableName.length()<1){

tableName = userClass.getName().toUpperCase();

}

List columnNameSqls = new ArrayList<>();

StringBuilder createTable = new StringBuilder();

createTable.append("CREATE TABLE ").append(tableName).append("(");

for(Field field : userClass.getDeclaredFields()){

String columnName = null;

Annotation[] annotations = field.getDeclaredAnnotations();

if(annotations.length < 1){

continue;

}

if(annotations[0] instanceof SQLInteger){

SQLInteger sqlInteger = (SQLInteger) annotations[0];

if(sqlInteger.name().length()<1){

columnName = field.getName().toUpperCase();

} else {

columnName = sqlInteger.name();

}

columnNameSqls.add(columnName + " INT(" +sqlInteger.value() + ") " + getConstraints(sqlInteger.constraints()));

}

if(annotations[0] instanceof SQLString){

SQLString sqlString = (SQLString) annotations[0];

if(sqlString.name().length() < 1){

columnName = field.getName().toUpperCase();

}else {

columnName = sqlString.name();

}

columnNameSqls.add(columnName + " VARCHAR(" + sqlString.value() + ") " + getConstraints(sqlString.constraints()));

}

}

for(int i=0;i

if(i != (columnNameSqls.size()-1)){

createTable.append("\n ").append(columnNameSqls.get(i)).append(",");

} else {

createTable.append("\n ").append(columnNameSqls.get(i));

}

}

createTable.append("\n);");

System.out.println(createTable);

}

private static String getConstraints(Constraints con){

String constranints = "";

if(!con.allowNull()){

constranints += "NOT NULL";

}

if(con.primaryKey()){

constranints += "PRIMARY KEY";

}

return constranints;

}

}

先来看看运行结果把:

CREATE TABLE user(

id INT(10) PRIMARY KEY,

USERNAME VARCHAR(30) ,

AGE INT(10)

);

在main()方法中,使用forName()加载User实体类,并使用getAnnotation(DBTable.class)检查该实体类是否带有@DBTable注解。如果有,就将表名保存下来。然后读取这个类的所有域,并用getDeclaredAnnotations()进行检查,该方法返回一个域上的所有注解。最后用instanceof操作符来判断这些注解的类型。

在注解中嵌套使用的@Constraints注解被传递到getConstraints()方法中,由它负责构造一个包含SQL的String对象。

总结

通过一个简单的案例来说明注解的使用再合适不过了,对于注解的理解在我们平常的使用过程中也能更加得心应手。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值