Java 反射

一、定义

Java 反射(Reflection)是一个强大的机制,它允许程序在运行时查询、访问和修改类、接口、字段和方法信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被使用,例如 Spring 框架的依赖注入,Spring Boot、MyBatis 等等框架中也都使用了反射机制,主要是通过反射机制实现动态代理。

1.1 代理模式

代理模式就是为其他对象提供一种代理以控制这个对象的访问。所有访问这个对象的资源都需要通过代理。

1.2 静态代理的缺点

  1. 代码灵活度不高

    静态代理的时候,如果接口上定义了很多方法,代理类里面也要写很多方法;而动态代理实现的时候虽然接口上定义了很多方法,但是动态代理类始终只有一个 invoke() 方法。这样,当需要代理的接口发生变化的时候,动态代理的接口就不需要跟着变化。

  2. 开发及维护成本大

    在静态代理中,需要在代理类中,将原始类的所有方法都重新实现一遍,如果要添加附加功能的类不止一个,还需要针对每个类都创建一个代理类,这样大大增加了开发及维护成本。

1.3 动态代理

Java 对代理模式提供了内建的支持,在 java.lang.reflect 包下面,提供了一个 Proxy 类和一个 InvocationHandler 的接口。下面是实现步骤:

  1. 实现一个 InvocationHandler 接口。
  2. 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好后返回被代理的目标对象的接口,以便于客户端的操作。
  3. 需要实现 invoke() 方法。在这个方法中实现,判断具体调用的方法以及处理逻辑。

实例代码:

java 复制代码
public class UserInvocationHandler  implements InvocationHandler{
    /**
     *
     * 被代理的对象
     */
    private Object target;
    public Object newInstance(Object target) throws Exception{
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            System.out.println("开始事务....");
            method.invoke(this.target,args);
        }catch (Exception e){
            System.out.println("回滚事务....");
        }finally {
            System.out.println("提交事务....");
        }
        return  null;
    }
}

客户端使用这个动态代理,代码如下:

java 复制代码
IUserService userService = new UserServiceImpl();
        IUserService userServiceProxy = (IUserService) Proxy.
        newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),
                         new UserInvocationHandler());
        userServiceProxy.save();

二、反射机制的优缺点

  • 优点
    可以让代码更灵活、为各种框架提供开箱即用的功能提供了便利。
  • 缺点
    增加了安全问题,比如可以无视泛型参数的安全检查(发生在编译时)。另外,反射的性能也要稍差一点。不过对于框架来说,实际影响不大。

三、工作流程

  1. 获取 Class 对象:首先获取目标类的 Class 对象。
  2. 获取成员信息:通过 Class 对象,可以获取类的字段、方法、构造函数等信息。
  3. 操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。

3.1 Class 对象

在一个JVM中,每个类都只会有一个类对象,所以无论使用哪种方法获得的类对象都是同一个。 准确的讲是一个ClassLoader下,一种类,只会有一个类对象存在。通常一个JVM下,只会有一个ClassLoader。

3.2 获取 Class 对象的四种方式

  • 知道具体类的情况下

    java 复制代码
    Class testClass = Test.class;
  • 通过 Class.foeName() 传入类的全路径获取

    java 复制代码
    Class test = Class.forName("com.org.example.Test");
  • 通过对象实例 instance.getClass() 获取

    java 复制代码
    Test t = new Test();
    Class t2 = t.getClass();
  • 通过类加载器 xxxClassLoader.loadClass() 传入类路径获取

    java 复制代码
    ClassLoader.getSystemClassLoader().loadClass("com.org.example.Test")

    通过类加载器获取 Class 对象不会进行初始化,这表示不会进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行。

四、应用

4.1 创建对象

可以使用反射动态创建对象:

复制代码
Class<?> clazz = Class.forName("java.lang.String");
Object obj = clazz.getDeclaredConstructor().newInstance();

4.2 访问字段

可以通过反射访问和修改类的字段:

java 复制代码
Class<?> clazz = Person.class;
Field field = clazz.getDeclaredField("name");
field.setAccessible(true); // 如果字段是私有的,需要设置为可访问
Object value = field.get(personInstance); // 获取字段值
field.set(personInstance, "New Name"); // 设置字段值

4.3 调用方法

可以通过反射调用类的方法:

java 复制代码
Class<?> clazz = Person.class;
Method method = clazz.getMethod("sayHello");
method.invoke(personInstance);

Method methodWithArgs = clazz.getMethod("greet", String.class);
methodWithArgs.invoke(personInstance, "World");

4.4 获取构造函数

可以使用反射获取和调用构造函数:

java 复制代码
Class<?> clazz = Person.class;
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("John", 30);

4.5 获取接口和父类

可以使用反射获取类实现的接口和父类:

java 复制代码
Class<?> clazz = Person.class;

// 获取所有接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
    System.out.println("Interface: " + i.getName());
}

// 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
相关推荐
烽学长2 小时前
(附源码)基于Spring Boot社区“邻里帮”平台的设计与实现
java
森林-2 小时前
MyBatis 从入门到精通(第一篇)—— 框架基础与环境搭建
java·tomcat·mybatis
能工智人小辰2 小时前
Java8 Swing实现计算器
开发语言
正在走向自律2 小时前
Java连接电科金仓数据库(KingbaseES)实战指南
java·数据库·oracle·国产数据库·kingbase
SccTsAxR2 小时前
[C语言]常见排序算法①
c语言·开发语言·经验分享·笔记·其他·排序算法
xiaoye37082 小时前
Java 事务失效场景全解析
java
weixin_436525073 小时前
Spring Boot 集成 EasyExcel 的最佳实践:优雅实现 Excel 导入导出
java·spring boot·后端
ChinaRainbowSea3 小时前
9. LangChain4j + 整合 Spring Boot
java·人工智能·spring boot·后端·spring·langchain·ai编程
ゞ 正在缓冲99%…3 小时前
leetcode35.搜索插入位置
java·算法·leetcode·二分查找