今日内容

  • 反射
  • 注解
  • 动态代理

    教学目标

  • 能够使用反射技术获取Class字节码对象

  • 能够通过反射技术获取构造方法对象,并创建对象。
  • 能够通过反射获取成员方法对象,并且调用方法。
  • 能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
  • 能够说出动态代理模式的作用

    第一章 反射

    1 类加载器

    1.1 类的加载

  • 程序运行后,某个类在第一次使用时,会将该类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中

day17【反射,注解,动态代理模式】 - 图1
说明:

  1. 上图:Class对象是指:java.lang.Class类的对象,此类由Java类库提供,专门用于存储类的信息。
  2. 我们程序中可以通过:”类名.class”,或者”对象.getClass()”方法获取这个Class对象

1.2 类的加载时机

  1. 创建类的实例。
  2. 类的静态变量,或者为静态变量赋值。
  3. 类的静态方法。
  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
  5. 初始化某个类的子类。
  6. 直接使用java.exe命令来运行某个主类。
    以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。

    1.3 类加载器

    类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
    以JDK8为例,有三种类加载器,它们分别用于加载不同种类的class:

  7. 启动类加载器(Bootstrap ClassLoader):用于加载系统类库\bin目录下的class,例如:rt.jar。

  8. 扩展类加载器(Extension ClassLoader):用于加载扩展类库\lib\ext目录下的class。
  9. 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
    1. public class Test{
    2. public static void main(String[] args){
    3. System.out.println(Test.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
    4. System.out.println(String.class.getClassLoader());//null(API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null。 )
    5. }
    6. }

    1.4 双亲委派机制

    day17【反射,注解,动态代理模式】 - 图2
    上图展示了”类加载器”的层次关系,这种关系称为类加载器的”双亲委派模型”:
  • “双亲委派模型”中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的”父级类加载器”。
  • 这种关系不是通过”继承”实现的,通常是通过”组合”实现的。通过”组合”来表示父级类加载器。
  • “双亲委派模型”的工作过程:

    • 某个”类加载器”收到类加载的请求,它首先不会尝试自己去加载这个类,而是把请求交给父级类加载器。
    • 因此,所有的类加载的请求最终都会传送到顶层的”启动类加载器”中。
    • 如果”父级类加载器”无法加载这个类,然后子级类加载器再去加载。

      1.5 双亲委派机制的好处

      双亲委派机制的一个显而易见的好处是:Java的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如:java.lang.Object。它存放在rt.jar中。无论哪一个类加载器要加载这个类,最终都是委派给处于顶端的”启动类加载器”进行加载,因此java.lang.Object类在程序的各种类加载器环境中都是同一个类。
      相反,如果没有”双亲委派机制”,如果用户自己编写了一个java.lang.Object,那么当我们编写其它类时,这种隐式的继承使用的将会是用户自己编写的java.lang.Object类,那将变得一片混乱。

      2 反射的概述

      2.1 反射的引入

  • 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?

    1. 通过反射技术对象类进行了解剖得到了类的所有成员。

    2.2 反射的概念

    1. 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)

    2.3 使用反射操作类成员的前提

    1. 要获得该类字节码文件对象,就是Class对象

    2.4 反射在实际开发中的应用

    ```java

  • 开发IDE(集成开发环境),比如IDEA,Eclipse
  • 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits…. ```

    3 Class对象的获取方式

    3.1 三种获取方法

    ```java
  • 方式1: 通过类名.class获得
  • 方式2:通过对象名.getClass()方法获得
  • 方式3:通过Class类的静态方法获得: static Class forName(“类全名”)
    • 每一个类的Class对象都只有一个。 ```
  • 示例代码 ```java package com.itheima._03反射; public class Student{

}

  1. ```java
  2. public class ReflectDemo01 {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. // 获得Student类对应的Class对象
  5. Class c1 = Student.class;
  6. // 创建学生对象
  7. Student stu = new Student();
  8. // 通过getClass方法
  9. Class c2 = stu.getClass();
  10. System.out.println(c1 == c2);
  11. // 通过Class类的静态方法获得: static Class forName("类全名")
  12. Class c3 = Class.forName("com.itheima._03反射.Student");
  13. System.out.println(c1 == c3);
  14. System.out.println(c2 == c3);
  15. }
  16. }

3.2 Class类常用方法

  1. String getSimpleName(); 获得类名字符串:类名
  2. String getName(); 获得类全名:包名+类名
  3. T newInstance() ; 创建Class对象关联类的对象
  • 示例代码
    1. public class ReflectDemo02 {
    2. public static void main(String[] args) throws Exception {
    3. // 获得Class对象
    4. Class c = Student.class;
    5. // 获得类名字符串:类名
    6. System.out.println(c.getSimpleName());
    7. // 获得类全名:包名+类名
    8. System.out.println(c.getName());
    9. // 创建对象
    10. Student stu = (Student) c.newInstance();
    11. System.out.println(stu);
    12. }
    13. }

    4 反射之操作构造方法

    4.1 Constructor类概述

    1. 反射之操作构造方法的目的
    2. * 获得Constructor对象来创建类的对象。
    3. Constructor类概述
    4. * 类中的每一个构造方法都是一个Constructor类的对象

    4.2 Class类中与Constructor相关的方法

    ```java
  1. Constructor getConstructor(Class… parameterTypes)
    1. * 根据参数类型获得对应的Constructor对象。
    2. * 只能获得public修饰的构造方法
    1. Constructor getDeclaredConstructor(Class… parameterTypes)
      • 根据参数类型获得对应的Constructor对象
      • 可以是public、protected、(默认)、private修饰符的构造方法。
    2. Constructor[] getConstructors() 获得类中的所有构造方法对象,只能获得public的
    3. Constructor[] getDeclaredConstructors() 获得类中的所有构造方法对象 可以是public、protected、(默认)、private修饰符的构造方法。 ```

      1.4.3 Constructor对象常用方法

      ```java
  2. T newInstance(Object… initargs)”—— 根据指定的参数创建对象
  3. void setAccessible(true) 设置”暴力反射”——是否取消权限检查,true取消权限检查,false表示不取消

    1. <a name="X9lM6"></a>
    2. #### 4.4 示例代码
    3. ```java
    4. public class Student{
    5. private String name;
    6. private String sex;
    7. private int age;
    8. //公有构造方法
    9. public Student(String name,String sex,int age){
    10. this.name = name;
    11. this.sex = sex;
    12. this.age = age;
    13. }
    14. //私有构造方法
    15. private Student(String name,String sex){
    16. this.name = name;
    17. this.sex = sex;
    18. }
    19. }
    public class ReflectDemo03 {
     /*
       Constructor[] getConstructors()
            获得类中的所有构造方法对象,只能获得public的
       Constructor[] getDeclaredConstructors()
             获得类中的所有构造方法对象,包括private修饰的
     */
     @Test
     public void test03() throws Exception {
         // 获得Class对象
         Class c = Student.class;
         //  获得类中的所有构造方法对象,只能获得public的
         // Constructor[] cons = c.getConstructors();
         // 获取类中所有的构造方法,包括public、protected、(默认)、private的
         Constructor[] cons = c.getDeclaredConstructors();
         for (Constructor con:cons) {
             System.out.println(con);
         }
     }
     /*
        Constructor getDeclaredConstructor(Class... parameterTypes)
            根据参数类型获得对应的Constructor对象
     */
     @Test
     public void test02() throws Exception {
         // 获得Class对象
         Class c = Student.class;
         // 获得两个参数构造方法对象
         Constructor con = c.getDeclaredConstructor(String.class,String.class);
         // 取消权限检查(暴力反射)
         con.setAccessible(true);
         // 根据构造方法创建对象
         Object obj = con.newInstance("rose","女");
         System.out.println(obj);
     }
     /*
         Constructor getConstructor(Class... parameterTypes)
             根据参数类型获得对应的Constructor对象
      */
     @Test
     public void test01() throws Exception {
         // 获得Class对象
         Class c = Student.class;
         // 获得无参数构造方法对象
         Constructor con = c.getConstructor();
         // 根据构造方法创建对象
         Object obj = con.newInstance();
         System.out.println(obj);
         // 获得有参数的构造方法对象
         Constructor con2 = c.getConstructor(String.class, String.class,int.class);
         // 创建对象
         Object obj2 = con2.newInstance("jack", "男",18);
         System.out.println(obj2);
     }
    }
    

    5 反射之操作成员方法

    5.1 Method类概述

    反射之操作成员方法的目的
     * 操作Method对象来调用成员方法
    Method类概述
     * 每一个成员方法都是一个Method类的对象。
    

    5.2 Class类中与Method相关的方法

    ```java

  • Method getMethod(String name,Class…args);
    • 根据方法名和参数类型获得对应的构造方法对象,只能获得public的
  • Method getDeclaredMethod(String name,Class…args);
    • 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的
  • Method[] getMethods();
    • 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
  • Method[] getDeclaredMethods();
    • 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的 ```

      5.3 Method对象常用方法

      ```java
  • Object invoke(Object obj, Object… args)
    • 调用指定对象obj的该方法,如果该方法时静态方法可以传入null
    • args:调用方法时传递的参数
  • void setAccessible(true) 设置”暴力访问”——是否取消权限检查,true取消权限检查,false表示不取消 ```

    5.4 示例代码

    ```java public class Student { public Student() { }

    public static void eat(String food) {

     System.out.println("吃" + food);
    

    }

    private void sleep() {

     System.out.println("睡觉");
    

    }

    public void study() {

     System.out.println("学习");
    

    }

    public void study(String subject) {

     System.out.println("学习" + subject);
    

    }

}

```java
public class ReflectDemo04 {
    // 反射操作静态方法
    @Test
    public void test04() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 根据方法名获得对应的公有成员方法对象
        Method method = c.getDeclaredMethod("eat",String.class);
        // 通过method执行对应的方法
        method.invoke(null,"蛋炒饭");
    }
    /*
     * Method[] getMethods();
        * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
     * Method[] getDeclaredMethods();
        * 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
   */
    @Test
    public void test03() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
        // Method[] methods = c.getMethods();
        // 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
        Method[] methods = c.getDeclaredMethods();
        for (Method m: methods) {
            System.out.println(m);
        }
    }
    /*
       Method getDeclaredMethod(String name,Class...args);
           * 根据方法名和参数类型获得对应的构造方法对象,
    */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 根据Class对象创建学生对象
        Student stu = (Student) c.newInstance();
        // 获得sleep方法对应的Method对象
        Method m =  c.getDeclaredMethod("sleep");
        // 暴力反射
        m.setAccessible(true);
        // 通过m对象执行stuy方法
        m.invoke(stu);
    }
    /*
        Method getMethod(String name,Class...args);
            * 根据方法名和参数类型获得对应的构造方法对象,
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;

        // 根据Class对象创建学生对象
        Student stu = (Student) c.newInstance();
        // 获得study方法对应的Method对象
        Method m =  c.getMethod("study");
        // 通过m对象执行stuy方法
        m.invoke(stu);
        /// 获得study方法对应的Method对象
        Method m2  = c.getMethod("study", String.class);
        // 通过m2对象执行stuy方法
        m2.invoke(stu,"java");
    }
}

6 反射之操作成员变量【自学】

6.1 Field类概述

反射之操作成员变量的目的
    * 通过Field对象给对应的成员变量赋值和取值
Field类概述
    * 每一个成员变量都是一个Field类的对象。

6.2 Class类中与Field相关的方法

* Field getField(String name);
    *  根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
    *  根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
* Field[] getFields();
    * 获得所有的成员变量对应的Field对象,只能获得public的
* Field[] getDeclaredFields();
    * 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的

6.3 Field对象常用方法

void set(Object obj, Object value) 
void setInt(Object obj, int i)     
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z) 
void setDouble(Object obj, double d) 
Object get(Object obj)  
int    getInt(Object obj) 
long getLong(Object obj) 
boolean getBoolean(Object ob)
double getDouble(Object obj) 
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。

setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。 getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

6.4 示例代码

public class Student{
    public String name; 
    private String gender;

    public String toString(){
        return "Student [name = " + name + " , gender = " + gender + "]";
    }
}
public class ReflectDemo05 {
    /*
        Field[] getFields();
            * 获得所有的成员变量对应的Field对象,只能获得public的
        Field[] getDeclaredFields();
            * 获得所有的成员变量对应的Field对象,包含private的
     */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c  = Student.class;
        // 获得所有的成员变量对应的Field对象
        // Field[] fields = c.getFields();
        // 获得所有的成员变量对应的Field对象,包括private
        Field[] fields = c.getDeclaredFields();
        for (Field f: fields) {
            System.out.println(f);
        }
    }
    /*
        Field getField(String name);
            根据成员变量名获得对应Field对象,只能获得public修饰
        Field getDeclaredField(String name);
            *  根据成员变量名获得对应Field对象,包含private修饰的
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c  = Student.class;
        // 创建对象
        Object obj = c.newInstance();
        // 获得成员变量name对应的Field对象
        Field f = c.getField("name");
        // 给成员变量name赋值
        // 给指定对象obj的name属性赋值为jack
        f.set(obj,"jack");
        // 获得指定对象obj成员变量name的值
        System.out.println(f.get(obj)); // jack
        // 获得成员变量的名字
        System.out.println(f.getName()); // name
        // 给成员变量gender赋值
        // 获得成员变量gender对应的Field对象
        Field f1 = c.getDeclaredField("gender");
        // 暴力反射
        f1.setAccessible(true);
        // 给指定对象obj的gender属性赋值为男
        f1.set(obj,"男");
        System.out.println(obj);
    }
}

第二章 注解

1 注解的概述

1.1 注解的概念

从JDK5开始,java增加了对元数据(描述数据属性的信息)的支持。其实就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。

1.2 注解的作用

以下几个常用操作中都使用到了注解:

  1. 编译检查_@_Override
    • _@_Override :用来修饰方法声明。
      • 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
    • day17【反射,注解,动态代理模式】 - 图3
  2. 框架的配置( 框架 = 代码 + 配置 )
    • 具体使用请关注框架课程的内容的学习。
  3. 生成帮助文档@author和_@_version

    • _@_author :用来标识作者姓名。
    • _@_version :用于标识对象的版本号,适用范围:文件、类、方法。
      • 使用@author和_@_version 注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
        day17【反射,注解,动态代理模式】 - 图4
        IDEA中生成文档的方式:
        day17【反射,注解,动态代理模式】 - 图5

        1.3 常见注解

  4. _@_Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。

  5. _@_FunctionnalInterface :修饰接口,表示该接口为函数式接口
  6. _@_Test :Junit测试注解

    2 自定义注解

    2.1 定义格式

    关键字:_@_interface

    public @interface 注解名{
    }
    

    注解本质上就是一个接口。所有注解都会继承一个接口:Annotation

    2.2 注解的属性

  7. 属性的格式

    • 格式1:数据类型 属性名();
    • 格式2:数据类型 属性名() default 默认值;
  8. 属性定义示例

    // 姓名
    String name();
    // 年龄
    int age() default 18;
    // 爱好
    String[] hobby();
    
  9. 属性适用的数据类型【记住】

    1.八种数据数据类型(int,short,long,double,byte,char,boolean,float)
    2.String,Class,注解类型,枚举类
    3.以上类型的一维数组形式
    

    3 使用自定义注解

    3.1 定义注解

  10. 定义一个注解:Book

    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
  11. 代码实现
    @interface Book {
     String value();
     double price() default 100;
     String[] author();
    }
    

    3.2 使用注解

  • 如果属性有默认值,则使用注解的时候,属性可以不用赋值。
  • 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
  • 如果属性是数组,有多个值需要使用大括号,内部的值之间使用逗号分隔
  1. 定义类在成员方法上使用Book注解

    /*
     把注解Book定义到下面的类中:
     当使用注解是,格式: @注解名(属性=值)
     有多个属性,属性要全部赋值,以逗号分隔。如果有默认值可以不用赋值,如果是数组赋值,需要使用大括号
    */
    @Book(value = "水浒传", price = 50, author = {"施耐庵", "张三"})
    class BookStore {
     @Book(value = "西游记", author = {"吴承恩"})
     String str1;
     @Book(value = "西游记", author = {"吴承恩"})
     public BookStore(String str1) {
         this.str1 = str1;
     }
     @Book(value = "西游记", author = {"吴承恩"})
     public void sell() {
     }
    }
    

    3.3 特殊属性value

    如果注解中只有一个属性且名字叫value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名。
    如果注解中除了value属性之外还有其他属性且只要有一个属性没有默认值,则在给属性赋值时value属性名也不能省略了。
    因此,如果注解中只有一个属性时,一般都会将该属性名命名为value,给value赋值时,可以不用写出value,直接给值。

    public class Demo01 {
     //@A(value = "Hello")
     @A("Hello")
     String str1;
     @B("Hello")
     String str2;
     @B(value = "Hello", name = "李四")
     String str3;
    }
    @interface A {
     String value();
     //String aa();
    }
    @interface B {
     String value();
     String name() default "张三";
    }
    

    4 注解之元注解

    4.1元注解概述

    Java官方提供的注解 ,元注解是用来限定我们自定义注解用的, 任何官方提供的非元注解的定义都使用到了元注解。
    元注解的作用:用来限定其他注解的生命周期,限定可以使用的地方。

    4.2 常用的元注解

    _@_Target

    作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
     ElementType[] value();
    }
    枚举:内部定义的就是一个一个的常量,使用时直接类型名.常量
    
    可使用的值定义在ElementType枚举类中,常用值如下
    TYPE,类,接口
    FIELD, 成员变量
    METHOD, 成员方法
    PARAMETER, 方法参数
    CONSTRUCTOR, 构造方法
    LOCAL_VARIABLE, 局部变量
    

    _@_Retention

    作用:用来标识注解的生命周期(有效范围)

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
    
     RetentionPolicy value();
    }
    
    可使用的值定义在RetentionPolicy枚举类中,常用值如下
    SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
    CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,【默认值】
    RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
    

    【代码示例】
    示例中为JDK定义的@override注解
    day17【反射,注解,动态代理模式】 - 图6

    5 注解解析

    什么是注解解析? 使用Java反射技术获得注解上数据的过程则称为注解解析。
    与注解解析相关的接口
    1.【Annotation】
    注解类的父类型,该类是所有注解的父接口。根据多态,该类型可以接收所有注解实例。
    2.【AnnotatedElement】
    该接口定义了与注解解析相关的方法 ```java

  2. boolean isAnnotationPresent(Class annotationClass)
    判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

  3. T getAnnotation(Class annotationClass) 根据注解类型获得对应注解对象

    <a name="YNgVm"></a>
    #### 获取注解数据的原理
    Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
    

    注解作用在哪个成员上就会得该成员对应的对象来获得注解 比如注解作用成员方法,则要获得该成员方法对应的Method对象 比如注解作用在类上,则要该类的Class对象 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。 ``` 类型上的注解,先获取Class对象,再解析注解
    方法上的注解,先获取Method对象,再解析注解
    字段上的注解,先获取Field对象,在解析注解

    案例实践

    需求如下:

  4. 定义注解Book,要求如下:

    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
    • 限制注解使用的位置:类和成员方法上 【Target】
    • 指定注解的有效范围:RUNTIME 【Retention】
  5. 定义BookStore类,在类和成员方法上使用Book注解
  6. 定义TestAnnotation测试类获取Book注解上的数据

代码实现
注解Book

public class TestAnnotation {
    public static void main(String[] args) throws NoSuchMethodException {
        //解析BookStores类型上的Book注解
        //1.先获取BookStore的Class对象
        Class<BookStore> cls = BookStore.class;
        //2.判断类型上是否存在Book注解
        if (cls.isAnnotationPresent(Book.class)) {
            //3.如果存在就解析获取Book注解对象
            Book book = cls.getAnnotation(Book.class);
            System.out.println("类型上:book.name() = " + book.name());
            System.out.println("类型上:book.price() = " + book.price());
            System.out.println("类型上:book.authors() = " + Arrays.toString(book.authors()));
        }
        //解析BookStores类型上test方法上的Book注解
        //1先获取方法对象
        Method testMethod = cls.getMethod("test");
        //2.判断是否存在注解定义
        if (testMethod.isAnnotationPresent(Book.class)) {
            //3.如果存在就进行解析
            Book book = testMethod.getAnnotation(Book.class);
            System.out.println("方法上:book.name() = " + book.name());
            System.out.println("方法上:book.price() = " + book.price());
            System.out.println("方法上:book.authors() = " + Arrays.toString(book.authors()));
        }
    }
}
//1. 定义注解Book,要求如下:
//   - 包含属性:String value()   书名
//   - 包含属性:double price()  价格,默认值为 100
//   - 包含属性:String[] authors() 多位作者
//   - 限制注解使用的位置:类和成员方法上 【Target】
//   - 指定注解的有效范围:RUNTIME  【Retention】
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Book {
    String name();//书名
    double price() default 100;
    String[] authors();
}
//2. 定义BookStore类,在类和成员方法上使用Book注解
@Book(name = "西游记", authors = "吴承恩")
//数组赋值如果只有一个值,可以不用大括号
class BookStore {
    //@Book(name="红楼梦",price = 100,authors = {"曹雪芹","高鹗"})
    String book;//字段位置
    @Book(name = "红楼梦", price = 100, authors = {"曹雪芹", "高鹗"})
    public void test() {
    }
}

6 注解综合案例

6.1 案例说明

  • 模拟Junit测试的_@_Test

    6.2 案例分析

  1. 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
  2. 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
  3. 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。

    6.3 案例代码

    MyTest注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyTest {
    }
    
    TestMyTest类,使用注解
    public class MyTestClass {
     @MyTest
     public void show1() {
         System.out.println("show 1方法执行");
     }
     @MyTest
     public void show2() {
         System.out.println("show 2方法执行");
     }
     public void show3() {
         System.out.println("show 3方法执行");
     }
    }
    
    测试
    public class Demo {
     public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
         //1.先获取MyTestClass的字节码对象
         Class<MyTestClass> cls = MyTestClass.class;
         //2.获取所有的方法,遍历方法对象
         Method[] methods = cls.getMethods();
         //3.判断方法是否被MyTest注解,如果是直接运行起来
         MyTestClass obj = cls.newInstance();
         for (Method method : methods) {
             //System.out.println("method = " + method);
             if (method.isAnnotationPresent(MyTest.class)) {
                 method.invoke(obj);
             }
         }
     }
    }
    

    第三章 动态代理

    1.1 代理模式【Proxy Pattern】

     为什么要有“代理”?生活中就有很多例子,例如委托业务等等,**代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事**,这才是“代理”存在的原因。例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。<br />        在我们的代码中,假如有以下业务情景:<br />        用户登录到我们的系统后,我们的系统会为其产生一个ArrayList集合对象,内部存储了一些用户信息,而后,这个对象需要被传给后面的很多其它对象,但要求其它对象不能对这个ArrayList对象执行添加、删除、修改操作,只能get()获取元素。那么为了防止后面的对象对集合对象进行添加、修改、删除操作,我们应该怎样办呢?<br />        要想实现这种要求,方案有很多种。"代理模式"就是其中的一种,而且是非常合适的一种。我们看一下用代理模式怎样实现这种需求:
    
    1. 为ArrayList定义一个代理类:ArrayListProxy
import java.util.*;
public class ArrayListProxy implements List<String> {
    //被代理的List集合
    private List<String> list;
    //通过构造方法将被代理的集合传入
    public ArrayListProxy(List<String> list) {
        this.list = list;
    }
    /*
        以下方法都是List接口中的方法:
            允许被使用的方法:调用被代理对象的原方法
            不允许被使用的方法:抛出异常
     */
    @Override
    public int size() {
        return this.list.size();
    }
    @Override
    public boolean isEmpty() {
        return this.list.isEmpty();
    }
    @Override
    public boolean contains(Object o) {
        return this.list.contains(o);
    }
    @Override
    public Iterator<String> iterator() {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public Object[] toArray() {
        return this.list.toArray();
    }
    @Override
    public <T> T[] toArray(T[] a) {
        return this.list.toArray(a);
    }
    @Override
    public boolean add(String s) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean remove(Object o) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean containsAll(Collection<?> c) {
        return this.list.containsAll(c);
    }
    @Override
    public boolean addAll(Collection<? extends String> c) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean addAll(int index, Collection<? extends String> c) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean removeAll(Collection<?> c) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean retainAll(Collection<?> c) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public void clear() {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public String get(int index) {
        return this.list.get(index);
    }
    @Override
    public String set(int index, String element) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public void add(int index, String element) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public String remove(int index) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public int indexOf(Object o) {
        return this.list.indexOf(o);
    }
    @Override
    public int lastIndexOf(Object o) {
        return this.list.lastIndexOf(o);
    }
    @Override
    public ListIterator<String> listIterator() {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public ListIterator<String> listIterator(int index) {//不允许使用
        throw new UnsupportedOperationException();
    }
    @Override
    public List<String> subList(int fromIndex, int toIndex) {//不允许使用
        throw new UnsupportedOperationException();
    }
}
  1. 在测试类中定义一个方法,接收一个ArrayList对象,然后返回这个代理对象,并在main()方法中测试:
import java.util.ArrayList;
import java.util.List;
public class Demo {
    public static void main(String[] args) {
        //定义一个集合对象
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");

        //调用getList()方法,根据这个集合获取一个它的"不可变"对象
        List<String> listProxy = getList(list);
        for (int i = 0; i < listProxy.size(); i++) {//OK的
            System.out.println(listProxy.get(i));//OK的
        }
        //其它方法都会抛出异常
//        listProxy.add("赵六");//UnsupportedOperationException
//        listProxy.remove(0);//UnsupportedOperationException
//        listProxy.set(0, "张老三");//UnsupportedOperationException
    }
    //此方法接收一个List<String>对象,返回一个不可变的"代理对象"
    public static List<String> getList(List<String> list) {
        ArrayListProxy proxy = new ArrayListProxy(list);
        return proxy;
    }
}

1.2 动态代理概述

    动态代理简单来说是:**拦截对真实对象方法的直接访问,增强真实对象方法的功能**<br />        动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。**动态代理可以对被代理对象的方法进行增强**,**可以在不修改方法源码的情况下,增强被代理对象方法的功能**,**在方法执行前后做任何你想做的事情**。动态代理技术都是在框架中使用居多,例如:Struts1、Struts2、Spring和Hibernate等后期学的一些主流框架技术中都使用了动态代理技术。

1.3 案例引出

演示Java已经实现的动态代理

java.util.Collections:操作集合的工具类
static <T> List<T> unmodifiableList(List<? extends T> list)
        返回指定列表的不可修改视图。
        此方法允许模块为用户提供对内部列表的“只读”访问。
        在返回的列表上执行的查询操作将“读完”指定的列表。
        试图修改返回的列表(不管是直接修改还是通过其迭代器进行修改)将导致抛出         
        UnsupportedOperationException:不支持操作异常

unmodifiableList作用:传递List接口,方法内部对List接口进行代理,返回一个被代理后的List接口
        对List进行代理之后,调用List接口的方法会被拦截
        如果使用的size,get方法,没有对集合进行修改,则允许执行
        如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常

代码实现:

public class Demo01Proxy {
    @Test
    public void show(){
        //使用多态创建List集合,并添加元素
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        //调用Collections中的方法unmodifiableList对List集合进行代理
        list = Collections.unmodifiableList(list);
        //如果使用的size,get方法,没有对集合进行修改,则允许执行
        System.out.println(list.size());
        System.out.println(list.get(1));
        //如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
        //list.add("d");//UnsupportedOperationException
        //list.remove(0);//UnsupportedOperationException
        list.set(1, "www");//UnsupportedOperationException
    }
}

1.4 重点类和方法

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一个静态方法来获取代理对象。
image.png
参数说明:
ClassLoader loader:类加载器,在在程序运行时,将动态生成的类即代理类加载到Java虚拟机中,以便使用。

  1. Class[] interfaces: 被代理类的接口字节码对象
  2. InvocationHandler h :调用处理器,代理对象调用的方法会触发该类型内部的invoke方法。
  3. InvocationHandler接口的invoke方法及参数详解

java.lang.reflect.InvocationHandler:调用处理器接口,我们要实现这个接口的invoke方法,代理对象调用方法时,会触发这个invoke方法执行。我们的代理逻辑就在invoke中实现。
image.png
invoke方法说明:
返回值:就是代理对象调用方法后返回的结果
参数:

  1. Object proxy :代理对象
  2. Method method:代理对象调用的方法
  3. Object[] args:代理对象调用方法时传入的参数

1.5 动态代理综合案例

需求:
模拟unmodifiableList方法,对List接口进行代理
调用List接口的方法会被拦截
如果使用的size,get方法,没有对集合进行修改,则允许执行
如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
分析:
1.定义一个代理方法proxyList
参数:传递List集合
返回值:被代理之后的List集合
2.方法内部可以使用Proxy类中的方法实现动态代理
代码实现:

@SuppressWarnings("all")
public class Demo02Proxy {
    //1.定义一个代理方法proxyList
    public List proxyList(List<String> list){
        //2.方法内部可以使用Proxy类中的方法实现动态代理
        List<String> pList = (List<String>) Proxy.newProxyInstance(Demo02Proxy.class.getClassLoader(),
                list.getClass().getInterfaces(),
                new InvocationHandlerImpl(list));
        return pList;
    }
    @Test
    public void show(){
        //使用多态创建List集合,并添加元素
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        //调用Collections中的方法unmodifiableList对List集合进行代理
        list = proxyList(list);
        //如果使用的size,get方法,没有对集合进行修改,则允许执行
        System.out.println(list.size());
        System.out.println(list.get(1));
        //如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
        //list.add("d");//UnsupportedOperationException
        //list.remove(0);//UnsupportedOperationException
        //list.set(1, "www");//UnsupportedOperationException
    }
}
public class InvocationHandlerImpl implements InvocationHandler{
    //定义一个List集合变量
    private List<String> list;
    //给List集合赋值
    public InvocationHandlerImpl(List<String> list) {
        super();
        this.list = list;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //invoke方法会获取List集合的方法,在invoke方法内部对List集合的方法进行拦截
        //获取List集合方法的名字
        String listMethodName = method.getName();
        //对获取的名字进行判断
        //如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
        if("add".equals(listMethodName)){
            throw new UnsupportedOperationException("add no run");
        }
        if("remove".equals(listMethodName)){
            throw new UnsupportedOperationException("remove no run");
        }
        if("set".equals(listMethodName)){
            throw new UnsupportedOperationException("set no run");
        }
        //如果使用的size,get方法,没有对集合进行修改,则允许执行
        Object v = method.invoke(list,args);
        return v;
    }
}

1.6 总结

优点:

  1. 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。
  2. 可以为被代理对象的所有方法做代理。
  3. 可以在不改变方法源码的情况下,实现对方法功能的增强。
  4. 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。

缺点:

  1. 只能针对接口的实现类做代理对象,普通类是不能做代理对象的。