• Java基础--注解、反射


    一、注解(Annotation)

    1、什么是注解?

      从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。

    2、Annotation与注释的区别:

      (1)Annotation不是程序本身,可以对程序进行解释,此处可以理解为注释。但是Annotation可以被其他程序(比如编译器)读取,并进行处理。
      (2)注解与注释最大的区别就是注解存在被处理的流程,即注解是会被程序处理的。

    3、注解的格式:

      (1)以 “@注释名” 的形式在代码中存在。
      (2)注解可以附加在程序元素( 包、类、构造器、方法、成员变量、参数、局域变量 )上面,为其添加额外的辅助信息,可以通过反射机制访问这些数据。
      (3)Annotation不能运行,其只有成员变量,没有方法。Annotation与public、final等修饰符地位类似,属于程序元素的一部分,但不能作为程序元素使用。

    4、常见注解:

      (1)@Override
        定义在java.lang.Override中,此注释只用于修饰方法,表示重写一个父类的方法。

    【举例:】
     @Override
     public String toString() {
         return "Hello";
    }

      (2)@Deprecated

        定义在java.land.Deprecated中,此注释可用于修饰方法、属性、类,表示该方法、类、属性不推荐使用(废弃)。在方法、类、属性上会有一条删除线(形如toString())。

    【举例:】
    @Deprecated
    public String toString() {
        return "TimerTaskDemo []";
    }

      (3)@SuppressWarnings
        定义在java.lang.SuppressWarnings中,用来阻止编译时的警告信息。其使用时需要设置参数。

    【参数为:】
    deprecation,使用了过时的类或方法的警告。
    unchecked,执行了未检查的转换时的异常,比如集合未指定泛型。
    fallthrough,当在switch语句发生case穿透时的警告。
    path,当类路径、源文件路径不存在时的警告。
    serial,可序列化类缺少serialVersionUID时的警告。
    finally,任何finally不能完成时的警告。
    all,以上所有警告。
    
    【格式:】
    @SuppressWarnings("all")
    或者
    @SuppressWarnings(value = { "serial", "unchecked" })

    注意:

      @SuppressWarnings("resource")

      对于J2EE,可以使用@Resource来完成依赖注入或者叫资源注入,但是当你在一个类中使用已经使用注解的类,却没有为其注入依赖,代码会提示相关信息,可以使用@SuppressWarnings("resource")来取消提示信息。

    5、元注解:

      (1)元注解的作用就是负责注解其他的注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。存在于java.lang.annotation中。

      (2)元注解分类:
        @Target
        @Retention
        @Documented
        @Inherited

      (3)@Target元注解:
        用于描述注解的使用范围。Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

    【格式:】
    public @interface Target
    {
      ElementType[] value();
    }
    
    【参数:ElementType】
    CONSTRUCTOR:用于描述构造器
    FIELD:用于描述成员变量
    LOCAL_VARIABLE:用于描述局部变量
    METHOD:用于描述方法
    PACKAGE:用于描述包
    PARAMETER:用于描述参数
    TYPE:用于描述类、接口(包括注解类型) 或enum声明
    
    【举例:】
    @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE})

      (4)@Retention元注解:
        用于描述注解的声明周期。某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

    【格式:】
    public @interface Retention
    {
      RetentionPolicy value();
    }
    
    【参数:RetentionPoicy】
    SOURCE:在源文件中有效(即源文件保留)
    CLASS:在class文件中有效(即class保留)
    RUNTIME:在运行时有效(即运行时保留,可以通过反射机制读取)
    
    【举例:】
    @Retention(RetentionPolicy.SOURCE)

      (5)@Documented元注解:
        用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

      (6)@Inherited元注解:
        @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
    注意:
        @Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
        当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    6、自定义注解:

      (1)自定义注解时,需要使用@interface用来声明一个注解,其会自动继承java.lang.annotation.Annotation接口。

    【格式:】
    public @interface 注解名 {定义体}
    【或者:】
    public @interface 注解名 {
      类型 value() default 默认值;   //这里是参数,不是抽象方法。
    }
    
    其中定义体实质是声明了一个配置参数(注:此处不是抽象方法)。
    1、方法名指的是参数名。
    2、返回值类型指的是参数的类型(只能为基本类型、Class、String、enum、Annotation类型、以及以上所有类型的数组)。
    3、可以通过default来声明参数的默认值。
    4、如果只有一个参数,那么参数名(方法名)一般为value。
    5、只能使用public, default两个权限修饰符。  

      (2)方法:

    判断类或者方法是否有注解
        boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
    
    获得注解对象
        <A extends Annotation> A getAnnotation(Class<A> annotationClass)  //获取指定注解
        Annotation[] getAnnotations()  //获取当前元素上的所有注解
    【举例:】
    package com.test;
    
    import java.lang.annotation.Annotation;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Table {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FieldDemo {
        String columnName() default "";
    
        String type() default "";
    
        int length() default 10;
    }
    
    @Table("student")
    class Student {
        @FieldDemo(columnName = "id", length = 10, type = "int")
        private int id;
    
        @FieldDemo(columnName = "name", length = 20, type = "varchar")
        private String name;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    public class AnnotationDemo {
        public static void main(String[] args) {
            try {
                // 获取Student类的信息
                Class classDemo = Class.forName("com.test.Student");
                // Class<Student> classDemo = (Class<Student>)Class.forName("com.test.Student");
                System.out.println(classDemo); // 输出class com.test.Student
    
                // 获取当前元素上的所有注解,此时获取的是@Table
                Annotation[] annotations = classDemo.getAnnotations();
                for (Annotation annotation : annotations) {
                    System.out.println(annotation);
                } // 输出@com.test.Table(value=student)
    
                // 直接获取指定的某注解
                Table table = (Table) classDemo.getAnnotation(Table.class);
                System.out.println(table.value()); // 输出student
    
                // 获取类的属性的注解
                Field field = classDemo.getDeclaredField("name");
                // 获取指定注解
                FieldDemo fieldDemo = field.getAnnotation(FieldDemo.class);
                // 输出name varchar 20
                System.out.println(fieldDemo.columnName() + " " + fieldDemo.type() + " " + fieldDemo.length());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    二、反射机制

    1、什么是反射?

      JAVA反射机制指的是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;即这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
      JAVA中与反射相关的类,放在java.lang.reflect包中。

    2、Class类

      Class对象包含了一个类的完整的结构信息。通过Class对象,可以对类进行操作,即为反射。
      (1)规则:
        Class类拥有泛型作为定义。
        Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
        Class类没有public构造方法。
        Class对象是在加载类时由Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。
        一个类只有一个Class对象。

      (2)内置class实例(class对象):

    byte.classshort.classint.classlong.classchar.classfloat.classdouble.classboolean.classvoid.class.
    注:
    int.class != Integer.classint.class == Integer.Type  。

      (3)对于数组类型class实例:
        每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class 对象。

    int[] a = new int[100];
    int[] b = new int[10];
    long[] c = new long[10];
    int[][] d = new int[10][2];
    System.out.println(a.getClass());//输出class [I
    System.out.println(b.getClass());//输出class [I
    System.out.println(c.getClass());//输出class [J
    System.out.println(d.getClass());//输出class [[I
    System.out.println(a.getClass() == b.getClass());//输出true

      (4)获取Class实例的方法:

    【方法1:】
    根据传入的参数动态装载一个类,并且做类的初始化。
        Class.forName()方法
    
    【方法2:】
    获得对象运行时所指的真正对象(多态的场合返回子类的类名)。
        Class.getClass() 方法
    
    【方法3:】
    JVM将使用类A的类装载器,将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象。
        A.class属性

      (5)通过Class实例创建对象:

    Class.newInstance()方法 。调用默认构造函数,获得一个实例
    
    Class.newInstance方法与new的区别
    newInstance: 弱类型。低效率。只能调用无参构造。
    new:强类型。相对高效。能调用任何public构造。

      (6)常用方法:

    【获得构造器:】
    Constructor<T> getDeclaredConstructor(Class<?>...)  获得指定构造方法
    Constructor<?>[] getDeclaredConstructors()    获得所有构造方法(声明顺序)
    Constructor<T> getConstructor(Class<?>...)    获得权限为public的指定构造方法
    Constructor<?>[] getConstructors()    获得权限为public的所有构造方法
    
    【获得普通方法(成员方法):】
    Method[] getDeclaredMethods()   获得该类中定义的所有方法(不包含父类继承)
    Method getDeclaredMethod(String name, Class<?>... parameterTypes)    根据该类中定义的指定方法(不包含父类继承)
    Method[] getMethods()  获得权限为public的所有的方法 (包含父类继承)
    Method getMethod(String name, Class<?>... parameterTypes)    获得权限为public的指定的方法 (包含父类继承)
    
    【获得属性(成员变量):】
    Field[] getDeclaredFields()   获得该类中定义的所有属性(不包含继承)
    Field getDeclaredField(String name)获得该类中定义的指定属性(不包含继承)
    Field[] getFields()  获得该类中所有public的属性(包含继承)
    Field getField (String name)  获得该类中指定的public属性(包含继承)
    
    【获得内部类:】
    Class<?>[] getDeclaredClasses()  获得所有内部类 (不包含继承)
    Class<?>[] getClasses()   获得所有权限为public的内部类(包含继承)
    
    【其他:】
    Package getPackage()   获得Package对象
    String getName()     获得类的全称,即包名+类名
    String getSimpleName()  获得类的简称,即类名
    Class<? super T> getSuperclass()  获得继承的类
    Class<?>[] getInterfaces()  获得实现的接口

      (7)获得构造器后,可以进行的操作

      (8)获得成员方法后,可以进行的操作

      (9)获得成员变量后,可以进行的操作

    package com.test;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    class Teacher {
        private String name;
        private int age;
    
        public Teacher() {
        }
    
        public Teacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    public class ReflectionDemo {
        public static void main(String[] args) {
            try {
                // 加载Teacher.class对象
                Class<Teacher> teacherClass = (Class<Teacher>) Class.forName("com.test.Teacher");
    
                // 获取无参构造器,若Teacher类没有无参构造方法,则会报错
                Teacher teacher = teacherClass.newInstance();
                System.out.println(teacher + ", " + teacher.getName() + ", " + teacher.getAge());
    
                // 获取有参构造器
                Constructor<Teacher> constructor = teacherClass.getDeclaredConstructor(String.class, int.class);
                Teacher teacher2 = constructor.newInstance("tom", 20);
                System.out.println(teacher2 + ", " + teacher2.getName() + ", " + teacher2.getAge());
    
                // 获取成员方法
                Teacher teacher3 = teacherClass.newInstance();
                Method method = teacherClass.getDeclaredMethod("setAge", int.class);
                method.invoke(teacher3, 30);
                System.out.println(teacher3.getAge());
    
                Method method2 = teacherClass.getDeclaredMethod("getAge");
                System.out.println(method2.invoke(teacher3));
    
                // 获取成员变量
                Teacher teacher4 = teacherClass.newInstance();
                Field field = teacherClass.getDeclaredField("age");
                field.setAccessible(true);// 忽略安全检查,可以获取private类型的数据,破坏封装性。
                System.out.println(field.get(teacher4));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    【结果:】
    com.test.Teacher@2a139a55, null, 0
    com.test.Teacher@15db9742, tom, 20
    30
    30
    0

    3、反射机制性能问题

      setAccessible,是启用和禁用安全检查的开关,其值为true时,表示禁用Java语言访问的安全性检查,为false时,表示启用安全性检查,将其值设为true,可以提高反射的效率。

    三、静态代理与动态代理

    1、静态代理

    (1)静态代理其实就是指设计模式中的代理模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。代理类只能为一个接口服务,使用静态代理会产生很多代理类。
    (2)UML图:

    (3)代码演示:

    【Subject.java】
    package construction.pattern.proxy;
    
    /**
     * 定义一个代理类与被代理类的公共接口,使得被代理类出现的地方,代理类均能出现
     */
    interface Subject {
        public abstract void request();
    }
    
    
    【RealSubject.java】
    package construction.pattern.proxy;
    
    /**
     * 被代理类。定义 代理类 所代表的真实实体。
     *
     */
    public class RealSubject implements Subject {
    
        @Override
        public void request() {
            System.out.println("这是真实请求");
        }
    }
    
    【RealSubjectProxy.java】
    package construction.pattern.proxy;
    
    /**
     * 代理类,用于控制被代理类。保存一个被代理类的引用使得代理可以访问实体。
     *
     */
    public class RealSubjectProxy implements Subject {
        private RealSubject realSubject;//保存一个被代理类的引用
    
        @Override
        public void request() {
            System.out.println("代理开始......");
            if (realSubject == null) {
                realSubject = new RealSubject();
            }
            realSubject.request();
            System.out.println("代理结束......");
        }
    }
    
    【ProxyDemo.java】
    package construction.pattern.proxy;
    
    /**
     * 静态代理的小demo。
     * 被代理类能出现的地方,代理类均能出现。
     */
    public class ProxyDemo {
        public static void main(String[] args) {
            Subject subject = new RealSubjectProxy();
            subject.request();
        }
    }
    
    【执行结果:】
    代理开始......
    这是真实请求
    代理结束......

    (4)代码分析:
    静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

    2、动态代理

    (1)为了解决静态代理的问题,动态代理的想法就被提出。即在运行状态下,在需要被代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,可以避免类臃肿。

      即动态代理指的是 在程序运行时动态的创建目标类的代理类的对象,且通过代理对象来调用目标对象。

    (2)Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。

    /**
      * 参数
      *  proxy - 代理的真实对象。
      *  method - 所要调用真实对象的某个方法的 Method 对象 
      *  args - 所要调用真实对象某个方法时接受的参数
      */
    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

    (3)动态代理步骤:
      step1:获取 RealSubject 上的所有接口列表;
      step2:确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
      step3:根据需要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
      step4:将对应的字节码转换为对应的 class 对象;
      step5:创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用;
      step6:Proxy 的 class 对象 以创建的 handler 对象为参数,实例化一个 proxy 对象。

    【Proxy常用的方法】
    /**
     * loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
     * interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
     * h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上
    */
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

    (4)UML图:

    (5)代码实现:

    【Subject.java】
    package construction.pattern.proxy.dynamicProxy;
    
    /**
     * 定义一个代理类与被代理类的公共接口,使得被代理类出现的地方,代理类均能出现
     */
    interface Subject {
        void hello();
    
        String bye();
    }
    
    【RealSubject.java】
    package construction.pattern.proxy.dynamicProxy;
    
    /**
     * 被代理类。定义 代理类 所代表的真实实体。
     *
     */
    public class RealSubject implements Subject {
    
        @Override
        public void hello() {
            System.out.println("Hello World");
        }
    
        @Override
        public String bye() {
            return "GoogBye";
        }
    }
    
    【DynamicProxy.java】
    package construction.pattern.proxy.dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 动态代理类。 每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个
     * Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke
     * 方法来进行调用。
     *
     */
    public class DynamicProxy implements InvocationHandler {
    
        // 用于保存被代理对象(真实对象)
        private Object subject;
    
        /**
         * 构造方法,给被代理对象赋初值
         * 
         * @param subject
         *            真实对象
         */
        public DynamicProxy(Object subject) {
            this.subject = subject;
        }
    
        /**
         * 参数: 
         *  proxy - 代理的真实对象(一般在代码中不使用)。 
         *  method - 所要调用真实对象的某个方法的 Method 对象 
         *  args - 所要调用真实对象某个方法时接受的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("可以在代理前,进行一些操作...");
            System.out.println("Before proxy......");
            System.out.println();
    
            // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            System.out.println("当前调用的方法为:" + method);
            Object object = method.invoke(this.subject, args);
            System.out.println();
    
            System.out.println("可以在代理后,进行一些操作...");
            System.out.println("After proxy......");
            System.out.println();
            return object;
        }
    
    }
    
    
    【DynamicProxyDemo.java】
    package construction.pattern.proxy.dynamicProxy;
    
    import java.lang.reflect.Proxy;
    
    /**
     * 演示动态代理的小Demo
     *
     */
    public class DynamicProxyDemo {
        public static void main(String[] args) {
            // 创建一个被代理的对象(真实对象)
            Subject realSubject = new RealSubject();
            // 输出真实对象的类名
            System.out.println(realSubject.getClass().getName());
    
            // 将真实对象传入 InvocationHandler 中,即DynamicProxy中
            DynamicProxy proxy = new DynamicProxy(realSubject);
    
            // 通过Proxy类的 newProxyInstance 方法来创建一个代理对象
            // 第一个参数, proxy.getClass().getClassLoader(), 使用proxy这个类的ClassLoader对象来加载我们的代理对象
            // 第二个参数, subject.getClass().getInterfaces(), 获取真实对象的所有接口,从而使代理对象能调用被代理对象的方法
            // 第三个参数, proxy, 关联 InvocationHandler 对象
            Subject subject = (Subject) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), proxy);
            
            // 输出代理对象的类名
            System.out.println(subject.getClass().getName());
            subject.hello();//会自动触发代理类的invoke方法,
            System.out.println(subject.bye());
        }
    }
    
    【结果:】
    construction.pattern.proxy.dynamicProxy.RealSubject
    construction.pattern.proxy.dynamicProxy.$Proxy0
    可以在代理前,进行一些操作...
    Before proxy......
    
    当前调用的方法为:public abstract void construction.pattern.proxy.dynamicProxy.Subject.hello()
    Hello World
    
    可以在代理后,进行一些操作...
    After proxy......
    
    可以在代理前,进行一些操作...
    Before proxy......
    
    当前调用的方法为:public abstract java.lang.String construction.pattern.proxy.dynamicProxy.Subject.bye()
    
    可以在代理后,进行一些操作...
    After proxy......
    
    GoogBye
  • 相关阅读:
    Python并发编程:协程-greenlet模块
    Python并发编程:协程介绍
    Python并发编程:多线程-进程池与线程池
    Python并发编程:多线程-线程queue
    程序员能力矩阵(好到这个好有压力...)
    如何让搜索引擎抓取AJAX内容? 转
    使用ReSharper打造团队代码检查流程
    JQuery Easy Ui dataGrid 数据表格 -->转
    TCP/IP协议(1):各层协议帧格式
    VC调试小结
  • 原文地址:https://www.cnblogs.com/l-y-h/p/11111539.html
Copyright © 2020-2023  润新知