64-Spring进阶1-传统Javaweb开发的困惑 + IoC、DI和AOP思想提出 + Spring框架的诞生 + 基于xml的Spring应用

79 阅读37分钟

Spring进阶1-传统Javaweb开发的困惑 + IoC、DI和AOP思想提出 + Spring框架的诞生 + 基于xml的Spring应用

笔记内容来源于黑马程序员教学视频

一、传统Javaweb开发的困惑

①:代码分析(用户模块)

image.png

image.png

1.困惑1\color{#00FF00}{1. 困惑1}

image.png

2.困惑2\color{#00FF00}{2. 困惑2}

image.png

②:开发困惑及解决方案

1.困惑一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起\color{#00FF00}{1. 困惑一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起}

  • 解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象

image.png

2.困惑二:通用的事务功能耦合在业务代码中,通用的日志功能耦合在业务代\color{#00FF00}{2.困惑二:通用的事务功能耦合在业务代码中,通用的日志功能耦合在业务代} 码中\color{#00FF00}{码中}

  • 解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象的代理对象,代理对象内部动态结合业务和通用功能

image.png

二、IoC、DI和AOP思想提出

①:IoC 控制反转思想的提出

实际开发中,对象之间的耦合关系,就类似手表内部的齿轮,每个齿轮都紧密啮合在一起,一旦某个齿轮发生故障,那么整个系统也意味着崩溃。尽可能让对象之间的关系保持松耦合状态是我们期望的。

image.png

IoC思想Inversion of Control,翻译为“控制反转”或“反转控制”,强调的是原来在程序中创建Bean的权利反 转给第三方。

例如:

原来在程序中手动的去new UserServiceImpl()手动的去new UserDaoImpl(),而根据IoC思想的指导,寻求一个第三方去创建UserServiceImpl对象和UserDaoImpl对象。这样程序与具体对象就失去的直接联系。

谁去充当第三方角色呢?

  • 工厂设计模式,BeanFactory来充当第三方的角色,来产生Bean实例BeanFactory怎么知道产生哪些Bean实例呢?

  • 可以使用配置文件配置Bean的基本信息,BeanFactory根据配置文件来生产Bean实例

image.png

例如,用IoC思想去反转UserServiceImpl的创建权,由原来程序中创建反转给通过BeanFactory去创建

image.png

②:DI 依赖注入思想的提出

上面使用BeanFactory的方式已经实现的"控制反转" 将Bean的创建权交给了BeanFactory,如果我们想将UserDao的创建权也反转给BeanFactory,与此同时UserService内部还需要用到UserDao实例对象,那应该怎样操作呢?

  1. 在程序中,通过BeanFactory获得UserService
  2. 在程序中,通过BeanFactory获得UserDao
  3. 在程序中,将UserDao设置给UserService

该方式是否存在一些问题?

问题:UserService存在于BeanFactory中,UserDao也存在于BeanFactory中,可以在BeanFactory内部进行结合

image.png

解决方法

将UserDao在BeanFactory内部设置给UserService的过程叫做 “注入”,而UserService需要依赖UserDao 的注入才能正常工作,这个过程叫做 “依赖注入”

image.png

面试题:IoC 和 DI 的关系?

首先,先回答IoC和DI的是什么:

  • IoC: Inversion of Control,控制反转,将Bean的创建权由原来程序反转给第三方

  • DI:Dependency Injection,依赖注入,某个Bean的完整创建依赖于其他Bean(或普通参数)的注入

其次,在回答IoC和DI的关系:

  • 第一种观点:IoC强调的是Bean创建权的反转,而DI强调的是Bean的依赖关系,认为不是一回事

  • 第二种观点:IoC强调的是Bean创建权的反转,而DI强调的是通过注入的方式反转Bean的创建权,认为DI 是IoC的其中一种实现方式

③:AOP 面向切面思想的提出

image.png

AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个 事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程

image.png

三种思想总结

  1. IoC控制反转,是将程序创建Bean的权利反转给第三方;
  2. DI依赖注入,某个完整Bean需要依赖于其他Bean(或属性)的注入;
  3. AOP面向切面编程,用横向抽取方法(属性、对象等)思想,组装成一个功能性切面

④:框架概念的出现

上面提出了一些思想来解决遇到的问题,而这些思想的实现就需要通过编码去落地,往往我们把具备一定业务领域解决方案的"工具"称为框架。

框架的基本特点:

  1. 框架(Framework),是基于基础技术之上,从众多业务中抽取出的通用解决方案;

  2. 框架是一个半成品,使用框架规定的语法开发可以提高开发效率,可以用简单的代码就能完成复杂的基础业务;

  3. 框架内部使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等;

  4. 框架一般都具备扩展性;

  5. 有了框架,我们可以将精力尽可能的投入在纯业务开发上而不用去费心技术实现以及一些辅助业务。

Java中常用的框架:

不同语言,不同领域都有属于自己的框架,使用框架开发是作为程序员的最基础的底线。Java语言中的框架,可以分为基础框架和服务框架:

  • 基础框架:完成基本业务操作的框架,如MyBatis、Spring、SpringMVC、Struts2、Hibernate等
  • 服务框架:特定领域的框架,一般还可以对外提供服务框架,如MQ、ES、Nacos等

⑤:思想、框架和编码关系

image.png

  • 架构师(高级程序员):把思想落地变为实现的人,例如上面的设计和BeanFactory的编写,即框架的设计和实现者。
  • 程序员:使用框架完成业务的人,其中UserServiceImpl、beans.xml、测试类都是我们编写的。

三、Spring框架的诞生

①:Spring框架概述

spring是一个开源的轻量级Java开发应用框架,可以简化企业级应用开发。Spring解决了开发者在JavaEE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。是当前企业中Java开发几乎不能缺少的框架之一。Spring的生态及其完善,不管是Spring哪个领域的解决方案都是依附于在Spring Framework基础框架的。

Spring的官网:www.spring.io image.png

②:Spring 框架的历史

  • Jsp 默默扛下所有;
  • MVC+三层架构分工明确,但开发成本及其高;
  • EJB 重量级框架出现,走出一个困境,有进入另一个困境;
  • Spring 春天来到,随之,SSH风生水起、称霸武林;
  • Spring 稳住江湖大哥位置,SSM开始上位;
  • Spring 本着“拿来主义”的思维快速发展,生态不断健全;
  • SpringBoot 又一里程碑崛起,把“约定大于配置“思想玩儿的炉火纯青;
  • SpringCloud 打包了微服务众多解决方案,应对互联网项目更加easy!

③:Spring Framework技术栈图示

image.png

④:BeanFactory 快速入门

根据下图,分析一下Spring的BeanFactory的开发步骤:

image.png

  1. 导入Spring的jar包或Maven坐标;
  2. 定义UserService接口及其UserServiceImpl实现类;
  3. 创建beans.xml配置文件,将UserServiceImpl的信息配置到该xml中;
  4. 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象。

1. 快速入门1

  1. 导入Spring的jar包或Maven坐标
<!--Spring核心-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>
  1. 定义UserService接口及其UserServiceImpl实现类
public interface UserService {}
public class UserServiceImpl implements UserService {}
  1. 创建beans.xml配置文件,将UserServiceImpl的信息配置到该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="userService" class="com.it.service.impl.UserServiceImpl"/>
</beans>
  1. 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象
public static void main(String[] args) {
    // 创建BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    // 创建读取器
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    // 加载配置文件
    reader.loadBeanDefinitions("beans.xml");
    // 获取Bean实例对象
    UserService userService = (UserService) beanFactory.getBean("userService");
    
    System.out.println("userService = " + userService);
}
  1. 测试

image.png

1. 快速入门2

  1. 定义UserDao接口及其UserDaoImpl实现类
public interface UserDao {}

public class UserDaoImpl implements UserDao {}
  1. 修改UserServiceImpl代码,添加一个setUserDao(UserDao userDao)用于接收注入的对象
public class UserServiceImpl implements UserService {

    private UserDao userDao;
    public void setUserDao(UserDao userDao){
        this.userDao= userDao;
        System.out.println("userDao = " + userDao);
    }
}
  1. 修改beans.xml配置文件,将UserDaoImpl的信息配置到该xml中
<bean id="userService" class="com.it.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

<bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>
  1. 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象
public static void main(String[] args) {
    // 创建BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    // 创建读取器
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    // 加载配置文件
    reader.loadBeanDefinitions("beans.xml");
    // 获取Bean实例对象
    UserService userService = (UserService) beanFactory.getBean("userService");

    System.out.println("userService = " + userService);
}
  1. 测试

image.png

⑤:ApplicationContext快速入门

ApplicationContext 称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用ApplicationContext 进行开发时,xml配置文件的名称习惯写成applicationContext.xml

public static void main(String[] args) {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 根据beanName获得容器中的Bean实例
    UserService userService = (UserService) context.getBean("userService");
    System.out.println("userService = " + userService);
}

⑥:BeanFactory与ApplicationContext的关系

  1. BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;

  2. ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;

  3. Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。

  4. Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。

ApplicationContext除了继承了BeanFactory外,还继承了ApplicationEventPublisher(事件发布器)、ResouresPatternResolver(资源解析器)、MessageSource(消息资源)等。但是ApplicationContext的核心功能还是BeanFactory。

image.png

image.png

验证BeanFactory和ApplicationContext对Bean的初始化时机,在UserDaoImpl的无参构造内打印一句话,验证构造方法的执行时机

public class UserDaoImpl implements UserDao {

    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建了...");
    }
}

断点观察,BeanFactory方式时,当调用getBean方法时才会把需要的Bean实例创建,即延迟加载(懒汉);而ApplicationContext是加载配置文件,容器创建时就将所有的Bean实例都创建好了(饿汉),存储到一个单例池中,当调用getBean时直接从单例池中获取Bean实例返回

⑦:BeanFactory的继承体系

BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory,而ApplicationContext内部维护的Beanfactory的实现类也是它

image.png

⑧:ApplicationContext的继承体系

1.只在Spring基础环境下,即只导入springcontext坐标时,此时\color{#00FF00}{1. 只在Spring基础环境下,即只导入spring-context坐标时,此时} ApplicationContext的继承体系\color{#00FF00}{ApplicationContext的继承体系}

image.png

2.只在Spring基础环境下,常用的三个ApplicationContext作用如下:\color{#00FF00}{2. 只在Spring基础环境下,常用的三个ApplicationContext作用如下:}

实现类功能描述
ClassPathXmlApplicationContext加载类路径下的xml配置的ApplicationContext
FileSystemXmlApplicationContext加载磁盘路径下的xml配置的ApplicationContext
AnnotationConfigApplicationContext加载注解配置类的ApplicationContext

3.如果Spring基础环境中加入了其他组件解决方案,如web层解决方案,即导\color{#00FF00}{3. 如果Spring基础环境中加入了其他组件解决方案,如web层解决方案,即导} springweb坐标,此时ApplicationContext的继承体系\color{#00FF00}{入spring-web坐标,此时ApplicationContext的继承体系}

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.7</version>
</dependency>

image.png

4.Springweb环境下,常用的两个ApplicationContext作用如下:\color{#00FF00}{4. 在Spring的web环境下,常用的两个ApplicationContext作用如下:}

实现类功能描述
XmlWebApplicationContextweb环境下,加载类路径下的xml配置的ApplicationContext
AnnotationConfigWebApplicationContextweb环境下,加载磁盘路径下的xml配置的ApplicationContext

PS:web环境下的这两个ApplicationContext,在学习Spring集成web时在进行讲解

四、基于xml的Spring应用

①:SpringBean 的配置详解

1. xml方式Bean的配置概述

Spring开发中主要是对Bean的配置,Bean的常用配置一览如下:

Xml配置方式功能描述
<bean id="" class="">Bean的id和全限定名配置
<bean name="">通过name设置Bean的别名,通过别名也能直接获取到Bean实例
<bean scope="">Bean的作用范围,BeanFactory作为容器时取值singleton和prototype
<bean lazy-init="">Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效
<bean init-method="">Bean实例化后自动执行的初始化方法,method指定方法名
<bean destroy-method="">Bean实例销毁前的方法,method指定方法名
<bean autowire="byType">设置自动注入模式,常用的有按照类型byType,按照名字byName
<bean factory-bean="" factory-method=""/>指定哪个工厂Bean的哪个方法完成Bean的创建

2. beanName和别名配置

1.Bean的基础配置\color{#00FF00}{1. Bean的基础配置}

例如:配置UserDaoImpl由Spring容器负责管理

<bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>

此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaoImpl对象,可以根据beanName获取Bean实例

public static void main(String[] args) {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 根据beanName获得容器中的Bean实例
    UserDao userDao = (UserDao) context.getBean("userDao");
    System.out.println("userDao = " + userDao);
}

如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName

UserDao userDao = (UserDao) context.getBean("com.it.dao.impl.UserDaoImpl");

2.Bean的别名配置\color{#00FF00}{2. Bean的别名配置}

可以为当前Bean指定多个别名,根据别名也可以获得Bean对象

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl"/>
applicationContext.getBean("userDao");

applicationContext.getBean("aaa");

applicationContext.getBean("bbb");

3. Bean的范围配置

默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype

  • singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;

  • prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。

1.scope设置为singleton时,获得两次对象打印结果是一样的\color{#00FF00}{1. 当scope设置为singleton时,获得两次对象打印结果是一样的}

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl" scope="singleton"/>
public static void main(String[] args) {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 根据beanName获得容器中的Bean实例
    UserDao userDao = (UserDao) context.getBean("userDao");
    UserDao userDao2 = (UserDao) context.getBean("userDao");
    System.out.println("userDao = " + userDao);
    System.out.println("userDao2 = " + userDao2);
}

image.png

2.scope设置为prototype时,获得两次对象打印结果是不一样的\color{#00FF00}{2. 当scope设置为prototype时,获得两次对象打印结果是不一样的}

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl" scope="prototype"/>

image.png

4. Bean的延迟加载

当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl" lazy-init="true"/>

5. Bean的初始化和销毁方法配置

Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
public static void main(String[] args) throws IOException {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 根据beanName获得容器中的Bean实例
    UserDao userDao = (UserDao) context.getBean("userDao");
    System.out.println("userDao = " + userDao);
    // 关闭容器,调用销毁方法
    context.close();
}

image.png

扩展:除此之外,我们还可以通过实现 InitializingBean 接口,完成一些Bean的初始化操作,如下:

public class UserDaoImpl implements UserDao, InitializingBean {

    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建了...");
    }

    public void destroy() {
        System.out.println("执行了销毁方法~ ");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行了初始化方法~ ");
    }
}

6. Bean的实例化配置

Spring的实例化方式主要如下两种:

  • 构造方式实例化:底层通过构造方法对Bean进行实例化
  • 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化

构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的<bean>几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean

public class UserDaoImpl implements UserDao{

    // 无参构造方法
    public UserDaoImpl() {
        System.out.println("执行了UserDaoImpl的无参构造方法~");
    }
    // 有参构造方法
    public UserDaoImpl(String name, int age) {
        System.out.println("姓名:" + name + " 年龄" + age +  "执行了UserDaoImpl的有参构造方法~");
    }
}

有参构造在实例化Bean时,需要参数的注入,通过标签,嵌入在标签内部提供构造参数,如下:

<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl">
   <constructor-arg name="name" value="小🐕"/>
   <constructor-arg name="age" value="18"/>
</bean>

image.png

工厂方式实例化Bean,又分为如下三种:

  • 静态工厂方法实例化Bean

  • 实例工厂方法实例化Bean

  • 实现FactoryBean规范延迟实例化Bean

1.静态工厂方法实例化Bean\color{#00FF00}{1. 静态工厂方法实例化Bean}

静态工厂方法实例化Bean,其实就是定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其静态方法配置给Spring即可

// 工厂类
public class UserDaoFactoryBean {
    // 静态工厂方法
    public static UserDaoImpl userDaoImpl(String name, int age){
        // 可以在此编写一些其他逻辑代码
        return new UserDaoImpl(name, age);
    }
}
<bean id="userDaoImpl" class="com.it.factory.UserDaoFactoryBean" factory-method="userDaoImpl">
    <constructor-arg name="name" value="小🐕"/>
    <constructor-arg name="age" value="18"/>
</bean>
  • 测试
public class TestMyBeanFactory {
    public static void main(String[] args) {
        // 创建ApplicationContext,加载配置文件,实例化容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 根据beanName获得容器中的Bean实例
        UserDaoImpl userDaoImpl = (UserDaoImpl) context.getBean("userDaoImpl");
        System.out.println("userDaoImpl = " + userDaoImpl);
    }
}

image.png

PS:<constructor-arg>标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过<constructor-arg>标签完成,例如上面通过静态工厂方法实例化Bean所传递的参数也是要通过<constructor-arg>进行传递的

2.实例工厂方法实例化Bean\color{#00FF00}{2. 实例工厂方法实例化Bean}

实例工厂方法,也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,在配置目标Bean

// 工厂类
public class UserDaoFactoryBean2 {
    // 非静态工厂方法
    public  UserDaoImpl userDaoImpl2(String name, int age){
        // 可以在此编写一些其他逻辑代码
        return new UserDaoImpl(name, age);
    }
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.it.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDaoImpl2" factory-bean="userDaoFactoryBean2" factory-method="userDaoImpl2">
    <constructor-arg name="name" value="小🐱"/>
    <constructor-arg name="age" value="18"/>
</bean>
  • 测试
public static void main(String[] args) {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 根据beanName获得容器中的Bean实例
    UserDaoImpl userDaoImpl2 = (UserDaoImpl) context.getBean("userDaoImpl2");
    System.out.println("userDaoImpl2 = " + userDaoImpl2);
}

image.png

3.实现FactoryBean规范延迟实例化Bean\color{#00FF00}{3. 实现FactoryBean规范延迟实例化Bean}

上面不管是静态工厂方式还是非静态工厂方式,都是自定义的工厂方法,Spring提供了FactoryBean的接口规范,FactoryBean接口定义如下:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    
    //获得实例对象方法
    @Nullable
    T getObject() throws Exception;

    //获得实例对象类型方法
    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

定义工厂实现FactoryBean

// 工厂类
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {

    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}
<bean id="userDao2" class="com.it.factory.UserDaoFactoryBean3"/>

通过Spring容器根据beanName可以正常获得UserDaoImpl

public static void main(String[] args) throws IOException {
   // 创建ApplicationContext,加载配置文件,实例化容器
   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   // 根据beanName获得容器中的Bean实例
   UserDao userDao2 = (UserDao) context.getBean("userDao2");
   System.out.println("userDao2 = " + userDao2);
}

image.png

通过断点观察发现Spring容器创建时,FactoryBean被实例化了,并存储到了单例池singletonObjects中,但是getObject() 方法尚未被执行,UserDaoImpl也没被实例化,当首次用到UserDaoImpl时,才调用getObject() ,此工厂方式产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中,并且后期每次使用到userDao都从该缓存池中返回的是同一个userDao实例。

image.png

7. Bean的依赖注入配置

Bean的依赖注入有两种方式:

注入方式配置方式
通过Bean的set方法注入<property name="userDao" ref="userDao"/>
<property name="userDao" value="haohao"/>
通过构造Bean的方法进行注入<constructor-arg name="name" ref="userDao"/>
<constructor-arg name="name" value="haohao"/>

其中,ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value 用于注入普通属性值。

1.依赖注入的数据类型有如下三种:

  • 普通数据类型,例如:String、int、boolean等,通过value属性指定。
  • 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
  • 集合数据类型,例如:List、Map、Properties等。

1.注入List<T>集合–普通数据\color{#00FF00}{1. 注入 List<T> 集合 – 普通数据}

public class UserServiceImpl implements UserService {

    private List<String> strList;

    public void setStrList(List<String> strList) {
        this.strList = strList;
    }

    @Override
    public void showList() {
        System.err.println("strList = " + strList);
    }
}
public interface UserService {
    public void showList();
}
<bean id="userService2" class="com.it.service.impl.UserServiceImpl">
    <property name="strList">
        <list>
            <value>张三</value>
            <value>李四</value>
        </list>
    </property>
</bean>

2.注入List<T>集合–引用数据\color{#00FF00}{2. 注入 List<T> 集合 – 引用数据}

public class UserServiceImpl implements UserService {

    private List<String> strList;

    private List<UserService> userList;

    public void setStrList(List<String> strList) {
        this.strList = strList;
    }

    public void setUserList(List<UserService> userList) {
        this.userList = userList;
    }

    @Override
    public void showList() {
        System.err.println("strList = " + strList);
        System.err.println("userList = " + userList);
    }
}
<bean id="userService2" class="com.it.service.impl.UserServiceImpl">
    <property name="strList">
        <list>
            <value>张三</value>
            <value>李四</value>
        </list>
    </property>
    <property name="userList">
        <list>
            <ref bean="user1"/>
            <ref bean="user2"/>
            <ref bean="user3"/>
        </list>
    </property>
</bean>

<bean id="user1" class="com.it.service.impl.UserServiceImpl"/>
<bean id="user2" class="com.it.service.impl.UserServiceImpl"/>
<bean id="user3" class="com.it.service.impl.UserServiceImpl"/>

image.png

3.注入Set<T>集合\color{#00FF00}{3. 注入 Set<T> 集合}

public class UserServiceImpl implements UserService {

    private Set<String> strSet;
    private Set<UserDao> strUserDao;

    public void setStrSet(Set<String> strSet) {
        this.strSet = strSet;
    }

    public void setStrUserDao(Set<UserDao> strUserDao) {
        this.strUserDao = strUserDao;
    }

    @Override
    public void showList() {
        System.out.println("strSet = " + strSet);
        System.out.println("strUserDao = " + strUserDao);
    }
}
<bean id="userService2" class="com.it.service.impl.UserServiceImpl">
    <property name="strSet">
        <set>
            <value>张三</value>
            <value>李四</value>
        </set>
    </property>
    <property name="strUserDao">
        <set>
            <ref bean="user1"/>
            <ref bean="user2"/>
            <ref bean="user3"/>
        </set>
    </property>
</bean>

<bean id="user1" class="com.it.dao.impl.UserDaoImpl"/>
<bean id="user2" class="com.it.dao.impl.UserDaoImpl"/>
<bean id="user3" class="com.it.dao.impl.UserDaoImpl"/>
  • 测试

public class TestApplicationContext {
    public static void main(String[] args) throws IOException {
        // 创建ApplicationContext,加载配置文件,实例化容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 根据beanName获得容器中的Bean实例
        UserService userService2 = (UserService) context.getBean("userService2");
        userService2.showList();
    }
}

image.png

4.注入Map<K,V>集合\color{#00FF00}{4. 注入 Map<K,V> 集合}

public class UserServiceImpl implements UserService {

    private Map<String, String> stringMap;
    private Map<String,UserDao> userDaoMap;

    public void setStringMap(Map<String, String> stringMap) {
        this.stringMap = stringMap;
    }

    public void setUserDaoMap(Map<String, UserDao> userDaoMap) {
        this.userDaoMap = userDaoMap;
    }

    @Override
    public void showList() {
        System.out.println("stringMap = " + stringMap);
        System.out.println("userDaoMap = " + userDaoMap);
    }
}
<bean id="userService2" class="com.it.service.impl.UserServiceImpl">
    <property name="stringMap">
        <map>
            <entry key="张三" value="地址:河南"/>
            <entry key="李四" value="地址:北京"/>
        </map>
    </property>
    <property name="userDaoMap">
        <map>
            <entry key="张三" value-ref="user1"/>
            <entry key="李四" value-ref="user2"/>
            <entry key="王五" value-ref="user3"/>
        </map>
    </property>
</bean>
<bean id="user1" class="com.it.dao.impl.UserDaoImpl"/>
<bean id="user2" class="com.it.dao.impl.UserDaoImpl"/>
<bean id="user3" class="com.it.dao.impl.UserDaoImpl"/>
  • 测试

image.png

5.注入Properties键值对\color{#00FF00}{5. 注入 Properties 键值对}

public class UserServiceImpl implements UserService {

    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public void showList() {
        System.out.println("properties = " + properties);
    }
}
<bean id="userService2" class="com.it.service.impl.UserServiceImpl">
    <property name="properties">
    <props>
        <prop key="小🐋">坐标:河南</prop>
        <prop key="小🦌">坐标:上海</prop>
    </props>
    </property>
</bean>
  • 测试

image.png

6.扩展:自动装配方式\color{#00FF00}{6. 扩展:自动装配方式}

如果被注入的属性类型是Bean引用的话,那么可以在 标签中使用 autowire 属性去配置自动注入方式,属性值有两个:

  • byName:通过属性名自动装配,即去匹配 setXxx 与 id="xxx"(name="xxx")是否一致;
  • byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void showUserDao() {
        System.err.println("userDao = " + userDao);
    }
}
<bean id="userDao" name="aaa,bbb" class="com.it.dao.impl.UserDaoImpl" lazy-init="true">
  <constructor-arg name="name" value="小🐕"/>
  <constructor-arg name="age" value="18"/>
</bean>
<bean id="userService" class="com.it.service.impl.UserServiceImpl" autowire="byName"/>
  • 测试

image.png

8. Spring的其他配置标签

Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签

  • 默认标签:就是不用额外导入其他命名空间约束的标签,例如<bean>标签

  • 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 <context:property-placeholder/> 标签

1.Spring的默认标签用到的是Spring的默认命名空间\color{#00FF00}{1. Spring的默认标签用到的是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">

</beans>

该命名空间约束下的默认标签如下:

标签作用
<beans>一般作为 xml 配置根标签,其他标签都是该标签的子标签
<bean>Bean的配置标签,上面已经详解了,此处不再阐述
<import>外部资源导入标签
<alias>指定Bean的别名标签,使用较少

2.<beans>标签\color{#00FF00}{2.<beans>标签}

<beans>标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境

<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
  <bean id="userService" class="com.it.service.impl.UserServiceImpl" autowire="byName">
  </bean>

  <bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>
</beans>

<!-- 配置开发环境下,需要加载的Bean实例 -->
<beans profile="dev">

</beans>

可以使用以下两种方式指定被激活的环境:

  • 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test

  • 使用代码的方式设置环境变量 System.setProperty("spring.profiles.active","test")

public class UserServiceImpl implements UserService {

   private UserDao userDao;

   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
   }

   @Override
   public void showUserDao() {
       System.err.println("userDao = " + userDao);
   }
}
public static void main(String[] args) throws IOException {
    // 指定被激活的环境
    System.setProperty("spring.profiles.active","test");
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    // 根据beanName获得容器中的Bean实例
    UserService userService = (UserService) context.getBean("userService");
    userService.showUserDao();
}
  • 测试

image.png

3.<import>标签\color{#00FF00}{3. <import>标签}

<import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过<import>标签导入到一个主配置文件中,项目加载主配置文件就连同<import> 导入的文件一并加载了

<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
  • 创建applicationContext-user.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="userService" class="com.it.service.impl.UserServiceImpl" autowire="byName">
    </bean>

    <bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>
</beans>
  • 在beans.xml文件中引入applicationContext-user.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">
    <!--导入用户模块配置文件-->
    <import resource="applicationContext-user.xml"/>
</beans>
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void showList() {
    }

    @Override
    public void showUserDao() {
        System.err.println("userDao = " + userDao);
    }
}
  • 测试

image.png

4.<alias>标签\color{#00FF00}{4. <alias> 标签}

<alias> 标签是为某个Bean添加别名,与在<bean> 标签上使用name属性添加别名的方式一样,我们为UserServiceImpl指定四个别名:aaa、bbb、xxx、yyy

<!--配置UserService-->
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userService" alias="yyy"/>

断点调试,在beanFactory中维护着一个名为aliasMap的Map<String,String>集合,存储别名和beanName 之间的映射关系

image.png

5.Spring的自定义标签\color{#00FF00}{5. Spring的自定义标签}

Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring xml扩展点方式之一

使用自定义标签是需要引入对应依赖的

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.7</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.7</version>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>2.7.4.1</version>
    </dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       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-contexts.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://dubbo.apache.org/schema/dubbo 
       http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!--默认标签-->
    <bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>

    <!--自定义标签-->
    <context:property-placeholder/>
    <mvc:annotation-driven/>
    <dubbo:application name="application"/>
</beans>

\color{#00FF00}{}

②:Spring 的get方法

方法定义返回值和参数
Object getBean (String beanName)根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转
T getBean (Class type)根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转
T getBean (String beanName,Class type)根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转
//根据beanName获取容器中的Bean实例,需要手动强转
UserService userService = (UserService) applicationContext.getBean("userService");

//根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
UserService userService2 = applicationContext.getBean(UserService.class);

//根据beanName获取容器中的Bean实例,指定Bean的Type类型
UserService userService3 = applicationContext.getBean("userService",
UserService.class);

③:Spring 配置非自定义Bean

以上在 xml 中配置的Bean都是自己定义的,例如:UserDaoImpl,UserServiceImpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置

配置非自定义的Bean需要考虑如下两个问题:

  • 被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式;

  • 被配置的Bean是否需要注入必要属性。

1.配置Druid数据源交由Spring管理\color{#00FF00}{1. 配置 Druid 数据源交由Spring管理}

  • 导入Druid和mysql驱动坐标
<!-- druid数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
  • 配置 DruidDataSource
<!--配置 DruidDataSource数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <!--配置必要属性-->
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&amp;characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
  • 测试

image.png

2.配置Connection交由Spring管理\color{#00FF00}{2. 配置Connection交由Spring管理}

Connection 的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置

<bean class="java.lang.Class" factory-method="forName">
    <constructor-arg name="className" value="com.mysql.cj.jdbc.Driver"/>
</bean>

<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
    <constructor-arg name="url" value="jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&amp;characterEncoding=utf-8"/>
    <constructor-arg name="user" value="root"/>
    <constructor-arg name="password" value="root"/>
</bean>
  • 测试

image.png

3.配置日期对象交由Spring管理\color{#00FF00}{3. 配置日期对象交由Spring管理}
产生一个指定日期格式的对象,原始代码按如下:

String currentTimeStr = "2022-11-11 13:20:42";

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Date date = simpleDateFormat.parse(currentTimeStr);

可以看成是实例工厂方式,使用Spring配置方式产生Date实例

<bean id="dataFormat" class="java.text.SimpleDateFormat">
    <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>

<bean id="data" factory-bean="dataFormat" factory-method="parse">
    <constructor-arg name="source" value="2022-11-11 13:20:42"/>
</bean>
  • 测试

image.png

4.配置MyBatisSqlSessionFactory交由Spring管理\color{#00FF00}{4. 配置MyBatis的SqlSessionFactory交由Spring管理}

  • 导入MyBatis的相关坐标:
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
<!--mybatis框架-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>
  • MyBatis原始获得SqlSessionFactory的方式:
// 加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
   
// 创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
   
// 调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
  • SqlSessionFactory交由Spring管理配置如下:
<!--静态工厂方式产生Bean实例-->
<bean id="inputStream" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
    <constructor-arg name="resource" value="mybatis-config.xml"/>
</bean>
    
<!--无参构造方式产生Bean实例-->
<bean id="builder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
    
<!--实例工厂方式产生Bean实例-->
<bean id="sqlSessionFactory" factory-bean="builder" factory-method="build">
    <constructor-arg name="inputStream" ref="inputStream"/>
</bean>
  • 测试

image.png

④:Bean 实例化的基本流程

Spring容器在进行初始化时,会将xml配置的的信息封装成一个BeanDefinition对象,所有的 BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。

Bean信息定义对象-BeanDefinition

<bean id="" class="" name="" lazy-init="" scope="" init-method=""destroy-method=""factory-bean=""factory-method="" abstract=""depends-on="" parent="">
    <property name="" ref=""/>
    <property name="" ref=""/>
    <property name="" value=""/>
</bean>
  • 信息封装如下:
public interface BeanDefinition {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    
    void setBeanClassName(@Nullable String var1);
    String getBeanClassName();
    
    void setScope(@Nullable String var1);
    String getScope();
    
    void setLazyInit(boolean var1);
    boolean isLazyInit();
    
    void setFactoryBeanName(@Nullable String var1);
    String getFactoryBeanName();
    
    void setFactoryMethodName(@Nullable String var1);
    String getFactoryMethodName();
    
    void setInitMethodName(@Nullable String var1);
    String getInitMethodName();
    //..... 省略部分属性和方法
}

DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap

public class DefaultListableBeanFactory extends ... implements ... {
    //存储<bean>标签对应的BeanDefinition对象
    //key:是Bean的beanName,value:是Bean定义对象BeanDefinition
    private final Map<String, BeanDefinition> beanDefinitionMap;
}

Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作

Bean实例及单例池singletonObjects, beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类 DefaultSingletonBeanRegistry中,维护着singletonObjects,源码如下:

public class DefaultSingletonBeanRegistry extends ... implements ... {
    //存储Bean实例的单例池
    ////key:是Bean的beanName,value:是Bean的实例对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
} 

Bean 实例化的基本流程

  • 加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
  • 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
  • ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
  • 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
  • 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

image.png

⑤:Spring的后处理器

Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册 BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
  • BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。

1. 工厂后处理器

Bean工厂后处理器 – BeanFactoryPostProcessor

BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回 调该接口的方法,用于对BeanDefinition注册和修改的功能。

BeanFactoryPostProcessor 定义如下:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

1. 编写MyBeanFactoryPostProcessor

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor 执行了~");
    }
}

2. 配置BeanFactoryPostProcessor

<bean id="processor" class="com.it.MyBeanFactoryPostProcessor"/>

postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的BeanDefinition进行操作了 ,例如对UserDaoImpl的BeanDefinition进行修改操作

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //获得userService定义对象
        BeanDefinition userService = beanFactory.getBeanDefinition("userService");
        // 修改成UserDao对象
        userService.setBeanClassName("com.it.dao.impl.UserDaoImpl");
        System.out.println("MyBeanFactoryPostProcessor 执行了~");
    }
}
  • 测试
public static void main(String[] args) throws IOException {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    // 根据beanName获得容器中的Bean实例
    Object userService = context.getBean("userService");
    System.out.println("userService = " + userService);
}

image.png

上面已经对指定的BeanDefinition进行了修改操作,下面对BeanDefiition进行注册操作

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 强转成子类DefaultListableBeanFactory
        if (beanFactory instanceof DefaultListableBeanFactory){
            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
            RootBeanDefinition beanDefinition = new RootBeanDefinition();
            beanDefinition.setBeanClassName("com.it.dao.impl.UserDaoImpl2");
            //  进行注册操作
            defaultListableBeanFactory.registerBeanDefinition("userDao2", beanDefinition);
        }
        System.out.println("MyBeanFactoryPostProcessor 执行了~");
    }
}
  • 测试
public static void main(String[] args) throws IOException {
    // 创建ApplicationContext,加载配置文件,实例化容器
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    // 根据beanName获得容器中的Bean实例
    Object userDao2 = context.getBean("userDao2");
    System.out.println("userDao2 = " + userDao2);
}

image.png

Spring 提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作

public class MyBeanFactoryRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanDefinitionRegistry) throws BeansException {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.it.dao.impl.UserDaoImpl2");
        beanDefinitionRegistry.registerSingleton("userDao2", beanDefinition);
    }
}
<bean id="processor" class="com.it.MyBeanFactoryRegistryPostProcessor"/>
  • 测试

image.png

  • BeanFactoryPostProcessorSpringBean的实例化过程中的体现

image.png

2. 案例(自定义注解扫描)

要求

  1. 自定义@MyComponent注解,使用在类上;

  2. 使用资料中提供好的包扫描器工具BaseClassScanUtils 完成指定包的类扫描;

  3. 自定义BeanFactoryPostProcessor完成注解@MyComponent的解析,解析后最终被Spring管理

1.自定义@MyComponent注解,使用在类上\color{#00FF00}{1. 自定义@MyComponent注解,使用在类上}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
    
    //显示的指定Bean的beanName
    String value() default "";
}

2.在类上使用@MyComponent\color{#00FF00}{2. 在类上使用@MyComponent}

@MyComponent("otherBean")
public class OtherBean {

}

3.工具类\color{#00FF00}{3. 工具类}

public class BaseClassScanUtils {

    //设置资源规则
    private static final String RESOURCE_PATTERN = "/**/*.class";

    public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {

        //创建容器存储使用了指定注解的Bean字节码对象
        Map<String, Class> annotationClassMap = new HashMap<String, Class>();

        //spring工具类,可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = refractory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(classname);
                //判断是否属于指定的注解类型
                if(clazz.isAnnotationPresent(MyComponent.class)){
                    //获得注解对象
                    MyComponent annotation = clazz.getAnnotation(MyComponent.class);
                    //获得属value属性值
                    String beanName = annotation.value();
                    //判断是否为""
                    if(beanName!=null&&!beanName.equals("")){
                        //存储到Map中去
                        annotationClassMap.put(beanName,clazz);
                        continue;
                    }

                    //如果没有为"",那就把当前类的类名作为beanName
                    annotationClassMap.put(clazz.getSimpleName(),clazz);

                }
            }
        } catch (Exception exception) {
        }

        return annotationClassMap;
    }

    public static void main(String[] args) {
        Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.it");
        System.out.println(stringClassMap);
    }
}

4.自定义BeanFactoryPostProcessor完成注解解析\color{#00FF00}{4. 自定义BeanFactoryPostProcessor完成注解解析}

public class MyBeanFactoryRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //指定要扫描的包
        String basePackage = "com.it";
        //调用扫描工具扫描指定包及其子包下的@MyComponent
        Map<String, Class> stringClassMap = BaseClassScanUtils.scanMyComponentAnnotation(basePackage);
        //遍历Map集合,创建BeanDefinition对象进行注册
        stringClassMap.forEach((name, clazz) -> { // name = otherBean
            RootBeanDefinition beanDefinition = new RootBeanDefinition();
            beanDefinition.setBeanClassName(clazz.getName());  //clazz.getName() =  com.it.beans.OtherBean
            beanDefinitionRegistry.registerBeanDefinition(name, beanDefinition);
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanDefinitionRegistry) throws BeansException {
    }
}

5.将自定义BeanFactoryPostProcessor交给Spring管理\color{#00FF00}{5. 将自定义BeanFactoryPostProcessor交给Spring管理}

<bean id="processor" class="com.it.MyBeanFactoryRegistryPostProcessor"/>

6.测试\color{#00FF00}{6. 测试}

image.png

3. Spring的Bean后处理器

Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。

BeanPostProcessor的接口定义如下:

public interface BeanPostProcessor {
    @Nullable
    //在属性注入完毕,init初始化方法执行之前被回调
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws
BeansException {
        return bean;
    }

    @Nullable
    //在初始化方法执行之后,被添加到单例池singletonObjects之前被回调
    default Object postProcessAfterInitialization(Object bean, String beanName) throws
BeansException {
        return bean;
    }
}

1.自定义MyBeanPostProcessor,完成快速入门测试\color{#00FF00}{1. 自定义MyBeanPostProcessor,完成快速入门测试}

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof UserDaoImpl){
            UserDaoImpl userDaoImpl = (UserDaoImpl) bean;
            userDaoImpl.setUsername("小🐕");
        }
        System.out.println(beanName + "postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "postProcessAfterInitialization");
        return bean;
    }
}

2.配置MyBeanPostProcessor\color{#00FF00}{2. 配置MyBeanPostProcessor}

<bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>

<bean class="com.it.MyBeanPostProcessor"/>

3.测试\color{#00FF00}{3. 测试}

image.png

4. 案例-对Bean方法进行执行时间日志增强

要求如下:

  • Bean的方法执行之前控制台打印当前时间;
  • Bean的方法执行之后控制台打印当前时间。

分析:

  • 对方法进行增强主要就是代理设计模式和包装设计模式;
  • 由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
  • 在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean

编写BeanPostProcessor,增强逻辑编写在 after方法中

public class TimeLogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //对Bean进行动态代理,返回的是Proxy代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    long start = System.currentTimeMillis();
                    System.out.println("开始时间:" + new Date(start));
                    // 执行目标方法
                    Object result = method.invoke(bean, args);
                    long end = System.currentTimeMillis();
                    System.out.println("结束时间:" + new Date(end));
                    return result;
                }
        );
        //返回代理对象
        return proxyInstance;
    }
}
<bean id="userDao" class="com.it.dao.impl.UserDaoImpl"/>

<bean class="com.it.MyBeanPostProcessor"/>

<bean class="com.it.TimeLogBeanPostProcessor"/>

image.png

BeanPostProcessor 在 SpringBean的实例化过程中的体现

image.png

⑥:Spring Bean的生命周期

Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:

  • Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的, 是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;

  • Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的;

  • Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池 singletonObjects中去了,即完成了Spring Bean的整个生命周期。

1. Bean的初始化阶段的步骤

Spring Bean的初始化过程涉及如下几个过程:

  • Bean实例的属性填充
  • Aware接口属性注入
  • BeanPostProcessor的before()方法回调
  • InitializingBean接口的初始化方法回调
  • 自定义初始化方法init回调
  • BeanPostProcessor的after()方法回调

2. Bean实例属性填充-循环依赖

BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,例如UserService的属性信息如下:

image.png image.png

1.Spring在进行属性注入时,会分为如下几种情况:\color{#00FF00}{1. Spring在进行属性注入时,会分为如下几种情况:}

  • 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
  • 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
  • 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案

2.多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环\color{#00FF00}{2. 多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环} 引用"\color{#00FF00}{引用"}

image.png

public class UserServiceImpl implements UserService{
    public void setUserDao(UserDao userDao) {}
}
public class UserDaoImpl implements UserDao{
    public void setUserService(UserService userService){}
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
    <property name="userService" ref="userService"/>
</bean>

3.代码验证后,分析出UserServiceUserDao实例化与初始化的顺序如下:\color{#00FF00}{3. 代码验证后,分析出UserService与UserDao实例化与初始化的顺序如下:}

image.png

image.png

3. 解决循环引用问题

Spring提供了三级缓存存储 完整Bean实例半成品Bean实例 ,用于解决循环引用问题 在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:

public class DefaultSingletonBeanRegistry ... {
    //1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
    Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

    //2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
    Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

    //3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
    Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
} 
    

1.UserServiceUserDao循环依赖的过程结合上述三级缓存描述一下\color{#00FF00}{1. UserService和UserDao循环依赖的过程结合上述三级缓存描述一下}

  • UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;

  • UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;

  • UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;

  • UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;

  • UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;

  • UserService 注入UserDao;

  • UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。

2.常用的Aware接口\color{#00FF00}{2. 常用的Aware接口}

Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

Aware接口回调方法作用
ServletContextAwaresetServletContext(ServletContext context)Spring框架回调方法注入ServletContext对象,web环境下才生效
BeanFactoryAwaresetBeanFactory(BeanFactory factory)Spring框架回调方法注入beanFactory对象
BeanNameAwaresetBeanName(String beanName)Spring框架回调方法注入当前Bean在容器中的beanName
ApplicationContextAwaresetApplicationContext(ApplicationContext applicationContext)Spring框架回调方法注入applicationContext对象

⑦:Spring IoC 整体流程总结

image.png image.png

⑧:Spring xml方式整合第三方框架

xml整合第三方框架有两种整合方案:

  • 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis;
  • 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo。

1. 整合MyBatis

01.整合MyBatis原始操作代码

1.导入依赖\color{#00FF00}{1. 导入依赖 }

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
    
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
    
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

2.创建实体类\color{#00FF00}{2. 创建实体类}

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    private Long id;
    private String name;
    private String email;
    private int deleted;
}

3.创建UserMapper\color{#00FF00}{3. 创建UserMapper}

public interface UserMapper {
    List<User> findAll();
}

4.创建UserMapper.xml(与UserMapper)路径保持一致\color{#00FF00}{4. 创建UserMapper.xml(与UserMapper)路径保持一致}

image.png

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.it.mapper.USerMapper">
    <select id="findAll" resultType="com.it.pojo.User">
        select *
        from user
    </select>

</mapper>

5.mybatisconfig.xml文件中引入UserMapper.xml\color{#00FF00}{5. 在mybatis-config.xml文件中引入UserMapper.xml}

<mappers>
    <package name="com.it.mapper"/>
</mappers>

6.编写测试方法\color{#00FF00}{6. 编写测试方法}

@Test
public void testFindAll() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sessionFactory = builder.build(inputStream);
    SqlSession sqlSession = sessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.findAll();
    userList.forEach(System.out::println);
}

image.png

02.MyBatis整合Spring实现

1.导入MyBatis整合Spring的相关坐标\color{#00FF00}{1.导入MyBatis整合Spring的相关坐标}

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
</dependency>

2.编写MapperMapper.xml\color{#00FF00}{2.编写Mapper和Mapper.xml}

上面已经编写可以参考     

3.配置SqlSessionFactoryBeanMapperScannerConfigurer\color{#00FF00}{3.配置SqlSessionFactoryBean和MapperScannerConfigurer}

在applicationContext2.xml进行编写以下内容

<bean id="userService" class="com.it.service.impl.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
</bean>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url"
              value="jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&amp;useSSL=false&amp;characterEncoding=utf-8&amp;allowPublicKeyRetrieval=true"/>
    <property name="password" value="root"/>
    <property name="username" value="root"/>
</bean>

<!--    配置 SqlSessionFactoryBean, 作用将SqlSessionFactory存储到Spring容器-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--    MapperScannerConfigurer,作用扫描指定包,产生Mapper对象存储到Spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.it.mapper"/>
</bean>

4.UserServiceUserServiceImpl\color{#00FF00}{4.UserService 和 UserServiceImpl}

public interface UserService {
    List<User> findAll();
}
public class UserServiceImpl implements UserService {

    // 需要Mapper
    private UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public List<User> findAll() {
        return userMapper.findAll();
    }
     .......
}

5.编写测试代码\color{#00FF00}{5.编写测试代码}

@Test
public void testMyBatisBySpring(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
    UserService userService = (UserService) context.getBean("userService");
    List<User> userList = userService.findAll();
    userList.forEach(System.out::println);
}

image.png

03. Spring整合MyBatis的原理剖析

整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:

  • SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;

  • MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;

  • MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;

  • ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以 MapperFactoryBean中的setSqlSessionFactory会自动注入进去。

配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法

SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
    public void afterPropertiesSet() throws Exception {
        //创建SqlSessionFactory对象
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
    public SqlSessionFactory getObject() throws Exception {
        return this.sqlSessionFactory;
    }
}

配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean

class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean {
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }
}
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }
    }
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        //设置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
        definition.setBeanClass(this.mapperFactoryBeanClass);
        definition.setAutowireMode(2); //设置MapperBeanFactory 进行自动注入
    }
}

PS:autowireMode取值:1是根据名称自动装配,2是根据类型自动装配

class ClassPathBeanDefinitionScanner{
    public int scan(String... basePackages) {
        this.doScan(basePackages);
    }
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //将扫描到的类注册到beanDefinitionMap中,此时beanClass是当前类全限定名
        this.registerBeanDefinition(definitionHolder, this.registry);
        return beanDefinitions;
    }
}
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
    }
    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }
}

2. 加载外部properties文件

Spring 整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在于Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!--配置应用名称-->
    <dubbo:application name="dubbo1-consumer"/>
    <!--配置注册中心地址-->
    <dubbo:registry address="zookeeper://localhost:2181"/>
    <!--扫描dubbo的注解-->
    <dubbo:annotation package="com.itheima.controller"/>
    <!--消费者配置-->
    <dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>

为了降低我们此处的学习成本,不在引入Dubbo第三方框架了,以Spring的 context 命名空间去进行讲解,该方式也是命名空间扩展方式。

需求:加载外部properties文件 (jdbc.properties),将键值对存储在Spring容器中

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
jdbc.password=root
jdbc.username=root

引入context命名空间,在使用context命名空间的标签,使用SpEL表达式在xml或注解中根据key获得value

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <context:property-placeholder location="jdbc.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="username" value="${jdbc.username}"/>
    </bean>
</beans>

其实,加载的properties文件中的属性最终通过Spring解析后会被存储到了Spring容器的environment中去,不仅自己定义的属性会进行存储,Spring也会把环境相关的一些属性进行存储

image.png

image.png

3. 自定义命名空间解析原理

原理剖析解析过程,只能从源头ClassPathXmlApplicationContext入手,经历复杂的源码追踪,找到如下两个点:

1.在创建DefaultNamespaceHandlerResolver时,为处理器映射地址handlerMappingsLocation属性赋值,并加载命名空间处理器到Map<String, Object> handlerMappings 中去

this.handlerMappingsLocation = "META-INF/spring.handlers";

image.png

image.png

image.png

第一点完成后,Map集合handlerMappings就被填充了很多XxxNamespaceHandler,继续往下追代码

2.在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,发现如下逻辑:

//如果是默认命名空间
if (delegate.isDefaultNamespace(ele)) {
    this.parseDefaultElement(ele, delegate);
    //否则是自定义命名空间
} else {
    delegate.parseCustomElement(ele);
}

如果是默认命名空间,则执行parseDefaultElement方法

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }
}

如果是自定义命名空间,则执行parseCustomElement方法

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    //解析命名空间
    String namespaceUri = this.getNamespaceURI(ele);
    //获得命名空间解析器
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    //解析执行的标签
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

在执行resovle方法时,就是从Map<String, Object> handlerMappings中根据命名空间名称获得对应的处理器对 象,此处是ContextNamespaceHandler,最终执行NamespaceHandler的parse方法

ContextNamespaceHandler源码如下,间接实现了NamespaceHandler接口,初始化方法init会被自动调用。由于context命名空间下有多个标签,所以每个标签又单独注册了对应的解析器,注册到了其父类NamespaceHandlerSupport的Map<String, BeanDefinitionParser> parsers中去了

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    public ContextNamespaceHandler() {
    }
    public void init() {
        this.registerBeanDefinitionParser("property-placeholder", new
                PropertyPlaceholderBeanDefinitionParser());
        this.registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        this.registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        this.registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        this.registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        this.registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        this.registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

通过上述分析,我们清楚的了解了外部命名空间标签的执行流程,如下:

  • 将自定义标签的约束 与 物理约束文件与网络约束名称的约束 以键值对形式存储到一个spring.schemas文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;

  • 将自定义命名空间的名称 与 自定义命名空间的处理器映射关系 以键值对形式存在到一个叫spring.handlers文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;

  • 准备好NamespaceHandler,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NamespaceHandler的parse方法时在分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)。

4. 案例 (自定义标签)

设想自己是一名架构师,进行某一个框架与Spring的集成开发,效果是通过一个指示标签,向Spring容器中自动注入一个BeanPostProcessor

步骤分析:

1.确定命名空间名称、schema虚拟路径、标签名称;\color{#00FF00}{1.确定命名空间名称、schema虚拟路径、标签名称;}

2.编写schema约束文件haohaoannotation.xsd\color{#00FF00}{2.编写schema约束文件haohao-annotation.xsd}

image.png

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns="http://www.itCoke.com/haohao"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.itCoke.com/haohao">

    <xsd:element name="annotation-driven"></xsd:element>

</xsd:schema>

3.在类加载路径下创建METAINF目录,编写约束映射文件\color{#00FF00}{3.在类加载路径下创建META-INF目录,编写约束映射文件} spring.schemas和处理器映射文件spring.handlers\color{#00FF00}{spring.schemas和处理器映射文件spring.handlers}

image.png

  • spring.handlers
http://www.itCoke.com/haohao=com.it.handlers.HaohaoNamespaceHandler
  • spring.schemas
http://www.itCoke.com/haohao/haohao-annotation.xsd=com/it/haohao/config/haohao-annotation.xsd

4.编写命名空间处理器HaohaoNamespaceHandler,在init方法中注册\color{#00FF00}{4.编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册} HaohaoBeanDefinitionParser\color{#00FF00}{HaohaoBeanDefinitionParser}

image.png

public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        //初始化,一般情况下,一个自定义命名空间下有多个标签,会在init方法中为每一个标签注册一个标签解析器
        this.registerBeanDefinitionParser("annotation-driven",new HaohaoBeanDefinitionParser());
    }
}

5.编写标签的解析器HaohaoBeanDefinitionParser,在parse方法中注\color{#00FF00}{5.编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注} HaohaoBeanPostProcessor\color{#00FF00}{册HaohaoBeanPostProcessor}

public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //创建HaohaoBeanPostProcessor的BeanDefinition
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
    
        beanDefinition.setBeanClassName("com.it.processor.HaohaoBeanPostProcessor");
        //注册HaohaoBeanPostProcessor
        parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor",beanDefinition);

        return beanDefinition;
    }
}

6.编写HaohaoBeanPostProcessor\color{#00FF00}{6.编写HaohaoBeanPostProcessor}

image.png

public class HaohaoBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        System.out.println("postProcessAfterInitialization 执行了~~~");
        return bean;
    }
}

======以上五步是框架开发者写的,以下是框架使用者写的======

1.applicationContext.xml配置文件中引入命名空间\color{#00FF00}{1.在applicationContext.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:haohao="http://www.itCoke.com/haohao"
       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
       http://www.itCoke.com/haohao
       http://www.itCoke.com/haohao/haohao-annotation.xsd">

</beans>

2.applicationContext.xml配置文件中使用自定义的标签\color{#00FF00}{2.在applicationContext.xml配置文件中使用自定义的标签 }

<!-- 使用自定义命名空间的标签   -->
<haohao:annotation-driven></haohao:annotation-driven>

3.测试\color{#00FF00}{3. 测试}

image.png