关于注解我们需要知道的

1,543 阅读5分钟

从JDK5开始,Java增加对注解的支持,注解可以在编译,类加载和运行时被读取,并执行相应一些定义好的处理。通过注解可以在不改变原有代码和逻辑的情况下进行一些其他的补充操作。

系统注解

元注解

在java中系统为我们预置了一部分注解,我们可以通过这些注解来定义其他注解的作用和有效范围等特性。

@Target

@Target用于说明Annotation所修饰的对象范围,所能修饰的范围都被定义在枚举类ElementType中。

public enum ElementType {
    TYPE,//表示可以用于类,接口,注解或者枚举定义中
    FIELD,//字段
    METHOD,//方法(不包括构造方法)
    PARAMETER,//方法的参数
    CONSTRUCTOR,//构造方法上
    LOCAL_VARIABLE,//局部变量
    ANNOTATION_TYPE,//只能用在注解上
    PACKAGE,//作用包上 package-info.java 
    TYPE_PARAMETER,//表示注解能写在类型变量(泛型参数)的声明语句中如 List<Integer> list = new @Save ArrayList<>();
    TYPE_USE //表示注解能写在使用类型的任何语句中(声明语句、泛型和强制转换语句中的类型)。
}
TYPE_PARAMETER
@Target(ElementType.TYPE_PARAMETER)
public @interface Save {
}

public class Test<@Save T> {
    List<Integer> list = new @Save ArrayList<>();//仅用于展示可以用到的地方
}

@Retention

Retention 定义了该Annotation被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即被描述的注解在什么范围内有效),取值被定义在枚举类RetentionPolicy中:

public enum RetentionPolicy {
    SOURCE,//表示在源代码时有效,编译后的文件没有该注解,一般该类注解仅用于标识如@SuppressWarnings

    CLASS, //默认行为 自定义注解如果没有显示的声明则默认为该行为 在编译时不会被抛弃,但是会被虚拟机抛弃

    RUNTIME //保留到运行时,可以通过反射来获取 一般该类注解会影响系统的运行
}

@Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

从注解定义可以看到该注解用在注解定义上。

@Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因 此可以被如javadoc之类的工具文档化。但是实际使用并不多,有其他更好的替代。

@Inherited

@Inherited是一个标记注解,@Inherited表示被其标注的类型是被继承的。如果一 个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。

简单来说就是在子类中如果想要获取父类被那些注解修饰,那么子类能拿到的仅仅是被@Inherited标注过得注解。而其他没有使用 @Inherited的注解是无法再子类获取的。

标准注解

上面介绍的几种元注解是在我们进行自定义注解的时候会用到的,而下面我们介绍几种平时业务开发会经常使用的注解。

@Deprecated

@Deprecated用来描述在当前系统中已经被废弃不推荐使用的类或方法等。

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})

如果我们使用了被@Deprecated标注的类或方法等,在进行编译的时候会显示相应的提示信息。

@Override

@Override是我们使用很频繁的一个注解,由于重写的操作仅存在于方法中,所以@Override也只能对方法进行标注。

@Override功能主要是用来校验当前被标注的方法是否为重写方法,平时我们在继承抽象类或实现接口时都应使用该注解来标注被重写的方法。

@SuppressWarnings

@SuppressWarnings用于可选择的抑制编译器在编译时产生警告信息。

//作用范围
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

@SuppressWarnings可选择的值有很多:

  • deprecation:不产生使用过期方法(...)的警告,@SuppressWarnings("deprecation")
  • unchecked:执行了未检查的转换的警告
  • finally:finally语句无法正常完成时的警告
  • ...
  • all:任意类型的警告

自定义注解与解析

定义注解

自定义一个注解及其简单,使用@interface关键字即可完成。同时我们需要确定我们定义的注解使用范围和其具体用途,根据此来确定使用元注解的哪些参数来修饰我们定义的注解。

这里我们定义一个@Log注解用于在对数据库进行增删改时插入一条日志进行记录。由于该注解需要在运行时对数据库进行修改,所以很明显作用有效期为RUNTIME,作用范围可以使方法也可以使类。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Log {
    String value();//value的值为本次操作类型:insert,delete等
}

注解解析

定义接口并实现:

public interface UserService {
    @Log(OperationType.INSERT)
    void save(User user);
}
public class LogProxyExt implements InvocationHandler {

    private Object obj;

    static Connection connection = null;

    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        if(connection == null){
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/fuxi?serverTimezone=UTC", "root", "root");
        }
        return connection;
    }

    public LogProxyExt(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //查找方法上是否存在该注解
        Log annotation = method.getAnnotation(Log.class);
        if(annotation != null){
            User user = (User) args[0];
            if(user!=null && StringUtils.isNotBlank(user.getName())){
                connection = getConnection();
                PreparedStatement statement = connection.prepareStatement("insert into log(`log_detail`,`operation`) values(?,?)");
                statement.setString(1, user.getName());
                statement.setString(2, annotation.value());
                statement.executeUpdate();
            }
        }

        //查找类上是否存在该注解
        proxy.getClass().getAnnotation(Log.class);
        //对类上的注解进行解析
        //todo
        Object invoke = method.invoke(obj, args);

        return invoke;
    }

    public static void main(String[] args) {
        UserService operat = new UserServiceImpl();
        LogProxyExt ext = new LogProxyExt(operat);
        //这里不能是具体的类,必须是接口 否则会抛出类转换异常
        UserService proxy = (UserService) Proxy.newProxyInstance(operat.getClass().getClassLoader(), operat.getClass().getInterfaces(), ext);
        User user = new User();
        user.setName("lisi");
        proxy.save(user);
    }
}

上述解析通过JDK动态代理进行实现,对UserService接口进行代理,保证在UserService中任意位置使用@Log注解都能完成插入日志操作。实际使用可以配合Spring Aop或者拦截器之类的对全局请求进行处理。

小结

注解是一个很有用的特性,在现有的框架中大多数都已经支持或正在支持注解。使用注解可以大大提升我们平时的开发速度,保证代码的简洁性。除了框架和JDK自带的注解之外,我们也可以通过自定义注解来对我们的代码功能进行检查和增强。