Java之枚举、注解与反射

????创作不易,各位看官点赞收藏.

Java之枚举、注解与反射

1、枚举类

通常对象只有有限个并且确定时,我们通常使用枚举类来表示这些类。当我们需要的定义一组常量时,强烈建议使用枚举类。

1.1、自定义枚举类

​ 在JDK5.0之前使用枚举类都是自定义枚举类,自己编写枚举类。

// 自定义枚举类
class Season{

    // 1.定义private final的属性
    private final String name;

    // 2.私有构造器,并给属性赋值
    private Season(String name){
        this.name = name;
    }

    // 3.提供多个枚举对象,修饰必须是 public static final表示可以外部直接通过类进行实例化一个不可修改的对象
    public static final Season SPRING = new Season("春天");
    public static final Season SUMMER = new Season("夏天");
    public static final Season AUTUMN = new Season("秋天");
    public static final Season WINTER = new Season("冬天");
    
    // 获取枚举类属性
    public String getName() {
        return name;
    }
    
    // 重写toString
    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + ''' +
                '}';
    }
}

1.2、Enum 枚举类

​ 在JDK5.0之后可以使用Enum关键字来定义一个枚举类。

// 1.使用关键字enum定义一个枚举类
public enum Pay {

    // 2.提供枚举对象,每个对象使用逗号隔开,最后一个对象使用分号结束
    WECHAT("微信"),
    ALIPAY("支付宝"),  // 这里其实就是省略了自定义类中的public static final 和 new Pay
    CARD("银行卡"),
    CASH("现金");

    // 3.定义枚举类属性
    private final String type;

    // 4.私有构造器,构造器自动私有,可省略private
    private Pay(String type){
        this.type = type;
    }

    // 5.获取属性值
    public String getType() {
        return type;
    }

    // 6.重写toString方法
    @Override
    public String toString() {
        return "Pay{" +
                "type='" + type + ''' +
                '}';
    }
}

注意:

  • enum中提供的对象,实质省略了public static final 和 new Pay
  • enum自动继承java.lang.Enum这个类,不是继承至Object类。
  • enum如果没有重写toString方法,那么打印的是对象名称。

1.3、枚举类常用方法

public static void main(String[] args) {
    // 常用方法1:values() -> 用于获取枚举类中所有对象,返回值是一个枚举类数组
    Pay[] values = Pay.values();
    for (Pay value : values) {
        System.out.println(value);
    }

    // 方法2:valueOf(),参数需要一个枚举类中对象名字的字符串,并返回这个对象,枚举类中没有字符串对应对象名称,就会报错
    Pay wechat = Pay.valueOf("WECHAT");
    System.out.println(wechat);
}

1.4、枚举类实现接口

方式一:在枚举类中所有对象都执行至一个重写接口的方法。

interface Test{
    void show();
}
public enum Pay implements Test{
    @Override
    public void show() {
        System.out.println("所有对象执行同一个方法");
    }
}

方式二:每一个枚举对象都可以重写接口中的方法,这样每一个枚举对象执行的方法就会执行自己对应不同重写方法。

// 1.使用关键字enum定义一个枚举类
public enum Pay implements Test{

    // 2.提供枚举对象,每个对象使用逗号隔开,最后一个对象使用分号结束
    WECHAT("微信"){
        @Override
        public void show() {
            System.out.println("使用微信支付");
        }
    },
    ALIPAY("支付宝"){
        @Override
        public void show() {
            System.out.println("使用支付宝支付");
        }
    },  // 这里其实就是省略了自定义类中的public static final 和 new PAy
    CARD("银行卡"){
        @Override
        public void show() {
            System.out.println("使用银行卡支付");
        }
    },
    CASH("现金"){
        @Override
        public void show() {
            System.out.println("使用现金支付");
        }
    };
}

2、注解

注解(annotation):不是程序的本身,但是可以对程序作出解释可以被其它程序读取,是一种特殊标记。

2.1、文档注解

​ 生成文档相关的注解。

//   文档注解
/**
 * @author 开发者名称
 * @version 1.0.0 版本号
 * @see 参考内容
 * @since 1.0.0 从哪个版本增加
 */
public class Demo01 {

    /**
     * @param args String[] 方法参数,可以并列写
     * @exception 异常类型 说明
     * @return 返回值类型 说明
     */
    public static void main(String[] args) {

    }
}

2.2、内置注解

注解名 解释
@Override 注释于方法上,用于注解该方法是重写父类的方法
@Deprecated 注释于类和方法上,注释该类和方法不推荐使用,但是可以使用
@SuppressWarnings 用于镇压警告提示,作用于方法和类上,被注解的块中的警告提示全部消失

2.3、元注解

元注解:用于注解其它的注解,用来对其它的注解类型作进行说明。

注解名 解释
@Target 用于描述注解的作用范围,参数是一个ElementType枚举类型的数组
@Retention 用于描述注解的生命周期,参数是一个RetentionPolicy枚举类型
@Documented 设置注解是否包含到javadoc文档中,设置就包含
@Inherited 设置子类是否继承父类的注解,设置了就继承父类的注解
/*
    ElementType.METHOD      可以用于方法上
    ElementType.TYPE        可以用于class
    ElementType.CONSTRUCTOR 可以用于构造方法上
    ElementType.PACKAGE     可以用于包上
    ElementType.FILED       可以用于属性上
 */
// 元注解设置作用范围,如果不写就是任何地方都可以使用
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.PACKAGE}) 
/*
    RetentionPolicy.RUNTIME     注解在运行时有效
    RetentionPolicy.SOURCE      注解在源码时有效
    RetentionPolicy.CLASS       注解在字节码时有效(默认是这种形式)
    作用范围: RUNTIME > CLASS > SOURCE
    一般我们把注解的声生命周期设置为 RUNTIME
 */
 // 设置注解的生命周期
@Retention(value = RetentionPolicy.CLASS)
@Documented // 设置注解是否包含到javadoc文档中,设置就包含默认是不包含的
@Inherited // 设置子类是否继承父类的注解,设置后父类使用了这个注解,子类中就会继承这个注解,相当于也使用了注解
@interface MyAnnotation{
    // 自定义的注解
}

2.4、自定义注解

// 自定义注解
@Target({ElementType.METHOD,ElementType.TYPE}) // 能作用在方法、类、接口上
@Retention(RetentionPolicy.RUNTIME) // 生命周期是运行级别
@interface MyAnnotation1 {
    // 注解的参数,格式:参数类型 参数名() default 默认值
    // 参数名后面必须加括号,不是作为方法,而是参数
    int id() default 100;
    String name() default "张三";
    String[] hobby() default {"篮球","羽毛球"};
}

注解和反射一起使用才有意义,可以通过反射获取注解中的信息。

@MyAnnotation1(id = 1,name = "李四",hobby = {"乒乓球","棒球"})
public void test(){}

注意:

  • 注解参数类型:基本数据类型、类、enum、注解及它们的数组形式。
  • 如果没有指定默认值,在使用注解的时候必须传入参数。
  • 如果只有一个参数,建议使用value,这样在使用注解时可以省略value

2.5、Java8注解新特性

可重复注解:在一个地方可以使用多个注解。

// 创建一个新注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
    String name() default "";
    String vale();
}
// 创建另一个注解,只有一个参数,其参数类型是注解的数组
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTests {
    AnnotationTest[] value();
}
// 在注解上添加元注解 Repeatable
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnnotationTests.class) // jdk1.8才有的新元注解
public @interface AnnotationTest {
    String name() default "";
    String vale();
}
// 在同一个地方使用多个相同注解
@AnnotationTest(vale = "张三")
@AnnotationTest(vale = "李四")
public void test1(){

}

注意 AnnotationTests除了没有Retention注解,其它的元注解都应该和AnnotationTest的元注解相同。

类型注解:关于@target注解,在jdk1.8新增了TYPE_PARAMETERTYPE_USE表示注解可以使用到变量类型上。

/*
* ElementType.TYPE_PARAMETER:表示注解可以用于类型变量的申请语句中
* ElementType.TYPE_USE:注解可以写到任何类型的语句中
 */
public void test2(){
    // 用于任何类型TYPE_USE
    ArrayList<@AnnotationTest(vale = "张三") String> array = new ArrayList<>();
    // TYPE_PARAMETER,类型的声明语句中
    class B<@AnnotationTest(vale = "李四") T>{
    }
}

3、反射(Reflection)

反射被视为java作为动态语言的关键,反射机制允许程序执行期间通过Reflection取得任何类得内部信息,包括私有属性,并能直接操作任意对象的内部属性和方法。

加载完类后,在堆内存的方法区中就产生一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了类的完整结构,我们可以通过这个对象来操作这个类。

Java反射机制有什么作用?

  • 在运行时可以判断任意一个对象的所属类。
  • 在运行时我们可以去创建任何一个类的对象。
  • 在运行时可以判断一个类具有的所有属性和方法。
  • 在运行时可以获取泛型信息。
  • 在运行时可以调用一个对象的属性和方法。
  • 在运行时可以获取注解中的值。

image-20211108144151234

3.1、Class 类

Class:是java.lang包下的一个类,在虚拟机加载一个class字节码文件到内存中这称为类加载,加载到内存中的类我们称为运行时类,这个类就可以作为Class的一个实例对象。这个对象中包含了这个类的所有结构,这个类的所有实例都有一个相同的Class类对应的对象。你无论创建多少个实例,但是Class类对应实例对象只有一个。(万事万物皆对象)

哪些类型可以拥有Class类的实例对象?

  • class类(普通类、接口、内部类)。
  • 数组、枚举、注解。
  • void、基本数据类型。
// Class类的实例对应一个运行时类,下面是获取运行时类的Class对象方法
// 所有类型获取Class的实例对象
public static void main(String[] args) {
    // 基本数据类型
    Class<Integer> integerClass = int.class;
    // void
    Class<Void> voidClass = void.class;
    // 注解
    Class<Override> overrideClass = Override.class;
    // 枚举
    Class<? extends ElementType> c1 = ElementType.METHOD.getClass(); // 方式1通过枚举的实例对象获取
    Class<ElementType> c2 = ElementType.class; // 方式2通过枚举类获取
    Class<?> c3 = null;
    try {
        c3 = Class.forName("java.lang.annotation.ElementType"); //方式3通过Class类forName(),参数是类的全限定类名
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    // 数组,如果数组的类型和维度都相同,则也是同一个Class对象
    int[] array = new int[10];
    Class<int[]> c4 = int[].class; // 方式1直接数组类
    Class<? extends int[]> c5 = array.getClass(); // 方式2通过实例对象获取
    // 普通类、接口
    Class<String> c6 = String.class; // 方式1通过类获取
    Class<? extends String> c7 = new String("你好").getClass(); // 通过实例对象获取
    try {
        Class<?> c8 = Class.forName("java.lang.String"); // 通过Class对象获取
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}
// 获取Class实例方法
public static void main(String[] args) {

    // 方式一:通过运行时类的class属性
    Class<String> c1 = String.class;
    // 方式二:通过运行时类的对象的getClass()
    Class<? extends String> c2 = "".getClass();
    // 方式三:通过Class的静态方法forName(运行时类的全类名)
    Class<?> c3 = null;
    try {
        c3 = Class.forName("java.lang.String");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    // 方式四:使用类的加载器,ClassLoader
    ClassLoader classLoader = Demo02.class.getClassLoader(); // 通过当前类获取一个类加载器
    Class<?> c4 = null;
    try {
        c4 = classLoader.loadClass("java.lang.String"); // 通过类加载器的loadClass(类的全限类名)
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    System.out.println(c1); // class java.lang.String
    System.out.println(c2); // class java.lang.String
    System.out.println(c3); // class java.lang.String
    System.out.println(c4); // class java.lang.String
}

类加载器加载配置文件:

方式一的资源相对路径相对于当前项目下,而方式二的资源相对路径相对于src目录下。推荐使用第二种方式。

// 读取配置文件
public static void main(String[] args){
    // 方式一:
    Properties properties = new Properties();
    // 通过文件流的方式,然后使用Properties.load()方法来加载一个文件,参数是文件流
    try (FileInputStream inputStream = new FileInputStream("注解和反射\src\jdbc.properties");){
        properties.load(inputStream);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        System.out.println("user: "+user+",password: "+password);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 方式二:通过一个类加载器来加载一个文件资源
    ClassLoader classLoader = Demo03.class.getClassLoader(); // 获取当前类的类加载器
    // 类加载器的getResourceAsStream()方法来加载一个文件资源
    try ( InputStream resourceAsStream = classLoader.getResourceAsStream("jdbc.properties");){
        properties.load(resourceAsStream);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        System.out.println("user: "+user+",password: "+password);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3.2、创建运行时类的对象

// 使用反射来创建运行时类的对象
public static void main(String[] args) {
    Class<String> c = String.class; // 获取String类的Class对象
    try {
        String s = c.newInstance(); // 通过Class对象的newInstance()方法创建一个String对象,属性值是默认的
        System.out.println(s); // null
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

注意:

  • 使用反射来创建运行时类的对象,实质还是使用的运行时类的无参构造器来创建对象。
  • 如果运行时类中没有无参构造器会报错,创建好对象后属性值是默认值。
  • 类构造器的权限需要满足当前程序。

3.3、获取运行时类的结构

我们通过反射获取的Class对象中包含了这个类的所有信息,名称、属性、方法等。

// 创建一个Student类,通过反射来获取它内部结构
public class Student {
    
    private int id;
    public String name;

    public Student() {}
    
    private Student(String name){
        this.name = name;
    }

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    private void method1(){
        System.out.println("我是一个没有返回值没有参数的private方法");
    }
    
    public String method2(){
        System.out.println("我是一个有返回值没有参数的public方法");
        return "返回值2";
    }
    
    public String method3(String value){
        System.out.println(value);
        System.out.println("我是一个有返回值有参数的public方法");
        return "返回值3";
    }
}
方法 解释
getName() 获取类的全限定类名。
getSimpleName() 获取类的类名。
getField(参数) 获取类指定的public修饰的属性,参数是属性名的字符串。返回值是Field类型。
getDeclaredField(参数) 获取类的指定的属性不管是什么修饰,参数是属性名的字符串。返回值是Field类型。
getFields() 获取类中的所有public修饰的属性,返回Field类型的数组。(包含继承至父类的属性)
getDeclaredFields() 获取类中的所有属性(任何权限),返回Field类型的数组。(不包含继承至父类的属性)
getMethod(参数1, 参数2) 获取类及父类中指定的public修饰的方法,参数1是方法名字符串,后面的参数是方法的参数的Class对象,可以有多个,返回值是Method类型。
getDeclaredMethod(参数1,参数2) 和上个方法类似,但是这个方法的修饰不限制,但是只能获取本类的方法。
getMethods() 获取类及父类中的所有public修饰的方法,返回值是一个Method的数组。
getDeclaredMethods() 获得本类中的所有方法,返回值是一个Method的数组。
getConstructors() 获得本类中public修饰的构造方法,返回值是一个Constructor的数组。
getDeclaredConstructors() 获得本类中的所有构造方法,返回值是一个Constructor的数组。

获取类的属性结构:

// 获取类属性结构
public static void main(String[] args) {
    Class<Student> c = Student.class;

    Field[] fields = c.getFields(); // 获取所有public修饰的属性,包含继承至父类中public修饰的属性
    for (Field field : fields) {
        System.out.println(field); // name
    }

    Field[] declaredFields = c.getDeclaredFields(); // 获取声明的所有属性(任何权限),不包含父类中所有属性
    for (Field field : declaredFields) {
        System.out.println(field); // id name
    }

    // 获取到的Filed对象来获取某一个属性的结构
    Field field = fields[0];

    int modifiers = field.getModifiers(); // 获取属性修饰符,返回值是一个整型,一个值对应一个修饰符
    System.out.println(Modifier.toString(modifiers)); // 输出对应整型对应的修饰符

    Class<?> type = field.getType(); // 获取属性的类型,返回值是一个类型对应的Class对象
    System.out.println(type);

    String name = field.getName(); // 获取声明时的名称
    System.out.println(name);
}

获取类方法结构

// 反射获取类中方法结构
public static void main(String[] args) {

    Class<Student> c = Student.class;

    // 获取所有及其父类中public修饰的方法
    Method[] methods = c.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

    // 获取类中所有方法(任何修饰符不包含父类)
    Method[] declaredMethods = c.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        System.out.println(declaredMethod);
    }

    Method method = declaredMethods[0];
    // 获取方法的修饰符
    int modifiers = method.getModifiers();
    System.out.println(Modifier.toString(modifiers));

    // 获取方法返回值
    Class<?> returnType = method.getReturnType();
    System.out.println(returnType.getSimpleName()); // 返回值类型名称

    // 获取方法名称
    String name = method.getName();
    System.out.println(name);
    
    // 获取方法上的注解
    Annotation[] annotations = method.getAnnotations();
    for (Annotation annotation : annotations) {
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println(myAnnotation.value());
        System.out.println(myAnnotation.name());
    }

    // 获取方法的参数列表
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
        System.out.println(parameterType.getSimpleName()); // 参数类型名称
    }

    // 获取方法抛出的异常
    Class<?>[] exceptionTypes = method.getExceptionTypes();
    for (Class<?> exceptionType : exceptionTypes) {
        System.out.println(exceptionType.getSimpleName()); // 异常类型名称
    }
}

获取类构造器的结构

// 获取类中构造器结构
public static void main(String[] args) {
    Class<Student> c = Student.class;

    // 获取类中所有public修饰的构造方法(不包含父类)
    Constructor<?>[] constructors = c.getConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println(constructor);
    }

    // 获取类中所有构造方法(任何修饰符)
    Constructor<?>[] declaredConstructors = c.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor);
    }
}

获取类实现的接口:

public static void main(String[] args) {
    Class<Student> c = Student.class;

    // 获取类实现的接口
    Class<?>[] interfaces = c.getInterfaces();
    for (Class<?> i : interfaces) {
        System.out.println(i);
    }

    // 获取类的父类的Class对象
    Class<? super Student> superclass = c.getSuperclass();
    System.out.println(superclass);

    // 获取类所在的包
    Package p = c.getPackage();
    System.out.println(p);

    // 获取类上的注解
    Annotation[] annotations = c.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }
}

3.4、调用运行时类指定结构

3.4.1、调用属性

// 通过反射操作运行时类的属性
public static void main(String[] args) {
    Class<Student> c = Student.class;

    Student student = null;
    // 因为需要操作属性,所以必须有对象才能操作对象的属性
    try {
        student =  c.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }

    // 获取public修饰的属性
    try {
        // 参数是获取属性对应的名称(只能获取public修饰的属性)
        Field name = c.getField("name");
        name.set(student,"张三"); // 直接设置student对象的name属性值为张三
        String nameFiled = (String) name.get(student); // 获取student对应name属性的值,默认是Object
        System.out.println(nameFiled);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }

    // 对于非public修饰的属性
    try {
        // 获取任何修饰的属性,参数是属性名称
        Field id = c.getDeclaredField("id");
        id.setAccessible(true); // 对于非public修饰的属性需要设置为true,不然不能操作这个属性
        id.set(student,10001);
        int idFiled = (int) id.get(student);
        System.out.println(idFiled);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

3.4.2、调用方法

// 通过反射调用方法
public static void main(String[] args) {
    Class<Student> c = Student.class;
    Student student = null;

    try {
        student = c.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }

    // 调用非静态方法
    try {
        // 获取任何修饰的某一个指定方法,参数一:方法名;参数二:是一个可变参数,方法调用时需要传递的参数类型Class对象
        Method method = c.getDeclaredMethod("method1", String.class);
        // 对于非public修饰的方法需要设置为true
        method.setAccessible(true);
        // 通过student对象来调用方法,后面是需要传递的参数,是一个可变参数
        // invoke()的返回值就是方法执行完毕后的返回值,如果没有返回值就是null
        Object result = method.invoke(student, "参数值");
        System.out.println(result);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }

    // 调用静态方法,静态方法不需要对象来调用,所有不需要实例化一个对象
    try {
        Method method = c.getDeclaredMethod("staticMethod", String.class);
        method.setAccessible(true);
        // 不要要对象来调用,直接通过Student.class类对象来调用
        Object result = method.invoke(Student.class, "参数值");
        System.out.println(result);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

3.4.3、调用构造器

// 反射调用构造器实例化对象
public static void main(String[] args) {
    Class<Student> c = Student.class;

    try {
        // 参数是构造器的参数列表,因为构造器名称固定,所以不需要指定名称
        Constructor<Student> constructor = c.getDeclaredConstructor(String.class);
        // 对于非public需要设置为true
        constructor.setAccessible(true);
        // 实例化一个对象,参数是构造器对应参数值
        Student student = constructor.newInstance("张三");
        System.out.println(student);
    } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

3.5、反射应用之动态代理

代理模式:使用一个代理对象将对象包装起来,然后使用代理对象取代原始对象。任何原始对象的调用都要通过代理对象,代理对象决定了是否以及何时去调用到原始对象上。代理模式分为静态代理、动态代理。

3.5.1、静态代理

image-20220530155401678

例如客户想要租房子,他可以通过中介去租房子。这样客户就是被代理类,中介就是代理类,他们共同事情都是租房子,租房子就是这个公共的接口。

公共接口:

// 这是代理和被代理的公共接口
public interface ProxyInterface {
    void rent();
}

被代理对象(顾客):

// 被代理对象,实现公共接口
public class ProxyImpl implements ProxyInterface {

    // 被代理对象中实现的方法
    @Override
    public void rent() {
        System.out.println("我是顾客,我需要租房子");
    }
}

代理对象(中介):

// 这是代理类,实现公共接口
public class ProxyObject implements ProxyInterface {

    // 这是代理类代理的对象,ProxyObject就是中介,ProxyImpl就是顾客
    private final ProxyInterface proxyImpl;

    public ProxyObject(ProxyInterface proxyImpl) {
        this.proxyImpl = proxyImpl;
    }

    // 这是代理类实现的方法,在这个方法中代理类可以执行其他方法,也可以执行被代理类中的方法
    @Override
    public void rent() {
        this.proxyImpl.rent(); // 执行被代理类中的方法
        System.out.println("我是中介,我正在帮顾客租房子");
        System.out.println("我已经帮顾客租到房子了");
    }
}

代理测试:

public static void main(String[] args) {
    ProxyImpl proxyImpl = new ProxyImpl(); // 这是被代理对象
    // 代理对象,参数需要指定一个被代理对象
    ProxyObject proxyObject = new ProxyObject(proxyImpl);
    // 代理对象替被代理对象执行对应方法
    proxyObject.rent();
}

3.5.2、动态代理

静态代理是在编译期间都确定下来了,不利于程序的扩展。同时一个代理类只能为一个接口代理,如果需要多个代理对象就需要创建多个代理类。所以为了解决这个问题就产生了动态代理,使用一个代理类来完成所有代理接口,这就需要反射来实现。

创建一个类,根据传入的不同的代理对象来生成对应不同的代理对象。

// 代理对象来创建代理类,需要实现一个InvocationHandler接口
public class ProxyFactory implements InvocationHandler {

    // 被代理对象,因为被代理对象类型不确定所以使用Object
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 创建一个代理类
    public  Object getProxyInstance(){
        /**
         * Proxy.newProxyInstance() 创建一个代理对象
         * 参数一:被代理对象的类加载器
         * 参数二:被代理类实现的接口,这样创建的代理类可以实现被代理类相同的接口
         * 参数三:一个实现了InvocationHandler接口的实现类,这样创建好的代理对象去调用被代理类相同方法时,
         *         就会执行实现类重新的invoke()方法
         */
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * 当代理类调用被代理类中相同方法时,就会执行这个方法
     * 参数一:代理类
     * 参数二:被代理类要执行的方法
     * 参数三:方法执行时需要的参数数组
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理类正在执行方法");

        // 被代理类执行对应方法,方法返回值作为本类invoke()的返回值
        Object result = method.invoke(this.target, args);

        System.out.println("代理类完成代理");
        return result;
    }
}

创建一个租房被代理类:

// 租房接口
public interface RentInterface {
    void rent();
}
// 一个需要代理的被代理类
public class RentClient implements RentInterface {

    @Override
    public void rent() {
        System.out.println("我是顾客,我需要租房子");
    }
}

创建一个衣服被代理类:

// 衣服接口
public interface ClothInterface {
    String make();
}
// 衣服被代理类,实现衣服接口
public class ClothImpl implements ClothInterface {

    @Override
    public String make() {
        System.out.println("我是衣服被代理类,我正在制造衣服");
        return "ok";
    }
}

动态代理测试:

public static void main(String[] args) {
    // 代理类工厂,产生代理类
    ProxyFactory proxyFactory = new ProxyFactory();

    // 租房被代理类对象
    RentInterface rentClient = new RentClient();
    // 设置被代理对象
    proxyFactory.setTarget(rentClient);
    // 创建一个代理对象,因为代理对象和被代理对象实现了相同的接口,所以可以进行强制转换成对应接口类型
    RentInterface proxy = (RentInterface) proxyFactory.getProxyInstance();
    // 代理类执行被代理类相同的方法,相当于执行了invoke()
    proxy.rent();

    // 衣服被代理类对象
    ClothImpl clothImpl = new ClothImpl();
    // 重新设置被代理对象
    proxyFactory.setTarget(clothImpl);
    // 创建一个代理对象
    ClothInterface clothProxy = (ClothInterface) proxyFactory.getProxyInstance();
    // 代理类执行被代理相同方法
    String make = clothProxy.make();
    System.out.println(make);
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>