Java IOC与反射

5 篇文章 0 订阅

Java开发过程中,我们常常需要借助Spring框架来实现业务需求。其中,AOP和IOC是两个非常重要的思想。那么,在Java中,我们是如何实现这两个思想的呢?本文将重点讲解IOC的概念,并结合Spring底层源码,分析其实现过程,同时介绍不同IOC实现方式可能存在的问题。

IOC(Inversion of Control),即控制反转。它是一种设计思想,其核心思想是将对象的创建、依赖注入等一系列操作交由框架来完成。这样,我们就可以将精力集中在业务逻辑的编写上,而不必过多关注对象创建等底层细节。


IOC容器如何存

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

注入的核心其实就是解析 xml 文件的内容,找到 元素,然后经过一系列加工,最后把这些加工后的对象存到一个公共空间,供调用者获取使用。

而至于使用注解方式的 bean,比如使用 @Bean@Service@Component 等注解的,只是解析这一步不一样而已,剩下的操作基本都一致。

这个公共空间是一个 Map,而且是一个 ConcurrentHashMap ,为了保证并发安全。它的声明如下,在 DefaultListableBeanFactory 中。

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256)

其中 beanName 作为 key,也就是例子中的 logger,value 是 BeanDefinition 类型,BeanDefinition 用来描述一个 Bean 的定义,我们在 xml 文件中定义的 元素的属性都在其中,还包括其他的一些必要属性。

向 beanDefinitionMap 中添加元素的过程,叫做 Bean 的注册,只有被注册过的 Bean 才能被使用。

实例化的Bean存储在哪?

有一个 Map 叫做 singletonObjects,其声明如下:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 在 refresh() 过程中,还会将 Bean 存到这里一份,这个存储过程发生在 finishBeanFactoryInitialization(beanFactory) 方法内,它的作用是将非 lazy-init 的 Bean 放到singletonObjects 中。

除了存我们定义的 Bean,还包括几个系统 Bean。


什么是Spring Bean?

Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="...">
   <constructor-arg value="..."/>
</bean>

下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。

org.springframework.beansorg.springframework.context 这两个包是 IoC 实现的基础,


Java依赖注入(Dependency Injection,DI)是指在一个对象需要依赖另一个对象时,由容器自动将依赖的对象注入到该对象中。常见的实现方式有构造函数注入、属性注入和接口注入等。

现在已经很少项目用 xml 这种配置方式了,基本上都是 Spring Boot,就算不用,也是在 Spring MVC 中用注解的方式注册、使用 Bean 了。其实整个过程都是类似的,只不过注册和获取的时候多了注解的参与。Srping 中 BeanFactoryApplicationContext都是接口,除此之外,还有很多的抽象类,使得我们可以灵活的定制属于自己的注册和调用流程,可以认为注解方式就是其中的一种定制。只要找到时机解析好对应的注解标示就可以了。

但是看 Spring Boot 的注册和调用过程没有 xml 方式的顺畅,这都是因为注解的特性决定的。注解用起来简单、方便,好处多多。但同时,注解会割裂传统的流程,传统流程都是一步一步主动调用,只要顺着代码往下看就可以了,而注解的方式会造成这个过程连不起来,所以读起来需要额外的一些方法。


IOC又是如何实现的?

像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。注解的实现也用到的反射机制。

在Java中,我们可以通过反射机制来实现IOC。

Spring框架中,IOC容器就是使用了反射机制实现的。当我们在配置文件中定义了一个Bean时,Spring容器就会根据该Bean的配置信息,利用反射机制创建出该Bean的实例,并将其注入到需要该Bean的地方。

不同的IOC实现方式可能会存在一些问题。

  • 使用XML配置文件配置Bean时,容易出现配置错误的问题;
  • 使用注解配置Bean时,可能会导致配置信息分散在各个类中,不易维护。

在实现一个最简单的IOC时,可以通过以下步骤将给定的名称转换成 bean:

  1. 定义一个接口或抽象类,表示 bean 的实现
  2. 实现一个工厂类,用于根据给定的名称生成相应的 bean 实例
  3. 在工厂类中,使用 Java 的反射机制获取指定名称的类,并创建相应的实例
  4. 将实例作为 bean 返回

反射

Java的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。

优点:

  • 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
  • 与 Java 动态编译相结合,可以实现无比强大的功能。
  • 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

  • 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  • 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

Java反射是指在程序运行时,根据类的全限定名获取该类的完整定义信息(包括属性、方法、构造方法等),并能够在运行时动态调用这些属性或方法的机制。

Java反射的主要作用是在运行时动态地获取类的信息,从而实现动态创建对象、动态调用方法等操作。Java反射的好处在于它能够提高程序的灵活性和可扩展性,缺点则在于它的运行速度相比于常规的函数调用会更慢。

在Spring开发过程中,反射机制被广泛应用。比如,Spring的IOC容器就是通过反射机制来实现Bean的动态创建和属性注入的。在使用Spring时,我们只需要通过配置文件或注解来定义Bean,Spring就可以利用反射机制创建出Bean的实例,并将其注入到需要使用该Bean的地方。

然而,反射机制也有其缺陷。由于反射机制需要在运行时动态获取类的信息,因此其运行速度相对较慢,且容易导致代码可读性和维护性降低。因此,在使用反射机制时,需要权衡其优缺点,选择最合适的方案。

总的来说,Java反射机制是一种非常强大和灵活的机制,可以帮助我们实现一些常规情况下难以实现的功能。然而,在使用反射时,需要注意其性能和可维护性等问题,以确保程序的稳定性和可扩展性。

可以通过反射访问的常用信息

类型访问方法返回值类型说明
包路径getPackage()Package 对象获取该类的存放路径
类名称getName()String 对象获取该类的名称
继承类getSuperclass()Class 对象获取该类继承的类
实现接口getlnterfaces()Class 型数组获取该类实现的所有接口
构造方法getConstructors()Constructor 型数组获取所有权限为 public 的构造方法
getDeclaredContruectors()Constructor 对象获取当前对象的所有构造方法
方法getMethods()Methods 型数组获取所有权限为 public 的方法
getDeclaredMethods()Methods 对象获取当前对象的所有方法
成员变量getFields()Field 型数组获取所有权限为 public 的成员变量
getDeclareFileds()Field 对象获取当前对象的所有成员变量
内部类getClasses()Class 型数组获取所有权限为 public 的内部类
getDeclaredClasses()Class 型数组获取所有内部类
内部类的声明类getDeclaringClass()Class 对象如果该类为内部类,则返回它的成员类,否则返回 null

反射实战

获取Class对象的四种方式

1. 知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2. 通过Class.forName()传入类的全路径获取:

Class alunbarClass1 = Class.forName("com.example.TargetObject");

3. 通过对象实例 instance.getClass() 获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

4. 通过类加载

ClassLoader.getSystemClassLoader().loadClass("com.example.reflect");

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

反射是如何破坏单例模式的?

在正常情况下,单例模式的实现是通过将构造函数私有化,然后通过一个静态方法来获取单例对象的。这样,我们就可以保证在程序运行期间只有一个实例对象。然而,如果使用反射来获取类的实例,就可以绕过单例模式的限制,从而创建出多个实例对象。为了避免这种情况的发生,我们可以在单例类的构造函数中添加一个判断,如果已经创建了一个实例对象,则抛出一个异常,阻止创建新的实例对象。


参考资料:

Spring中IOC容器是如何创建的?_哔哩哔哩_bilibili

《包你懂系列》一文讲清楚 Spring IoC 实现原理和过程 - 掘金 (juejin.cn)

Spring 常见面试题总结 | JavaGuide(Java面试+学习指南)

Java反射机制是什么? (biancheng.net)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值