• [Java5新特性]Annotation注解


    Annotation概述

    Annotation是JDK 5.0以后提供对元数据的支持,可以在编译、加载和运行时被读取,并执行相应的处理。所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包、类、构造器、方法、成员变量、参数和局部变量的声明,这些信息被存储在Annotation的“name=value”对中。

    Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据,比如一段代码的作者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行。

    基本Annotation

    在Java中提供了3个基本Annotation的用法,使用Annotation时要在其前面增加@符号,并把该Annotation当作一个修饰符使用,用于修饰它支持的程序元素。这3个基本Annotation都定义在java.lang包下,可以通过查看API文档来了解。

    • @Override:限定重写父类方法。@Override就是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。
    public class Fruit {
        public void info(){
            System.out.println("这是一个水果,想吃吗?");
        }
    }
    public class Apple extends Fruit {
        @Override
        public void info() {
            System.out.println("这不仅是一个水果,它是苹果.");
        }
    }
    

    如果Apple类的info()方法名写成了inf()的话,编译器会报错。值得注意的是,@Override只能修饰方法,不能修饰其他程序元素。

    • @Deprecated:标示已过时。@Deprecated用于表示某个程序元素已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。
    public class Fruit {
        @Deprecated
        public void info(){
            System.out.println("这是一个水果,想吃吗?");
        }
    }
    public class DeprecatedTest {
        public static void main(String[] args) {
            // 使用info()方法时将会出现划线,表示该方法已过时.
            new Fruit().info();
        }
    }
    
    • @SuppressWarnings:抑制编译器警告。@SuppressWarnings表示被该Annotation修饰的代码取消显示指定的编译器警告。
    public class SuppressWarningsTest {
        public static void main(String[] args) {
            @SuppressWarnings("rawtypes")
            /*
             * List集合在定义时,没有指定泛型类型.
             *  * 默认情况下,出现编译器警告.
             *  * 使用@SuppressWarnings注释后,取消警告信息.
             */
            List list = new ArrayList();
        }
    }
    

    自定义Annotation

    自定义一个Annotation类型使用@interface关键字,定义一个新的Annotation类型与定义一个接口非常像(只是多了一个@符号)。

    // 自定义一个Annotation类型
    public @interface Test {
    }
    

    在自定义一个Annotation类型通常可以用于修饰程序中的类、方法、变量、接口等。一般情况下,使用Annotation会在代码之前使用。

    // 自定义Annotation类型定义在类上.
    @Test
    public class AnnotationTest {
        // 自定义Annotation类型定义在成员变量上.
        @Test
        private int i;
        // 自定义Annotation类型定义在构造函数上.
        @Test
        public AnnotationTest(){}
        // 自定义Annotation类型定义在方法上.
        @Test
        // 自定义Annotation类型定义在方法参数上.
        public void fun(@Test String str){
            // 自定义Annotation类型定义在变量上.
            @Test
            int z;
        }
    }
    

    Annotation属性

    自定义Annotation不仅可以是这种简单形式,还可以包含成员变量。自定义的Annotation的成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

    /**
     * 自定义带有username和password属性的Annotation
     * 
     * @author 金云龙
     */
    public @interface UserInfo {
        String username();
        String password();
    }
    

    使用带有属性的自定义Annotation时,必须使用其属性指定值,否则会报错。

    @UserInfo(username="zhangwuji",password="123")
    public class UserInfoTest {
    }
    

    自定义Annotation不仅可以设置属性,还可以为属性设置默认值,使用default关键字。

    /**
     * 自定义带有username和password属性的Annotation
     *  * 为username属性设置默认值.
     * @author 金云龙
     */
    public @interface UserInfo {
        String username() default "zhangwuji";
        String password();
    }
    

    如果为自定义Annotation的属性设置了默认值,则在使用时可以不为该属性指定值(使用默认值)。也可以在使用该Annotation时为其属性指定值,则默认值不会起作用。

    自定义Annotation中具有名为value的属性,在使用该Annotation时如果只使用value属性的话,可以不写属性名直接指定值。

    @UserInfo("jiaozhu")
    public class UserInfoTest {
    }
    

    Annotation的属性类型只能是基本类型、String、Enum、Class及上述类型的一维数组类型。

    @Target注解

    @Target修饰自定义Annotation,指定该自定义Annotation可以用于修饰哪些程序单元,例如方法、成员变量等。@Target注解包含一个ElementType类型的value属性,该属性值只能是如下几个:

    • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation。
    • ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器。
    • ElementType.FIELD:指定该策略的Annotation只能修饰成员变量。
    • ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量。
    • ElementType.METHOD:指定该策略的Annotation只能修饰方法定义。
    • ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义。
    • ElementType.PARAMETER:指定该策略的Annotation只能修饰参数。
    • ElementType.TYPE:指定该策略的Annotation可以修饰类、接口或枚举定义。

    以下是@Target注解的源码和ElementType的源码:

    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }
    public enum ElementType {
        TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE
    }
    

    @Retention注解

    @Retention修饰自定义Annotation,指定自定义Annotation的生命周期。@Retention包含一个RetentionPolicy类型的value属性,该属性值只能是如下几个:

    • RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这时默认值。
    • RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也可以获取Annotation信息,程序可以通过反射获取该Annotation信息。
    • RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。

    以下是@Retention注解的源码和RetentionPolicy的源码:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }
    public enum RetentionPolicy {
        SOURCE, CLASS, RUNTIME
    }
    

    反射读取Annotation

    使用Annotation修饰了类、方法、成员变量等之后,这些Annotation不会自己生效,必须通过相应程序提取并处理Annotation信息。Java提供的Annotation接口是所有注解的父接口,在JDK 5.0新增加AnnotatedElement接口,该接口提供读取运行时Annotation的方法。只有当自定义的Annotation使用了@Retention(RetentionPolicy.RUNTIME)时,该Annotation才会在运行可见,JVM才能读取保存在class文件的Annotation信息。

    以下是AnnotatedElement接口提供的方法API:

    方法摘要
    <T extends Annotation> T getAnnotation(Class annotationClass)
    Annotation[] getAnnotations()
    Annotation[] getDeclaredAnnotations()
    boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

    实际获取某类使用的Annotation信息的方式如下:

    public class AnnotatedElementTest {
        public static void main(String[] args) throws Exception {
            // 获取对应类的Class对象.
            Class<UserInfoTest> clazz = UserInfoTest.class;
            // 获取对应类方法的Method对象.
            Method method = clazz.getMethod("fun");
            // 获取类上的注解.
            UserInfo anno1 = clazz.getAnnotation(UserInfo.class);
            // 打印该注解的username属性值.
            System.out.println(anno1.username());
            // 获取方法上的注解.
            UserInfo anno2 = method.getAnnotation(UserInfo.class);
            // 打印该注解的username属性值.
            System.out.println(anno2.password());
        }
    }
    

    注解配置JDBC案例

    使用JDBC连接MySQL数据库时,需要driverClassName、url、username和password四个参数。而之前的做法是将这四个参数写入一个配置文件,在JDBCUtils工具类中读取配置文件。目前可以将四个参数定义为一个注解,在JDBCUtils工具类中通过反射获取对应注解定义的四个参数内容。具体做法如下:

    • 定义一个Annotation用于定义JDBC连接MySQL数据库所需的四个参数内容。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface JDBCInfo {
        String driverClassName();
        String url();
        String username();
        String password();
    }
    
    • 定义JDBCUtils工具类,使用Annotation配置四个参数内容,并使用反射进行读取。
    public class JDBCUtils {
        @JDBCInfo(driverClassName = "com.mysql.jdbc.Driver", url = "jdbc:mysql://localhost:3306/jdbc", username = "root", password = "root")
        public static Connection getConnection() throws Exception {
            // 获取注解修饰目标对应的反射对象.
            Method method = JDBCUtils.class.getDeclaredMethod("getConnection");
            // 判断是否存在目前注解
            if (method.isAnnotationPresent(JDBCInfo.class)) {
                // 获取注解信息
                JDBCInfo jdbcInfo = method.getAnnotation(JDBCInfo.class);
                // 读取注解属性信息
                String driverClassName = jdbcInfo.driverClassName();
                String url = jdbcInfo.url();
                String username = jdbcInfo.username();
                String password = jdbcInfo.password();
                // Class类加载驱动
                Class.forName(driverClassName);
                // 返回连接对象
                return DriverManager.getConnection(url, username, password);
            }
            return null;
        }
    }
    
    • 编写一个测试类用于测试JDBCUtils工具类是否正确。
    public class JDBCTest {
        public static void main(String[] args) throws Exception {
            Connection conn = JDBCUtils.getConnection();
            String sql = "select * from products";
            PreparedStatement statement = conn.prepareStatement(sql);
            ResultSet rs = statement.executeQuery();
    
            while (rs.next()) {
                System.out.println(rs.getString("name") + "," + rs.getDouble("price"));
            }
    
            rs.close();
            statement.close();
            conn.close();
        }
    }
    

    转载说明:请注明作者和原文链接,谢谢!

  • 相关阅读:
    C# 本地文件夹上传至网络服务器中(待续)
    杭州"人才新政22条" 硕士来杭工作一次性补贴2万元
    12.Redis Select 命令
    HTML input 标签不可编辑的 readonly 属性
    让时间处理简单化 【第三方扩展类库org.apache.commons.lang.time】
    java 给指定时间加上天数or给当前日期加天数
    java事务处理
    基于注解的SpringMVC整合JPA
    Could not open JPA EntityManager for transaction; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: Cannot open connection
    <%@ include file=""%>与<jsp:include page=""/>两种方式的作用
  • 原文地址:https://www.cnblogs.com/longestory/p/4566982.html
Copyright © 2020-2023  润新知