Spring源码分析

Spring源码解析

1.Spring 整体架构图:

我们在学习 Spring 的时候,有两个非常重要的地方:

  1. Core Container
  2. AOP

这两个是核心,也是我们到时候源码分析的重点,其他的 Data Access 、Web 等,基本都是以这两个为基础扩展出来的功能。

我来给大家挨个介绍一下。

2.Spring 模块

2.1 Core Container

Core Container 是 Spring 容器的核心模块,里边主要包含四个模块:Beans、Core、Context 以及 Expression Language,我们所熟知的 IoC/DI 就是由 Beans 和 Core 来提供。

我来分别介绍一下这几个模块的作用。

  • Core:这个是 Spring 的核心模块,它里边主要是 Spring 框架的一些基础工具类,比如一些序列化工具、类型转换器、我们常用的优先级注解等等,都是它提供的。
  • Beans:Beans 就没啥好说的,我们所熟知的 IoC/DI 就是由它提供的。
  • Context:Context 虽然不像前两个模块那么基础,因为它是基于 Core 和 Beans 构建的,但是 Context 也是我们在 Web 项目中必不可少的工具,资源加载、Event 等等都需要 Context。
  • Expression Language:SpEL 虽然归类于 Core Container,但是在目前前后端分离的背景下,其实 SpEL 的使用场景大大缩水。SpEL 是一个支持查询并在运行时可以操纵一个对象图的表达式语言,它的语法类似于统一 EL,但提供了更多的功能,而且它可以独立使用。

2.2 AOP

AOP 也是 Spring 中一个非常重要的功能模块,其实小伙伴们从平时的面试中应该就能感觉出来 AOP 的分量,可以说,如果没有 AOP,你就见不到 Spring 中很多令人惊叹的功能。像我们熟知的 Spring 中的事务管理,就离不开 Spring AOP。关于 AOP 的更多介绍,大家可以参考已经录制的 【Spring 基础篇】的视频,我就不再赘述。

2.3 Data Access

Data Access 模块中,主要是封装了一些数据库持久化相关的操作。比如 JDBC、ORM、OXM、JMS 以及事务。

  • JDBC:这个是对传统的 JDBC 的封装,传统的 JDBC 里边有很多冗余代码,Spring 利用自身特性对其进行封装,简化了数据库访问。
  • ORM:ORM 为我们常见的 ORM 框架(如 Hibernate、MyBatis 等)提供了一个交互层。
  • OXM:OXM 模块抽象了对象和 XML 之间的转换,O 是 Object,X 是 XML。
  • JMS:JMS 主要是对消息中间件的消息发送/消费提供封装。

2.4 Web

Web 模块里边包含几个部分,不过对于我们而言,使用更多的是 webmvc,也就是我们常说的 SpringMVC。

Web 主要包含如下模块:

  • Web:提供基础的 Web 功能,构建 Web 上下文、提供文件上传等功能。
  • WebMVC:提供 MVC 支持。
  • 其他一些冷门的模块我就不多做介绍了。

2.5 Test

对测试功能提供支持。

Spring 源码第一篇开整!配置文件是怎么加载的?

1.从何说起

Spring 要从何说起呢?这个问题我考虑了很长时间。

因为 Spring 源码太繁杂了,一定要选择一个合适的切入点,否则一上来就把各位小伙伴整懵了,那剩下的文章估计就不想看了。

想了很久之后,我决定就先从配置文件加载讲起,在逐步展开,配置文件加载也是我们在使用 Spring 时遇到的第一个问题,今天就先来说说这个话题。

2.简单的案例

先来一个简单的案例,大家感受一下,然后我们顺着案例讲起。

首先我们创建一个普通的 Maven 项目,引入 spring-beans 依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

然后我们创建一个实体类,再添加一个简单的配置文件:

public class User {
    private String username;
    private String address;
    //省略 getter/setter
}

resources 目录下创建配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.javaboy.loadxml.User" id="user"/>
</beans>

然后去加载这个配置文件:

public static void main(String[] args) {
    XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
    User user = factory.getBean(User.class);
    System.out.println("user = " + user);
}

这里为了展示数据的读取过程,我就先用这个已经过期的 XmlBeanFactory 来加载,这并不影响我们阅读源码。

上面这个是一个非常简单的 Spring 入门案例,相信很多小伙伴在第一次接触 Spring 的时候,写出来的可能都是这个 Demo。

在上面这段代码执行过程中,首先要做的事情就是先把 XML 配置文件加载到内存中,再去解析它,再去。。。。。

一步一步来吧,先来看 XML 文件如何被加入到内存中去。

3.文件读取

文件读取在 Spring 中很常见,也算是一个比较基本的功能,而且 Spring 提供的文件加载方式,不仅仅在 Spring 框架中可以使用,我们在项目中有其他文件加载需求也可以使用。

首先,Spring 中使用 Resource 接口来封装底层资源,Resource 接口本身实现自 InputStreamSource 接口:

我们来看下这两个接口的定义:

public interface InputStreamSource {
 InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
 boolean exists();
 default boolean isReadable() {
  return exists();
 }
 default boolean isOpen() {
  return false;
 }
 default boolean isFile() {
  return false;
 }
 URL getURL() throws IOException;
 URI getURI() throws IOException;
 File getFile() throws IOException;
 default ReadableByteChannel readableChannel() throws IOException {
  return Channels.newChannel(getInputStream());
 }
 long contentLength() throws IOException;
 long lastModified() throws IOException;
 Resource createRelative(String relativePath) throws IOException;
 @Nullable
 String getFilename();
 String getDescription();

}

代码倒不难,我来稍微解释下:

  1. InputStreamSource 类只提供了一个 getInputStream 方法,该方法返回一个 InputStream,也就是说,InputStreamSource 会将传入的 File 等资源,封装成一个 InputStream 再重新返回。
  2. Resource 接口实现了 InputStreamSource 接口,并且封装了 Spring 内部可能会用到的底层资源,如 File、URL 以及 classpath 等。
  3. exists 方法用来判断资源是否存在。
  4. isReadable 方法用来判断资源是否可读。
  5. isOpen 方法用来判断资源是否打开。
  6. isFile 方法用来判断资源是否是一个文件。
  7. getURL/getURI/getFile/readableChannel 分别表示获取资源对应的 URL/URI/File 以及将资源转为 ReadableByteChannel 通道。
  8. contentLength 表示获取资源的大小。
  9. lastModified 表示获取资源的最后修改时间。
  10. createRelative 表示根据当前资源创建一个相对资源。
  11. getFilename 表示获取文件名。
  12. getDescription 表示在资源出错时,详细打印出出错的文件。

当我们加载不同资源时,对应了 Resource 的不同实现类,来看下 Resource 的继承关系:

可以看到,针对不同类型的数据源,都有各自的实现,我们这里来重点看下 ClassPathResource 的实现方式。

ClassPathResource 源码比较长,我这里挑一些关键部分来和大家分享:

public class ClassPathResource extends AbstractFileResolvingResource {

 private final String path;

 @Nullable
 private ClassLoader classLoader;

 @Nullable
 private Class<?> clazz;

 public ClassPathResource(String path) {
  this(path, (ClassLoader) null);
 }
 public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
  Assert.notNull(path, "Path must not be null");
  String pathToUse = StringUtils.cleanPath(path);
  if (pathToUse.startsWith("/")) {
   pathToUse = pathToUse.substring(1);
  }
  this.path = pathToUse;
  this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
 }
 public ClassPathResource(String path, @Nullable Class<?> clazz) {
  Assert.notNull(path, "Path must not be null");
  this.path = StringUtils.cleanPath(path);
  this.clazz = clazz;
 }
 public final String getPath() {
  return this.path;
 }
 @Nullable
 public final ClassLoader getClassLoader() {
  return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
 }
 @Override
 public boolean exists() {
  return (resolveURL() != null);
 }
 @Nullable
 protected URL resolveURL() {
  if (this.clazz != null) {
   return this.clazz.getResource(this.path);
  }
  else if (this.classLoader != null) {
   return this.classLoader.getResource(this.path);
  }
  else {
   return ClassLoader.getSystemResource(this.path);
  }
 }
 @Override
 public InputStream getInputStream() throws IOException {
  InputStream is;
  if (this.clazz != null) {
   is = this.clazz.getResourceAsStream(this.path);
  }
  else if (this.classLoader != null) {
   is = this.classLoader.getResourceAsStream(this.path);
  }
  else {
   is = ClassLoader.getSystemResourceAsStream(this.path);
  }
  if (is == null) {
   throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
  }
  return is;
 }
 @Override
 public URL getURL() throws IOException {
  URL url = resolveURL();
  if (url == null) {
   throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
  }
  return url;
 }
 @Override
 public Resource createRelative(String relativePath) {
  String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
  return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
    new ClassPathResource(pathToUse, this.classLoader));
 }
 @Override
 @Nullable
 public String getFilename() {
  return StringUtils.getFilename(this.path);
 }
 @Override
 public String getDescription() {
  StringBuilder builder = new StringBuilder("class path resource [");
  String pathToUse = this.path;
  if (this.clazz != null && !pathToUse.startsWith("/")) {
   builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
   builder.append('/');
  }
  if (pathToUse.startsWith("/")) {
   pathToUse = pathToUse.substring(1);
  }
  builder.append(pathToUse);
  builder.append(']');
  return builder.toString();
 }
}
  1. 首先,ClassPathResource 的构造方法有四个,一个已经过期的方法我这里没有列出来。另外三个,我们一般调用一个参数的即可,也就是传入文件路径即可,它内部会调用另外一个重载的方法,给 classloader 赋上值(因为在后面要通过 classloader 去读取文件)。
  2. 在 ClassPathResource 初始化的过程中,会先调用 StringUtils.cleanPath 方法对传入的路径进行清理,所谓的路径清理,就是处理路径中的相对地址、Windows 系统下的 \ 变为 / 等。
  3. getPath 方法用来返回文件路径,这是一个相对路径,不包含 classpath。
  4. resolveURL 方法表示返回资源的 URL,返回的时候优先用 Class.getResource 加载,然后才会用 ClassLoader.getResource 加载,关于 Class.getResource 和 ClassLoader.getResource 的区别,又能写一篇文章出来,我这里就大概说下,Class.getResource 最终还是会调用 ClassLoader.getResource,只不过 Class.getResource 会先对路径进行处理。
  5. getInputStream 读取资源,并返回 InputStream 对象。
  6. createRelative 方法是根据当前的资源,再创建一个相对资源。

这是 ClassPathResource,另外一个大家可能会接触到的 FileSystemResource ,小伙伴们可以自行查看其源码,比 ClassPathResource 简单。

如果不是使用 Spring,我们仅仅想自己加载 resources 目录下的资源,也可以采用这种方式:

ClassPathResource resource = new ClassPathResource("beans.xml");
InputStream inputStream = resource.getInputStream();

拿到 IO 流之后自行解析即可。

在 Spring 框架,构造出 Resource 对象之后,接下来还会把 Resource 对象转为 EncodedResource,这里会对资源进行编码处理,编码主要体现在 getReader 方法上,在获取 Reader 对象时,如果有编码,则给出编码格式:

public Reader getReader() throws IOException {
 if (this.charset != null) {
  return new InputStreamReader(this.resource.getInputStream(), this.charset);
 }
 else if (this.encoding != null) {
  return new InputStreamReader(this.resource.getInputStream(), this.encoding);
 }
 else {
  return new InputStreamReader(this.resource.getInputStream());
 }
}

所有这一切搞定之后,接下来就是通过 XmlBeanDefinitionReader 去加载 Resource 了。

Spring 源码第二弹!XML 文件解析流程

1.XmlBeanDefinitionReader

上篇文章中,小伙伴们可以看到,XmlBeanFactory 中加载 XML 文件流的对象是 XmlBeanDefinitionReader,因此关于 XML 的解析我们就从 XmlBeanDefinitionReader 开始讲起。

先来看一张 XmlBeanDefinitionReader 的继承关系图:

这张继承关系图中涉及到了几个接口,我这里和大家说一下:

  1. BeanDefinitionReader:这个接口主要定义了资源文件的读取并将资源转为 BeanDefinition。
  2. EnvironmentCapable:这个接口定义了获取 Environment 的方法。
  3. AbstractBeanDefinitionReader:实现了 BeanDefinitionReader 和 EnvironmentCapable 接口中所定义的方法。同时,AbstractBeanDefinitionReader 中多了一个比较关键的属性叫做 ResourceLoader,ResourceLoader 可以根据给定的资源返回对应的 Resource。
  4. XmlBeanDefinitionReader 则在 AbstractBeanDefinitionReader 的基础上继续扩展了它的功能。

这是 XmlBeanDefinitionReader 的继承关系。

打开 XmlBeanDefinitionReader 的源码,我们发现还有两个关键的对象:

  • BeanDefinitionDocumentReader:BeanDefinitionDocumentReader 接口只有一个实现类就是 DefaultBeanDefinitionDocumentReader ,在这里定义了对 Document 对象的读取并将读取到的属性转为 BeanDefinition。
  • DocumentLoader:将资源文件转为 Document 对象。

担心有的小伙伴可能不知道 Document 是啥,我这里再稍微说两句。Document 就是 XML 解析时获取到的文档对象,Document 对象代表了一个 XML 文档的模型树,所有的其他 Node 都以一定的顺序包含在 Document 对象之内,排列成一个树状结构,以后对 XML 文档的所有操作都与解析器无关,直接在这个 Document 对象上进行操作即可。主流的 XML 解析方式有 SAX 解析、DOM 解析以及 Pull 解析。如果大家对于 XML 文件解析不熟悉的话,可以自行复习,松哥这里就不再啰嗦了。

好了,了解了 XmlBeanDefinitionReader 的继承关系以及里边定义的两个关键类之后,我们来大概梳理一下 XmlBeanDefinitionReader 的功能:

  1. 首先 XmlBeanDefinitionReader 继承自 AbstractBeanDefinitionReader,利用 AbstractBeanDefinitionReader 中的 ResourceLoader 将配置文件路径转为对应的 Resource。
  2. 接下来,利用 DocumentLoader 将 Resource 转为 Document。
  3. 最后,利用 BeanDefinitionDocumentReader 去解析 Document。

把这些先搞清楚之后,接下来我们来走流程。

2.走流程

不知道还记不记得上篇文章中松哥给出的一个简单案例:

public static void main(String[] args) {
    XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
    User user = factory.getBean(User.class);
    System.out.println("user = " + user);
}

我们就跟着 XmlBeanFactory 的构造方法来走一遍。

先来看 XmlBeanFactory 的构造方法:

public class XmlBeanFactory extends DefaultListableBeanFactory {
 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
 public XmlBeanFactory(Resource resource) throws BeansException {
  this(resource, null);
 }
 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
  super(parentBeanFactory);
  this.reader.loadBeanDefinitions(resource);
 }

}

XmlBeanFactory 的源码很简单,其实它的主要功能都在 DefaultListableBeanFactory 中实现了,松哥后面会专门写一篇文章来介绍 DefaultListableBeanFactory,这里我们先不做过多展开。

XmlBeanFactory 中定义了 XmlBeanDefinitionReader 用来读取 Resource,默认情况下,parentBeanFactory 为 null,具体的读取操作则是由 XmlBeanDefinitionReader#loadBeanDefinitions 方法提供的,我们来看下该方法:

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
 return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
 if (!currentResources.add(encodedResource)) {
  throw new BeanDefinitionStoreException(
    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
 }
 try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
  InputSource inputSource = new InputSource(inputStream);
  if (encodedResource.getEncoding() != null) {
   inputSource.setEncoding(encodedResource.getEncoding());
  }
  return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
 }
 catch (IOException ex) {
  throw new BeanDefinitionStoreException(
    "IOException parsing XML document from " + encodedResource.getResource(), ex);
 }
 finally {
  currentResources.remove(encodedResource);
  if (currentResources.isEmpty()) {
   this.resourcesCurrentlyBeingLoaded.remove();
  }
 }
}
  1. 在 loadBeanDefinitions 方法中,首先会将传入的 Resource 转为一个 EncodedResource,也就是对传入的资源进行编码,所谓的编码大家不要想的过于复杂,其实就是在将来读取资源的时候添加一个编码格式的参数,具体可以参见 EncodedResource#getReader 方法,因为比较简单,我这里就不贴出来了。
  2. 继续调用另外一个重载的 loadBeanDefinitions 方法,传入编码后的资源。
  3. 首先将当前资源添加到一个 ThreadLocal 中,这样可以避免重复加载。
  4. 将 XML 配置文件的 IO 流转为一个 InputSource 对象,InputSource 是 XML 文件解析的起点,XML 文件解析这块大家自己复习下,松哥就不做过多介绍了。
  5. 如果资源有编码格式,那就给 inputSource 对象也设置上编码格式。
  6. 调用 doLoadBeanDefinitions 方法做进一步的解析操作。
  7. 最后从集合中移除资源。

在上面第 6 步的时候,调用了 doLoadBeanDefinitions 方法,这个方法要做的事情就是去将资源文件解析成 Document 对象,如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
  throws BeanDefinitionStoreException {
 try {
  Document doc = doLoadDocument(inputSource, resource);
  int count = registerBeanDefinitions(doc, resource);
  return count;
 }
 catch (BeanDefinitionStoreException ex) {
  throw ex;
 }
 catch (SAXParseException ex) {
  throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
 }
 catch (SAXException ex) {
  throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    "XML document from " + resource + " is invalid", ex);
 }
 catch (ParserConfigurationException ex) {
  throw new BeanDefinitionStoreException(resource.getDescription(),
    "Parser configuration exception parsing XML from " + resource, ex);
 }
 catch (IOException ex) {
  throw new BeanDefinitionStoreException(resource.getDescription(),
    "IOException parsing XML document from " + resource, ex);
 }
 catch (Throwable ex) {
  throw new BeanDefinitionStoreException(resource.getDescription(),
    "Unexpected exception parsing XML document from " + resource, ex);
 }
}

可以看到,这里就是调用 doLoadDocument 进行资源解析,最终获取到一个 Document 对象。

我们来看一下 doLoadDocument 方法:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
 return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
   getValidationModeForResource(resource), isNamespaceAware());
}

可以看到,这里最终调用的是 documentLoader#loadDocument 方法,documentLoader 也就是松哥在第一小节和大家介绍的 DefaultDocumentLoader 对象。

该方法的调用,一共需要五个参数:

  1. 第一个 InputSource 不用多说,这是要调用的资源文件。
  2. 第二个 EntityResolver 主要是处理文件的验证方式的。
  3. 第三个 ErrorHandler 是一个错误处理器。
  4. 第四个 validationMode 是指 XML 文件的验证模式。
  5. 第五个 namespaceAware 表示是否开启自动感知名称空间。

具体的调用如下:

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
  ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
 if (logger.isTraceEnabled()) {
  logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
 }
 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
 return builder.parse(inputSource);
}

这里我就不做过多解释了,基本上到了 XML 解析的范畴了。小伙伴们自行复习一下 Java 中 XML 的解析方式。

3.小结

本文松哥主要和大家介绍了在 Spring 中,我们如何获取到一个 Document 对象,拿到 Document 对象,接下来解析 Document 对象,获取各种属性,就能定义出 BeanDefinition 了。

Spring 源码第三弹!EntityResolver 是个什么鬼?

先来回顾下,在 EntityResolver 这个类在上篇文章哪里出现了。

我们在讲到 doLoadDocument 方法时,在该方法中调用 loadDocument 方法时,传递的第二个参数就是一个 EntityResolver 实例,当时我们说这个是用来处理文件的验证方式的,但是到底是怎么处理的,今天我们就来看下。

1.XML 验证模式

要了解 EntityResolver,就得先来看看 XML 文件验证模式。

现在我们大多数情况下可能都是使用 JSON 传递数据,XML 使用较少,可能有的小伙伴对 XML 文件的一些规则还不太熟悉,我这里稍微说一下。

XML 是指可扩展标记语言(eXtensible Markup Language),它是一种标记语言,类似 HTML;XML 标签没有被预定义,需要用户自行定义标签,也就是 XML 文件中的节点都是用户自定义的。XML 文件从设计之初就是为了传输数据,而非显示数据。

一般来说,一个 XML 文件由六个部分组成:

  • 文档生命
  • 元素
  • 属性
  • 注释
  • CDATA 区
  • 处理指令

虽然说 XML 文件本身是没有预定义 XML 标签,但是当 XML 文件作为框架的配置时,对于 XML 标签还是要有一定的约束,否则每个人都按照自己的喜好定义 XML 标签,框架就没法读取这样的 XML 文件了。

在 XML 技术中,开发者可以通过一个文档来约束一个 XML 的文档中的标签,这个文档称之为约束。遵循 XML 语法的 XML 我们称之为格式良好的 XML,而遵循 XML 约束的 XML 我们称之为有效的 XML。XML 约束文档主要定义了在 XML 中允许出现的元素名称、属性及元素出现的顺序等等。

要想约束 XML 标签,有两种方式:

  1. DTD
  2. Schema

DTD(Document Type Definition),全称为文档类型定义,一个 DTD 约束文件我们既可以定义在 XML 文件内部,也可以定义一个本地文件,也可以引用一个网络上的公共的 DTD。

XML Schema 也是一种用于定义和描述 XML 文档结构与内容的模式语言,相比于 DTD,Schema 对于名称空间的支持更加友好,同时也支持更多的数据类型,而且它的约束能力也比较强大,另外还有非常重要的一点是,Schema 文档本身也是 XML 文档,而不是像 DTD 一样使用自成一体的语法。

所以,Schema 目前在 XML 约束这块更具备优势,也在逐渐替代 DTD。

大家在日常开发中,这两种约束可能都见过,但是有的人可能没注意。我给大家简单举一个例子。

早期的 Spring 配置头部是这样的(Spring2.x),这就是 DTD 约束:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"  
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">  
<beans>  
  
</beans>  

现在大家看到的 Spring 配置头部一般都是这样,这就是 Schema 约束:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

</beans>

schema 约束对命名空间有着很好的支持,命名空间可以防止命名冲突,schema 中的名称空间和约束文件都是成对出现的。

有了约束,XML 文件中该写什么不该写什么就固定下来了,这样框架才能成功解析出 XML 文件。

但是大家同时也发现了一个新的问题,无论是 DTD 还是 Schema 约束,给出的约束文件地址都是一个在线地址,这就意味着项目启动时必须能够访问到该在线地址,才能加载到约束文件,如果访问在线约束文件失败,那么项目启动也会失败。

为了解决这个问题,框架一般都是将约束文件放在本地的,在本地哪里呢?实际上就在你下载的 jar 包里。以 spring-beans 为例,在下载的 jar 包里有如下两个文件:

​ org.springframework.spring-bean:5.2.7.RELEASE------->spring.handlers,spring.schemas;

spring.handlers 文件内容如下:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

这其实一个映射配置,每一个名称空间对应的处理类在这里进行配置。

spring.schemas 文件内容如下(部分):

http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

可以看到,各种版本以及没有版本号的约束文件,都对应了同一个文件,就是 org/springframework/beans/factory/xml/spring-beans.xsd,打开这个文件目录,我们就可以看到约束文件:

所以我们虽然在 Spring 的 XML 配置中看到的约束文件是一个在线地址,实际上约束文件是从本地 jar 中读取的。

2.两种解析器

EntityResolver 就是用来处理 XML 验证的。我们先来看下 EntityResolver 接口的定义:

public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;

}

接口中就只有一个方法,就是加载约束文件。在 Spring 中,EntityResolver 的实现类是 DelegatingEntityResolver:

public class DelegatingEntityResolver implements EntityResolver {
 public static final String DTD_SUFFIX = ".dtd";
 public static final String XSD_SUFFIX = ".xsd";
 private final EntityResolver dtdResolver;
 private final EntityResolver schemaResolver;
 public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
  this.dtdResolver = new BeansDtdResolver();
  this.schemaResolver = new PluggableSchemaResolver(classLoader);
 }
 public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
  this.dtdResolver = dtdResolver;
  this.schemaResolver = schemaResolver;
 }
 @Override
 @Nullable
 public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
   throws SAXException, IOException {
  if (systemId != null) {
   if (systemId.endsWith(DTD_SUFFIX)) {
    return this.dtdResolver.resolveEntity(publicId, systemId);
   }
   else if (systemId.endsWith(XSD_SUFFIX)) {
    return this.schemaResolver.resolveEntity(publicId, systemId);
   }
  }
  return null;
 }
 @Override
 public String toString() {
  return "EntityResolver delegating " + XSD_SUFFIX + " to " + this.schemaResolver +
    " and " + DTD_SUFFIX + " to " + this.dtdResolver;
 }
}

在 DelegatingEntityResolver 类中:

  1. 首先通过两种不同的后缀来区分不同的约束。
  2. 然后定义了 dtdResolver 和 schemaResolver 两个不同的变量,对应的类型分别是 BeansDtdResolver 和 PluggableSchemaResolver,也就是 dtd 和 schema 的约束验证分别由这两个类来处理。
  3. 在 resolveEntity 方法中,根据解析出来不同的后缀,分别交由不同的 EntityResolver 来处理。resolveEntity 解析中有两个参数,如果是 dtd 解析的话,publicId 是有值的,如果是 schema 解析,publicId 为 null,而 systemId 则始终指向具体的约束文件。

由于现在大部分都是 schema 约束,所以这里我们就来重点看下 PluggableSchemaResolver 类的实现:

public class PluggableSchemaResolver implements EntityResolver {
 public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
 private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);
 @Nullable
 private final ClassLoader classLoader;
 private final String schemaMappingsLocation;
 @Nullable
 private volatile Map<String, String> schemaMappings;
 public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
  this.classLoader = classLoader;
  this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
 }
 public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
  Assert.hasText(schemaMappingsLocation, "'schemaMappingsLocation' must not be empty");
  this.classLoader = classLoader;
  this.schemaMappingsLocation = schemaMappingsLocation;
 }
 @Override
 @Nullable
 public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
  if (logger.isTraceEnabled()) {
   logger.trace("Trying to resolve XML entity with public id [" + publicId +
     "] and system id [" + systemId + "]");
  }
  if (systemId != null) {
   String resourceLocation = getSchemaMappings().get(systemId);
   if (resourceLocation == null && systemId.startsWith("https:")) {
    resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
   }
   if (resourceLocation != null) {
    Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
    try {
     InputSource source = new InputSource(resource.getInputStream());
     source.setPublicId(publicId);
     source.setSystemId(systemId);
     if (logger.isTraceEnabled()) {
      logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
     }
     return source;
    }
    catch (FileNotFoundException ex) {
     if (logger.isDebugEnabled()) {
      logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
     }
    }
   }
  }
  return null;
 }
 private Map<String, String> getSchemaMappings() {
  Map<String, String> schemaMappings = this.schemaMappings;
  if (schemaMappings == null) {
   synchronized (this) {
    schemaMappings = this.schemaMappings;
    if (schemaMappings == null) {
     try {
      Properties mappings =
        PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
      schemaMappings = new ConcurrentHashMap<>(mappings.size());
      CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
      this.schemaMappings = schemaMappings;
     }
     catch (IOException ex) {
      throw new IllegalStateException(
        "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
     }
    }
   }
  }
  return schemaMappings;
 }
 @Override
 public String toString() {
  return "EntityResolver using schema mappings " + getSchemaMappings();
 }
}
  1. 在这个类中,一上来先通过 DEFAULT_SCHEMA_MAPPINGS_LOCATION 变量定义了 spring.schemas 文件的位置。
  2. getSchemaMappings 方法则是将 spring.schemas 文件中的内容读取成一个 Map 加载进来。
  3. 在 resolveEntity 方法中,根据 systemId 找到文件路径,systemId 是 http\://www.springframework.org/schema/beans/spring-beans.xsd 格式,文件路径则是 org/springframework/beans/factory/xml/spring-beans.xsd,如果第一次没有加载到,就把用户的 https: 替换成 http: 再去加载。
  4. 有了文件路径,接下来调用 ClassPathResource 去获取一个 Resource 对象,这块可以参考本系列第二篇,这里我就不再赘述。
  5. 最后构造一个 InputSource 返回即可。

上篇文章中,我们获取 EntityResolver 是通过 getEntityResolver 方法来获取的:

protected EntityResolver getEntityResolver() {
 if (this.entityResolver == null) {
  // Determine default EntityResolver to use.
  ResourceLoader resourceLoader = getResourceLoader();
  if (resourceLoader != null) {
   this.entityResolver = new ResourceEntityResolver(resourceLoader);
  }
  else {
   this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
  }
 }
 return this.entityResolver;
}

这里最终返回的是 ResourceEntityResolver,ResourceEntityResolver 继承自 DelegatingEntityResolver,当调用 resolveEntity 方法时,也是先调用父类的该方法,进行处理,如果父类方法处理成功了,就直接返回父类方法给出的结果,如果父类方法处理失败了,则在 ResourceEntityResolver 中通过资源的相对路径再次尝试加载。

3.小结

好啦,经过上面的介绍,相信大家对于 XMl 约束和 EntityResolver 都有一定的了解啦。

Spring 源码第四弹!深入理解 BeanDefinition

上篇文章和小伙伴们介绍了 Spring 源码中的 EntityResolver,这个是用来解决 XML 文件校验问题的。

接下来本来应该接着第二弹的 XML 文件解析流程继续往下走了,考虑到接下来我们会涉及到一个重要的概念 BeanDefinition,而有的小伙伴对此可能还不熟悉,因此本文松哥就先来和大家捋一捋 BeanDefinition 是什么东西!

本文是 Spring 源码解读第五篇,阅读本系列前面文章有助于更好的理解本文:

  1. Spring 源码解读计划
  2. Spring 源码第一篇开整!配置文件是怎么加载的?
  3. Spring 源码第二弹!XML 文件解析流程
  4. Spring 源码第三弹!EntityResolver 是个什么鬼?

1.BeanDefinition

在 Spring 容器中,我们广泛使用的是一个一个的 Bean,BeanDefinition 从名字上就可以看出是关于 Bean 的定义。

事实上就是这样,我们在 XML 文件中配置的 Bean 的各种属性,这些属性不仅仅是和对象相关,Spring 容器还要解决 Bean 的生命周期、销毁、初始化等等各种操作,我们定义的关于 Bean 的生命周期、销毁、初始化等操作总得有一个对象来承载,那么这个对象就是 BeanDefinition。

XML 中定义的各种属性都会先加载到 BeanDefinition 上,然后通过 BeanDefinition 来生成一个 Bean,从这个角度来说,BeanDefinition 和 Bean 的关系有点类似于类和对象的关系。

要理解 BeanDefinition,我们从 BeanDefinition 的继承关系开始看起。

BeanDefinition 是一个接口,继承自 BeanMetadataElement 和 AttributeAccessor 接口。

  • BeanMetadataElement:该接口只有一个方法 getSource,该方法返回 Bean 的来源。
  • AttributeAccessor:该接口主要规范了问任意对象元数据的方法。

我们来看下 AttributeAccessor:

public interface AttributeAccessor {
 void setAttribute(String name, @Nullable Object value);
 @Nullable
 Object getAttribute(String name);
 @Nullable
 Object removeAttribute(String name);
 boolean hasAttribute(String name);
 String[] attributeNames();
}

这里定义了元数据的访问接口,具体的实现则是 AttributeAccessorSupport,这些数据采用 LinkedHashMap 进行存储。

这是 BeanDefinition 所继承的两个接口。接下来我们来看下 BeanDefinition 接口:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
 String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
 String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
 int ROLE_APPLICATION = 0;
 int ROLE_SUPPORT = 1;
 int ROLE_INFRASTRUCTURE = 2;
 void setParentName(@Nullable String parentName);
 @Nullable
 String getParentName();
 void setBeanClassName(@Nullable String beanClassName);
 @Nullable
 String getBeanClassName();
 void setScope(@Nullable String scope);
 @Nullable
 String getScope();
 void setLazyInit(boolean lazyInit);
 boolean isLazyInit();
 void setDependsOn(@Nullable String... dependsOn);
 @Nullable
 String[] getDependsOn();
 void setAutowireCandidate(boolean autowireCandidate);
 boolean isAutowireCandidate();
 void setPrimary(boolean primary);
 boolean isPrimary();
 void setFactoryBeanName(@Nullable String factoryBeanName);
 @Nullable
 String getFactoryBeanName();
 void setFactoryMethodName(@Nullable String factoryMethodName);
 @Nullable
 String getFactoryMethodName();
 ConstructorArgumentValues getConstructorArgumentValues();
 default boolean hasConstructorArgumentValues() {
  return !getConstructorArgumentValues().isEmpty();
 }
 MutablePropertyValues getPropertyValues();
 default boolean hasPropertyValues() {
  return !getPropertyValues().isEmpty();
 }
 void setInitMethodName(@Nullable String initMethodName);
 @Nullable
 String getInitMethodName();
 void setDestroyMethodName(@Nullable String destroyMethodName);
 @Nullable
 String getDestroyMethodName();
 void setRole(int role);
 int getRole();
 void setDescription(@Nullable String description);
 @Nullable
 String getDescription();
 ResolvableType getResolvableType();
 boolean isSingleton();
 boolean isPrototype();
 boolean isAbstract();
 @Nullable
 String getResourceDescription();
 @Nullable
 BeanDefinition getOriginatingBeanDefinition();
}

BeanDefinition 中的方法虽然多,但是结合我们平时在 XML 中的配置,这些方法其实都很好理解:

  1. 首先一开始定义了两个变量用来描述 Bean 是不是单例的,后面的 setScope/getScope 方法可以用来修改/获取 scope 属性。
  2. ROLE_xxx 用来描述一个 Bean 的角色,ROLE_APPLICATION 表示这个 Bean 是用户自己定义的 Bean;ROLE_SUPPORT 表示这个 Bean 是某些复杂配置的支撑部分;ROLE_INFRASTRUCTURE 表示这是一个 Spring 内部的 Bean,通过 setRole/getRole 可以修改。
  3. setParentName/getParentName 用来配置 parent 的名称,这块可能有的小伙伴使用较少,这个对应着 XML 中的 <bean parent=""> 配置。
  4. setBeanClassName/getBeanClassName 这个就是配置 Bean 的 Class 全路径,对应 XML 中的 <bean class=""> 配置。
  5. setLazyInit/isLazyInit 配置/获取 Bean 是否懒加载,这个对应了 XML 中的 <bean lazy-init=""> 配置。
  6. setDependsOn/getDependsOn 配置/获取 Bean 的依赖对象,这个对应了 XML 中的 <bean depends-on=""> 配置。
  7. setAutowireCandidate/isAutowireCandidate 配置/获取 Bean 是否是自动装配,对应了 XML 中的 <bean autowire-candidate=""> 配置。
  8. setPrimary/isPrimary 配置/获取当前 Bean 是否为首选的 Bean,对应了 XML 中的 <bean primary=""> 配置。
  9. setFactoryBeanName/getFactoryBeanName 配置/获取 FactoryBean 的名字,对应了 XML 中的 <bean factory-bean=""> 配置,factory-bean 松哥在之前的入门视频中讲过,小伙伴们可以参考这里:https://www.bilibili.com/video/BV1Wv41167TU。
  10. setFactoryMethodName/getFactoryMethodName 和上一条成对出现的,对应了 XML 中的 <bean factory-method=""> 配置,不再赘述。
  11. getConstructorArgumentValues 返回该 Bean 构造方法的参数值。
  12. hasConstructorArgumentValues 判断上一条是否是空对象。
  13. getPropertyValues 这个是获取普通属性的集合。
  14. hasPropertyValues 判断上一条是否为空对象。
  15. setInitMethodName/setDestroyMethodName 配置 Bean 的初始化方法、销毁方法。
  16. setDescription/getDescription 配置/返回 Bean 的描述。
  17. isSingleton Bean 是否为单例。
  18. isPrototype Bean 是否为原型。
  19. isAbstract Bean 是否抽象。
  20. getResourceDescription 返回定义 Bean 的资源描述。
  21. getOriginatingBeanDefinition 如果当前 BeanDefinition 是一个代理对象,那么该方法可以用来返回原始的 BeanDefinition 。

这个就是 BeanDefinition 的定义以及它里边方法的含义。

2.BeanDefinition 实现类

上面只是 BeanDefinition 接口的定义,BeanDefinition 还拥有诸多实现类,我们也来大致了解下。

先来看一张继承关系图:

这么多实现类看着有点眼花缭乱,不过搞清楚了每一个接口和类的作用,再看就很容易了。

2.1 AbstractBeanDefinition

AbstractBeanDefinition 是一个抽象类,它根据 BeanDefinition 中定义的接口提供了相应的属性,并实现了 BeanDefinition 中定义的一部分方法。BeanDefinition 中原本只是定义了一系列的 get/set 方法,并没有提供对应的属性,在 AbstractBeanDefinition 中将所有的属性定义出来了。

后面其他的实现类也基本上都是在 AbstractBeanDefinition 的基础上完成的。

2.2 RootBeanDefinition

这是一个比较常用的实现类,对应了一般的元素标签。

2.3 ChildBeanDefinition

可以让子 BeanDefinition 定义拥有从父 BeanDefinition 那里继承配置的能力。

2.4 GenericBeanDefinition

GenericBeanDefinition 是从 Spring2.5 以后新加入的 BeanDefinition 实现类。GenericBeanDefinition 可以动态设置父 Bean,同时兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能。

2.5 AnnotatedBeanDefinition

表示注解类型 BeanDefinition,拥有获取注解元数据和方法元数据的能力。

2.6 AnnotatedGenericBeanDefinition

使用了 @Configuration 注解标记配置类会解析为 AnnotatedGenericBeanDefinition。

3.实践

理论讲了这么多,接下来我们通过几行代码来实践下,验证一下我们前面所说的对不对。

首先项目中添加 spring-context 依赖,如下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

然后我们来创建一个 User 类,如下:

public class User {
    private String username;
    private String address;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

接下来我们先来验证 RootBeanDefinition。我们自己纯手工定义一个 RootBeanDefinition,并且将之注册到 Spring 容器中去。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
ctx.refresh();
User bean = ctx.getBean(User.class);
System.out.println(bean);

MutablePropertyValues 是定义对象中的一个一个属性,构造 RootBeanDefinition 的时候,我们传入了类名称和属性集合,最终把 rootBeanDefinition 注册到容器中去。剩下的事情由容器完成,然后我们就可以从容器中获取到 User 对象了。

最终输出结果如下:

User{username='javaboy', address='www.javaboy.org'}

看了这个例子,小伙伴们应该能够大致明白,我们在 XML 中定义的各种属性,就是先被解析到 BeanDefinition 中,然后再注册到 Spring 容器中去,最后拿到我们需要的 Bean。

ChildBeanDefinition 具有从父 Bean 继承数据的能力,我们来看下这个怎么用。

首先新建一个 Person 类,Person 类在 User 类的基础上增加一个 nickname 属性,这样 Person 就可以继承到 User 的 username 和 address 两个属性的值了:

public class Person {
    private String username;
    private String address;
    private String nickname;

    @Override
    public String toString() {
        return "Person{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
}

接下来自定义 ChildBeanDefinition:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition("user");
childBeanDefinition.setBeanClass(Person.class);
childBeanDefinition.getPropertyValues().add("nickname", "江南一点雨");
ctx.registerBeanDefinition("person", childBeanDefinition);
ctx.refresh();
User user = ctx.getBean(User.class);
Person person = ctx.getBean(Person.class);
System.out.println("user = " + user);
System.out.println("person = " + person);

首先定义 RootBeanDefinition 并注册到 Spring 容器中,然后再定义 ChildBeanDefinition,ChildBeanDefinition 继承了 RootBeanDefinition 中现有的属性值。

最后我们从 Spring 容器中获取 User 和 Person,打印结果如下:

user = User{username='javaboy', address='www.javaboy.org'}
person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一点雨'}

可以看到,Person 确实继承了 User 的属性值。

RootBeanDefinition 和 ChildBeanDefinition 都可以被 GenericBeanDefinition 代替,效果一样,如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
rootBeanDefinition.setBeanClass(User.class);
rootBeanDefinition.setPropertyValues(pvs);
ctx.registerBeanDefinition("user",rootBeanDefinition);
GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
childBeanDefinition.setParentName("user");
childBeanDefinition.setBeanClass(Person.class);
childBeanDefinition.getPropertyValues().add("nickname", "江南一点雨");
ctx.registerBeanDefinition("person", childBeanDefinition);
ctx.refresh();
User user = ctx.getBean(User.class);
Person person = ctx.getBean(Person.class);
System.out.println("user = " + user);
System.out.println("person = " + person);

运行结果如下:

user = User{username='javaboy', address='www.javaboy.org'}
person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一点雨'}

可以看到,和前面的运行效果一致。

在我们本系列前面文章(Spring 源码第一篇开整!配置文件是怎么加载的?)的案例中,默认使用的也是 GenericBeanDefinition,如下:

现在 Spring Boot 广泛流行之后,Java 配置使用越来越多,以 @Configuration 注解标记配置类会被解析为 AnnotatedGenericBeanDefinition;以 @Bean 注解标记的 Bean 会被解析为 ConfigurationClassBeanDefinition。

我们新建一个 MyConfig 配置类,如下:

@Configuration
public class MyConfig {
    @Bean
    User user() {
        return new User();
    }
}

查看获取到的 BeanDefinition 结果如下:

而其他 @Service、@Controller、@Repository 以及 @Component 等注解标记的 Bean 则会被识别为 ScannedGenericBeanDefinition。这个我就不一一演示了,小伙伴们可以自行测试哦。

4.小结

好啦,今天主要是和小伙伴们介绍一下 Spring 中的 BeanDefinition。通过上面的原理+案例,相信小伙伴们已经明白,我们通过 XML 或者 Java 注解配置的 Bean,我们定义的东西会先被解析成 BeanDefinition,然后再通过 BeanDefinition 来生成我们所需要的 Bean。

下篇文章我们就来看这个 BeanDefinition 到底是怎么从 XML 文件中生成的。

//todo

Spring 源码第六弹!松哥和大家聊聊容器的始祖 DefaultListableBeanFactory

要说 XmlBeanFactory 就不得不先说它的父类 DefaultListableBeanFactory,因为 XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 IO 流的读取做了一些定制而已。

DefaultListableBeanFactory 是一个完整的、功能成熟的 IoC 容器,如果你的需求很简单,甚至可以直接使用 DefaultListableBeanFactory,如果你的需求比较复杂,那么通过扩展 DefaultListableBeanFactory 的功能也可以达到,可以说 DefaultListableBeanFactory 是整个 Spring IoC 容器的始祖。

我们先来看一下 DefaultListableBeanFactory 的继承关系:

从这张类的关系图中可以看出,DefaultListableBeanFactory 实际上也是一个集大成者。在 Spring 中,针对 Bean 的不同操作都有不同的接口进行规范,每个接口都有自己对应的实现,最终在 DefaultListableBeanFactory 中将所有的实现汇聚到一起。从这张类的继承关系图中我们大概就能感受到 Spring 中关于类的设计是多么厉害,代码耦合度非常低。

这些类,在本系列后面的介绍中,大部分都会涉及到,现在我先大概介绍一下每个类的作用,大家先混个脸熟:

  1. BeanFactory:这个接口看名字就知道是一个 Bean 的工厂,BeanFactory 接口定义了各种获取 Bean 的方法、判断 Bean 是否存在、判断 Bean 是否单例等针对 Bean 的基础方法。
  2. ListableBeanFactory:这个接口继承自 BeanFactory,在 BeanFactory 的基础上,扩展了 Bean 的查询方法,例如根据类型获取 BeanNames、根据注解获取 BeanNames、根据 Bean 获取注解等。
  3. AutowireCapableBeanFactory:该接口继承自 BeanFactory,在 BeanFactory 的基础上,提供了 Bean 的创建、配置、注入、销毁等操作。有时候我们需要自己手动注入 Bean 的时候,可以考虑通过实现该接口来完成。AutowireCapableBeanFactory 在 Spring Security 中有一个重要的应用就是 ObjectPostProcessor,这个松哥将在 👉Spring Security 系列中和大家详细介绍。
  4. HierarchicalBeanFactory:该接口继承自 BeanFactory,并在 BeanFactory 基础上添加了获取 parent beanfactory 的方法。
  5. SingletonBeanRegistry:这个接口定义了对单例 Bean 的定义以及获取方法。
  6. ConfigurableBeanFactory:这个接口主要定了针对 BeanFactory 的各种配置以及销毁的方法。
  7. ConfigurableListableBeanFactory:这是 BeanFactory 的配置清单,这里定义了忽略的类型、接口,通过 Bean 的名称获取 BeanDefinition 、冻结 BeanDefinition 等。
  8. AliasRegistry:这个接口定义了对 alias 的注册、移除、判断以及查询操作。
  9. SimpleAliasRegistry:这个类实现了 AliasRegistry 接口并实现了它里边的方法,SimpleAliasRegistry 使用 ConcurrentHashMap 做载体,实现了对 alias 的注册、移除判断以及查询操作。
  10. DefaultSingletonBeanRegistry:这个类基于 Java 中的集合,对 SingletonBeanRegistry 接口进行了实现。
  11. FactoryBeanRegistrySupport:该类继承自 DefaultSingletonBeanRegistry,并在 DefaultSingletonBeanRegistry 的基础上,增加了获取 FactoryBean 类型、移除 FactoryBean 缓存的方法等等操作。
  12. AbstractBeanFactory:实现了 ConfigurableBeanFactory 接口并继承自 FactoryBeanRegistrySupport,在 AbstractBeanFactory 中对 ConfigurableBeanFactory 中定义的方法进行了实现。
  13. AbstractAutowireCapableBeanFactory:该类继承自 AbstractBeanFactory 并对 AutowireCapableBeanFactory 接口中定义的方法进行了落地实现。
  14. BeanDefinitionRegistry:这个接口继承自 AliasRegistry 接口,并增加了一系列针对 BeanDefinition 的注册、移除、查询、判断等方法。
  15. 最后的 DefaultListableBeanFactory 自然就具备了上面所有的功能。

上面的内容可能看的大家眼花缭乱,松哥这里通过几个简单实际的例子,来带大家使用一下 DefaultListableBeanFactory 的功能,可能大家的理解就比较清晰了。

DefaultListableBeanFactory 作为一个集大成者,提供了非常多的功能,我们一个一个来看。

2.代码改造

首先文章中一开始的三行代码我们可以对其略加改造,因为我们已经说了 XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 IO 流的读取做了一些定制而已,文件的读取主要是通过 XmlBeanDefinitionReader 来完成的(本系列前面文章已经讲过),我们可以对文章一开始的三行代码进行改造,以便更好的体现“XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了”:

ClassPathResource res=new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
User user = factory.getBean(User.class);
System.out.println("user = " + user);

使用前四行代码代替 XmlBeanFactory,这样 XmlBeanFactory 的功能是不是就很明确了?就是前四行代码的功能。

3.动态注册 Bean

动态注册 Bean,这是 DefaultListableBeanFactory 的功能之一,不过准确来说应该是动态注册 BeanDefinition 。

我们先来看一个简单的例子:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
userBeanDefinition.setPropertyValues(pvs);
userBeanDefinition.setBeanClass(User.class);
defaultListableBeanFactory.registerBeanDefinition("user", userBeanDefinition);
User user = defaultListableBeanFactory.getBean(User.class);
System.out.println("user = " + user);

首先我们自己手动构建一个 DefaultListableBeanFactory 对象。当然也可以使用前面的 XmlBeanFactory。

然后再手动构建一个 GenericBeanDefinition。在前面的文章中,松哥和大家讲过,现在默认使用的 BeanDefinition 就是 GenericBeanDefinition,所以这里我们自己也手动构建一个 GenericBeanDefinition。有了 GenericBeanDefinition 之后,我们设置相关的类和属性。

接下来再将 userBeanDefinition 注册到 defaultListableBeanFactory。注册完成之后,我们就可以从 defaultListableBeanFactory 中获取相应的 Bean 了。

这里说一句题外话,希望大家在阅读本系列每一篇文章的时候,能够将本系列前后文章联系起来一起理解,这样会有很多意料之外的收获。例如上面的,我们既可以声明一个 DefaultListableBeanFactory,也可以声明一个 XmlBeanFactory,那你大概就能据此推断出 XmlBeanFactory 的主要目的可能就是对资源文件进行读取和注册。

那么到底是怎么注册的呢?我们来看一下 defaultListableBeanFactory.registerBeanDefinition 方法的定义:

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  throws BeanDefinitionStoreException {
 Assert.hasText(beanName, "Bean name must not be empty");
 Assert.notNull(beanDefinition, "BeanDefinition must not be null");
 if (beanDefinition instanceof AbstractBeanDefinition) {
  try {
   ((AbstractBeanDefinition) beanDefinition).validate();
  }
  catch (BeanDefinitionValidationException ex) {
   throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
     "Validation of bean definition failed", ex);
  }
 }
 BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
 if (existingDefinition != null) {
  if (!isAllowBeanDefinitionOverriding()) {
   throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
  }
  else if (existingDefinition.getRole() < beanDefinition.getRole()) {
   // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
   if (logger.isInfoEnabled()) {
    logger.info("Overriding user-defined bean definition for bean '" + beanName +
      "' with a framework-generated bean definition: replacing [" +
      existingDefinition + "] with [" + beanDefinition + "]");
   }
  }
  else if (!beanDefinition.equals(existingDefinition)) {
   if (logger.isDebugEnabled()) {
    logger.debug("Overriding bean definition for bean '" + beanName +
      "' with a different definition: replacing [" + existingDefinition +
      "] with [" + beanDefinition + "]");
   }
  }
  else {
   if (logger.isTraceEnabled()) {
    logger.trace("Overriding bean definition for bean '" + beanName +
      "' with an equivalent definition: replacing [" + existingDefinition +
      "] with [" + beanDefinition + "]");
   }
  }
  this.beanDefinitionMap.put(beanName, beanDefinition);
 }
 else {
  if (hasBeanCreationStarted()) {
   // Cannot modify startup-time collection elements anymore (for stable iteration)
   synchronized (this.beanDefinitionMap) {
    this.beanDefinitionMap.put(beanName, beanDefinition);
    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
    updatedDefinitions.addAll(this.beanDefinitionNames);
    updatedDefinitions.add(beanName);
    this.beanDefinitionNames = updatedDefinitions;
    removeManualSingletonName(beanName);
   }
  }
  else {
   // Still in startup registration phase
   this.beanDefinitionMap.put(beanName, beanDefinition);
   this.beanDefinitionNames.add(beanName);
   removeManualSingletonName(beanName);
  }
  this.frozenBeanDefinitionNames = null;
 }
 if (existingDefinition != null || containsSingleton(beanName)) {
  resetBeanDefinition(beanName);
 }
 else if (isConfigurationFrozen()) {
  clearByTypeCache();
 }
}

registerBeanDefinition 方法是在 BeanDefinitionRegistry 接口中声明的,DefaultListableBeanFactory 类实现了 BeanDefinitionRegistry 接口,并实现了该方法,我们来看分析下该方法:

  1. 首先对传入的 beanDefinition 对象进行校验,这也是注册前的最后一次校验,不过这个时候 BeanDefinition 对象已经到手了,所以这个校验并非 XML 文件校验,这里主要是对 methodOverrides 的校验。
  2. 接下来会根据 beanName 从 beanDefinitionMap 中获取 BeanDefinition,看看当前 Bean 是否已经定义过了。beanDefinitionMap 是一个 Map 集合,这个集合中 key 是 beanName,value 是 BeanDefinition 对象。
  3. 如果 BeanDefinition 已经存在了,那么接下来会判断是否允许 BeanDefinition 覆盖,如果不允许,就直接抛出异常(不知道小伙伴们有没有印象,在松哥前面的 OAuth2 系列教程中,经常需要配置允许 BeanDefinition 的覆盖,就是因为这个原因,公众号【江南一点雨】后台回复 OAuth2 获取该教程),如果允许 BeanDefinition 的覆盖,那就向 beanDefinitionMap 中再次存一次值,覆盖之前的值。
  4. 如果 BeanDefinition 不存在,那就直接注册。直接注册分两种情况:项目已经运行了和项目还没运行。
  5. 如果项目已经运行,由于 beanDefinitionMap 是一个全局变量,可能存在并发问题,所以要加锁处理。否则就直接注册,所谓的注册就是把对象存入 beanDefinitionMap 中,同时将 beanName 都存入 beanDefinitionNames 集合中。

这便是 registerBeanDefinition 方法的工作流程。

有小伙伴会说,这个方法从头到尾都是 BeanDefinition,跟 Bean 有什么关系呢?

咋一看确实好像和 Bean 没有直接关系。

其实这涉及到另外一个问题,就是 Bean 的懒加载。这个时候先把 BeanDefinition 定义好,等到真正调用 Bean 的时候,才会去初始化 Bean。我们可以在 User 类的构造方法中打印日志看下,如下:

public class User {
    private String username;
    private String address;

    public User() {
        System.out.println("--------user init--------");
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

从下图可以看到,当 BeanDefinition 注册完成后,User 并没有初始化,等到 getBean 方法被调用的时候,User 才初始化了。

需要注意的是,我们日常开发中使用的 ApplicationContext 并非懒加载,这个在松哥的 Spring 入门视频中可以看到效果【👉https://www.bilibili.com/video/BV1Wv41167TU】,具体原理松哥将在本系列后面的文章中和大家分享。

那么如果不想懒加载该怎么办呢?当然有办法。

4.提前注册 Bean

在 DefaultListableBeanFactory 中还有一个 preInstantiateSingletons 方法可以提前注册 Bean,该方法是在 ConfigurableListableBeanFactory 接口中声明的,DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口并实现了接口中的方法:

@Override
public void preInstantiateSingletons() throws BeansException {
 if (logger.isTraceEnabled()) {
  logger.trace("Pre-instantiating singletons in " + this);
 }
 // Iterate over a copy to allow for init methods which in turn register new bean definitions.
 // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
 List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
 // Trigger initialization of all non-lazy singleton beans...
 for (String beanName : beanNames) {
  RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
   if (isFactoryBean(beanName)) {
    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    if (bean instanceof FactoryBean) {
     final FactoryBean<?> factory = (FactoryBean<?>) bean;
     boolean isEagerInit;
     if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
      isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
          ((SmartFactoryBean<?>) factory)::isEagerInit,
        getAccessControlContext());
     }
     else {
      isEagerInit = (factory instanceof SmartFactoryBean &&
        ((SmartFactoryBean<?>) factory).isEagerInit());
     }
     if (isEagerInit) {
      getBean(beanName);
     }
    }
   }
   else {
    getBean(beanName);
   }
  }
 }
 // Trigger post-initialization callback for all applicable beans...
 for (String beanName : beanNames) {
  Object singletonInstance = getSingleton(beanName);
  if (singletonInstance instanceof SmartInitializingSingleton) {
   final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
   if (System.getSecurityManager() != null) {
    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
     smartSingleton.afterSingletonsInstantiated();
     return null;
    }, getAccessControlContext());
   }
   else {
    smartSingleton.afterSingletonsInstantiated();
   }
  }
 }
}

preInstantiateSingletons 方法的整体逻辑比较简单,就是遍历 beanNames,对符合条件的 Bean 进行实例化,而且大家注意,这里所谓的提前初始化其实就是在我们调用 getBean 方法之前,它自己先调用了一下 getBean。

我们可以在案例中手动调用该方法:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("username", "javaboy");
pvs.add("address", "www.javaboy.org");
userBeanDefinition.setPropertyValues(pvs);
userBeanDefinition.setBeanClass(User.class);
defaultListableBeanFactory.registerBeanDefinition("user", userBeanDefinition);
defaultListableBeanFactory.preInstantiateSingletons();
User user = defaultListableBeanFactory.getBean(User.class);
System.out.println("user = " + user);

此时在调用 getBean 方法之前,User 就已经初始化了,如下图:

5.getBean

DefaultListableBeanFactory 中另外一个重量级方法就是 getBean 了。不过 getBean 方法的真正实现是在 DefaultListableBeanFactory 的父类 AbstractBeanFactory 中,具体的实现方法是 doGetBean,本来想和大家子在这里聊一聊这个问题,但是发现这是一个非常庞大的问题,BeanFactory 和 FactoryBean 都还没和大家分享,所以这个话题我们还是暂且押后,一个点一个点来。

6.小结

好啦,今天就先说这么多,每篇源码我都尽量配置套一些小案例来演示,这样避免大家看的太枯燥了,我们下周继续~

spring bean的生命周期

  • 1.Spring对Bean进行实例化(相当于程序中的new Xx())

  • 2.Spring将值和Bean的引用注入进Bean对应的属性中

  • 3.如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法(实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的)

  • 4.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)

  • 5.如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把y应用上下文作为参数传入.(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )

  • 6.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能)

  • 7.如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。

  • 8.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )

  • 9.经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁

  • 10.如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。

spring的BeanFactory和FactoryBean有什么区别。

一、BeanFactory

BeanFactory是一个接口,它是Spring中工厂的顶层规范,是SpringIoc容器的核心接口,它定义了getBean()containsBean()等管理Bean的通用方法。Spring的容器都是它的具体实现如:

  • DefaultListableBeanFactory
  • XmlBeanFactory
  • ApplicationContext

这些实现类又从不同的维度分别有不同的扩展。

1.1、源码

public interface BeanFactory {

	//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
	//如果需要得到工厂本身,需要转义
	String FACTORY_BEAN_PREFIX = "&";

	//根据bean的名字,获取在IOC容器中得到bean实例
	Object getBean(String name) throws BeansException;

	//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
	<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	//提供对bean的检索,看看是否在IOC容器有这个名字的bean
	boolean containsBean(String name);

	//根据bean名字得到bean实例,并同时判断这个bean是不是单例
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

	//得到bean实例的Class类型
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
	String[] getAliases(String name);
}

1.1、使用场景

  • 从Ioc容器中获取Bean(byName or byType)
  • 检索Ioc容器中是否包含指定的Bean
  • 判断Bean是否为单例

二、FactoryBean

首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。

2.1、源码

public interface FactoryBean<T> {

	//从工厂中获取bean
	@Nullable
	T getObject() throws Exception;

	//获取Bean工厂创建的对象的类型
	@Nullable
	Class<?> getObjectType();

	//Bean工厂创建的对象是否是单例模式
	default boolean isSingleton() {
		return true;
	}
}

从它定义的接口可以看出,FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象,而不是A本身,如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。

  • getObject('name')返回工厂中的实例
  • getObject('&name')返回工厂本身的实例

通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的 角色;但少数情况下,容器中的 bean 本身就是工厂,作用是产生其他 bean 实例。由工厂 bean 产生的其他 bean 实例,不再由 Spring 容器产生,因此与普通 bean 的配置不同,不再需要提供 class 元素。

2.2、示例

先定义一个Bean实现FactoryBean接口

@Component
public class MyBean implements FactoryBean {
    private String message;
    public MyBean() {
        this.message = "通过构造方法初始化实例";
    }
    @Override
    public Object getObject() throws Exception {
        // 这里并不一定要返回MyBean自身的实例,可以是其他任何对象的实例。
        //如return new Student()...
        return new MyBean("通过FactoryBean.getObject()创建实例");
    }
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
    public String getMessage() {
        return message;
    }
}

MyBean实现了FactoryBean接口的两个方法,getObject()是可以返回任何对象的实例的,这里测试就返回MyBean自身实例,且返回前给message字段赋值。同时在构造方法中也为message赋值。然后测试代码中先通过名称获取Bean实例,打印message的内容,再通过&+名称获取实例并打印message内容。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class FactoryBeanTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void test() {
        MyBean myBean1 = (MyBean) context.getBean("myBean");
        System.out.println("myBean1 = " + myBean1.getMessage());
        MyBean myBean2 = (MyBean) context.getBean("&myBean");
        System.out.println("myBean2 = " + myBean2.getMessage());
        System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
    }
}

myBean1 = 通过FactoryBean.getObject()初始化实例
myBean2 = 通过构造方法初始化实例
myBean1.equals(myBean2) = false

2.3、使用场景

说了这么多,为什么要有FactoryBean这个东西呢,有什么具体的作用吗?
FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象

我们知道AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean

所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。

三、区别

  • 他们两个都是个工厂,但FactoryBean本质上还是一个Bean,也归BeanFactory管理
  • BeanFactory是Spring容器的顶层接口,FactoryBean更类似于用户自定义的工厂接口。
posted @ 2020-08-21 14:08  MrOldx  阅读(130)  评论(0编辑  收藏  举报