【Spring源码这样读】-再次走近容器Spring IOC 一

509 阅读8分钟

准备工作已经做完了,现在我们就可以开始正式进入阅读环节。本文如题,主要讲的就是我们Spring的核心部分容器。这个词相比都是不陌生的,很多人接触Spring的时候就会接触到Spring的两大核心功能IOC和AOP。这里我们换一种角度来读,希望能给大家带来一些新的认知。大佬请略过,有异议的地方还请各位大佬指出。

什么是容器

说到容器,估计很多人都会浮现两个概念:IOC、DI。

  • IOC:Inversion of Control,即控制反转。 IOC并不是一种什么技术,而是一种编程思想。那什么是控制反转,为什么控制反转? 先来看一个简单的示例:
public class Test {
    public static void main(String[] args) {
        new Test1().testMethod();
    }
}

class Test1 {
    public void testMethod() {
        System.out.println("testMethod方法被调用");
    }
}

这是一段很简单的代码,在一个类里面调用了另外一个类的方法。调用另外一个类的方法时,我们直接创建该类的对象。在我们框架的使用当中我们是不需要去创建对象的,都是框架给我们在做这些事情。那这里我们就能明显的看到一个问题,对象在以不同的方式被使用。

谁控制谁?在我们上面的例子当中,可以明显的看到,new对象是我们当前方法来控制的。但是容器呢?我们基本看不到new的操作,我们都是直接注入使用,所以可以直接推断出对象的new动作不是当前使用的程序,而是框架。具体点说就是框架在做这件事情。 什么是正转、反转?所谓的正转,就是程序内部直接new对象,并赋值。反转就是容器new对象,主动给程序提供

这种操作就解决了一个问题,解耦。试着想一下,如果我们的代码都通过写new来使用对象,对象的管理工作会很繁杂。

  • DI—Dependency Injection 即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。上面我们提到的框架再给我们new对象,然后给我们提供可以用的对象,其实这里就包含了对象的注入,如果不注入,框架创建的对象怎么被我们写的代码引用呢?

Spring IOC容器的应用场景

在 Spring 中,Spring IoC 提供了一个基本 JavaBean 容器,通过 IoC 模式管理依赖关系,并通过依赖注入和 AOP 切面增强了为 JavaBean 这样的 POJO 对象提供了事务管理、声明周期管理等功能。其实如果我们把IOC看做一个水桶,那么项目里面那些Bean就都是水。在没有框架的情况下,水是很不好收集保留的,但是当我们有了这个容器之后,我们发现,他给我们带来了很大的便利,不仅不需要来回的去new,也不需要去关心,他有多少了。

Spring IOC容器的实现

目前Spring IOC的主流实现就两种方式,BeanFactory和ApplicationContext,我们不妨来体验一下两种实现方式。

  • BeanFactory 这里需要创建一个简单的Spring项目,引入Spring的基本依赖即可,然后我们来尝试使用一下容器 先来创建一个简单的bean
public class TestBean {

    private String name = "this is bean";

    public String getName() {
        return this.name;
    }

}

这个bean并没有什么特别的地方,很普通。然后我们再来创建一个配置文件testBeanFactory.xml,将bean交给容器管理

<?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 id="testBean" class="com.echo.demo.TestBean"/>

</beans>

在我们的普通程序中,我们要使用bean就需要去new,但是这里,我们不需要了。可以直接通过容器来获取bean

获取被容器管理的bean

package com.echo.demo;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Test {

    public static void main(String[] args) {

        XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("testBeanFactory.xml"));
        TestBean testBean = (TestBean)xmlBeanFactory.getBean("testBean");
        System.out.println(testBean.getName());

    }

}

到这里为止,我们已经体验了BeanFactory的基本用法。很简单,我们仅仅只是建了一个bean,并且声明了一下,然后就可以拿到这个bean的实例了。

  • ApplicationContext 我们不妨用ApplicatonContext也来做个实例,比对一下。和上面一样,来个一样的bean
public class TestBean {

    private String name = "this is bean";

    public String getName() {
        return this.name;
    }

}

注意ApplicatonContext的实现也需要xml文件

<?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 id="testBean" class="com.echo.demo.TestBean" />

</beans>

到目前为止,其实我们看到的两种方式也没啥区别,但是我们看看获取bean的代码就知道了,使用上也就是获取bean的这两行代码不一样

package com.echo.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testApplicationContext.xml");
        TestBean testBean = (TestBean) applicationContext.getBean("testBean");
        System.out.println(testBean.getName());
    }

}

这两段简易的代码,其实从我们一个使用的角度来讲,并没有很实质性的区别,无非就是用了另外一个类来调用获取bean的方法。这两段代码,不管是从表面上看还是从他的实现来看,都逃不过这么几部:

  • 读取配置文件
  • 根据配置文件找到对应的类并通过反射来实例化
  • 然后存入容器,被调用的时候取出

那真的是这样的子的吗?我们深入实现来复核一下我们的想法

这里我们已经有了猜想,同时代码实例也已经有了,我们不妨来通过时序图深入了解一下。这个时序图是从我们代码的Test类开始的,这里我们画了一个XmlBeanFactory初始化的时序图来看我们的代码逻辑到底做了件什么事情。 在这里插入图片描述

当我们看到这个时序图的时候,其实应该是能比较明了的知道前面我们的代码,到底做了些什么事情。代码从ClassPathResource获取文件开始,最终拿到对象。如果光从时序图上来看的话,跟我们之前的推测没啥很大的出入,为了进一步的来了解我们的推测,我们用源码来应正一下。

  • 代码中ClassPathResource到底做了件什么事情?先看这里
/**
 * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
 * A leading slash will be removed, as the ClassLoader resource access
 * methods will not accept it.
 * <p>The thread context class loader will be used for
 * loading the resource.
 * @param path the absolute path within the class path
 * @see java.lang.ClassLoader#getResourceAsStream(String)
 * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
 */
public ClassPathResource(String path) {
	this(path, (ClassLoader) null);
}

/**
 * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
 * A leading slash will be removed, as the ClassLoader resource access
 * methods will not accept it.
 * @param path the absolute path within the classpath
 * @param classLoader the class loader to load the resource with,
 * or {@code null} for the thread context class loader
 * @see ClassLoader#getResourceAsStream(String)
 */
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());
}


/**
 * Return the default ClassLoader to use: typically the thread context
 * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
 * class will be used as fallback.
 * <p>Call this method if you intend to use the thread context ClassLoader
 * in a scenario where you clearly prefer a non-null ClassLoader reference:
 * for example, for class path resource loading (but not necessarily for
 * {@code Class.forName}, which accepts a {@code null} ClassLoader
 * reference as well).
 * @return the default ClassLoader (only {@code null} if even the system
 * ClassLoader isn't accessible)
 * @see Thread#getContextClassLoader()
 * @see ClassLoader#getSystemClassLoader()
 */
@Nullable
public static ClassLoader getDefaultClassLoader() {
	ClassLoader cl = null;
	try {
		cl = Thread.currentThread().getContextClassLoader();
	}
	catch (Throwable ex) {
		// Cannot access thread context ClassLoader - falling back...
	}
	if (cl == null) {
		// No thread context class loader -> use class loader of this class.
		cl = ClassUtils.class.getClassLoader();
		if (cl == null) {
			// getClassLoader() returning null indicates the bootstrap ClassLoader
			try {
				cl = ClassLoader.getSystemClassLoader();
			}
			catch (Throwable ex) {
				// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
			}
		}
	}
	return cl;
}


/**
 * Returns the system class loader for delegation.  This is the default
 * delegation parent for new <tt>ClassLoader</tt> instances, and is
 * typically the class loader used to start the application.
 *
 * <p> This method is first invoked early in the runtime's startup
 * sequence, at which point it creates the system class loader and sets it
 * as the context class loader of the invoking <tt>Thread</tt>.
 *
 * <p> The default system class loader is an implementation-dependent
 * instance of this class.
 *
 * <p> If the system property "<tt>java.system.class.loader</tt>" is defined
 * when this method is first invoked then the value of that property is
 * taken to be the name of a class that will be returned as the system
 * class loader.  The class is loaded using the default system class loader
 * and must define a public constructor that takes a single parameter of
 * type <tt>ClassLoader</tt> which is used as the delegation parent.  An
 * instance is then created using this constructor with the default system
 * class loader as the parameter.  The resulting class loader is defined
 * to be the system class loader.
 *
 * <p> If a security manager is present, and the invoker's class loader is
 * not <tt>null</tt> and the invoker's class loader is not the same as or
 * an ancestor of the system class loader, then this method invokes the
 * security manager's {@link
 * SecurityManager#checkPermission(java.security.Permission)
 * <tt>checkPermission</tt>} method with a {@link
 * RuntimePermission#RuntimePermission(String)
 * <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
 * access to the system class loader.  If not, a
 * <tt>SecurityException</tt> will be thrown.  </p>
 *
 * @return  The system <tt>ClassLoader</tt> for delegation, or
 *          <tt>null</tt> if none
 *
 * @throws  SecurityException
 *          If a security manager exists and its <tt>checkPermission</tt>
 *          method doesn't allow access to the system class loader.
 *
 * @throws  IllegalStateException
 *          If invoked recursively during the construction of the class
 *          loader specified by the "<tt>java.system.class.loader</tt>"
 *          property.
 *
 * @throws  Error
 *          If the system property "<tt>java.system.class.loader</tt>"
 *          is defined but the named class could not be loaded, the
 *          provider class does not define the required constructor, or an
 *          exception is thrown by that constructor when it is invoked. The
 *          underlying cause of the error can be retrieved via the
 *          {@link Throwable#getCause()} method.
 *
 * @revised  1.4
 */
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

基本上核心代码都已经拿出来了,不难发现,其实就是做了一些初始化,还做了一个权限的判断。但是这一步代码好像并没有去获取到文件?没有获取到文件的真正内容?这一个问题由于本文过长,我们在下一篇文章再分析