反射 - 注解 - 动态代理

129 阅读8分钟

反射 - 注解 - 动态代理

1 反射

1.1 理解反射
1.1.1 反射是什么

​ 以编程的方式获取类中的各种成分 (成员变量, 方法, 构造器等)

1.1.2 好处

​ 无视修饰符,功能更强大灵活

1.1.3 应用场景

​ 在Java中,反射主要用于以下几个方面:

  1. 动态加载类:通过反射可以在运行时动态地加载类,而不需要在编译时就将类的信息确定下来。这样可以实现一些动态扩展的功能,例如插件系统
  2. 获取类的信息:通过反射可以获取类的构造方法、字段、方法等信息,可以用于实现一些通用的代码处理逻辑,例如序列化、对象拷贝等
  3. 动态创建对象:通过反射可以在运行时动态地创建对象,可以根据配置文件、用户输入等动态地决定创建哪个类的对象
  4. 动态调用方法:通过反射可以在运行时动态地调用对象的方法,可以根据配置文件、用户输入等动态地决定调用哪个方法
  5. 修改私有字段和方法的访问权限:通过反射可以绕过访问权限,访问和修改对象的私有方法
1.2 获取类对象

​ 类对象相当于这个类的所有内容

1.2.1 获取类对象的方式
1 Class c1 = 类名.class
2 Object提供的方法: public Class getClass(),对象.getClass()
3 调用Class提供方法:public static Class forName(String package)
//String package = 类全名 = 包名+类名
//1 根据对象名 反射对象
Class c2 = Student.class;

//2 根据现有对象 反射对象
Student s = new Student();
Class c1 = s.getClass();

//3 /\_/\ 根据相对路径(类全名) 反射对象
Class c3 = Class.forName("com.itheima._02getClass.Student");
1.3 获取类构造器
1.3.1 获取构造器的方法
1 获取所有public构造器: getConstructors()
2 获取一个public构造器: getConstructor (Class<?>... parameterTypes)
3 获取所有存在的构造器: getDeclaredConstructors()
4 获取一个存在的构造器: getDeclaredConstructor (Class<?>... parameterTypes)
1.3.2 反射得到的构造器的作用

​ 1) 创建对象

​ public newInstance(Object... initargs)

​ 2) 如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象

​ setAccessible(boolean)

​ 反射可以得到私有构造器

1.4 获取类成员变量
1.4.1 获取成员变量的方法
1 返回所有成员变量对象的数组(只能拿public的): public Field[] getFields()
2 返回所有成员变量对象的数组, 存在就能拿到: public Field[] getDeclaredFields()
3 返回单个成员变量对象(只能拿public): public Field getField(String name)
4 返回单个成员变量对象,存在就能拿到: public Field getDeclaredField(String name)
1.4.2 反射得到的成员变量的作用

​ 1) 赋值: void set(Object obj, Object value):

​ 2) 取值: Object get(Object obj)

​ 3) 暴力反射 (然后在取值 赋值): setAccessible(boolean)

1.5 获取类成员方法
1.5.1 获取类成员的方法
1 返回所有成员方法对象的数组 (只能拿public的) : Method[] getMethods()
2 返回所有成员方法对象的数组,存在就能拿到: Method[] getDeclaredMethods()
3 返回单个成员方法对象(只能拿public的): Method getMethod (String name, Class<?>... parameterTypes) 
4 返回单个成员方法对象,存在就能拿到: Method getDeclaredMethod​(String name, Class<?>... parameterTypes)
1.5.2 反射得到的累的成员方法的作用

​ 1) 触发某个对象的该方法执行: public Object invoke(Object obj, Object... args)

​ 2) 设置为true,表示取消访问检查,进行暴力反射: public void setAccessible(boolean flag)

1.6 作用及应用场景

​ /\ 可以在运行时得到一个类的全部成分然后操作。 ​ /\ 可以破坏封装性。(很突出) ​ /\ 更重要的用途是适合:做Java高级框架 ​ /\ 基本上主流框架都会基于反射设计一些通用技术功能。

1.7 总结

![image-20230828001828780](D:\Note\JAVA学习\反射 - 注解 - 动态代理.assets\image-20230828001828780.png)

2 注解 Annotation

2.1 概述

​ 注解就是代码里特殊标记 (@Override、@FunctionalInterface、@Test)

​ 用于对Java中类、方法、成员变量做标记,然后根据业务需求进行特殊处理

2.2 自定义注解
2.2.1 格式
public @interface 注解名称 {
	public 属性类型 属性名() default 默认值 ;
}
//基本数据类型
String
Class
注解
枚举
以上类型的一维数组
2.3 元注解

​ 元注解是修饰注解的注解。

​ @Target: 约束自定义注解只能在哪些地方使用, ​ @Retention:申明注解的生命周期

2.3.1 @Target

​ Source: 注解只作用于源码阶段, 生成的字节码文件中不存在

​ Class: 注解作用在源码阶段, 字节码阶段, 运行阶段不存在, 默认值

​ Runtime: 注解作用在 源码, 字节码, 运行阶段(开发常用)

2.4 注解的解析
2.4.1 定义

​ 注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

2.4.2 相关接口

​ Annotation: 注解的顶级接口,注解都是Annotation类型的对象 ​ AnnotatedElement:该接口定义了与注解解析相关的解析方法

2.4.3 常用方法
1) 获得当前对象上使用的所有注解,返回注解数组: Annotation[]    getDeclaredAnnotations()
2) 根据注解类型获得对应注解对象: T getDeclaredAnnotation(Class<T> annotationClass)
3) 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false: boolean isAnnotationPresent(Class<Annotation> annotationClass)
2.5 应用场景

​ 模拟Junit框架

3 动态代理

3.1 定义

​ 动态代理主要是对被代理对象的行为进行代理。 对功能进行增强。

3.2 动态代理开发步骤

​ 1) 必须定义接口,里面定义一些行为,用来约束被代理对象和代理对象都要完成的事情。 ​ 2) 定义一个实现类实现接口,这个实现类的对象代表被代理的对象。 ​ 3) 定义一个测试类,在里面创建被代理对象,然后为其创建一个代理对象返回。(重点) ​ 4) 代理对象中,需要模拟收首付款,真正触发被代理对象的行为,然后接收尾款操作。 ​ 5) 通过返回的代理对象进行方法的调用,观察动态代理的执行流程。

3.3 创建代理对象

​ Java中代理的代表类是:java.lang.reflect.Proxy,它提供了一个静态方法,用于为被代理对象,产生一个代理对象返回。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

为被代理对象返回一个代理对象。
参数一:类加载器 加载代理类,产生代理对象。,。
参数二:真实业务对象的接口。(被代理的方法交给代理对象)
参数三:代理的核心处理程序。
3.4 通过代理对象调用方法的流程

​ 1) 先走向代理 ​ 2) 代理中可以真正触发被代理对象的方法执行。 ​ 3) 回到代理中,由代理负责返回结果给调用者。

3.5 好处

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

3.6 代码实现栗子
public interface Show {
    public void sing();
    public void dance();
}
public class YuiYui implements Show {
    @Override
    public void sing() {
        System.out.println("Yui在唱如果我不是偶像");
    }

    @Override
    public void dance() {
        System.out.println("Yui在跳无论如何都喜欢你");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ConcertDay {
    public static void main(String[] args) {
        //     创建被代理对象
        YuiYui yui = new YuiYui();
        //     创建代理对象
        Show o = (Show)Proxy.newProxyInstance(
     	// 指定代理类的类加载器。
                ConcertDay.class.getClassLoader(),
        // 指定代理类需要实现的接口数组。这里使用 yui.getClass().getInterfaces() 来获取 yui 对象所实现的接口数组。
                yui.getClass().getInterfaces(),
        // 指定处理方法调用逻辑的 InvocationHandler 接口实例
        // InvocationHandler 是一个接口,用于定义代理对象的方法调用逻辑。
                new InvocationHandler() {
                    @Override
        // proxy:代理对象本身。如果需要对代理对象进行操作或者获取代理对象的信息,可以使用该参数。通常情况下不会直接使用 proxy 参数,
		 // 而是将其强制转换为特定的类型(如 (Show) proxy)来调用具体的方法。
         // method:被调用的方法对象,可以获取关于被调用方法的信息,如方法名、参数列表、返回类型等
         // args:方法的参数数组: 如果方法有多个参数,则 args 数组按照参数顺序存储了相应的参数值。
         // 在 invoke() 方法中,可以根据需要使用 args 参数来访问和操作方法的参数。
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 代理者做的事情
                        System.out.println("肥秋出场收门票了!");
                        method.invoke(yui);
                        System.out.println("肥秋出场卖周边了!");
                        return null;
                    }
                }
                );
  //     调用代理实现类
        o.dance();
        o.sing();
    }
}

// 通过这三个参数,我们可以在 invoke() 方法中根据被调用方法的不同,实现自定义的逻辑和行为。
// 可以通过 proxy 对象操作代理对象,通过 method 对象获取方法相关信息,通过 args 数组访问方法的参数值。
// 这样就可以在方法调用前后进行一些额外的操作,例如记录日志、执行权限验证、实施事务管理等。