Spring常见知识点和面试题

1.spring framework4的架构图

(1)核心层

  • Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块

(2)AOP层

  • AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强==

  • Aspects:AOP是思想,Aspects是对AOP思想的具体实现

(3)数据层

  • Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术

  • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis

  • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容

(4)Web层

  • 这一层的内容将在SpringMVC框架具体学习

(5)Test层

  • Spring主要整合了Junit来完成单元测试和集成测试

2.IOC、Bean、DI

在学习spring之前我们要想获得某个对象中的属性、方法都需要通过创建对象的形式来获取,但是大量的创建对象就会对jvm堆内存以及运行效率造成一定影响,内存资源浪费,下面就是目前项目中遇到的问题

(1)业务层需要调用数据层的方法,就需要在业务层new数据层的对象

(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署

(3)所以,现在代码在编写的过程中存在的问题是:==耦合度偏高==

针对这个问题,该如何解决呢?

如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?

答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。

所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?

针对这个问题,Spring就提出了一个解决方案:

  • 使用对象时,在程序中不要主动使用new产生对象,转换为由==外部==提供对象

这种实现思就是Spring的一个核心概念

2.1.IOC、Bean、DI简述

  1. ==IOC(Inversion of Control)控制反转==

(1)什么是控制反转呢?

  • 使用对象时,由主动new产生对象转换为由==外部==提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。

    • 业务层要用数据层的类对象,以前是自己new

    • 现在自己不new了,交给别人[外部]来创建对象

    • 别人[外部]就反转控制了数据层对象的创建权

    • 这种思想就是控制反转

    • 别人[外部]指定是什么呢?继续往下学

(2)Spring和IOC之间的关系是什么呢?

  • Spring技术对IOC思想进行了实现

  • Spring提供了一个容器,称为==IOC容器==,用来充当IOC思想中的"外部"

  • IOC思想中的别人[外部]指的就是Spring的IOC容器

(3)IOC容器的作用以及内部存放的是什么?

  • IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象

  • 被创建或被管理的对象在IOC容器中统称为==Bean==

  • IOC容器中放的就是一个个的Bean对象

(4)当IOC容器中创建好service和dao对象后,程序能正确执行么?

  • 不行,因为service运行需要依赖dao对象

  • IOC容器中虽然有service和dao对象

  • 但是service对象和dao对象没有任何关系

  • 需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系

像这种在容器中建立对象与对象之间的绑定关系就要用到DI:

  1. ==DI(Dependency Injection)依赖注入==

(1)什么是依赖注入呢?

  • 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

    • 业务层要用数据层的类对象,以前是自己new

    • 现在自己不new了,靠别人[外部其实指的就是IOC容器]来给注入进来

    • 这种思想就是依赖注入

(2)IOC容器中哪些bean之间要建立依赖关系呢?

  • 这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建立依赖关系

介绍完Spring的IOC和DI的概念后,我们会发现这两个概念的最终目标就是:==充分解耦==,具体实现靠:

  • 使用IOC容器管理bean(IOC)

  • 在IOC容器内将有依赖关系的bean进行关系绑定(DI)

  • 最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系.

2.2DI相关内容

注入方式:setter:

    • 简单数据类型

      <bean ...>
          <property name="" value=""/>
      </bean>

    • 引用数据类型

      <bean ...>
          <property name="" ref=""/>
      </bean>

构造器:

  • 简单数据类型

    <bean ...>
        <constructor-arg name="" index="" type="" value=""/>
    </bean>

  • 引用数据类型

    <bean ...>
        <constructor-arg name="" index="" type="" ref=""/>
    </bean>

集合:

注入数组类型数据

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

注入List类型数据

<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

注入Set类型数据

<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>boxuegu</value>
    </set>
</property>

注入Map类型数据

<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

注入Properties类型数据

<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

自动配置:

​ 注意事项:

  • 需要注入属性的类中对应属性的setter方法不能省略

  • 被注入的对象必须要被Spring的IOC容器管理

  • 按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException

一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:

<?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="com.itheima.dao.impl.BookDaoImpl"/>
    <!--autowire属性:开启自动装配,通常使用按类型装配-->
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName"/>
​
</beans>

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作

  2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用

  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用

  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

2.3 小结

这节比较重要,重点要理解什么是IOC/DI思想什么是IOC容器什么是Bean

(1)什么IOC/DI思想?

  • IOC:控制反转,控制反转的是对象的创建权

  • DI:依赖注入,绑定对象与对象之间的依赖关系

(2)什么是IOC容器?

Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器

(3)什么是Bean?

容器中所存放的一个个对象就叫Bean或Bean对象

3.bean的作用域

创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个

配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中

的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在

Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表

五种作用域中,request、session 和 global session 三种作用域仅在基于web的应用中使用(不必关

心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。

3.1singleton——唯一 bean 实例

当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所

有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。 singleton 是单例

类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他

都存在了,每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要

在XML中将 bean 定义成 singleton ,可以这样配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

也可以通过 @Scope 注解(它可以显示指定bean的作用范围。)的方式

@Service
@Scope("singleton")
public class ServiceImpl{
}

3.2prototype——每次请求都会创建一个新的 bean 实例

当一个bean的作用域为 prototype,表示一个 bean 定义对应多个对象实例。prototype 作用域的

bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的

getBean() 方法)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候

并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是

同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使

用 singleton 作用域。** 在 XML 中将 bean 定义成 prototype ,可以这样配置:

 

3.3request——每一次HTTP请求都会产生一个新的bean,该bean 仅在当前HTTP request内有效

request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP

request内有效,当请求结束后,该对象的生命周期即告结束。 在 XML 中将 bean 定义成 prototype

,可以这样配置:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

3.4session——每一次HTTP请求都会产生一个新的 bean,该bean 仅在当前 HTTP session 内有效

session只适用于Web程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,

同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样,可以根据需要放心的更改所创建

实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定

于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内

的bean也会被废弃掉。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

3.5globalSession

global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才

有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的

portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围

内。

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

简单总结:

(1)Singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。

(2)Prototype:为每一个bean请求创建一个实例。

(3)Request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)Session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。

(5)GlobalSession:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

4.bean的生命周期

Bean的生命周期流程如上图所示,流程大致如下:

1.首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化,

2.按照Bean定义信息配置信息,注入所有的属性,

3.如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,

此时该Bean就获得了自己在配置文件中的id,

4.如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的

BeanFactory,这样该Bean就获得了自己所在的BeanFactory,5.如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入

该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext,

6.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方

法,

7.如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法,

8.如果Bean配置了init-method方法,则会执行init-method配置的方法,

9.如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方

法,

10.经过流程9之后,就可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓

存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,期生命周

期就交给调用方管理了,不再是Spring容器进行管理了

11.容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,

12.如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean的

生命周期结束

Spring 中 Bean 的生命周期较复杂,可以表示为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。

Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

 

5.BeanFactory与FactoryBean的区别

BeanFactory是Spring容器的顶级接⼝,给具体的IOC容器的实现提供了规范。

FactoryBean也是接⼝,为IOC容器中Bean的实现提供了更加灵活的⽅式,FactoryBean在IOC容器的基

础上给Bean的实现加上了⼀个简单⼯⼚模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者

模式,Decoration) 我们可以在getObject()⽅法中灵活配置。其实在Spring源码中有很多FactoryBean

的实现类.

区别:BeanFactory是个Factory,也就是IOC容器或对象⼯⼚,FactoryBean是个Bean。在Spring中,

所有的Bean都是由BeanFactory(也就是IOC容器)来进⾏管理的。

但对FactoryBean⽽⾔,这个Bean不是简单的Bean,⽽是⼀个能⽣产或者修饰对象⽣成的⼯⼚Bean,

它的实现与设计模式中的⼯⼚模式和修饰器模式类似

5.1BeanFactory

​ BeanFactory,以Factory结尾,表示它是⼀个⼯⼚类(接⼝), 它负责⽣产和管理bean的⼀个⼯⼚。在

Spring中,BeanFactory是IOC容器的核⼼接⼝,它的职责包括:**实例化、定位、配置应⽤程序中的

对象及建⽴这些对象间的依赖。**

​ BeanFactory只是个接⼝,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如

DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是

常⽤的⼀个,该实现将以XML⽅式描述组成应⽤的对象及对象间的依赖关系。XmlBeanFactory类将持

有此XML配置元数据,并⽤它来构建⼀个完全可配置的系统或应⽤。

​ 都是附加了某种功能的实现。它为其他具体的IOC容器提供了最基本的规范,例如

DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext 等具体的容器都是实现了

BeanFactory,再在其基础之上附加了其他的功能。

BeanFactory和ApplicationContext就是spring框架的两个IOC容器,现在⼀般使⽤

ApplicationnContext,其不但包含了BeanFactory的作⽤,同时还进⾏更多的扩展。

​ BeanFacotry是spring中⽐较原始的Factory。如XMLBeanFactory就是⼀种典型的BeanFactory。

原始的BeanFactory⽆法⽀持spring的许多插件,如AOP功能、Web应⽤等。ApplicationContext接⼝,

它由BeanFactory接⼝派⽣⽽来 ApplicationContext包含BeanFactory的所有功能,通常建议⽐BeanFactory优先

ApplicationContext以⼀种更向⾯向框架的⽅式⼯作以及对上下⽂进⾏分层和实现继承,

ApplicationContext包还提供了以下的功能:

​ 1MessageSource, 提供国际化的消息访问

​ 2资源访问,如URL和⽂件

​ 3事件传播

4载⼊多个(有继承关系)上下⽂ ,使得每⼀个上下⽂都专注于⼀个特定的层次,⽐如应⽤的web

层;

​ BeanFactory提供的⽅法及其简单,仅提供了六种⽅法供客户调⽤:

1.boolean containsBean(String beanName) 判断⼯⼚中是否包含给定名称的bean定义,若有

则返回true

2.Object getBean(String) 返回给定名称注册的bean实例。根据bean的配置情况,如果是

singleton模式将返回⼀个共享实例,否则将返回⼀个新建的实例,如果没有找到指定bean,该⽅法

可能会抛出异常

3.Object getBean(String, Class) 返回以给定名称注册的bean实例,并转换为给定class类型

4.Class getType(String name) 返回给定名称的bean的Class,如果没有找到指定的bean实例,则排

除NoSuchBeanDefinitionException异常

5.boolean isSingleton(String) 判断给定名称的bean定义是否为单例模式

6.String[] getAliases(String name) 返回给定bean名称的所有别名

5.2FactoryBean

​ ⼀般情况下,Spring通过反射机制利⽤ <bean> 的class属性指定实现类实例化Bean,在某些情况下,

实例化Bean过程⽐较复杂,如果按照传统的⽅式,则需要在 <bean> 中提供⼤量的配置信息。配置⽅

式的灵活性是受限的,这时采⽤编码的⽅式可能会得到⼀个简单的⽅案。

​ Spring为此提供了⼀个org.springframework.bean.factory.FactoryBean的⼯⼚类接⼝,⽤户可以通过

实现该接⼝定制实例化Bean的逻辑。FactoryBean接⼝对于Spring框架来说占⽤重要的地位,Spring⾃

身就提供了70多个FactoryBean的实现。它们隐藏了实例化⼀些复杂Bean的细节,给上层应⽤带来了

便利。从Spring3.0开始,FactoryBean开始⽀持泛型,即接⼝声明改为 FactoryBean<T> 的形式

以Bean结尾,表示它是⼀个Bean,不同于普通Bean的是:它是实现了 FactoryBean<T> 接⼝的

Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,⽽

不是FactoryBean本身,如果要获取FactoryBean对象,请在id前⾯加⼀个&符号来获取。

​ 例如⾃⼰实现⼀个FactoryBean,功能:⽤来代理⼀个对象,对该对象的所有⽅法做⼀个拦截,在调⽤

前后都输出⼀⾏LOG,模仿ProxyFactoryBean的功能。

​ FactoryBean是⼀个接⼝,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String

BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,⽽是这个实现类中的getObject()⽅

法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上

&。

package org.springframework.beans.factory; 
public interface FactoryBean<T> { 
 T getObject() throws Exception; 
 Class<?> getObjectType(); 
 boolean isSingleton(); 
}

在该接⼝中还定义了以下3个⽅法:

​ TgetObject():返回由FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放

到Spring容器中单实例缓存池中;

​ booleanisSingleton():返回由FactoryBean创建的Bean实例的作⽤域是singleton还是

prototype;

​ ClassgetObjectType():返回FactoryBean创建的Bean类型。

当配置⽂件中 <bean> 的class属性配置的实现类是FactoryBean时,通过getBean()⽅法返回的不是

FactoryBean本身,⽽是FactoryBean#getObject()⽅法所返回的对象,相当于

FactoryBean#getObject()代理了getBean()⽅法。

总结

BeanFactory是个Factory,也就是IOC容器或对象⼯⼚,FactoryBean是个Bean。在Spring中,所有的

Bean都是由BeanFactory(也就是IOC容器)来进⾏管理的。但对FactoryBean⽽⾔,这个Bean不是简单

的Bean,⽽是⼀个能⽣产或者修饰对象⽣成的⼯⼚Bean,它的实现与设计模式中的⼯⼚模式和修饰器模

式类似

6.bean的实例化

6.1 构造方法实例化

在上述的环境下,我们来研究下Spring中的第一种bean的创建方式构造方法实例化:

步骤1:准备需要被创建的类

准备一个BookDao和BookDaoImpl类

public interface BookDao {
    public void save();
}
​
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
​
}
步骤2:将类配置到Spring容器
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
​
</beans>
步骤3:编写运行程序
public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
​
    }
}
步骤4:类中提供构造函数测试

在BookDaoImpl类中添加一个无参构造函数,并打印一句话,方便观察结果。

public class BookDaoImpl implements BookDao {
    public BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,如果控制台有打印构造函数中的输出,说明Spring容器在创建对象的时候也走的是构造函数

步骤5:将构造函数改成private测试
public class BookDaoImpl implements BookDao {
    private BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见Spring底层用的是反射

步骤6:构造函数中添加一个参数测试
public class BookDaoImpl implements BookDao {
    private BookDaoImpl(int i) {
        System.out.println("book dao constructor is running ....");
    }
    public void save() {
        System.out.println("book dao save ...");
    }
​
}

运行程序,

程序会报错,说明Spring底层使用的是类的无参构造方法。

6.1.1分析Spring的错误信息

接下来,我们主要研究下Spring的报错信息来学一学如阅读。

  • 错误信息从下往上依次查看,因为上面的错误大都是对下面错误的一个包装,最核心错误是在最下面

  • Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()

    • Caused by 翻译为引起,即出现错误的原因

    • java.lang.NoSuchMethodException:抛出的异常为没有这样的方法异常

    • com.itheima.dao.impl.BookDaoImpl.<init>():哪个类的哪个方法没有被找到导致的异常,<init>()指定是类的构造方法,即该类的无参构造方法

如果最后一行错误获取不到错误信息,接下来查看第二层:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()

  • nested:嵌套的意思,后面的异常内容和最底层的异常是一致的

  • Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;

    • Caused by: 引发

    • BeanInstantiationException:翻译为bean实例化异常

    • No default constructor found:没有一个默认的构造函数被发现

看到这其实错误已经比较明显,给大家个练习,把倒数第三层的错误分析下吧:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bookDao' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()。

至此,关于Spring的构造方法实例化就已经学习完了,因为每一个类默认都会提供一个无参构造函数,所以其实真正在使用这种方式的时候,我们什么也不需要做。这也是我们以后比较常用的一种方式。

6.2静态工厂实例化

接下来研究Spring中的第二种bean的创建方式静态工厂实例化:

6.2.1工厂方式创建bean

在讲这种方式之前,我们需要先回顾一个知识点是使用工厂来创建对象的方式:

(1)准备一个OrderDao和OrderDaoImpl类

public interface OrderDao {
    public void save();
}
​
public class OrderDaoImpl implements OrderDao {
    public void save() {
        System.out.println("order dao save ...");
    }
}

(2)创建一个工厂类OrderDaoFactory并提供一个==静态方法==

//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

(3)编写AppForInstanceOrder运行类,在类中通过工厂获取对象

public class AppForInstanceOrder {
    public static void main(String[] args) {
        //通过静态工厂创建对象
        OrderDao orderDao = OrderDaoFactory.getOrderDao();
        orderDao.save();
    }
}

(4)运行后,可以查看到结果

(2)在AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试

public class AppForInstanceOrder {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
​
        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
​
        orderDao.save();
​
    }
}

(3)运行后,可以查看到结果

看到这,可能有人会问了,你这种方式在工厂类中不也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?

主要的原因是:

  • 在工厂的静态方法中,我们除了new对象还可以做其他的一些业务操作,这些操作必不可少,如:

public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");//模拟必要的业务操作
        return new OrderDaoImpl();
    }
}

之前new对象的方式就无法添加其他的业务内容,重新运行,查看结果:

介绍完静态工厂实例化后,这种方式一般是用来兼容早期的一些老系统,所以==了解为主==。

6.3实例工厂与FactoryBean

接下来继续来研究Spring的第三种bean的创建方式实例工厂实例化:

6.3.1 环境准备

(1)准备一个UserDao和UserDaoImpl类

public interface UserDao {
    public void save();
}
​
public class UserDaoImpl implements UserDao {
​
    public void save() {
        System.out.println("user dao save ...");
    }
}

(2)创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

(3)编写AppForInstanceUser运行类,在类中通过工厂获取对象

public class AppForInstanceUser {
    public static void main(String[] args) {
        //创建实例工厂对象
        UserDaoFactory userDaoFactory = new UserDaoFactory();
        //通过实例工厂对象创建对象
        UserDao userDao = userDaoFactory.getUserDao();
        userDao.save();
}

(4)运行后,可以查看到结果

对于上面这种实例工厂的方式如何交给Spring管理呢?

6.3.2 实例工厂实例化

具体实现步骤为:

(1)在spring的配置文件中添加以下内容:

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  • 创建实例化工厂对象,对应的是第一行配置

  • 调用对象中的方法来创建bean,对应的是第二行配置

    • factory-bean:工厂的实例对象

    • factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:

factory-mehod:具体工厂类中创建对象的方法名

(2)在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}

(3)运行后,可以查看到结果

实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。

6.3.3 FactoryBean的使用

具体的使用步骤为:

(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

(2)在Spring的配置文件中进行配置

<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

(3)AppForInstanceUser运行类不用做任何修改,直接运行

这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。

查看源码会发现,FactoryBean接口其实会有三个方法,分别是:

T getObject() throws Exception;
​
Class<?> getObjectType();
​
default boolean isSingleton() {
        return true;
}

方法一:getObject(),被重写后,在方法中进行对象的创建并返回

方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象

方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?

思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。

public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao1 = (UserDao) ctx.getBean("userDao");
        UserDao userDao2 = (UserDao) ctx.getBean("userDao");
        System.out.println(userDao1);
        System.out.println(userDao2);
    }
}

打印结果,如下:

通过验证,会发现默认是单例,那如果想改成单例具体如何实现?

只需要将isSingleton()方法进行重写,修改返回为false,即可

//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
​
    public Class<?> getObjectType() {
        return UserDao.class;
    }
​
    public boolean isSingleton() {
        return false;
    }
}

重新运行AppForInstanceUser,查看结果

从结果中可以看出现在已经是非单例了,但是一般情况下我们都会采用单例,也就是采用默认即可。所以isSingleton()方法一般不需要进行重写。

6.3 bean实例化小结

(1)bean是如何创建的呢?

构造方法



(2)Spring的IOC实例化对象的三种方式分别是:

  • 构造方法(常用)

  • 静态工厂(了解)

  • 实例工厂(了解)

    • FactoryBean(实用)

7aop

AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。

  • OOP(Object Oriented Programming)面向对象编程

  • 作用:在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式。

前面咱们有技术就可以实现这样的功能即代理模式

Spring的循环依赖指的是在Bean的创建过程中,存在A依赖于B,而B又依赖于A的情况,形成了一个循环引用的关系。这种循环依赖会导致Spring无法完成Bean的初始化,从而引发异常。

Spring提供了三种解决循环依赖的方式:

  1. 构造函数注入:通过构造函数注入依赖可以解决循环依赖。当A依赖于B,而B依赖于A时,可以将B的依赖通过构造函数的方式注入到A中,同时将A的依赖通过构造函数的参数传递给B。这样,在创建A时,会创建B并将其注入到A中,然后再创建B时,会使用A的实例,从而打破了循环依赖。

  2. Setter方法注入:如果使用通过Setter方法注入依赖的方式,Spring无法在创建Bean时解决循环依赖。但是,可以使用@Lazy注解延迟加载Bean,将Bean的创建和初始化过程推迟到真正使用时,借此解决循环依赖的问题。

  3. 使用代理:Spring启用了循环依赖检测机制,在发现循环依赖的情况时,会通过提前暴露半成品Bean的方式,将尚未初始化完全的Bean提前暴露给其他Bean使用。使用代理的方式进行循环依赖的解决,将A和B均使用代理对象,代理对象拦截对循环引用Bean的访问,确保Bean在完全初始化之前返回的是代理对象。

需要注意的是,在循环依赖的场景下,过于复杂或不正确的依赖关系会导致Spring无法解决循环依赖,最终引发异常。因此,在设计和使用Bean时,应尽量避免出现循环依赖的情况,并且合理设计依赖关系,以保证Bean的正确初始化和使用。

为什么使用三级缓存解决spring的循环依赖,而不是二级缓存

在Spring中,三级缓存是指在Bean的创建过程中,使用三个缓存来保存Bean实例,包括singletonObjects、earlySingletonObjects和singletonFactories。当需要创建一个Bean时,Spring首先在singletonObjects缓存中查找是否已经存在该Bean的实例,如果存在则直接返回。如果不存在则尝试从earlySingletonObjects缓存中获取半成品Bean(尚未完全初始化的Bean),如果获取成功则将其返回,否则尝试从singletonFactories缓存中获取创建Bean的工厂方法。

当使用三级缓存时,可以在循环依赖的场景下提前暴露半成品Bean,在半成品Bean中可以引用已经创建好的其他Bean实例,并在初始化完全后将其作为已创建的Bean存放在singletonObjects缓存中。

相较而言,二级缓存是指在创建Bean的过程中,使用两个缓存singletonObjects和singletonFactories来保存Bean实例。在循环依赖的场景下,二级缓存无法直接解决循环依赖,因为在二级缓存中无法获取到半成品Bean。

分析解决循环依赖 三级缓存singletonFactories

首先,singletonFactories存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory放入singletonFactoies中。【ObjectFactory是一个函数式接口,所以支持Lambda表达式:()->getEarlyReference(beanName, mbd, bean)】

ObjectFactory(上文的Lambda表达式),中间有getEarlyBeanReference方法【获取代理对象】,注意此时我们存入singletonFactories时不会执行Lambda表达式,也就是不会执行getEarlyBeanReference方法。

getEarlyBeanReference():①得到cachekey(beanName) ②将beanName和bean(原始对象)存入earlyProxyReferences ③调用wraplfNecessary进行AOP,得到代理对象

1、从singletonFactories根据beanName得到ObjectFactory,然后执行其getEarlyBeanReference方法,此时会得到A经过AOP后的代理对象,然后将其放入earlySingletonObjects中,此时并没有将代理对象放入singletonObjects中2、earlySingletonObjects作用:此时,我们只得到了A的代理对象,这个对象还不完整,因为还未进行属性注入,所以此时只能将A的代理对象放入earlySingletonObjects(singletonObjects单例池中放入的是全部生命周期后的bean),这样就能保证其他依赖了A对象的类获取到的就是同一个代理对象了。3、在B创建完之后,A继续进行生命周期,在A完成属性注入后,会按照本来的逻辑进行AOP,而此时A的原始对象已经经历过了AOP,所以对于A本身而言就不会再去进行AOP了。

如何判断是否已经进行AOP?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断档期按beanName是否存在earlyProxyReferences,如果存在则表示已经提前进行过AOP,无需再次进行。

4、对于A而言,进行AOP判断以及BeanPostProcessor(AOP实现)执行后,需要将A对应的对象放入singletonObjects中,但是,此时应该是从earlySingletonObjects中得到A的代理对象,然后放入singletonObjects(单例池)中

至此,整个循环依赖解决完毕。

总结1.singletonObjects:缓存某个经历了完整生命周期的bean2.earlySingletonObjects:缓存提前拿到原始对象并进行了AOP之后的代理对象;未进行属性注入的以及后续的3.BeanPostProcessor【AOP】等生命周期的bean4.singletonFactories:缓存的是一个ObjectFactory,用来生成进行了AOP之后的代理对象(在每个Bean的生成过程中都会提前暴露一个工厂),这个工厂可能用得到,也可能用不到5.earlyProxyReferences:记录对象是否进行了AOP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值