spring的基本使用

2022/01/03 spring专题 共 7135 字,约 21 分钟
闷骚的程序员

1. IOC容器

IOC容器是具有依赖注入功能的容器,负责对象的实例化、对象的初始化、对象和对象之间的依赖关系组装、对象的销毁、对外提供对象的查找等操作。
对象的整个生命周期都由IOC容器进行控制,IOC容器就是spring容器。
我们需要的任何对象都可以交给IOC容器进行管理,不需要我们再手动通过new的方式创建对象,由IOC容器直接帮我们创建并组装好,当我们需要使用对象时,直接去IOC容器中进行查找就行了。

那么spring IOC容器是如何知道需要管理哪些对象呢?

我们要给spring容器一个配置清单,这个配置清单支持xml格式和java注解的方式,在配置清单中列出需要让IOC容器管理的java对象,以及可以指定让IOC容器如何构建这些对象,当spring容器启动的时候,就会去加载这个配置清单,然后将这些对象创建并组装好放入到缓存中,外部需要使用时直接去spring容器中查找即可。

2. 什么是Bean?

我们在接触spring之前,几乎没有bean的概念。
bean实际上就是java对象,任何一个java对象只要交给spring去管理,我们都统称为Bean对象。
bean就是普通的java对象,和我们new出来的对象一模一样。
只不过我们需要将这些bean对象的相关信息提供给spring容器,这样spring容器才能根据我们提供的bean清单去帮我们管理这些bean。
这些提供给spring容器的bean清单信息,统称为bean的配置元数据定义信息,spring通过读取这些bean元数据信息来帮我们构建和组装这些bean。

3. spring容器的使用步骤

1.引入spring相关的maven配置
2.创建bean配置文件,比如bean xml配置文件
3.在bean xml文件中定义好需要spring容器管理的bean对象
4.创建spring容器,并给容器指定需要装载的bean配置文件,当spring容器启动之后,会加载这些配置文件,然后创建好配置文件中定义好的bean对象,将这些对象放在容器中以供使用
5.通过容器提供的方法获取容器中的对象,然后使用

4. spring容器对象

4.1 啥叫spring容器对象?

spring容器既然帮我们创建bean并且组装bean的关系,那么spring容器必然需要向外部暴露容器对象。
这样当我们需要使用某些目标bean时,才能够通过spring容器对象去获取指定的目标bean。
spring容器既然是个容器,就可一将其理解成一个大缓存对象。
spring内部提供了很多表示spring容器的接口和对象,我们先看几个比较常见的容器接口和实现类。

4.2 BeanFactory接口

org.springframework.beans.factory.BeanFactory;

spring容器中具有代表性的容器就是BeanFactory接口,这是spring容器的顶层接口,提供了容器最基本的功能。
常用的几个方法:

// 根据bean的id配置或者别名配置查找容器中的bean对象
Object getBean(String name) throws BeansException
// 这是一个泛型方法,按照bean的id配置或者别名查找指定类型的bean,返回指定类型的bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 返回容器中指定类型的bean对象
<T> T getBean(Class<T> requiredType) throws BeansException;
// 获取指定类型bean对象的获取器,这个方法比较特别,以后会专门来讲
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
// 根据bean的id配置或者别名,返回目标bean的所有别名
String[] getAliases(String var1);
// 根据bean的id配置或者别名,返回bean的Class实例
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

4.3 ApplicationContext接口

org.springframework.context.ApplicationContext;

这个接口继承了BeanFactory接口,所以其内部包含了BeanFactory接口的所有功能,并且在其基础上进行了很多扩展,增加很多企业级的功能,比如AOP、国际化、事件支持等。

4.4 ClassPathXmlApplicationContext类

org.springframework.context.support.ClassPathXmlApplicationContext;

这个类实现了ApplicationContext接口,注意这个类名包含了ClassPath Xml,说明这个容器类可以从classpath中加载bean xml配置文件,然后创建xml中配置的bean对象。

4.5 AnnotationConfigApplicationContext类

org.springframework.context.annotation.AnnotationConfigApplicationContext;

这个类也实现了ApplicationContext接口,注意这个类名包含Annotation和Config这俩单词,说明当我们需要使用注解的方式来定义bean时,就需要使用这个容器类来装载bean了。
这个容器内部会解析注解标注的类,用来构建和管理我们需要的bean对象。
注解的方式相较于xml的方式更加灵活方便,也是我们非常推荐的方式。

4.6 DefaultListableBeanFactory类

org.springframework.beans.factory.support.DefaultListableBeanFactory;

这个类简直太重要了。
很有必要梳理一下spring暴露给我们的容器对象。
首先BeanFactory是容器根接口;ApplicationContext是继承了BeanFactory接口的一个子接口,扩展了很多顶层方法。
其次,向ClassPathXmlApplicationContext类、AnnotationConfigApplicationContext类都是ApplicationContext接口的实现类。
讲到这里,那 DefaultListableBeanFactory类是个啥玩意儿呢?
DefaultListableBeanFactory类的继承结构很复杂,关于它的继承图解可以上网搜一下。
总的来说,DefaultListableBeanFactory类也是顶层容器接口BeanFactory的一个实现类。并且可以明确的说,DefaultListableBeanFactory类才是spring容器对象中唯一的一个功能完备、可以直接暴露给客户端使用的容器类。
DefaultListableBeanFactory类只有XmlBeanFactory一个子类,它还被标记过时了的。
我们可以直接编码使用 DefaultListableBeanFactory:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
reader.loadBeanDefinitions(new FileSystemResource("/spring/applicationContext.xml"));
defaultListableBeanFactory.getBean("person");

实际上spring暴露的容器只有 DefaultListableBeanFactory。
我们观察ClassPathXmlApplicationContext或者AnnotationConfigApplicationContext类的源码,可以看到这两个类虽然也是BeanFactory接口的实现类,但是他们是通过持有一个DefaultListableBeanFactory引用来运行spring容器的。
因此,真正核心拉动spring容器的类只有 DefaultListableBeanFactory。

5. 案例

5.1 新建bean xml配置文件

在classpath下指定目录创建bean的定义文件,一般叫做applicationContext.xml。
其中注意配置bean的元信息。

<bean id="person" class="com.zeh.main.pojo.Person">

5.2 测试程序

创建程序,测试代码执行:

package com.zeh.main.controller;
import com.zeh.main.pojo.Person;
public class PersonController {
    public static void main(String[] args) {
        // 1. bean配置文件位置
        String beanXml = "classpath:/spring/applicationContext.xml";
        // 2. 创建classPathXmlApplicationContext容器对象,给容器指定需要加载的bean xml文件
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(beanXml);
        // 3. 从容器中获取需要的bean
        Person person = applicationContext.getBean("person", Person.class);
        // 4. 使用bean
        person.testPerson();
    }
}

6. spring容器对象(AbstractApplicationContext)的refresh()方法

上一个案例,我们传入了xml文件的路径,然后构建了一个ClassPathXmlApplicationContext容器,这个容器是属于 ApplicationContext 系列的。
而且我们也知道,一系列的ApplicationContext就是spring容器,也叫做sprign上下文。
ApplicationContext里面存放了各种bean实例,我们需要的时候就直接获取即可。

那DefaultListableBeanFactory又是啥呢?
从名字就可以看出,它实际上就是个bean工厂,说白了就是用来生产bean的一个工具。
它不能称为spring容器,它确切的含义就是bean工厂。

因此,DefaultListableBeanFactory是真正操作bean对象的工厂,它直面各个Bean定义。
各种ApplicationContext看起来也拥有BeanFactory的各种操作bean的方法,其实底层是委托DefaultListableBeanFactory去操作的。
因此,DefaultListableBeanFactory才是真正用来操作spring容器中的bean的原始API,它只是一个Bean的操作者。

那ApplicationContext系列和DefaultListableBeanFactory到底有啥区别呢?
就拿上个案例来看:

Person person = applicationContext.getBean("person", Person.class);

通过ApplicationContext上下文去获取bean,它底层委托DefaultListableBeanFactory去操作的。

    public <T> T getBean(Class<T> requiredType) throws BeansException {
        this.assertBeanFactoryActive();
        return this.getBeanFactory().getBean(requiredType);
    }

org.springframework.context.support.GenericApplicationContext

    public final ConfigurableListableBeanFactory getBeanFactory() {
        return this.beanFactory;
    }

看一下这个beanFactory到底是个啥:

private final DefaultListableBeanFactory beanFactory;

所以明白了吧,ApplicationContext的getBean()实际山调用的是DefaultListableBeanFactory的getBean()方法。
其他的bean相关方法也类似。

既然DefaultListableBeanFactory是直接操作容器中bean的对象,那么还搞ApplicationContext系列干啥啊?

当然没有我们想的那么简单。
ApplicationContext称为spring上下文,也称为spring容器。
既然是容器,就说明它有生命周期,说明它里面能存放很多东西,而不仅仅是一个操作bean的工具对象。
org.springframework.context.support.AbstractApplicationContext中有个refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

这个方法才是spring容器的核心方法。
ApplicationContext系列通过扩展出这个方法,将spring容器中的所有bean都直接创建了,并且根据规范注入了很多其他钩子,方便外部集成。

可以这么说,DefaultListableBeanFactory它就是个对象,直接解析xml的,根据xml中的配置然后去反射创建bean,它只暴露原始操作bean的API,只能一个一个的去操作容器中的这些bean。
比如你要获取容器中的一个bean,那就通过它去调用getBean()方法,然后它就会去解析xml,然后反射bean,返回给你。
它只是一个底层操作Bean的原始API。
很明然,它还不具备容器的属性。
而ApplicationContext就扩展了容器的属性,既然是容器,我就需要一次性启动你之后,你应该将xml中的所有bean都给我组装好塞到容器里。
并且,应该为我做好其他附加的配置。
也就是说,你应该为容器定义一种运行规范和生命周期,让它一次性启动,替我做好所有事,而不是我一个一个的调用你的API去单独获取。
ApplicationContext出现了,它就是spring容器的上下文对象,它持有spring的bean工厂并对它进行扩展,让spring真正成为一个容器。
而refresh()方法就是启动spring容器的方法,它会为我们做一些容器准备工作,并且将xml中所有的bean都装载到容器中。

文档信息

Search

    Table of Contents