14-JavaSE【反射,注解、动态代理】

一、反射

1、类的加载

类加载的过程
  • 当程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中
    在这里插入图片描述

说明:Class对象是指java.lang.Class类的对象,![在这里插入图片
此类由Java类库提供,专门用于存储类型的信息

类的加载机制

在以下情况下会加载类:

  1. 创建一个类的对象(第1次)

  2. 调用类的静态方法,静态变量 (第1次)

  3. 使用一个类的子类时 (第1次)

  4. 通过反射进行加载类

  5. java命令执行某一个类 (第1次)运行java程序

    public class HelloWorld{
        public static void main(String[] args){
            System.out.println("Hello World!");
        }
    }
    
    //编译: javac HelloWorld.java    //生成:HelloWorld.class  字节码文件
    //运行: Java HelloWorld   //使用java命令执行HelloWorld类(默认调用main方法)
                              //底层: HelloWorld.main(null);
    
小结

问题1:Student.class文件中都包含什么内容?

  • 构造方法、成员变量、成员方法

在jvm执行某个类时,如果该类是第一次被执行:

  • 先把该类的.class文件读取到内存中
  • 基于.class文件创建一个Class对象(方法区)
    • Class对象中存储的是.class文件中的内容:构造方法、成员变量、成员方法
      • Class对象中存储的构造方法:构造器对象 Constructor对象
      • Class对象中存储的成员变量:字段对象 Field对象
      • Class对象中存储的成员方法:方法对象 Method对象

2、类加载器

什么是类加载器
  • 加载器是Java运行时环境的一部分,负责加载字节码文件
类加载器的作用
  • 负责将磁盘上的某个class文件读取到内存并生成Class的对象
类加载器的分类

Java中有三种类加载器,它们分别用于加载不同种类的class:

  • 启动类加载器(Bootstrap ClassLoader):用于加载系统类库<JAVA_HOME>\bin目录下的class,例如:rt.jar。
  • 扩展类加载器(Extension ClassLoader):用于加载扩展类库<JAVA_HOME>\lib\ext目录下的class。
  • 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
获取类加载器的方式

来自Class类型获取类加载器的方法:

public ClassLoader getClassLoader()  //返回该类的类加载器
//有些实现可能使用null来表示引导类加载器(启动类加载器)
//如果该类由引导类加载器(启动类加载器)加载,则此方法在这类实现中将返回 null 

代码实践:

//自定义类型
class Student{
    private int age;
}

//测试类
public class Demo01 {
    public static void main(String[] args){
        //获取自定义类的类加载器
        //1. 先获取Student对应的Class对象
        Class<Student> cls = Student.class;  
        
        //2. 再通过该class对象获取类加载器
        ClassLoader loader = cls.getClassLoader();
        
        System.out.println("loader = "+loader);//ClassLoaders$AppClassLoader@2437c6dc

        
        //获取String类的类加载器
        ClassLoader loader2 = String.class.getClassLoader();
        System.out.println("loader2 = " + loader2);
    }
}
小结

类加载器的作用:

  • 把硬盘上的.class文件读取到内存中并创建Class对象

类加载器可以分为3种:

  • 引导类加载器(启动类加载器) 负载加载系统类库(java.lang包)
  • 扩展类加载器 负责加载jdk扩展的类库
  • 应用程序类加载器 负责加载程序员自己写的类

3、双亲委派机制

3种类加载器的关系

在这里插入图片描述

从上图可知,三种类加载器存在一定的关系:

  • 应用程序类加载器的父级类加器是扩展类加载器
  • 扩展类加载器的父级类加载器是启动类加载器

结论:这种关系称为类加载器的"双亲委派模型"

双亲委派机制

"双亲委派模型"的工作机制:

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

"双亲委派模型"中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的"父级类加载器"

双新委派机制的好处

双亲委派机制的一个显而易见的好处是:

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

双亲委派机制:

  • 在使用一个类时,类并不会由应用程序类加载器来加载,而是由"双亲:启动类加载器、扩展类加载器"先来进行加载,当"双亲"无法加载时,才由应用程序类加载器来完成.class的加载及Class对象的创建

4、反射:概述

反射技术
  • 其实就是对类进行解剖的技术
    • 类中有什么?
      • 构造方法
      • 成员变量
      • 成员方法

结论:反射技术就是把一个类进行了解剖,然后获取到 构造方法、成员变量、成员方法

反射技术的应用案例
  • idea
  • 框架技术:Spring
想要使用反制技术有一个必备条件
  • Class对象
    • 原因:.class文件由类加载器读取并创建Class对象。Class对象中存储了.class文件中的内容:构造方法、成员变量、成员方法
反射技术的作用

使用反射技术,可以对类进行解剖,可以获取到类中的:构造方法、成员变量、成员方法

  • 构造方法: 可以创建对象

  • 成员方法: 可以调用执行

  • 成员变量: 赋值、取值

  • 不用使用new关键字,就可以创建对象

  • 不用使用"对象名.方法"形式,就可以调用方法

  • 不用使用"对象名.属性"形式,就可以给属性赋值、取值

示例:

给一个字符串:"cn.icast.pojo.Student"

创建一个对象:?????   //使用new做不到  
           使用反射技术可以实现

示例:

给一个Student.class

程序在运行中,不能停止
    创建Student对象         //类加载器只能读取.class文件
   
    解决方案:反射   动态的加载Student.class
            
小结

反射技术 :对类进行解剖的技术

反射技术的作用:可以不通过传统方式,来实现类的实例化、方法的调用

  • 实现的提前:需要使用Class对象

5、反射:Class类

Class类
  • Class就是用来描述正在运行的java类型

  • Class类的实例表示Java中任何正在运行的类型,每一个类型都有与之对应的Class对象

    • 比如:类,接口,枚举,注解,数组,基本数据类型,void 都有与之对应的Class对象

      • 类名.class
        接口名.class
        int.class
        boolean.class
        array.class    
        
获取Class对象

获取Class类对象的方式有3种:

方式一:类型名.class   //Student.class
方式二:对象.getClass()  //对象名.getClass()
方式三:Class.forName(String className) //className是全路径类名 = 包名+类型名
//方式1:类型名.class
//应用场景: 当类名明确时,可以直接使用"类名.class"
Class clz = String.class 
Class clz = int.class 
Class clz = double.class
    
//方式2:对象.getClass()
//应用场景:通常是应用方法中
 public void method(Student stu){
    Class clz = stu.getClass();   
 } 

//方式3: Class.forName("类的全名称");//带有包名的类
//应用场景: 通常使用在读取配置文件中的类型
pro.properties文件
--------------文件内容------------------    
className=cn.icast.pojo.Student   
---------------------------------------   
//代码实现    
ResourceBundler r = ResourceBundler.getBundler("pro");
String className = r.getString("className");//"cn.icast.pojo.Student"
    
Class StudentClass = Class.forName(className);//className="cn.icast.pojo.Student" 

//当获取到Class对象了,就可以对类进行解剖了
Class类中的常用方法
String getSimpleName()  // 获得类名字符串:类名
String getName()   // 获得类全名:包名+类名
T newInstance() // 创建Class对象关联类的对象 (前提:类中有一个无参构造方法)
    
//示例:
      Studentod类    cn.itcast.pojo.Student     //public Student(){}
      Class stuClass = Student.class;
      System.out.println("带有包名的类:"+studentClass.getName());
      System.out.println("类名:"+studentClass.getSimpleName());
      Object obj = stuClass.newInstance();//调用Student()  创建Student对象
      Student stu = (Student) obj;
小结

获取Class对象的方式:

  1. 类型名.class
  2. 对象名.getClass()
  3. Class类中的静态方法:forName(“带有包名的类”)

6、反射:构造器Constructor类

Class中的类型:

在这里插入图片描述

构造方法对应的类型:  Constructor类型
字段:Field
方法:Method
Constructor类
  • 代表构造方法(构造器)
  • 类中的每一个构造方法都是一个Constructor类的对象

反射技术中构造器的目的:

  • 获得Constructor对象来创建类的对象
    * 大白话:不使用new关键字,通过Constructor来创建对象
获取Constructor对象的方法

Constructor对象的获取和Class类中方法有关:

Constructor[] getConstructors()
        //获得类中的所有构造方法对象,只能获得public的

Constructor[] getDeclaredConstructors()
        //获得类中的所有构造方法对象
    	//可以是public、protected、(默认)、private修饰符的构造方法    
     
Constructor getConstructor( Class... parameterTypes)
        //根据参数类型获得对应的Constructor对象   获取public修饰的
        //只能获得public修饰的构造方法
/*示例: Student       public Student(String name, int age)    public Student(int age) 
        Class stuClass = Student.class;
        //根据给定的参数类型,来获取匹配的构造器对象
        Constructor c = stuClass.getConstructor( String.class , int.class );
*/  
    
    
Constructor getDeclaredConstructor(Class... parameterTypes)
        //根据参数类型获得对应的Constructor对象
    	//可以是public、protected、默认、private修饰符的构造方法
Constructor类常用方法
T newInstance(Object... initargs)
 	//根据指定的参数创建对象
/*
        Class stuClass = Student.class;
        //根据给定的参数类型,来获取匹配的构造器对象
        Constructor c = stuClass.getConstructor( String.class , int.class );
        //使用构造器对象,来实例化Student
        c.newInstance( "张三", 22 );//Student(String name, int age)
           
        //无参构造
        Constructor c = stuClass.getConstructor();
        Student stu = (Student) c.newInstance();
*/    
    
    
void setAccessible(true)//应用场景:仅适用于访问有权限检查的成员
   //设置"暴力反射" ——是否取消权限检查,true取消权限检查,false表示不取消
使用构造器创建对象
无参构造器创建对象
@Test
    public void testMethod1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象(Student.class)
        Class stuClass = Student.class;

        //利用Class对象,来获取构造器对象
        Constructor con = stuClass.getConstructor();//方法中的参数为可变参数,可以不传递值

        //使用Constructor对象中的方法,来实例化Student类对象
        Student stu = (Student) con.newInstance();//方法中的参数是可变参数

        stu.study();
    }
有参构造器创建对象
//有参构造方法
    @Test
    public void testMethod2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象(Student.class)
        Class stuClass = Student.class;

        //public Student(String name, int age, String gender)
        //获取带有参数的构造器对象
        //参数:是用来设置构造方法中参数的类型是什么
        Constructor con = stuClass.getConstructor(String.class, int.class, String.class);

        //实例化有参构造方法
        //参数:要传递给Student(String name, int age, String gender)的数据
        Student stu = (Student) con.newInstance("熊大", 22, "男");

        //调用对象中的方法
        stu.study();
    }

私有构造器创建对象
 @Test
    public void testMethod3() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象(Student.class)
        Class stuClass = Student.class;

        // private Student(String name)
        //获取私有构造器对象
        Constructor con = stuClass.getDeclaredConstructor(String.class);

        //当需要对私有成员操作时,需要先取消JVM对访问权限的检查操作
        con.setAccessible(true);//暴力破解(取消权限检查) //仅在当前次取消

        //使用私有构造器,实例化Student对象
        Student stu = (Student) con.newInstance("文平");

        System.out.println(stu.name);
        stu.study();
    }

注意:

当获取到私有构造器后,要使用该构造器创建对象,

需要:取消权限检查:setAccessible(true)

8、反射:属性Field类

反射之操作成员变量的目的
    * 通过Field对象给对应的成员变量赋值和取值

Field类概述
    * 每一个成员变量都是一个Field类的对象。
Class类中与Field相关的方法
* Field getField(String name);
    *  根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
    *  根据成员变量名获得对应Field对象,包括publicprotected(默认)private* Field[] getFields();
    * 获得所有的成员变量对应的Field对象,只能获得public* Field[] getDeclaredFields();
    * 获得所有的成员变量对应的Field对象,包括publicprotected(默认)private
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对应的属性值的,针对不同的类型选取不同的方法。

9、反射:方法Method类

Method类
  • 代表一个成员方法

    • 每一个成员方法都是一个Method类的对象

反射技术中使用Method的目的:

  • 通过Method对象来调用成员方法
获取Method对象的方式

/获取Method对象的步骤:
1、先获取Class对象
2、使用Class对象,获取Method对象
3、使用Method对象,执行方法

Method对象的获取和Class类中方法有关:

Method[] getMethods();
    //获得当前类和其父类中的所有public成员方法对象,返回数组

Method[] getDeclaredMethods();
    //获得当前类中的所有成员方法对象,返回数组
    //只获得本类的,包括public、protected、默认、private的

Method getMethod(String name,Class...args);
    //根据方法名和参数类型获得对应的构造方法对象,只能获得public的
    //参数说明:
             name : 类中方法的名字
             args : 方法中参数类型的Class     例:int.class     

Method getDeclaredMethod(String name,Class...args);
    //根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的

Method类常用方法
//使用方法对象,调用对象中的方法执行(入栈执行)
Object invoke(Object obj, Object... args) 
    // obj: 对象   //"对象名.方法"
    // args:调用方法时传递的实参
    
    
void setAccessible(true)
    // 设置"暴力访问"  ——是否取消权限检查,true取消权限检查,false表示不取消
方法调用案例
  1. 案例:调用无参无返回值的方法
  2. 案例:调用有参有返回值的方法
  3. 案例:调用私有方法
  4. 案例:调用静态方法

案例:调用无参无返回值的方法

 @Test
    public void testMethod1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象
        Class stuClass = Student.class;

        //因为在调用Method时,需要传递Student对象
        Constructor con = stuClass.getConstructor();
        Student stu = (Student)con.newInstance();

        //获取public void study()方法的对象
        Method method = stuClass.getMethod("study");

        //使用Method对象 执行study()方法
        method.invoke( stu );
    }

案例:调用有参有返回值的方法

 //有参有返回值
    @Test
    public void testMethod2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象
        Class stuClass = Student.class;

        //因为在调用Method时,需要传递Student对象
        Constructor con = stuClass.getConstructor();
        Student stu = (Student)con.newInstance();

        //获取public String sayHello(String name)方法的Method对象
        Method method = stuClass.getMethod("sayHello", String.class);

        //调用method方法
       Object result = method.invoke(stu,"小明");

        System.out.println(result);
    }

案例:调用私有方法

//私有方法
    @Test
    public void testMethod3() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //获取Class对象
        Class stuClass = Student.class;

        //因为在调用Method时,需要传递Student对象
        Constructor con = stuClass.getConstructor();
        Student stu = (Student)con.newInstance();

        //获取 private void eat(String name)方法的Method对象
        Method method = stuClass.getDeclaredMethod("eat", String.class);

        //去除JVM对当前次权限的检查
        method.setAccessible(true);

        //执行method方法
        method.invoke(stu,"红烧肉");
    }

案例:调用静态方法

//静态方法
    @Test
    public void testMethod4() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //获取Class对象
        Class stuClass = Student.class;

        //静态方法的调用不需要对象。"类名.静态方法()"
        //获取public static void sleep()方法的Method对象
        Method method = stuClass.getMethod("sleep");

        //执行静态方法
        method.invoke(null);//不需要传递对象(null就表示执行静态方法)
    }

小结

Method对象的使用步骤:

1、获取Class对象

2、基于Class对象,获取Method对象

  • //有参方法
    Method method = Class对象.getMethod("方法名",参数1类型.class,参数2类型.class ...);
    //无参方法
    Method method = class对象.getMethod("方法名");
    

3、使用Method对象,执行方法

  • //调用非静态方法
    method对象.invoke(实例对象,方法中需要的实参)
        
    //调用静态方法
    method对象.invoke(null,方法中需要的实参)    
    
小结

在反射中获取Method对象的步骤:

1、 先获取Class对象

2、基于Class对象,获取Method对象

3、使用Method对象,执行该方法

获取Method对象的方式:

Method getMethod(String name,Class...args);

Method getDeclaredMethod(String name,Class...args);

Method类中常用方法:

//执行Method对象所代表的方法
Object invoke(Object 对象 ,  Object... 方法中需要的实参);

void setAccessible(true)

二、注解

1、注解:概述

目标

  • 能够理解注解在程序中的作用 【了解】

路径

  1. 什么是注解
  2. 注解的作用

注解

什么是注解?

  • 注解(Annotation)也称为元数据,是一种代码级别的说明
  • 注解是JDK1.5版本引入的一个特性,和类、接口是在同一个层次
  • 注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明

注解:就是具有特殊含义的标记(注解是给机器阅读的)

注解的作用

注解就是在代码里添加一些特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行操作

注解的作用:

  1. 编译检查

    • @Override:用来修饰方法声明。

    • 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败

在这里插入图片描述

  1. 代码分析:

    • 通过代码里标识的注解对代码进行分析

    • 框架的配置( 框架 = 代码 + 配置 )

      • 具体使用请关注框架课程的内容的学习(注解去配置数据)
  2. 生成帮助文档

    • @author:用来标识作者姓名

    • @version:用于标识对象的版本号,适用范围:文件、类、方法

      • 使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中
        在这里插入图片描述

使用过的注解:

  • @Override

    • 用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败
  • @Test

    • Junit测试注解
  • @FunctionalInterface //函数式接口

小结

注解:在代码中添加的标记,JVM看到注解标记后会执行一些操作

注解的作用:

  • 编译检查
  • 运行时代码分析
  • 生成文档
2、注解:自定义注解

目标

  • 能够熟悉自定义注解的格式 【会用】

路径

  1. 注解的定义格式
  2. 定义带有属性的注解

注解的定义格式

定义注解使用关键字:@interface

public @interface 注解名{
   //内容
}

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

public interface 自定义注解名 extends java.lang.annotation.Annotation {}

带有属性的注解

  1. 属性的格式

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

    public @interface Student {
      String name();                // 姓名
      int age() default 18;         // 年龄
      String gender() default "男"; // 性别
    } 
    // 该注解就有了三个属性:name,age,gender
    
  3. 属性适用的数据类型【记住】

    1. 八种数据数据类型(intshortlongdoublebytecharbooleanfloat)
    2. String,Class,注解类型,枚举类
    3. 以上类型的一维数组形式
    

小结

注解的定义格式:

public @interface 注解名
{
    //属性格式 :  数据类型 属性名()
    String 属性名();
    int 属性名() default 默认值;
}
//注意:属性的类型只能是8种基本数据类型、String、Class、枚举、注解、(以及一维数组形式)
       
3、注解:注解的使用

目标

  • 能够掌握注解的基本使用 【会用】

路径

  1. 注解的使用格式
  2. 案例代码
  3. 特殊属性value

注解的使用格式

格式:

//无属性注解
@注解名                      例:@Test

//有属性注解
@注解名(属性=值,属性=值)

注解可以在类上,成员变量上,构造方法上,方法上,参数上…

有默认值的属性,可以不用进行赋值。

案例代码

将Book注解使用起来

public @interface Book {
    String name();

    double price() default 100.0;

    String[] author();
}

建立一个BookStore的类,在类中定义成员变量,构造方法,成员方法

@Book(name = "西游记", author = {"吴承恩", "张三"})
public class BookStore {
    @Book(name = "三国", price = 10, author = {"罗贯中"})
    private String book;

    @Book(name = "三国", price = 10, author = {"罗贯中"})
    public BookStore() {
    }


    @Book(name = "三国", price = 10, author = {"罗贯中"})
    public void test() {

    }
}

特殊属性value

如果注解中只有一个属性要赋值,而且名字是value,可以将value给省略,可以直接给值

@interface A{
    String value();
}

@interface B{
    String value();
    String name();
}

@interface C{
    String value();
    int price() default 100; //有默认值
}
//测试类
public class Demo {
    @A("值")  //当自定义注解中仅有一个value属性时,可以省略value属性名
    public void test1() {
    }

    @B(value = "值",name="aa") //当自定义注解中除了value属性外,还有其它属性,value不能省略
    public void test2() {

    }

    @C("值")
    public void test3() {

    }
}

小结

自定义注解:

public @interface Book{
    //属性
    String name();
    double price() default 100;
}

在程序代码中使用自定义注解:

//注解可以应用在:类、方法、变量、构造方法

@Book(name="属性值")  //price属性使用默认值
public class BookStore{
    
    @Book(name="属性值",price="新的值") //新的price值,会覆盖默认值
    public void method(){
        
    }
}
4、注解:元注解

目标

  • 熟悉Target和Retention两个元注解的作用 【会用】

路径

  1. 元注解的作用
  2. 常用的元注解
  3. 元注解的使用

元注解的作用

默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方

如果要限制自定义注解的使用位置怎么办?那就要学习一个新的知识点:元注解

结论:元注解是用来约束自定义注解的使用范围、生命周期

常用的元注解

常用元注解:@Target、@Retention

@Target

  • 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用

  • @Target可选的参数值在枚举类ElemenetType中包括:

    • TYPE: 用在类,接口上
    • FIELD:用在成员变量上
    • METHOD: 用在方法上
    • PARAMETER:用在参数上
    • CONSTRUCTOR:用在构造方法上
    • LOCAL_VARIABLE:用在局部变量上

@Retention

  • 作用:定义该注解的生命周期(有效范围)。

  • @Retention可选的参数值在枚举类型RetentionPolicy中包括:

    • SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了

      • 使用场景:针对一些检查性的操作,比如:@Override ,就使用SOURCE注解
    • CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值

      • 使用场景:在编译时进行一些预处理操作,比如:生成一些辅助代码,就用CLASS注解
    • RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解

      • 使用场景:要在运行时去动态获取注解信息,那只能用 RUNTIME 注解

元注解的使用

@Target({ElementType.METHOD,ElementType.TYPE}) //元注解
@interface Stu{
    String name();
}

//类
@Stu(name="jack")  //成功
public class AnnotationDemo02 {
    // 成员变量
    @Stu(name = "lily")  // 编译失败
    private String gender;
    
    // 成员方法
    @Stu(name="rose")  //成功
    public void  test(){
    }
   
    // 构造方法
    @Stu(name="lucy") // 编译失败
    public AnnotationDemo02(){}
}

小结

元注解的作用:

  • 限定自定义注解的使用范围、生命周期
    • 限制使用范围:@Target
    • 限定生命周期:@Retention
5、注解:注解解析

目标

  • 能够使用反射的技术完成注解数据的解析 【会用】

路径

  1. 获取注解数据的原理
  2. 案例代码

获取注解数据的原理

Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口

AnnotatedElement 是一个接口,定义了解析注解的方法:

1. boolean isAnnotationPresent(Class<Annotation> annotationClass)   
   判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
   
   public class BookStore{
       @Book(name="书名")
       public void buy() // Method对象 判断该对象上是否使用了@Book注解
       {                 // boolean flag = Method对象.isAnnotationPresent(Book.class)
       }
   } 
    
  
2. T getAnnotation(Class<T> annotationClass) 
   根据注解类型获得对应注解对象
   // 获取对象上的自定义注解
   // Book bookAnno = Method对象.getAnnotation(Book.class); 
   //      bookAnno.属性   //获取注解中属性的值
    

Class,Constructor,Method,Field都会实现AnnotatedElement 接口

  • 解析类型上的注解:借助字节码对象(Class对象)
  • 解析构造方法上的注解:借助构造器对象(Constructor对象)
  • 解析方法上的注解:借助方法对象(Method对象)
  • 解析字段上的注解:借助字段对象(Field对象)

注解解析的步骤:(注解是书写在:类、方法、变量上)

1、利用反射技术获取注解作用的对象:类、方法、变量、构造方法

2、判断对象上是否有自定义注解存在

3、有:获取对象上的自定义注解

4、使用获取到的自定义注解对象,拿到注解中的属性值

注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)

案例代码

需求如下:

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

@Target({ElementType.METHOD,ElementType.TYPE}) //使用范围:方法、类
@Retention(RetentionPolicy.RUNTIME) //保证注解在程序执行时有效(适用于注解解析)
public @interface Book {
    String value();
    double price() default 100;
    String[] authors();
}


//类
public class BookStore {
    @Book(value = "Java入门", authors = {"张老师", "毕老师"})
    public void buy() {
        System.out.println("购书.....");
    }
}

//对注解中的数据进行解析
public class TestBookStore {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //反射套路:1、Class  2、构造器   3、Method
        Class<BookStore> bookStoreClass = BookStore.class;

        //获取构造器
        Constructor<BookStore> con = bookStoreClass.getConstructor();
        BookStore bookStore = con.newInstance();//实例化对象

        //获取Method
        Method method = bookStoreClass.getMethod("buy");

        //解析注解的步骤
        if(method.isAnnotationPresent(Book.class)){
            //获取Method对象上的Book注解
            Book bookAnno = method.getAnnotation(Book.class);

            //获取注解上的数据
            String bookName = bookAnno.value();
            double bookPrice = bookAnno.price();
            String[] bookAuthors = bookAnno.authors();

            System.out.println("书名:"+bookName);
            System.out.println("价格:"+bookPrice);
            System.out.println("作者:"+ Arrays.toString(bookAuthors));
        }
    }
}

小结

注解解析的步骤:

  1. 利用反射技术获取相关的对象:类、构造器、方法、变量
  2. 使用方法getAnnotation,获取自定义注解对象
  3. 使用注解对象,分别获取注解中的属性值

注解扩展小示例:

public class Student{
    private String name;//姓名
    private int age;//年龄
    private String gender;//性别
    private double score;//成绩
    private String id;//学号
}
数据:
    张三,20,,90,it1001

利用注解+反射,给Student类中的成员变量赋值

//自定义注解
public @interface Entity{
    String value;
}
//Student类
public class Student{
    @Entity(value="张三")
    private String name;//姓名
    
    @Entiry(value="20")
    private int age;//年龄
    
    private String gender;//性别
    
    private double score;//成绩
    
    private String id;//学号
}
6、注解:综合案例

目标

  • 熟悉反射结合注解的使用 【会用】

路径

  1. 案例需求
  2. 案例分析
  3. 案例代码

需求

需求:模拟Junit测试的@Test

案例分析

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

案例代码

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解
@Target(ElementType.METHOD)//仅能应用在方法上
@Retention(RetentionPolicy.RUNTIME)//生命周期 :运行时
public @interface MyTest {
    //无属性注解
}


public class TestAnnotationParse {

    //方法1
    @MyTest
    public void method1(){
        System.out.println("我是方法1");
    }

    @MyTest
    public void method3(){
        System.out.println("我是方法3");
    }


    public void method2(){
        System.out.println("我是方法2");
    }

}



public class Test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        //获取Class对象
        Class<TestAnnotationParse> testAnnotationParseClass = TestAnnotationParse.class;

        //获取Class对象中,所有的Method方法
        Method[] methods = testAnnotationParseClass.getMethods();
        
        //遍历数组
        for (int i = 0; i < methods.length; i++) {
            //获取每一个Method对象
            Method method = methods[i];

            //判断Method对象上,是否存在@MyTest
            if(method.isAnnotationPresent(MyTest.class)){
                method.invoke(testAnnotationParseClass.newInstance());
            }
        }
    }
}

小结

注解解析的步骤:

  1. 获取Class对象 //类名.class、对象名.class、Class.forName("…")
  2. 基于Class对象,来获取:构造器/成员方法/成员变量 //getConstructor() getMethod() getField()
  3. 基于构造器/成员方法/成员变量,判断是否存在自定义注解 // isAnnotationPresent(注解.class)
  4. 存在:获取自定义注解对象 // 注解 对象 = 构造器/方法/变量/类 .getAnnotation(注解.class)
  5. 基于自定注解对象,获取其属性值 // 注解对象.属性()

三、代理模式

1、代理模式概念

我很忙,忙的没空理你,那你要找我呢,就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,被代理人虽然不想干活,但是代理的人能干活呀。

生活中的房屋中介

在这里插入图片描述

2、代理模式分类

静态代理:
【定义】
在程序运行之前,代理类.class文件就已经被创建
【实现】
由程序员创建或特定工具自动生成源代码,在对其编译	

动态代理:
【定义】
程序运行时通过反射机制动态创建的,对方法的【增强】,不需要修改源码
【实现】
基于接口:JDK动态代理
基于子类:CGLib动态代理
1、代理模式:静态代理
静态代理
  • 是由程序员创建或工具生成代理类的源码,再编译成为字节码 (字节码文件在没有运行java之前就存在了)

  • 在编译时就已经将接口、代理类、被代理类的关系确定下来了

  • 在程序运行之前就已经存在代理类的.class文件

  • 需要有三个角色:抽象角色(父接口)、被代理角色(被代理类)、代理角色(代理类)

  • 被代理类和代理类,都是实现父接口

  • 在程序执行之前,就要确定了三个角色关系,生成.class文件

  • 随着程序的执行,.class文件都会被加载到jvm中

静态代理的实现方式:

  • 被代理类和代理类,都实现父接口
  • 被代理类,会作为代理类中的成员存在

静态代理和装饰模式的对比:

原则上的区别,代理为了控制对某个函数前后的操作,而装饰着模式是为了添加某一操作(其实目标没差太远)

2、代理模式:动态代理
动态代理概述

在上面静态代理的例子中,代理类(XM)是自己定义好的,在程序运行之前就已经编译完成。

然而动态代理中,代理类是在程序运行时利用反射机制动态创建的,这种代理方式被成为动态代理。

在实际开发过程中动态代理是通过JDK提供的Proxy类在程序运行时,运用反射机制动态创建而成。

虽然我们不需要自己定义代理类创建代理对象,但是我们要定义对被代理对象访问方法的拦截逻辑

代理类的作用,就是让被代理对象的某个方法执行之前或者执行之后加入其他增强逻辑(要在被代理对象的方法执行前进行拦截)

动态代理技术在框架中使用居多,例如:很快要学到的数据库框架MyBatis框架等后期学的一些主流框架技术(Spring,SpringMVC)中都使用了动态代理技术

动态代理:

- 不用程序员自己书写代理类,使用JDK提供的Proxy类实现代理
- 动态代理是在程序执行过程中,利用反射技术,动态的创建代理类对象
动态代理流程图

在这里插入图片描述

动态代理API

在java中实现动态代理,关键要使用到一个Proxy类和一个InvocationHandler接口

Proxy类

java.lang.reflect.Proxy:是 Java 动态代理机制的主类(父类),它提供了用于创建动态代理类和实例的静态方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handle ) 
**解释说明:    
- 返回值:     该方法返回就是动态生成的代理对象
- 参数列表说明:
  ClassLoader loader         :定义代理类的类加载器
  Class<?>[] interfaces      :代理类要实现的接口列表,要求与被代理类的接口一样
  InvocationHandler handle   :就是具体实现代理逻辑的接口 
    
    
//参数的应用:
ClassLoader loader     //对象.getClass().getClassLoader()  
           //目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()方法来获取类加载器
           /*获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java              虚拟机中,以便运行时需要*/ 
    
Class<?>[] interfaces  //对象.getClass().getInterfaces() 
           //获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法
    
    
InvocationHandler 
          //用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问  

InvocationHandler接口

java.lang.reflect.InvocationHandler是代理对象的实际处理代理逻辑的接口,具体代理逻辑在其 invoke 方法中实现

public Object invoke(Object proxy, Method method, Object[] args)
**解释说明:
- 返回值:方法被代理后执行的结果
- 参数列表说明:
  Object proxy   :  就是代理对象(通常不使用)
  Method method  :  代理对象调用的方法
  Object[] args  :  被代理方法中的参数 (因为参数个数不定,所以用一个对象数组来表示)
                     如果方法不使用参数,则为 null
    
//所有代理对象调用的方法,执行是都会经过invoke
//因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义    

3、静态代理演示

在这里插入图片描述

静态代理:在程序运行前手动创建代理类,代理类和目标类需要实现相同接口
步骤:
1、创建项目;
2、定义接口:HouseAgencyCompany及接口中租房的方法:rentingHouse;
3、定义房主类:HouseOwner 中介类 HouseProxy均实现接口HouseAgencyCompany;
4、租客类Customer调用HouseProxy完成租房
定义HouseAgencyCompany
/**
 * @Description:中介公司
 */
public interface HouseAgencyCompany {

    /**
     * @Description 租房子
     */
    void rentingHouse();
}
定义HouseOwner、HouseProxy
/**
 * @Description:被代理人(房东)
 */
public class HouseOwner implements HouseAgencyCompany {

    @Override
    public void rentingHouse() {
        System.out.println("房东签合同");
    }
}

/**
 * @Description:中介(代理人)
 */
public class HouseProxy implements HouseAgencyCompany {

    /**
     * 被代理人
     */
    private HouseOwner houseOwner;

    public HouseProxy() {
        this.houseOwner = new HouseOwner();
    }

    @Override
    public void rentingHouse() {
        System.out.println("中介带看房子");
        System.out.println("中介约房东");
        
        houseOwner.rentingHouse();
        System.out.println("中介完成租房");
    }
}

定义Customer
import org.junit.jupiter.api.Test;

/**
 * @Description:租客
 */
public class Customer {

    @Test
    public void needHouse(){
        HouseOwner houseOwner  = new HouseOwner();
        houseOwner.rentingHouse();
        
        System.out.println("==================================");
        
        HouseAgencyCompany houseAgencyCompany = new HouseProxy();
        houseAgencyCompany.rentingHouse();
    }
}

在这里插入图片描述

4、动态代理演示

# 【1】有什么问题?
如果目标类中有多个方法都需要增强,我们得为每一个服务都得创建代理类,工作量太大,
不易管理。同时接口一旦发生改变,代理类也得相应修改。
	
# 【2】动态代理概述
代理类在程序运行时创建的方式被成为动态代理。也就是说,代理类并不是在Java代码中定的,而是在运行时根据我们在Java代码中的动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
【1】jdk动态代理

必须基于接口的动态代理

1、JDK动态代理:基于接口的;
2、JDK动态代理实现要点:
Proxy类
newProxyInstance静态方法
InvocationHandler增强方法

java.lang.reflect.Proxy:
Java动态代理机制的主类,提供了一组静态方法来为一组接口动态地生成代理类及其实例。

//方法1: 该方法用于获取指定动态代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
 
//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
 
//方法3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
 
//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理对象:1、类加载器 2、接口数组、调用处理器(增强部分的业务代码)
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

java.lang.reflect.InvocationHandler:
调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理对象时都需要指定一个实现了该接口的调用处理器对象。

InvocationHandler的核心方法:

//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理对象,第二个参数是被调用的方法对象,第三个方法是调用参数。
//调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
Object invoke(Object proxy, Method method, Object[] args)

需求:使用jdk动态代理的方式,增强租房方法,使得租房方法前后打印输入日志信息;

删除HouseProxy

修改Customer

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Description:租客
 */
public class Customer {

    HouseOwner houseOwner = new HouseOwner();

    @Test
    public void needHouse(){
        
		HouseAgencyCompany houseProxy = (HouseAgencyCompany) 		 	           Proxy.newProxyInstance(
            houseOwner.getClass().getClassLoader(), //类加载器
                houseOwner.getClass().getInterfaces(), //被代理类实现的接口字节码数组(代理类需要实现这些接口)
               //new Class[]{HouseAgencyCompany.class},//被代理类实现的接口字节码数组
                 new InvocationHandler() { // 对需要增强的方法进行增强,对不需要增强的方法调用原来的逻辑
                     /**
                     * 每次调用代理类的方法时,此方法都会执行
                     * @param proxy : 生成的代理类对象(慎用)
                     * @param method : 当前所执行的方法的字节码对象
                     * @param args : 当前执行的方法所传递的参数
                     */
                     
                    @Override
                    public Object invoke(Object proxy, 
                                         Method method, 
                                         Object[] args) throws Throwable {
                        System.out.println("中介公司让中介带客户看房");
                        Object object = method.invoke(houseOwner, args);
                        System.out.println("中介公司让中介完成租房业务");
                        return object;
                    }
                });
        houseProxy.rentingHoues();

    }
}

在这里插入图片描述

案例二:List调用remove(Object obj)方法后,能够删除集合中所有匹配的元素【动态代理】

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/*
对Collection接口的子类ArrayList进行代理,
以前的remove(Object obj)方法是删除集合中第一次出现的元素
(比如集合中有多个“abc”,调用remove(“abc”)后只会删除一个元素)。
代理后,要求在调用remove(Object obj)方法后,能够删除集合中所有匹配的元素【动态代理】
 */
public class Work01 {
    public static void main(String[] args) {

        // 获得被代理类对象 List的对象
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("abc");
        list.add("123");
        list.add("abc");
        list.add("abc");
        list.add("World");
        list.add("abc");
        list.add("abc");
        System.out.println("1->"+list);

        // 创建代理类的方法 -》去代理list
        List<String> list1 = proxyMethod(list); // 获取代理类的对象
        list1.remove("abc");  // 使用代理类 拦截重后的remove()方法
        System.out.println("3->"+list1);
    }

    public static List<String> proxyMethod(List<String> list) {
        ClassLoader listLoader = list.getClass().getClassLoader();
        Class<?>[] interfaces = list.getClass().getInterfaces();

        Object obj = Proxy.newProxyInstance(listLoader, interfaces, new InvocationHandler() {
            // 实现InvocationHandler接口 拦截 remove()  方法,进行增强
            // 调用remove(Object obj)方法后  list.add("abc"); 删除所有的"abc"
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // System.out.println(method.getName());
                if ("remove".equals(method.getName())) {
                    // System.out.println(method.getName());
                    // System.out.println(args[0]);
                        for (int i = 0; i < list.size(); i++) {
                            if(args[0] == list.get(i)){
                                System.out.println("-->"+list);
                                list.remove(i);
                                i--;
                            }
                        }
                }

                System.out.println("2->"+list);
                return method.invoke(list, args); // 返回的是处理方法的对象
            }
        });

        System.out.println(obj);
        return (List<String>) obj; // 返回的是代理类的实例对象 --> 代理List集合
    }
}

【2】cglib动态代理
# 思考
如果目标类没有实现接口呢?
	那么就无法使用JDK的动态代理,因此这种方式有其局限性,必须实现一个接口。
可以使用的方案:
	使用CGLIB动态代理:基于子类(包含本类)

# Cglib动态代理
1、基于类,无需实现接口;
2、被代理的目标类不能被final修饰

net.sf.cglib.proxy.Enhancer

Enhancer类是CGLib中的一个字节码增强器,作用用于生成代理对象,跟上一章所学的Proxy类相似,常用方式为:

//方法1:该方法用于为指定目标类、回调对象 1、类的类型,2、调用处理器
 public static Object create(Class type, Callback callback)

net.sf.cglib.proxy.MethodInterceptor

//方法1:
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

修改Customer

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.Test;

import java.lang.reflect.Method;

/**
 * @Description:
 */
public class Customer {
    @Test
    public void needHouse(){
        System.out.println("=====================");
        //代理模式下的租房
        HouseOwner houseOwnerProxy = (HouseOwner) Enhancer.create(
            	HouseOwner.class, // 参数1:被代理对象的字节码
                new MethodInterceptor() { // 参数2: 接口,在原方法执行前对方法进行拦截处理
               /**
                 * @param o : 生成的代理类对象(被代理类的子类)
                 * @param method : 当前执行的方法的字节码对象
                 * @param objects : 当前执行的方法携带的参数数组
                 * @param methodProxy : 当前执行的方法的代理对象(不用管)
                 */
            @Override
            public Object intercept(Object o, Method method,
                                    Object[] objects, MethodProxy methodProxy) throws Throwable {
                //增强
                System.out.println("中介小王:带你看房子");
                //返回结果
                return  methodProxy.invokeSuper(o, objects);
            }
        });

        houseOwnerProxy.renttingHouse();
    }
}

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值