怎么理解ioc,ioc指的是什么

  

  国际奥委会的概念   

  

  在Spring中,IoC的定义是Ioc容器,翻译过来就是Ioc容器。为什么叫容器?我们来对比一下日常生活中的容器,也就是那些瓶瓶罐罐。假设我们有一个米缸,米预先放在里面。当我们需要大米时,我们可以从米缸里得到。那么国际奥委会也是如此。有一个容器singletonObjects(提前透露这里的容器类型是ConcurrentHashMap),里面放了各种初始化的bean。当我们的代码需要被使用时,我们可以进去得到它们。   

  

  借助一张图来看看Spring Ioc的工作流程。整个过程类似于上面描述的。向Ioc提供业务pojo和一些元数据配置信息配置元数据,Ioc会根据你给的信息生成可用的beans。这里生成的beans可以直接使用。Ioc是否为我们节省了大量新工作?当然这里面涉及到很多细节,比如如何获取元数据,如何根据元数据生成想要的bean,后面会有解答。   

  

     

  

  所以问题是,为什么需要容器?我买新对象不甜吗?要讨论这个问题,可以比较一下有容器和没有容器的区别。我个人认为有以下几个明显的优点:   

  

  管理方便。容器提供了集中管理,这有利于其他操作,比如与Aop相关的功能。没有容器就无法集中管理Beans。所有的豆子都分散在项目的各个角落,如果要做一些额外的调整,还有很多点需要更改。   

  

  性能节约。容器只需要初始化bean一次,后续使用只需要直接获取即可。但是无容器对象每次都需要更新,开销肯定会更大。   

  

  漂亮的代码。容器屏蔽了复杂对象的构造过程,只需要直接获取使用即可。没有容器,每次都需要构造复杂的对象,代码重复率很高。想想你的项目充斥着各种新对象的代码,已经让你头疼了。   

  

  那么一个东西不可能没有坏处就有好处,凡事都需要辩证看待。那么提供容器后有什么坏处呢?个人认为有以下几个明显的缺点:   

  

  并发安全性。它提供了集中的容器管理,这不可避免地会导致多线程情况下的并发访问,因此需要额外的性能开销来确保线程安全。   

  

  慢慢开始。类似的,提供了集中式的容器管理,所以需要在启动之初初始化各种需要的bean,并把它们放入容器中,虽然这些bean可能用不到。如果不指定初始化时间,未使用的bean也会在启动之初被初始化,这当然比使用时重新创建要消耗额外的性能。   

  

  内存隐患。因为所有的对象都放在容器中,所以在大型对象很多或者对象的生命周期很长的情况下,要考虑对象太多带来的内存开销。   

  

  下面简单分析一下优缺点。当然,这只是一家之见。请指出任何错误或遗漏。目前来看,春天的利远大于弊,这也是春天持续时间长的原因。   

  

  经过以上介绍,相信你对Ioc有了初步的整体了解。也就是说,这是一个带有可以使用的bean的容器。请记住这个结论。然后,我们会介绍Ioc的一些知识体系,留下一个整体的轮廓,不做过多的源代码分析。   

  

  BeanFactory或应用程序上下文   

  

  本节解释BeanFactory和ApplicationContext容器级别之间的差异,以及它们对Ioc使用的影响。我相信任何尝试过阅读Ioc源代码的人都会被这两个弄糊涂。BeanFactory和ApplicationContext提供的功能看似相似,那么这两个东西有什么联系和区别呢?   

  

  我们通常建议使用ApplicationContext,除非有充分的理由不这样做,否则应该使用它。一般来说,GenericApplicationContext及其子类AnnotationConfigApplicationContext是自定义引导的常见实现。这些是Spring核心容器的主要入口点,它们用于所有常见目的:加载配置文件、触发类路径扫描、以编程方式注册bean定义和带注释的类,以及(从5.0开始)注册功能bean定义。   

  

  因为ApplicationContext包含了BeanFactory的所有功能,所以通常建议使用ApplicationContext,除非您需要完全控制bean处理场景。在ApplicationContext(如GenericApplicationContext的实现)中,根据契约(即通过bean名称或bean类型——尤其是后处理器)检测几种bean,但常见的DefaultListableBeanFactory不知道任何特殊bean。   

  

  对于许多扩展容器特性,比如注释处理和AOP代理,BeanPostProcessor扩展点是必不可少的。如果只使用常用的DefaultListableBeanFactory,这种后处理器默认是不会被检测和激活的。这种情况可能会令人困惑,因为您的bean配置实际上没有任何问题。相反,在这里   

种情况下,需要通过额外的设置来完全引导容器。

  

下表列出了 BeanFactory 和 ApplicationContext 接口和实现提供的功能。

  


  

  

要使用 DefaultListableBeanFactory 显式注册 bean 后处理器,您需要以编程方式调用 addBeanPostProcessor(),如以下示例所示:

  

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();// 用 bean 定义填充工厂 // 现在注册任何需要的 BeanPostProcessor 实例factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());factory.addBeanPostProcessor(new MyBeanPostProcessor()); // 现在开始使用工厂要将 BeanFactoryPostProcessor 应用于普通的 DefaultListableBeanFactory,您需要调用其 postProcessBeanFactory()方法,如以下示例所示:

  

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);reader.loadBeanDefinitions(new FileSystemResource("beans.xml")); // 从属性文件中引入一些属性值PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();cfg.setLocation(new FileSystemResource("jdbc.properties")); // 现在实际进行替换cfg.postProcessBeanFactory(factory);在这两种情况下,显式注册步骤都不方便,这就是为什么在 Spring 支持的应用程序中各种 ApplicationContext 变体优于普通 DefaultListableBeanFactory 的原因,尤其是在典型企业设置中依赖 BeanFactoryPostProcessor 和 BeanPostProcessor实例来扩展容器功能时。

  

AnnotationConfigApplicationContext 注册了所有常见的注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)在幕后引入额外的处理器。在 Spring 的基于注解的配置模型的抽象级别上,bean 后置处理器的概念变成了纯粹的内部容器细节。

  

Spring的统一资源加载策略

  

资源抽象Resource

  

在Spring里, org.springframework.core.io.Resource 为 Spring 框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource接口。作为所有资源的统一抽象,Resource 定义了一些通用的方法,由子类 AbstractResource 提供统一的默认实现。定义如下:

  

public interface Resource extends InputStreamSource { /** * 资源是否存在 */boolean exists(); /** * 资源是否可读 */default boolean isReadable() {return true;} /** * 资源所代表的句柄是否被一个 stream 打开了 */default boolean isOpen() {return false;} /** * 是否为 File */default boolean isFile() {return false;} /** * 返回资源的 URL 的句柄 */URL getURL() throws IOException; /** * 返回资源的 URI 的句柄 */URI getURI() throws IOException; /** * 返回资源的 File 的句柄 */File getFile() throws IOException; /** * 返回 ReadableByteChannel */default ReadableByteChannel readableChannel() throws IOException {return java.nio.channels.Channels.newChannel(getInputStream());} /** * 资源内容的长度 */long contentLength() throws IOException; /** * 资源最后的修改时间 */long lastModified() throws IOException; /** * 根据资源的相对路径创建新资源 */Resource createRelative(String relativePath) throws IOException; /** * 资源的文件名 */@NullableString getFilename(); /** * 资源的描述 */String getDescription(); }子类结构如下:

  

  

从上图可以看到,Resource 根据资源的不同类型提供不同的具体实现,如下:

  

FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。

  

ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。

  

UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。

  

ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。

  

InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。

  

org.springframework.core.io.AbstractResource ,为 Resource 接口的默认抽象实现。它实现了 Resource 接口的大部分的公共实现 。

  

资源定位ResourceLoader

  

Spring 将资源的定义和资源的加载区分开了,Resource 定义了统一的资源,那资源的加载则由 ResourceLoader 来统一定义。

  

org.springframework.core.io.ResourceLoader 为 Spring 资源加载的统一抽象,具体的资源加载则由相应的实现类来完成,所以我们可以将 ResourceLoader 称作为统一资源定位器。其定义如下:

  

/** * 用于加载资源(例如类路径或文件系统资源)的策略接口。 * 需要 {@link org.springframework.context.ApplicationContext} 来提供此功能, * 以及扩展的 {@link org.springframework.core.io.support.ResourcePatternResolver} 支持。 * <p>{@link DefaultResourceLoader} 是一个独立的实现,可以在 ApplicationContext 之外使用,也被 {@link ResourceEditor} 使用。 * <p>在 ApplicationContext 中运行时,可以使用特定上下文的资源加载策略从字符串中填充类型为 Resource 和 Resource 数组的 Bean 属性。 * */public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:". */String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; /** * Return a Resource handle for the specified resource location. * <p>The handle should always be a reusable resource descriptor, * allowing for multiple {@link Resource#getInputStream()} calls. * <p><ul> * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat". * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat". * <li>Should support relative file paths, e.g. "WEB-INF/test.dat". * (This will be implementation-specific, typically provided by an * ApplicationContext implementation.) * </ul> * <p>Note that a Resource handle does not imply an existing resource; * you need to invoke {@link Resource#exists} to check for existence. * @param location the resource location * @return a corresponding Resource handle (never {@code null}) * @see #CLASSPATH_URL_PREFIX * @see Resource#exists() * @see Resource#getInputStream() */Resource getResource(String location); /** * Expose the ClassLoader used by this ResourceLoader. * <p>Clients which need to access the ClassLoader directly can do so * in a uniform manner with the ResourceLoader, rather than relying * on the thread context ClassLoader. * @return the ClassLoader * (only {@code null} if even the system ClassLoader isn't accessible) * @see org.springframework.util.ClassUtils#getDefaultClassLoader() * @see org.springframework.util.ClassUtils#forName(String, ClassLoader) */@NullableClassLoader getClassLoader();#getResource(String location) 方法,根据所提供资源的路径 location 返回 Resource 实例,但是它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断。

  

该方法支持以下模式的资源加载:

  

URL位置资源,如 "file:C:/test.dat" 。

  

ClassPath位置资源,如 "classpath:test.dat 。

  

相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同。

  

该方法的主要实现是在其子类 DefaultResourceLoader 中实现,具体过程我们在分析 DefaultResourceLoader 时做详细说明。

  

#getClassLoader() 方法,返回 ClassLoader 实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,可以直接调用该方法来获取。在分析 Resource 时,提到了一个类 ClassPathResource ,这个类是可以根据指定的 ClassLoader 来加载资源的。

  

子类结构如下:

  

  

DefaultResourceLoader 与 AbstractResource 相似,org.springframework.core.io.DefaultResourceLoader 是 ResourceLoader 的默认实现。

  

FileSystemResourceLoader继承 DefaultResourceLoader ,且覆写了 #getResourceByPath(String) 方法,使之从文件系统加载资源并以 FileSystemResource 类型返回,这样我们就可以得到想要的资源类型。

  

ClassRelativeResourceLoader 是 DefaultResourceLoader 的另一个子类的实现。和 FileSystemResourceLoader 类似,在实现代码的结构上类似,也是覆写 #getResourceByPath(String path) 方法,并返回其对应的 ClassRelativeContextResource 的资源类型。

  

PathMatchingResourcePatternResolver为 ResourcePatternResolver 最常用的子类,它除了支持 ResourceLoader 和 ResourcePatternResolver 新增的 classpath*: 前缀外,还支持 Ant 风格的路径匹配模式(类似于 "**/*.xml")。

  

至此 Spring 整个资源记载过程已经分析完毕。下面简要总结下:

  

Spring 提供了 Resource 和 ResourceLoader 来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。

  

AbstractResource 为 Resource 的默认抽象实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。

  

DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。

  

DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 Resource getResource(String location) 方法,也实现了 Resource<> getResources(String locationPattern) 方法。

  

BeanFactory与ApplicationContext体系

  

BeanFactory体系

  

下面来介绍一下Ioc的核心实现有哪些重要的类,先看BeanFactory的体系,类结构如下,这里把spring-context部分的实现去掉了。

  

  

可以看到里面的类还是比较多的,但是各司其职,每个类都有自己对应的职责,下面来介绍几个比较重点的类。

  

AutowireCapableBeanFactory接口提供了对现有bean进行自动装配的能力,设计目的不是为了用于一般的应用代码中,对于一般的应用代码应该使用BeanFactory和ListableBeanFactory。其他框架的代码集成可以利用这个接口去装配和填充现有的bean的实例,但是Spring不会控制这些现有bean的生命周期。

  

ConfigurableBeanFactory提供了bean工厂的配置机制(除了BeanFactory接口中的bean的工厂的客户端方法)。该BeanFactory接口不适应一般的应用代码中,应该使用BeanFactory和ListableBeanFactory。该扩展接口仅仅用于内部框架的使用,并且是对bean工厂配置方法的特殊访问。

  

ConfigurableListableBeanFactory 接口继承自ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory。大多数具有列出能力的bean工厂都应该实现此接口。此了这些接口的能力之外,该接口还提供了分析、修改bean的定义和单例的预先实例化的机制。这个接口不应该用于一般的客户端代码中,应该仅仅提供给内部框架使用。

  

AbstractBeanFactory 继承自FactoryBeanRegistrySupport,实现了ConfigurableBeanFactory接口。AbstractBeanFactory是BeanFactory的抽象基础类实现,提供了完整的ConfigurableBeanFactory的能力。

  

单例缓存

  

别名的管理

  

FactoryBean的处理

  

用于子bean定义的bean的合并

  

bean的摧毁接口

  

自定义的摧毁方法

  

BeanFactory的继承管理

  

AbstractAutowireCapableBeanFactory继承自AbstractBeanFactory,实现了AutowireCapableBeanFactory接口。该抽象了实现了默认的bean的创建。

  

提供了bean的创建、属性填充、装配和初始化

  

处理运行时bean的引用,解析管理的集合、调用初始化方法等

  

支持构造器自动装配,根据类型来对属性进行装配,根据名字来对属性进行装配

  

DefaultListableBeanFactory 继承自AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable接口。这个类是一个非常完全的BeanFactory,基于bean的定义元数据,通过后置处理器来提供可扩展性。

  

XmlBeanFactory 继承自DefaultListableBeanFactory,用来从xml文档中读取bean的定义的一个非常方便的类。最底层是委派给XmlBeanDefinitionReader,实际上等价于带有XmlBeanDefinitionReader的DefaultListableBeanFactory。 该类已经废弃,推荐使用的是DefaultListableBeanFactory 。

  

ApplicationContext体系

  

接下来看看更高层次的容器实现ApplicationContext的体系。类结构图如下,这里只展示了常用的实现,并且去掉了大部分spring-web模块的实现类:

  

  

ConfigurableApplicationContext 从上面的类的继承层次图能看到,ConfigurableApplicationContext是比较上层的一个接口,该接口也是比较重要的一个接口,几乎所有的应用上下文都实现了该接口。该接口在ApplicationContext的基础上提供了配置应用上下文的能力,此外提供了生命周期的控制能力。

  

AbstractApplicationContext是ApplicationContext接口的抽象实现,这个抽象类仅仅是实现了公共的上下文特性。这个抽象类使用了模板方法设计模式,需要具体的实现类去实现这些抽象的方法。

  

GenericApplicationContext继承自AbstractApplicationContext,是为通用目的设计的,它能加载各种配置文件,例如xml,properties等等。它的内部持有一个DefaultListableBeanFactory的实例,实现了BeanDefinitionRegistry接口,以便允许向其应用任何bean的定义的读取器。为了能够注册bean的定义,refresh()只允许调用一次。

  

AnnotationConfigApplicationContext继承自GenericApplicationContext,提供了注解配置(例如:Configuration、Component、inject等)和类路径扫描(scan方法)的支持,可以使用register(Class... annotatedClasses)来注册一个一个的进行注册。实现了AnnotationConfigRegistry接口,来完成对注册配置的支持,只有两个方法:register()和scan()。内部使用AnnotatedBeanDefinitionReader来完成注解配置的解析,使用ClassPathBeanDefinitionScanner来完成类路径下的bean定义的扫描。

  

AbstractXmlApplicationContext继承自AbstractRefreshableConfigApplicationContext,用于描绘包含能被XmlBeanDefinitionReader所理解的bean定义的XML文档。子类只需要实现getConfigResources和getConfigLocations来提供配置文件资源。

  

ClassPathXmlApplicationContext继承自AbstractXmlApplicationContext,和FileSystemXmlApplicationContext类似,只不过ClassPathXmlApplicationContext是用于处理类路径下的xml配置文件。文件的路径可以是具体的文件路径,例如:xxx/application.xml,也可以是ant风格的配置,例如:xxx/*-context.xml。

  

AnnotationConfigWebApplicationContext继承自AbstractRefreshableWebApplicationContext,接受注解的类作为输入(特殊的@Configuration注解类,一般的@Component注解类,与JSR-330兼容的javax.inject注解)。允许一个一个的注入,同样也能使用类路径扫描。对于web环境,基本上是和AnnotationConfigApplicationContext等价的。使用AnnotatedBeanDefinitionReader来对注解的bean进行处理,使用ClassPathBeanDefinitionScanner来对类路径下的bean进行扫描。

  

小结

  

这篇主要做了一些基础知识的准备,简单介绍了一些Ioc的概念,这里并没有举代码例子,只是通过生活中的容器去类比了一下Spring的容器。接下来对比分析了BeanFactory和ApplicationContext区别与联系,然后介绍了Spring的资源加载,Spring的许多元数据加载通过统一资源加载的方式去获取的,特别是classpath路径下文件的获取。最后我们简单看了一下BeanFactory和ApplicationContext的体系结构,展示常见的类图,并且有简单的描述,但是没有涉及太多的代码分析,主要也是混个眼熟。

  


  


  

如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,

  

咱们下期见!

  

学习更多JAVA知识与技巧,关注博主查看个人资料 或评论留言

  

  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  


  

作者:老马说开发y
原文出处:https://blog.csdn.net/lyl54545/article/details/124620057

相关文章