java 深入理解注解

注解(也被称为元数据),为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
注解在一定程度上是把元数据和源代码结合在一起。

java5.0内置了三种标准注解
@override:表示当前的方法将覆盖超类中的方法。
@Depercated:如果程序员使用了该注解,那么编译器会发出警告信息。
@SuppressWarnings:关闭不当的编译器警告信息。

一、基本语法

注解的定义和接口类似,和其他java类一样也会被编译成class文件。

package annotation;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}

1. 元注解

定义注解时需要会一些元注解(meta-annotation),Java5.0定义了4个标准的meta-annotation类型

元注解说明
@Target表示该注解可以用到哪些地方,可以指定一个enum的值,也可以用逗号分隔的方式指定多个值(如果想要将注解应用于所有的ElementType,可以省去@Target元注解,不过这种情况不常见),可以选择的ElementType参数有:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMMETER:参数声明
TYPE:类、接口(包括注解类型)或enum声明
@Retention表示需要在什么级别保存该注解信息。可选参数RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃。
CLASS:注解在class文件中可用,但在VM中将被丢弃。
RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息。
@Documented将注解包含在javadoc中
@Inherited允许子类继承父类的注解


2. 注解元素:

package annotation;
import java.lang.annotation.*;

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {

    public int id();
    public String description() default "no description";
}

标签@UseCase由UseCase.java定义,其中包含int类型元素id和String类型元素description。

注解可用的类型如下:
- 所有基本类型(int、float、boolean等)
- String类型
- enum类型
- Annotation类型
- 以上类型的数组
注:也不允许使用任何包装类型

没有元素的注解被称为标记注解(marker annotation),例如上例中的@Test。

3. 默认值限制

元素不能有不确定的值,也就是说元素要么有默认值,要么在使用注解时提供元素的值。
对于非基本类型的值,无论是源代码声明时还是在注解接口中定义默认值时都不能使用null。这个约束是的处理器很难表现一个元素的存在或缺失状态,因为在每个元素的注解声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能自己定义一些特殊的值,如空字符串或负数等以此表示该元素不存在。

package annotation;
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Null {
    public int id() default 0;
    public String description() default "";
}

二、注解处理器

如果没有用来读取注解的工具,那注解也不会比注释更有用。使用注解的过程,最重要的就是编写和使用注解处理器
Java5.0扩展了反射机制的API,以帮助程序员构造这类工具。
下面是一个简单的注解处理器,它将通过反射机制读取PasswordUtils类中的标记。

package annotation;

import java.lang.reflect.Method;
import java.util.*;

public class UseCaseTracker {
    public static void trackerUseCase(List<Integer> useCases, Class<?> c) {
        for (Method m : c.getDeclaredMethods()) {
            UseCase uc = m.getAnnotation(UseCase.class);
            if (uc != null) {
                System.out.println("Found use case:" + uc.id() + "  " + uc.description());
                useCases.remove(new Integer(uc.id()));
            }
        }
        for (Integer u : useCases) {
            System.out.println("Missing use case-" + u);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        UseCaseTracker.trackerUseCase(useCases, PasswordUtils.class);
    }
}

这个程序用到了两个反射的方法:getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Field、Method等都实现了该接口)。getAnnotation():返回指定类型的注解对象,这里是UseCase。如果方法上没有该类型的注解,那么返回null值。然后我们通过id()和description()方法从UseCase对象中获取元素的值。

package annotation;

import java.util.List;

public class PasswordUtils {

    @UseCase(id=47, description="Passwords must contain at least one numeric")
    public boolean validatePassword(String password) {
        return (password.matches("\\w*\\d\\w*"));
    }

    @UseCase(id=48)
    public String encryptPassword(String password) {
        return new StringBuilder(password).reverse().toString();
    }

    @UseCase(id=49, description="New passwords can not equal previously used ones")
    public boolean checkForNewPassword(List<String> prePasswords, String password) {
        return !prePasswords.contains(password);
    }
}

运行结果:
Found use case:47 Passwords must contain at least one numeric
Found use case:48 no description
Found use case:49 New passwords can not equal previously used ones
Missing use case-50

encryptPassword没有指定description元素的值,因此处理器处理它对应的注解时通过description()方法获取的是默认值no description。

三、例子

假设你希望提供简单的对象/关系映射功能,能够自动生成数据库表,用来存储JavaBean对象。你可以使用XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。然而如果使用注解的话就可以将所有信息都保存到JavaBean源文件中。
为此我们需要一些新的注解

DBTable.java告诉处理器需要给我生成一个数据库表

package annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {

    public String name() default "";
}

注:@DBTable有一个元素name,通过这个元素为处理器创建数据库表提供表名。
下面为修饰JavaBean域提供的注解

package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contraints {

    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}
package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {

    int value() default 0;
    String name() default "";
    Contraints contrants() default @Contraints;
}
package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {

    String name() default "";
    Contraints contrants() default @Contraints;
}

上面这两个SQL类型具有name()和contrants()元素,后者用到了嵌套注解的功能

package annotation;

@DBTable(name="MEMBER")
public class Member {

    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger Integer age;
    @SQLString(value=30, contrants=@Contraints(primaryKey=true)) 
    String handle;
    static int memberCount;

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getHandle() {
        return handle;
    }
    public void setHandle(String handle) {
        this.handle = handle;
    }

    @Override
    public String toString() {
        return handle;
    }
}

可以使用多种不同的方式来定义自己的注解,上面只定义了String和Integer的注解,还有很多其他类型如果一一定义会很繁琐,这时可以使用@TableColumn单一的注解类,它包含一个enum元素,该枚举类定义了STRING,INTEGER,FLOAT…等,这就消除了所有SQL类型都需要定义一个注解类的负担。
一个目标可以使用多个注解,但是同一注解不能重复使用。

package annotation;

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 {
        /*if (args.length < 1) {
            System.out.println("arguments: annotation classes");
            System.exit(0);
        }*/
        args = new String[]{"annotation.Member"};
        for (String className : args) {
            Class<?> c = Class.forName(className);
            DBTable table = c.getAnnotation(DBTable.class);
            if (table == null) {
                System.out.println("NO DBTable in classes: " + className);
                continue;
            }
            String tableName = table.name();
            List<String> columnDefs = new ArrayList<String>();
            for (Field f : c.getDeclaredFields()) {
                String columnName = null;
                Annotation[] annos = f.getAnnotations();
                if (annos.length < 1) 
                    continue;
                if (annos[0] instanceof SQLString) {
                    SQLString sqlString = (SQLString) annos[0];
                    columnName = sqlString.name();
                    if (columnName.length() < 1) {
                        columnName = f.getName().toUpperCase();
                    }
                    columnDefs.add(columnName + " VARCHAR(" + sqlString.value() + ")" + getContraints(sqlString.contrants()));
                }
                if (annos[0] instanceof SQLInteger) {
                    SQLInteger sqlInt = (SQLInteger) annos[0];
                    columnName = sqlInt.name();
                    if (columnName.length() < 1) {
                        columnName = f.getName().toUpperCase();
                    }
                    columnDefs.add(columnName + " INT" + getContraints(sqlInt.contrants()));
                }
            }
            StringBuffer buf = new StringBuffer("CREATE TABLE " + tableName + "(\n");
            for (String column : columnDefs) {
                buf.append(column + ",\n");
            }
            buf.substring(0, buf.length()-1);
            buf.append(");");
            System.out.println(buf);
        }
    }

    /**
     * 获取约束
     * @param contrants
     * @return
     */
    private static String getContraints(Contraints con) {
        String contraints = "";
        if (con.primaryKey()) 
            contraints += " PRIMARY KEY";
        if (con.allowNull()) 
            contraints += " NULL";
        if (con.unique())
            contraints += " UNIQUE";
        return contraints;
    }
}

运行结果:

CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) NULL,
LASTNAME VARCHAR(50) NULL,
AGE INT NULL,
HANDLE VARCHAR(30) PRIMARY KEY NULL,
);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值