手写Spring第三篇番外,反射的基本使用

上一篇发出去之后,我有一个朋友说 beanDefinition.getBeanClass().newInstance() 这句代码太突兀了,就像是在甜甜的睡梦中,突然脚踩悬崖惊醒。

像我这种插朋友两刀的人必须安排了,不止安排 newInstance 还把反射基本用法也给安排了。

先看看反射是什么?知己知彼才能百战百胜。

Reflection is commonly used by programs which require the ability to examine or modify 
the runtime behavior of applications running in the Java virtual machine.
This is a relatively advanced feature and should be used only by developers 
who have a strong grasp of the fundamentals of the language. 
With that caveat in mind, reflection is a powerful technique 
and can enable applications to perform operations which would otherwise be impossible.

这写的什么破玩意,每个字母我都能看懂,但不知道为什么合在一起看就想睡觉。让 GPT 翻译一下:

反射通常用于需要检查或修改在Java虚拟机中运行的应用程序的运行时行为的程序。
这是一个相对高级的特性,应该仅由对语言基础知识有深刻理解的开发人员使用。
警告,反射是一种强大的技术,可以使应用程序执行在其他情况下不可能的操作。

我只看到了应该由对语言基础知识由深刻理解的开发人员使用。看来想低调都不行了,连 Oracle 都为我量身定做了反射这个功能。那不用就说不过去了,盘它。

要真正理解反射,必须先理解 运行时 。运行时是相对的,像万恶的空指针异常 NullPointerException 就是在运行时才出现的,编译时一点问题没有,运行的时候背刺。

我参考了好几篇文章,他们都用正射来理解反射,打不过就加入,那我也来。

我们需要 new 一个实例 TestService1,然后调用 test 方法。

正射的用法:

java 复制代码
@Test
public void direct_new_test() {
    TestService1 testService1 = new TestService1();
    testService1.test();
}

反射的用法:

java 复制代码
@Test
public void reflection_new_test() throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
    Class<?> clazz = Class.forName("pri.hongweihao.smallspring.TestService1");

    // 获取构造方法,初始化实例
    Constructor<?>[] constructors = clazz.getConstructors();
    Object o = constructors[0].newInstance();

    // 获取方法对象并调用
    Method method = clazz.getMethod("test");
    method.invoke(o);
}

对比两段代码,正射的代码清晰易懂。就像我要从广州到北京,正射是直接打飞的去,反射是先坐公交车到香港,然后从澳门走路去上海。我猜有人肯定会问:反射那么复杂为什么还要用呢?这不是没苦硬吃吗?

官方也说了反射应该由我这种对语言基础知识由深刻理解的开发人员来使用。如果我把代码改成这样,阁下如何应对。把反射的部分独立出来一个方法,只需要传类名和方法名就能调用,你就说牛不牛皮?

java 复制代码
@Test
public void reflection_new_test() throws Exception {
    reflection("pri.hongweihao.smallspring.TestService1", "test");
}

private void reflection(String className, String methodName) throws Exception {
    Class<?> clazz = Class.forName(className);

    // 获取构造方法,初始化实例
    Constructor<?>[] constructors = clazz.getConstructors();
    Object o = constructors[0].newInstance();

    // 获取方法对象并调用
    Method method = clazz.getMethod(methodName);
    method.invoke(o);
}

还有更牛皮的,如果我把类名和方法名写在配置文件里面,阁下还能应对吗?如果我的配置文件是这样的呢?

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="testDao" class="pri.hongweihao.smallspring.bean.TestDao"/>
    <bean id="testService" class="pri.hongweihao.smallspring.bean.TestService">
        <property name="name" value="karl"/>
        <property name="testDao" ref="testDao"/>
    </bean>
</beans>

这只是反射的一种用法,还有其他的骚操作等你来发掘,发掘了记得告诉我。

这就是运行时最基本的使用了,提前配置好一些参数,在程序运行的过程中加载进来,就能为所欲为了。

当年陈刀仔他能用20块赢到3700万,卢本伟用20万赢到500万不是问题!同理,当年陈刀仔他能用20块赢到3700万,我们用类名加方法名调用方法不是问题吧!埋伏他一手。

正片完,下面补充一点无关紧要的代码

获取Class对象:

java 复制代码
/**
 * 获取Class对象:通过全限定名字符串获取Class对象
 */
@Test
public void get_class_test1() throws ClassNotFoundException {
    Class<?> clazz = Class.forName("pri.hongweihao.smallspring.TestService1");
    Assert.assertNotNull(clazz);
}

/**
 * 获取Class对象:通过类.class获取Class对象
 */
@Test
public void get_class_test2() {
    Class<TestService1> clazz = TestService1.class;
    Assert.assertNotNull(clazz);
}

/**
 * 获取Class对象:通过实例.getClass()获取Class对象
 */
@Test
public void get_class_test3() {
    TestService1 testService = new TestService1();
    Class<? extends TestService1> clazz = testService.getClass();
    Assert.assertNotNull(clazz);
}
java 复制代码
/**
 * <p>
 * 获取类属性
 * </p>
 */
@Test
public void test() throws NoSuchFieldException {
    Class<TestService1> clazz = TestService1.class;

    System.out.println("公有属性:");
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println(field.getName());
    }

    System.out.println("全部属性:");
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);
    }

    System.out.println("查询共有属性:");
    Field field = clazz.getField("field1");
    System.out.println(field);

    System.out.println("clazz.getField查询私有属性报错");
    Assert.assertThrows(NoSuchFieldException.class, () -> clazz.getField("field2"));

    System.out.println("查询全部属性:");
    Field declaredField1 = clazz.getDeclaredField("field1");
    System.out.println(declaredField1);

    Field declaredField2 = clazz.getDeclaredField("field2");
    System.out.println(declaredField2);


}
java 复制代码
/**
 * 获取类方法
 */
@Test
public void test1() throws NoSuchMethodException {
    Class<TestService1> clazz = TestService1.class;

    System.out.println("当前类以及父类的所有公有方法: ");
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

    System.out.println("\n当前类的所有方法: ");
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method method : declaredMethods) {
        System.out.println(method);
    }
}
java 复制代码
/**
 * 初始化实例:通过 Class.newInstance() 调用无参构造方法初始化
 * TestService定义了一个无参构造方法
 */
@Test
public void create_instance1_test1() throws InstantiationException, IllegalAccessException {
    Class<TestService1> clazz = TestService1.class;
    TestService1 testService = clazz.newInstance();
    Assert.assertNotNull(testService);
    testService.test();
}

/**
 * 初始化实例:通过 Class.newInstance() 调用无参构造方法初始化
 * TestService1 没有定义构造方法
 */
@Test
public void create_instance1_test2() throws InstantiationException, IllegalAccessException {
    Class<TestService1> clazz = TestService1.class;
    TestService1 testService1 = clazz.newInstance();
    Assert.assertNotNull(testService1);
    testService1.test();
}

/**
 * 初始化实例:通过 Class.newInstance() 调用无参构造方法初始化
 * TestService2定义了有参构造方法
 */
@Test
public void create_instance1_test3() {
    Class<TestService2> clazz = TestService2.class;
    Assert.assertThrows(InstantiationException.class, clazz::newInstance);
}

/**
 * 初始化实例:通过 Class.newInstance() 调用无参构造方法初始化
 * TestService3定义了有参构造方法和无参构造方法
 */
@Test
public void create_instance1_test4() throws InstantiationException, IllegalAccessException {
    Class<TestService3> clazz = TestService3.class;
    TestService3 testService3 = clazz.newInstance();
    Assert.assertNotNull(testService3);
    testService3.test();
}

/**
 * 初始化实例:获取构造方法来初始化实例
 * TestService4定义了共有有参构造方法和私有无参构造方法
 */
@Test
public void create_instance2_test() throws Exception {
    Class<TestService4> clazz = TestService4.class;
    // 获取公有的构造方法
    Constructor<?>[] constructors = clazz.getConstructors();
    TestService4 testService4 = (TestService4) constructors[0].newInstance("fake");
    Assert.assertNotNull(testService4);
    testService4.test();

    // 获取所有的构造方法,调用私有构造方法会报错
    Constructor<TestService4> declaredConstructor = clazz.getDeclaredConstructor(Integer.class);
    Assert.assertThrows(IllegalAccessException.class, () -> declaredConstructor.newInstance(1));
}

Ref

Using Java Reflection (oracle.com)

大白话说Java反射:入门、使用、原理 - 陈树义 - 博客园 (cnblogs.com)

谈谈Java反射:从入门到实践,再到原理反射是Java底层框架的灵魂技术,学习反射非常有必要,本文将从入门概念,到实践, - 掘金 (juejin.cn)

相关推荐
Zyq10343 分钟前
Java基础-Wrapper Classes(包装类)
java·开发语言
杨荧3 分钟前
【JAVA开源】基于Vue和SpringBoot的宠物咖啡馆平台
java·前端·vue.js·spring boot·开源·宠物
ᴡᴀᴋᴜ⌓‿⌓ᴡᴀᴋᴜ10 分钟前
手动在Linux服务器上部署并运行SpringBoot项目(新手向)
linux·服务器·spring boot·后端
小刘同学要加油呀18 分钟前
每日一题:单例模式
java·开发语言·单例模式
遇上彩虹pkq1 小时前
补充面试知识点
java·开发语言
宇宙之一粟1 小时前
【译】Go 迭代器的乐趣
后端·go·掘金·金石计划
Dylanioucn1 小时前
【编程基础知识】掌握Spring MVC:从入门到精通
java·spring·mvc
raiseup21 小时前
尚硅谷rabbitmq2024 集群搭建和优先队列答疑 第41-48节
开发语言·后端·ruby
江南一点雨2 小时前
Spring Boot3集成 LiteFlow 实现业务流程编排
java·spring boot·后端
zz-zjx2 小时前
docker详解介绍+基础操作 (五)容器镜像2
java·docker·容器