menu ChaYedan
Spring IoC详解
2106 浏览 | 2020-05-16 | 阅读时间: 约 15 分钟 | 分类: Java | 标签: Java,spring
请注意,本文编写于 925 天前,最后修改于 859 天前,其中某些信息可能已经过时。

Spring IoC详解

写在最前

本文将主要写Spring最核心的部分,为什么写这篇的原因也是因为在刚开始学习Spring的时候,学得太粗糙了。感觉学了个皮毛,从Spring,Spring MVC到后面用到Spring Boot,Spring Cloud的时候明显感觉有点知其然而不知其所以然了。于是决定重新再来一遍。买了一本王福强老师的Spring揭秘来重新学习再加上网上的资料进行补充。本文以及后面写的相关文章也算是读书笔记了,如果有错误的地方,请各位大佬指出。

另外必备的知识我在博客中也写好了,如果还没有掌握相关知识的读者,我建议各位先去看看。

xml的相关知识

反射的相关知识

动态代理的相关知识

为什么是Spring

在学习Spring之前,我们先要明白,为什么要学习Spring。就像要知道为什么历史选择了久石奏?

上网一搜,会发现很多千篇一律的Spring的优点。但是,仿佛还是没有回答我们的疑问。

为什么是Spring?

为什么现在都常说Spring一把梭?

书中给了一部分答案。

Spring是于2003年兴起的一个轻量级的Java开发框架。最初目的主要是为了简化Java EE的企业级应用开发。

在Spring正式出来之前用的什么呢?

是一款叫EJB的重量级框架。因为使用EJB需要支付高昂的费用,并且开发难度大,部署周期长,而且绝大多数的场景,其实并没有太大的意义。但是因为EJB不分场景的滥用,开发人员开始对其深恶痛绝。于是历史的车轮滚滚而来,Spring这个轻量级框架就快速崛起了。

更重要的一点是,Spring的核心模块为其提供了强大的支撑。为Spring后来的蓬勃发展,遍地开花奠定了基础。

IoC容器

IoC的基本概念

IoC是随着近年来轻量级容器兴起的概念,全称为Inversion of Control,中文通常翻译为“控制反转”。具体来说就是从我自己做,变成了别人为我做。就比如在没钱的时候我穿衣服都是自己穿,但有钱后我自己雇佣了一名佣人,两腿一站,双手一伸,佣人就把衣服给我穿上了。

到这里,说这么多。看着好像没什么用,无非就是简化了那么一小丢丢的步骤,不就是别人给我穿衣服嘛,我不需要啊,我勤快,自己做不就完事了。

但其实不然,在这里我们先停一下。先去了解另外一个东西——依赖倒置。

依赖倒置

假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!

我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。

这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。

IoC详解

那么现在我们回来。IoC(控制反转)其实就是实现依赖倒置原则的一种代码设计的思路。而具体实现的方法,使用的是依赖注入。下图是概念的关系图,其中为方便依赖注入我们就使用了一个容器,来将所有需要的类放入里面统一进行管理和调度。但现在关系不大,先不要纠结与容器相关的问题。

为了理解这几个概念,我们还是用上面汽车的例子。只不过这次换成代码。我们先定义四个Class,车,车身,底盘,轮胎。然后初始化这辆车,最后跑这辆车。代码结构如下:

这样,就相当于上面第一个例子,上层建筑依赖下层建筑——每一个类的构造函数都直接调用了底层代码的构造函数。假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:

由于我们修改了轮胎的定义,为了让整个程序正常运行,我们需要做以下改动:

由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的定义

这里我们再把轮胎尺寸变成动态的,同样为了让整个系统顺利运行,我们需要做如下修改:

看到没?这里我只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试:比如开发这四个类的分别是四个不同的组,那么只要定义好了接口,四个不同的组可以同时进行开发而不相互受限制;而对于单元测试,如果我们要写Car类的单元测试,就只需要Mock一下Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。这里我们是采用的构造函数传入的方式进行的依赖注入。

书上在这章是将IoC和依赖注入当做一个概念在讲解,但其实是不一样的,依赖注入是IoC的一种方式。但作者也写了备注。另外讲解控制反转很多是从对象解耦的角度讲解的。如果感兴趣可以搜索相关文章。

依赖注入的方式

构造方法注入

顾名思义,构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。

就像上图中我画长方形框住的部分,即在构造方法时传递依赖的对象。

IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。同一个对象是不可能被构造两次的,因此,被注入对象的构造乃至其整个生命周期,应该是由IoC Service Provider来管理的。

setter 方法注入

顾名思义,通过setter方法给自己依赖的对象赋值。

接口注入

在这本09年的书上都说要被淘汰了,现在过去11年了。。。也不做赘述了。确实是没人在用了

构造方法注入和setter方法注入的比较

构造方法这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。

setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。

IoC容器最核心的职责

上面说了这么多,无非就是围绕这控制反转这个概念,那么明白了概念之后,下面就要正式的来揭开Spring IoC容器的面纱。

依然是这个图,在解释控制反转时我们回避掉了容器这个概念,那么现在就来具体的讲讲。

容器我在之前已经说了,它是将所有需要的类放入里面统一进行管理和调度的一个抽象的角色。可以形象的描述为一个管家。

经过上面的例子我们可以清晰的感受到,依赖注入最重要的就是要知道我该把谁注入给需要注入的对象。也就是各个对象之间依赖的关系。这就是容器的最核心的职责之一,业务对象间的依赖绑定

还有一个就是在最开始提到过的,我自己不需要去穿衣服了,佣人来给我穿。在IoC容器中就是由容器来给我们创建对象,我们不需要自己去创建了。这就是另一个容器的核心职责,业务对象的构建管理

一定要保持头脑的清醒,认清依赖注入和控制反转的关系。控制反转是为了实现依赖倒置原则而通过依赖注入的方式实现的,但实现控制反转的方法不止一种,还有其他的方式,只是最最主流的方式是使用依赖注入。

虽然现在很多资料都将IoC和依赖注入混为一个概念,但我觉得确实不妥。至少在学习中,需要将两个概念分开。

容器分类

Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部分原因,我们不能忽略的是“容器”。Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了相应的AOP框架支持、企业级服务集成等服务。Spring的IoC容器和IoC Service Provider所提供的服务之间存在一定的交集,二者的关系如图所示

Spring提供了两种容器类型: BeanFactoryApplicationContext

BeanFactory :基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load/Lazy Initialization)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。

ApplicationContext :ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory 的所有支持, ApplicationContext 还提供了其他高级特性。ApplicationContext 所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成(Early Initialization)。所以,相对于 BeanFactory 来说, ApplicationContext 要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之 BeanFactory 也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext 类型的容器是比较合适的选择。

两者之间的关系

Spring的IoC容器——BeanFactory

BeanFactory ,顾名思义,就是生产Bean的工厂。当然,严格来说,这个“生产过程”可能不像说起来那么简单。既然Spring框架提倡使用POJO,那么把每个业务对象看作一个JavaBean对象,或许更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器,BeanFactory 可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

BeanFactory 就像一个汽车生产厂。你从其他汽车零件厂商或者自己的零件生产部门取得汽车零件送入这个汽车生产厂,最后,只需要从生产线的终点取得成品汽车就可以了。相似地,将应用所需的所有业务对象交给 BeanFactory 之后,剩下要做的,就是直接从 BeanFactory 取得最终组装完成并且可用的对象。至于这个最终业务对象如何组装,你不需要关心, BeanFactory 会帮你搞定。

对象注册与依赖绑定方式

在书中介绍了很多种方法,但到现在,也几乎只用两种方法——XML和注解。所以这里就只写这两种方法。而且书中的代码是Spring2.x版本,对于现在有点古老了。我这里用的是Spring 5版本的写法。

XML方式——对象注册和调用

创建一个最初始的配置文件bean.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">
</beans>

将对象存入容器

<?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="demo01" class="site.chayedan.Demo01"></bean>
</beans>

这样就将我们写的Demo01这个类加入到容器了。

参数详解:

bean 标签:配置让 spring 创建对象,并且存入 ioc 容器之中

id 属性:对象的唯一标识。

class 属性:指定要创建对象的全限定类名

通过Spring容器获取该对象

// 使用ClassPathXmlApplicationContext类来获取Spring容器,并且配置文件是beans.xml
// 注意这里的路径名是按照ClassPath的路径来写的
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 根据bean的id获取对象,返回的是Object类型,强转一下
Demo01 demo01 = (Demo01) context.getBean("demo01");

ClassPathXmlApplicationContext这个方法采用的是饿汉式加载(Early Initialization),另外还有很多其他的加载方式。具体可以查阅API文档。

XML方式——对象注入属性

例如Service是依赖与Dao的,那么怎么给Service里面依赖的Dao赋值呢?

下面是setter方式的注入。注入前保证Service有set方法

    <bean id="accountService" class="org.example.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <bean id="accountDao" class="org.example.dao.impl.AccountDaoImpl"></bean>

property的name属性的值要和set方法名一致。ref的值为xml配置文件中id匹配的值。可以理解为ref指明要注入的对象,name标明用哪个set方法注入。

public void setAccountDao(AccountDao accountDao) {
    this.accountDao = accountDao;
}

下面是注入基本数据类型

    <bean id="accountService" class="org.example.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="id" value="1"/>
    </bean>
    <bean id="accountDao" class="org.example.dao.impl.AccountDaoImpl"></bean>

小总结

property属性:

  • name:找的是类中 set 方法后面的部分
  • ref:给属性赋值是其他 bean 类型的
  • value:给属性赋值是基本数据类型和 string 类型

使用ref的时候,spring容器会在引用后进行验证,验证当前的xml是否存在引用的bean。

使用value的时候,spring会在容器启动,实例化bean的时候进行验证。


下面是注入集合属性

定义

private String[] pokemon;
private List<String> list;
private Set<String> set;
private Map<String,String> map;

public void setPokemon(String[] pokemon) {
    this.pokemon = pokemon;
}

public void setList(List<String> list) {
    this.list = list;
}

public void setSet(Set<String> set) {
    this.set = set;
}

public void setMap(Map<String, String> map) {
    this.map = map;
}

注入

<!-- 注入集合数据
List 结构的标签:
array,list,set

Map 结构的标签:
map,entry,props,prop

在注入集合数据时,只要结构相同,标签可以互换
-->
<property name="pokemon">
    <list>
    <value>小智</value>
    <value>小茂</value>
    <value>小蓝</value>
    </list>
</property>
<property name="list">
    <list>
    <value>小智1</value>
    <value>小茂1</value>
    <value>小蓝1</value>
    </list>
</property>
<property name="set">
    <set>
    <value>小智2</value>
    <value>小茂2</value>
    <value>小蓝2</value>
    </set>
</property>
<property name="map">
    <map>
    <entry key="name" value="茶叶蛋"/>
    <entry key="age" value="18"/>
    </map>
</property>

XML方式——构造方法注入

比如有构造方法要传入两个参数,则可以在Bean标签下使用构造方法注入

<constructor-arg name="x" value="x坐标"></constructor-arg>
<constructor-arg name="y" value="y坐标"></constructor-arg>

工厂方法与 FactoryBean

Spring默认是按照空参构造方法来构造对象的。但有时候如果我们的类创建太复杂,比如说在构造的时候就需要引入第三方库或者实现某种复杂的逻辑,该怎么办呢?

我们可以把工厂类提供给Spring,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。也就是通过自己写的工厂创建对线后放入容器。

  • 静态工厂方法(不常用)

自己创建的工厂

public class MyBeanFactory {
    public static MyBean getMyBean(){
        MyBean myBean = new MyBean();
        //处理复杂逻辑
        //或者引入第三方库
        return myBean;
    }
}

在配置文件中引入

<bean id="myBean" class="com.pojo.MyBeanFactory" factory-method="getMyBean" ></bean>
  • 非静态工厂方法/实例工厂(偶尔)

Spring容器本来就是一个BeanFactory,这种方法就是通过Spring来创建工厂对象,然后工厂来创建我们需要的对象

工厂类

public class InstanceBeanFactory {
    public  MyBean getMyBean(){
        MyBean myBean = new MyBean();
        //伪代码 处理复杂过程
        return myBean;
    }
}

在配置文件引入

// 先声明工厂对象
<bean id="instanceFactory" class="com.pojo.InstanceBeanFactory"></bean>
// 利用工厂对象创建对象
<bean id="myBean" factory-bean="instanceFactory" factory-method="getMyBean"></bean>
  • FactoryBean

FactoryBean 是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean ,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已。只不过,这种类型的Bean本身就是生产对象的工厂。

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现 org.spring-framework.beans.factory.FactoryBean 接口,给出自己的对象实例化逻辑代码。

工厂类(实现FactoryBean接口)

public class MyBeanFactoryBean implements FactoryBean<MyBean> {
    @Override
    public MyBean getObject() throws Exception {
        MyBean myBean = new MyBean();
        return myBean;
    }

    // 规定返回类型
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

配置文件

<bean id="myBean" class="com.pojo.MyBeanFactoryBean"></bean>

这样就是通过工厂来创建对象了

注解方式——包扫描

在使用注解方式之前,我们需要配置包扫描,即将哪些类注册到容器里。使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层包(base package)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的 BeanDefinition ,然后把构建完的 BeanDefinition 注册到容器。如果要扫描的类定义存在于不同的源码包下面,也可以为 base-package 指定多个以逗号分隔的扫描路径。

在XML文件中配置包扫描

引入新的约束(第3,7,8行)并且配置包扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        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
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 告知 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="com.pojo"></context:component-scan>
</beans>

注解方式——对象注册和调用

除了XML方式写配置文件,还可以用注解的方式配置,注解底层采用的是暴力反射机制,所以可以不写setter方法,下面写一些常用的注解

创建对象:

@Component("名字") :作用等于在XML配置中bean id=名字,如果不写名字,默认注入到spring容器的名字就是类名,首字母小写

@Controller:@Component的别名,标识Controller层

@Service:@Component的别名,标识Service

@Repository:@Component的别名,标识Dao层

注入对象:

@Resource(name="名字"):从spring容器获取对象,注入对象属性

@AutoWired:根据类型获取对象。但要保证在容器中的类是唯一的

@Value(值):注入基本数据类型和 String 类型数据的。

如果要引入外部文件,可以在配置文件中添加

<context:property-placeholder location="classpath:配置文件1.properties,classpath:配置文件2.properties"></context:property-placeholder>

多个配置文件用逗号隔开

然后@Value("${配置文件名.属性名}")注入

或者使用注解@PropertySource来引入外部配置文件

@PropertySource("classpath:db.properties")
public class DaoConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
    @Bean("dataSource") //这个注解的说明在下面
    public DataSource getDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

注解方式——配置类

新建一个类

@Configuration
/**
 * 等同于xml
 *  <context:component-scan base-package="包名"></context:component-scan>
 */
@ComponentScan({"com.service","com.dao","com.pojo"})
/**
 * 引入其他的配置类的
 */
@Import({DaoConfig.class,UtilConfig.class})
public class Config {

}

第一个注解@Configuration标明这是一个配置类

第二个注解@ComponentScan就是包扫描

第三个注解@Import引入其他配置类

跟XML一样,刚开始都需要先来解析,然后才能取用

public void test() {
      //解析配置类
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

      AccountService accountService = context.getBean(AccountService.class);
}

另外,当Spring用注解的方式启动时,会扫描配置类的方法,如果方法上有@Bean注解,会将方法的返回值注入到容器中

比如说这样就将QueryRunner注入到了容器中,如果有参数的话,会检查容器里看是否有这个对象,然后传入这个参数。

@Configuration
/**
 * 等同于xml
 *  <context:component-scan base-package="包名"></context:component-scan>
 */
@ComponentScan({"com.service","com.dao","com.pojo"})
/**
 * 引入其他的配置类的
 */
@Import({DaoConfig.class,UtilConfig.class})
public class Config {
    @Bean
    public QueryRunner getQueryRunner(DataSource dataSource){
        QueryRunner queryRunner = new QueryRunner(dataSource);
        return queryRunner;
    }
}

bean 的 scope(生命周期管理)

书上叫生命周期管理,但又有叫作用域的。。。

属性有很多。下面写我们主要可能配到的属性

单例(singleton)/多例(prototype)

默认为单例,即每次创建的对象都是第一次创建的那个对象。多例则相反,每次创建一个对象时,都会重新创建一个对象。

配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。

下面是定义为多例

<bean id="accountDao" class="com.dao.impl.AccountDaoImpl" scope="prototype"></bean>

下面是注解来声明为对象是单例还是多例

@Scope("singleton"):单例

@Scope("prototype"):多例

初始化/销毁

spring在创建对象的时候会执行init-method指定的方法,在销毁的时候会执行destroy-method指定的方法。

另外从上面单例多例的生命周期可以看出,单例的对象,Spring会帮忙执行init-method和destroy-method。但多例则只负责执行init-method。

为什么多例不管呢?

因为只有在对象没有任何引用的时候,才会被回收销毁。如果Spring要管理销毁操作的话,一是在Spring的Map集合中会一直引用这该对象,导致JVM无法去回收。二是如果大量的创建,那么记录的条数就会迅速增加,引发内存溢出的问题。三是就算Spring自己出个垃圾回收机制,判断多例的时候除了Spring还引用着其他都没引用了,就将它回收了。但是我们知道JVM已经有垃圾回收机制了,何必再去造个轮子呢?明显是越俎代庖了。

类定义

public class AccountDaoImpl implements AccountDao {
    public void init(){
        System.out.println("初始化");
    }
    public void destroy(){
        System.out.println("我死了...");
    }
}

下面是XML配置文件的形式

<bean id="accountDao" class="com.dao.impl.AccountDaoImpl" init-method="init" destroy-method="destroy" scope="prototype"></bean>

下面是注解的形式

@PostConstruct:用于指定初始化方法。

@PreDestroy:用于指定销毁方法。

参考资料

Spring揭秘

https://juejin.im/post/5b9b0a695188255c3377ed42

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

发表评论

email
web

全部评论 (共 1 条评论)

    123
    2021-04-25 22:34
    很棒!感谢!!