zoukankan      html  css  js  c++  java
  • Java注解实践

    Java注解实践

    标签 : Java基础


    注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理.


    JDK 基本Annotation

    注解 说明
    @Override 重写
    @Deprecated 已过时
    @SuppressWarnings(value = "unchecked") 压制编辑器警告
    @SafeVarargs 修饰”堆污染”警告
    @FunctionalInterface Java8特有的函数式接口
    • value特权
      如果使用注解时只需要为value成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用name=value的形式. 如@SuppressWarnings("unchecked")(SuppressWarnings的各种参数
      请参考解析 @SuppressWarnings的各种参数)
    • 请坚持使用@Override注解: 如果在每个方法中使用Override注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.

    JDK 元Annotation

    Annotation用于修饰其他的Annotation定义.

    元注解 释义
    @Retention 注解保留策略
    @Target 注解修饰目标
    @Documented 注解文档提取
    @Inherited 注解继承声明
    • @Retention 注解的保留策略
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }

    value为SOURCE, CLASS, RUNTIME三值之一:

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }
    • @Target 指定Annotation可以放置的位置(被修饰的目标)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }
    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
    
        /** Field declaration (includes enum constants) */
        FIELD,
    
        /** Method declaration */
        METHOD,
    
        /** Parameter declaration */
        PARAMETER,
    
        /** Constructor declaration */
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
        ANNOTATION_TYPE,
    
        /** Package declaration */
        PACKAGE
    }
    • @Documented 指定被修饰的该Annotation可以被javadoc工具提取成文档.
    • @Inherited 指定被修饰的Annotation将具有继承性
      如果某个类使用@Xxx注解(该Annotation使用了@Inherited修饰)修饰, 则其子类自动被@Xxx注解修饰.

    Annotation

    /**
     * Created by jifang on 15/12/22.
     */
    @Inherited
    @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Testable {
    }

    Client

    public class Client {
    
        @Test
        public void client(){
            new SubClass();
        }
    }
    
    @Testable
    class SupperClass{
    }
    
    class SubClass extends SupperClass{
        public SubClass() {
            for (Annotation annotation : SubClass.class.getAnnotations()){
                System.out.println(annotation);
            }
        }
    }

    自定义注解

    • 根据Annotation是否包含成员变量,可以把Annotation分为两类:
      • 标记Annotation: 没有成员变量的Annotation; 这种Annotation仅利用自身的存在与否来提供信息;
      • 元数据Annotation: 包含成员变量的Annotation; 它们可以接受(和提供)更多的元数据;
    • 定义新注解使用@interface关键字, 其定义过程与定义接口非常类似(见上面的@Testable), 需要注意的是:Annotation的成员变量在Annotation定义中是以无参的方法形式来声明的, 其方法名返回值类型定义了该成员变量的名字类型, 而且我们还可以使用default关键字为这个成员变量设定默认值.
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface Tag {
        String name() default "该叫啥才好呢?";
    
        String description() default "这家伙很懒, 啥也没留下...";
    }
    • 自定义的Annotation继承了Annotation这个接口, 因此自定义注解中包含了Annotation接口中所有的方法;
    public interface Annotation {
        /**
         * @return true if the specified object represents an annotation
         *     that is logically equivalent to this one, otherwise false
         */
        boolean equals(Object obj);
    
        /**
         * @return the hash code of this annotation
         */
        int hashCode();
    
        /**
         * @return a string representation of this annotation
         */
        String toString();
    
        /**
         * Returns the annotation type of this annotation.
         */
        Class<? extends Annotation> annotationType();
    }
    

    提取Annotation信息

    • 使用Annotation修饰了类/方法/成员变量等之后,这些Annotation不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理Annotation信息(当然,只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析).
    • Java使用Annotation接口来代表程序元素前面的注解, 用AnnotatedElement接口代表程序中可以接受注解的程序元素.像Class Constructor Field Method Package这些类都实现了AnnotatedElement接口.
    public final
        class Class<T> implements java.io.Serializable,
                                  java.lang.reflect.GenericDeclaration,
                                  java.lang.reflect.Type,
                                  java.lang.reflect.AnnotatedElement {
    ...
    }
    public interface AnnotatedElement {
        /**
         * Returns true if an annotation for the specified type
         * is present on this element, else false.  This method
         * is designed primarily for convenient access to marker annotations.
         */
         boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
    
       /**
         * Returns this element's annotation for the specified type if
         * such an annotation is present, else null.
         */
        <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    
        /**
         * Returns all annotations present on this element. 
         */
        Annotation[] getAnnotations();
    
        /**
         * Returns all annotations that are directly present on this
         * element.  Unlike the other methods in this interface, this method
         * ignores inherited annotations.  (Returns an array of length zero if
         * no annotations are directly present on this element.)  The caller of
         * this method is free to modify the returned array; it will have no
         * effect on the arrays returned to other callers.
         */
        Annotation[] getDeclaredAnnotations();
    }

    这样, 我们只需要获取到Class Method Filed等这些实现了AnnotatedElement接口的类实例, 就可以获取到我们想要的注解信息了.

    /**
     * Created by jifang on 15/12/22.
     */
    public class Client {
    
        @Test
        public void client() throws NoSuchMethodException {
            Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation.annotationType().getName());
            }
        }
    }

    如果需要获取某个注解中的元数据,则需要强转成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据:

    @Tag(name = "client")
    public class Client {
    
        @Test
        public void client() throws NoSuchMethodException {
            Annotation[] annotations = this.getClass().getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof Tag) {
                    Tag tag = (Tag) annotation;
                    System.out.println("name: " + tag.name());
                    System.out.println("description: " + tag.description());
                }
            }
        }
    }

    模拟Junit框架

    我们用@Testable标记哪些方法是可测试的, 只有被@Testable修饰的方法才可以被执行.

    /**
     * Created by jifang on 15/12/27.
     */
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Testable {
    }

    如下定义TestCase测试用例定义了6个方法, 其中有4个被@Testable修饰了:

    public class TestCase {
    
        @Testable
        public void test1() {
            System.out.println("test1");
        }
    
        public void test2() throws IOException {
            System.out.println("test2");
            throw new IOException("我test2出错啦...");
        }
    
        @Testable
        public void test3() {
            System.out.println("test3");
            throw new RuntimeException("我test3出错啦...");
        }
    
        public void test4() {
            System.out.println("test4");
        }
    
        @Testable
        public void test5() {
            System.out.println("test5");
        }
    
        @Testable
        public void test6() {
            System.out.println("test6");
        }
    }

    为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.

    /**
     * Created by jifang on 15/12/27.
     */
    public class TestableProcessor {
    
        public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            int passed = 0;
            int failed = 0;
            Object obj = Class.forName(clazz).newInstance();
            for (Method method : Class.forName(clazz).getMethods()) {
                if (method.isAnnotationPresent(Testable.class)) {
                    try {
                        method.invoke(obj);
                        ++passed;
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");
                        e.printStackTrace(System.out);
                        ++failed;
                    }
                }
            }
    
            System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
        }
    
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            TestableProcessor.process("com.feiqing.annotation.TestCase");
        }
    }

    抛出特定异常

    前面介绍的只是一个标记Annotation,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在抛出特殊异常时才成功添加支持,这样就用到了具有成员变量的注解了:

    /**
     * Created by jifang on 15/12/28.
     */
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestableException {
        Class<? extends Throwable>[] value();
    }
    • TestCase
    /**
     * Created by jifang on 15/12/27.
     */
    public class TestCase {
    
        public void test1() {
            System.out.println("test1");
        }
    
        @TestableException(ArithmeticException.class)
        public void test2() throws IOException {
            int i = 1 / 0;
            System.out.println(i);
        }
    
        @TestableException(ArithmeticException.class)
        public void test3() {
            System.out.println("test3");
            throw new RuntimeException("我test3出错啦...");
        }
    
        public void test4() {
            System.out.println("test4");
        }
    
        @TestableException({ArithmeticException.class, IOException.class})
        public void test5() throws FileNotFoundException {
            FileInputStream stream = new FileInputStream("xxxx");
        }
    
        @Testable
        public void test6() {
            System.out.println("test6");
        }
    }
    • 注解处理器
    public class TestableExceptionProcessor {
    
        public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            int passed = 0;
            int failed = 0;
            Object obj = Class.forName(clazz).newInstance();
            for (Method method : Class.forName(clazz).getMethods()) {
                if (method.isAnnotationPresent(TestableException.class)) {
                    try {
                        method.invoke(obj, null);
                        // 没有抛出异常(失败)
                        ++failed;
                    } catch (InvocationTargetException e) {
                        // 获取异常的引发原因
                        Throwable cause = e.getCause();
    
                        int oldPassed = passed;
                        for (Class excType : method.getAnnotation(TestableException.class).value()) {
                            // 是我们期望的异常类型之一(成功)
                            if (excType.isInstance(cause)) {
                                ++passed;
                                break;
                            }
                        }
                        // 并不是我们期望的异常类型(失败)
                        if (oldPassed == passed) {
                            ++failed;
                            System.out.printf("Test <%s> failed <%s> %n", method, e);
                        }
                    }
                }
            }
            System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
        }
    
        public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
            process("com.feiqing.annotation.TestCase");
        }
    }
    

    注解添加监听器

    下面通过使用Annotation简化事件编程, 在传统的代码中总是需要通过addActionListener方法来为事件源绑定事件监听器:

    /**
     * Created by jifang on 15/12/27.
     */
    public class SwingPro {
        private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
    
        private JButton ok = new JButton("确定");
        private JButton cancel = new JButton("取消");
    
        public void init() {
            JPanel jp = new JPanel();
    
            // 为两个按钮设置监听事件
            ok.addActionListener(new OkListener());
            cancel.addActionListener(new CancelListener());
    
            jp.add(ok);
            jp.add(cancel);
            mainWin.add(jp);
            mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            mainWin.pack();
            mainWin.setVisible(true);
        }
    
        public static void main(String[] args) {
            new SwingPro().init();
        }
    }
    
    class OkListener implements ActionListener {
    
        @Override
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(null, "你点击了确认按钮!");
        }
    }
    
    class CancelListener implements ActionListener {
    
        @Override
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(null, "你点击了取消按钮!");
        }
    }

    下面我们该用注解绑定监听器:

    • 首先, 我们需要自定义一个注解
    /**
     * Created by jifang on 15/12/27.
     */
    @Inherited
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActionListenerFor {
        Class<? extends ActionListener> listener();
    }
    • 然后还要一个注解处理器
    /**
     * Created by jifang on 15/12/27.
     */
    public class ActionListenerInstaller {
    
        public static void install(Object targetObject) throws IllegalAccessException, InstantiationException {
            for (Field field : targetObject.getClass().getDeclaredFields()) {
                // 如果该成员变量被ActionListenerFor标记了
                if (field.isAnnotationPresent(ActionListenerFor.class)) {
                    // 设置访问权限
                    field.setAccessible(true);
    
                    // 获取到成员变量的值
                    AbstractButton targetButton = (AbstractButton) field.get(targetObject);
    
                    // 获取到注解中的Listener
                    Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener();
    
                    // 添加到成员变量中
                    targetButton.addActionListener(listener.newInstance());
                }
            }
        }
    }
    • 主程序(注意注释处)
    public class SwingPro {
    
        private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
    
        /**
         * 使用注解设置Listener
         */
        @ActionListenerFor(listener = OkListener.class)
        private JButton ok = new JButton("确定");
    
        @ActionListenerFor(listener = CancelListener.class)
        private JButton cancel = new JButton("取消");
    
        public SwingPro init() {
            JPanel jp = new JPanel();
    
            // 使得注解生效
            try {
                ActionListenerInstaller.install(this);
            } catch (IllegalAccessException | InstantiationException e) {
                e.printStackTrace(System.out);
            }
    
            jp.add(ok);
            jp.add(cancel);
            mainWin.add(jp);
            mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            mainWin.pack();
            mainWin.setVisible(true);
    
            return this;
        }
    
        //下同
    }

    重复注解

    在Java5到Java7这段时间里, 同一个程序元素前只能使用一个相同类型的Annotation; 如果需要在同一个元素前使用多个相同的Annotation, 则必须使用Annotation容器(在Java8中, 对这种情况做了改善, 但其实也只是一种写法上的简化, 其本质还是一样的).由于在实际开发中,Java8还未大面积的使用, 因此在此只介绍Java7中重复注解定义与使用.

    • Table Annotation定义(代表数据库表)
    /**
     * Created by jifang on 15/12/27.
     */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Table {
    
        String name() default "表名是啥?";
    
        String description() default "这家伙很懒, 啥也没留下...";
    }
    • Table 容器
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Tables {
    
        Table[] value();
    }

    注意: 容器注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃容器, 相应的注解也就丢失了.

    • Client
      使用时需要用Table容器来盛装Table注解
    @Tables({
            @Table(name = "t_user", description = "用户表"),
            @Table(name = "t_feed", description = "动态表")
    })
    public class Client {
    
        @Test
        public void client() {
            Tables tableArray = this.getClass().getAnnotation(Tables.class);
            Table[] tables = tableArray.value();
    
            for (Table table : tables) {
                System.out.println(table.name() + " : " + table.description());
            }
        }
    }

    在Java8中, 可以直接使用

    @Table(name = "t_user", description = "用户表")
    @Table(name = "t_feed", description = "动态表")

    的形式来注解Client, 但@Tables还是需要开发者来写的, 由此可以看出, 重复注解只是一种简化写法, 这种写法只是一种假象: 多个重复注解其实会被作为容器注解的value成员.


    参考:
    Effective Java
    疯狂Java讲义
    Java核心技术
  • 相关阅读:
    MySQL命令2
    MySQL命令1
    前端之HTML1
    linux命令之df dh
    python call java jar
    redis-py中的坑
    YARN应用程序的开发步骤
    Yarn的服务库和事件库使用方法
    SSH无密码验证
    在centos 6.5 在virtual box 上 安装增强版工具
  • 原文地址:https://www.cnblogs.com/itrena/p/5926918.html
Copyright © 2011-2022 走看看