准备工作已经做完了,现在我们就可以开始正式进入阅读环节。本文如题,主要讲的就是我们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;
}
基本上核心代码都已经拿出来了,不难发现,其实就是做了一些初始化,还做了一个权限的判断。但是这一步代码好像并没有去获取到文件?没有获取到文件的真正内容?这一个问题由于本文过长,我们在下一篇文章再分析