开发框架-Spring(1) - XML篇

1、Spring简介

Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。

Spring最初的出现是为了解决EJB(企业级Java Bean)臃肿的设计,以及难以测试等问题。

Spring为简化开发而生,让程序员只关注核心业务的实现,尽可能地不再关注非业务逻辑代码 (事务控制、安全日志等) 。

Spring是非侵入式的,Spring应用中的对象不依赖于Spring的特定类。

侵入式设计:在开发或者设计中,某一些类型或者API,依赖于别的API或者是别的容器,这样就属于侵入式设计。

  • 侵入式设计不好做单元测试
  • 例如 HttpServletRequest,如果某个方法依赖于 HttpServletRequest,而 HttpServletRequest 又存在于 tomcat 里面,需要人家tomcat的支撑,这样就不好做测试

也正是因为如此,Spring使我们能够编写出更干净、更可管理、并且更易于测试的代码

1.1 IOC

在传统的MVC架构模式中:表示层调用服务层,服务层再调用DAO层;

这样当我们实现业务的时候,只能new下一层的实现类对象来实现业务;

而且当我们有新的需求进来时,还必须重新编写原本可以运行的代码;

这样既违背了OCP原则,即开闭原则,又违背了DIP原则,即依赖倒置原则。

IOC(Inversion of Control),是一种编程思想,或者叫做一种新型的设计模式。将对象的创建权交出去,把对象和对象之间关系的维护权交出去,这个过程就叫做IOC。

上面提到:

Spring框架实现了IOC这种思想,它可以帮助我们:① new对象 ② 维护对象与对象之间的关系

Spring框架是一个实现了IOC思想的容器

那么,IOC的实现方式有很多种。其中比较重要的叫做依赖注入(Dependency Injection,简称DI)

让我们捋一下:

控制反转(IOC)是一种思想,依赖注入(DI)是这种思想的实现方式

而 DI 又包括两种常见的方式:① set注入(执行set方法给属性赋值) ② 构造方法注入(执行构造方法给属性赋值)

Dependency Injection:

  • dependency是指A对象和B对象之间的关系
  • Injection是指一种手段,通过这种手段可以让A对象和B对象产生关系
  • dependency injection说的是对象A和B之间的关系,靠注入来维护(set注入,构造注入)

1.2 Spring八大模块

Spring八大模块

  • Spring Core(IOC模块)是最核心的模块,所有的模块都是建立在 IOC 的基础上,其次就是

  • Spring AOP(AOP),其他的模块都建立在 IOC + AOP 的基础上

  • Spring Web MVC,是Spring自己的一套MVC框架

  • Spring Webflux,是Spring提供的一款响应式的Web框架

  • Spring Web,可以通过它去集成其他第三方的MVC框架

  • Spring DAO,是Spring自己内置的JDBC操作

  • Spring ORM,用来集成ORM框架

  • Spring Context,提供扩展的服务

2、Spring程序

2.1 BeanFactory版

BeanFactory版(Spring中最核心的接口,但是主流还是ApplicationContext)

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

① 新建maven项目,并导入对应依赖

 <!-- Spring依赖 -->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.3.7</version>
 </dependency>

 <!-- 单元测试 -->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13.2</version>
     <scope>test</scope>
 </dependency>

② 在resources目录下新建一个Spring Config配置文件(导入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直接对应工厂拿bean的bean name -->
    <bean id="userService" class="com.yao.service.impl.UserServiceImpl">
        <property name="userDao" ref="dao"/>
    </bean>

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

③ 编写对应的Service层和Dao层,并在ServiceImpl里面注入Dao属性并且创造对应的set方法:

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        System.out.println("在xml文件中配置dao属性后,DI会通过set注入方法调用这个set");
        this.userDao = userDao;
    }
}

④ 单元测试

public class UserServiceTest {

    /**
     * 测试第一个Spring程序,BeanFactory版
     */
    @Test
    public void testMyFirstSpringApplication() {
        // 创建工厂
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 创建一个读取器(xml文件)
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        // 读取配置文件给到工厂
        reader.loadBeanDefinitions("beans.xml");
        // 根据ID获取Bean实例对象
        UserService userService = (UserService) beanFactory.getBean("userService");
        UserDao userDao = (UserDao) beanFactory.getBean("dao");
        // 如果不为空,说明已经帮我们创建好了这个bean
        System.out.println(userService);
        System.out.println(userDao);
    }
}

上面使用BeanFactory完成了IOC的思想,对象的创造过程交给了Spring管理,以下是依赖注入的过程:

  1. 定义DAO接口及其对应的实现类
  2. 在Service实现类中注入DAO的属性并且添加Set方法
  3. 在Spring Bean的配置文件中通过<bean>标签中的<property>属性进行注入
  4. 再次拿去Service实现类的bean时,DAO属性已经通过Spring的DI注入,原理是里面的Set方法

2.2 ApplicationContext版

ApplicationContext版(主流,底层实际上还是对于BeanFactory的封装)

ApplicationContext称为Spring容器,内部封装了BeanFatocy,比BeanFactory的功能更强大(同时意味着启动后占用的资源更多)

只需要在单元测试里面换ApplicationContext的方式获取bean即可,其余的环境已经配好:

    /**
     * 测试第一个Spring程序,ApplicationContext版
     */
    @Test
    public void testApplicationContext() {

        // ApplicationContext context = new FileSystemXmlApplicationContext("磁盘绝对路径");

        // 默认去项目根路径下查找
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 获取对象
        UserService userService = (UserService) context.getBean("userService");
        // 如果不为空,说明已经帮我们创建好了这个bean
        System.out.println(userService);
    }

2.3 ApplicationContext与BeanFactory

BeanFactory与ApplicationContext的关系

  • BeanFactory称为“Spring的Bean工厂”,是Spring的早期接口,ApplicationContext是后期更高层的接口,称为“Spring容器”
  • ApplicationContext在BeanFactory的基础功能上进行了功能扩展,如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多都是对BeanFactory中这些底层API进行封装
  • Bean创建的主要逻辑和功能都被封装在BeanFactory中(所以说它是Spring中的核心),ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护者对于BeanFactory的引用所以两者间既有继承又有融合
  • Bean的初始化时机不同。BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并且初始化好

ApplicationContext继承体系

2.4 常用getBean的API

API返回值及参数
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类型实例,无需强转

3、基于XML的Spring应用

3.1 Bean的配置

首先是Bean的配置:

bean标签中的属性功能
idclassBean的id和全限定类名(如果不指定id,id就是他的全限定类名)
name通过name设置Bean的别名,使得通过这个name也能直接获取Bean实例
scopeBean的作用范围,BeanFactory作为容器时取值singleton和prototype
lazy-initBean的初始化时机,是否延迟加载,BeanFactory作为容器时无效
init-methodBean实例化后自动执行的初始化方法
destroy-methodBean实例销毁前执行的方法
autowire设置自动注入模式,常用的有按照类型byType,按照名字byName
factory-beanfactory-method指定哪个工厂Bean的哪个方法完成Bean的创建

1、Bean的别名配置

<!-- bean的id直接对应工厂拿bean时填入的bean的名字,name是别名,也可以通过它获取到该bean -->
<bean id="userService" name="aaa" class="com.yao.service.impl.UserServiceImpl"/>

2、Bean的作用范围

  • singleton:单例,默认值。Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例
  • prototype:原型。Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean时都会创建一个新的Bean实例
<bean id="userService" name="aaa" class="com.yao.service.impl.UserServiceImpl" scope="singleton">

3、Bean的延迟加载

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

4、Bean的创建销毁
实现类:

public class UserServiceImpl implements UserService, InitializingBean {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        System.out.println("在xml文件中配置后,DI会通过set注入方法调用这个set");
        this.userDao = userDao;
    }

    public void init() {
        System.out.println("init方法执行");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("在属性注入之后再执行该afterPropertiesSet方法");
    }

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

配置文件:

<bean id="userService" class="com.yao.service.impl.UserServiceImpl" init-method="init" destroy-method="destroy">
    <property name="userDao" ref="dao"/>
</bean>

单元测试:

    /**
     * 测试Bean的生命周期
     */
    @Test
    public void testApplicationLife() {
        // 默认去项目根路径下查找
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 获取对象
        UserService userService = (UserService) context.getBean("userService");
        // 关闭容器,如果指定了destroy方法则会先执行这个方法再关闭容器
        context.close();
    }

运行结果:

D:\Tools\jdk1.8\bin\java.exe ...
在xml文件中配置后,DI会通过set注入方法调用这个set
在属性注入之后再执行该afterPropertiesSet方法
init方法执行
destroy方法执行

3.2 Bean的构造方式

BeanFactory帮我们创造Bean时有两种方式:

  • 通过反射获取到构造方法创造Bean
  • BeanFactory中的工厂帮我们创造Bean,需要我们自己去指定

1、构造方法实例化bean(这里展示的是有参构造,前面在Bean的配置文件中配置的Bean就是无参)

    // 假如有有参构造方法,可以在配置文件中配置
    public UserServiceImpl(String name) {
        System.out.println("有参构造方法被执行....");
    }

配置文件:

    <bean id="userService" class="com.yao.service.impl.UserServiceImpl">
        <constructor-arg name="name" value="aabbc"></constructor-arg>
        <property name="userDao" ref="dao"/>
    </bean>

但是<constructor-arg>并不仅仅只可以用来有参构造方法传参,只要在创建Bean时需要的参数,都可以通过这个标签传入

2、工厂实例化bean

工厂实例化bean需要指定,又有3种方式:

  • 静态工厂方法实例化bean(不需要实例对象)
  • 实例工厂方法实例化bean(需要实例对象)
  • 实现FactoryBean规范延迟实例化Bean

① 静态工厂

// 自定义的静态工厂实例化bean
public class MyStaticBeanFactory {

    private static UserDao userDao() {
        // Bean在创建前可以进行一些其他业务的操作
        return new UserDaoImpl();
    }
}
    <!--
        有了factory-method的配置后,就会告诉Spring,这个bean的配置的目的不是为了创造这个bean,
        而是想要获得factory-method中返回的对象。采用这种方式实例化bean可以使业务代码更加灵活,
        因为可以使bean创造之前进行一些额外的业务操作;也可以直接引入某些java包中的静态方法
        比如DrivenManager.getConnection
    -->
    <bean id="staticFactory" class="com.yao.factory.MyStaticBeanFactory" factory-method="userDao"/>

② 实例工厂

// 自定义实例化工厂,获取bean的方法没有static
public class MyBeanFactory {

    private UserDao userDao() {
        // Bean在创建前可以进行一些其他业务的操作
        return new UserDaoImpl();
    }
}
    <!-- 用实例化工厂创造 Bean的实例 -->
    <bean id="factory" class="com.yao.factory.MyBeanFactory"></bean>
    <!--
        getBean("userDao") -> factory-bean指定的工厂对象 -> factory-method指定的方法 -> 方法中的返回值
        采用这种实例化工厂的方式创造bean实例,同样能够使业务代码更加灵活;在某些第三方jar包中的某些bean的
        产生就是通过某些对象的方法去产生的,就可以用来这样配置那些bean
    -->
    <bean id="userDao" factory-bean="factory" factory-method="userDao"></bean>

③ FactoryBean规范延迟实例化

用这种方法实例化bean:当接到实例化的请求时,FactoryBean不予理睬,只有当真正用到该bean时,再去创建;并且把创建过后的bean存到factoryBeanObjectCache里面,下次再用到该bean时直接从缓存中去取;如果没有,再去创建,以此往复

public class BeanFactory implements FactoryBean<UserDao> {
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}
    <!-- 配置该bean后,获取bean时得到的是getObject中返回的对象 -->
    <bean id="userDao2" class="com.yao.factory.BeanFactory"></bean>

Bean实例化的基本流程

  • Spring容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的BeanDefinition对象存储到一个名为beanDefinitionMap的Map集合中去,这个Map集合维护在本地的BeanFactory中
  • Spring可以在beanDefinitionMap这个Map集合中遍历取出每一个BeanDefinition的信息,然后通过反射来创建Bean的实例对象
  • 创建好的Bean对象就存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象并返回,这个Map集合也还是维护在本地的BeanFactory中
  • 当调用getBean(“名字”)时,会根据这个名字去singletonObjectsMap集合中去匹配,成功就拿到,不成功就报错

3.3 Bean的依赖注入配置

注入方式配置方式
通过Bean的set方法注入 (注入的bean在配置文件中有配置)<property name="userDao" ref="userDao" />
通过Bean的set方法注入 (注入的是普通属性)<property name="user" value="aabbc" />
通过构造Bean的方法注入 (注入的参数bean在配置文件中有配置)<constructor-arg name="userDao" ref="userDao" />
通过构造Bean的方法注入 (注入的参数是普通属性)<constructor-arg name="user" ref="aabbc" />

以上两种:set注入、构造方法注入,前面都有提及。现在介绍集合数据类型的注入
① List集合

public class UserServiceImplOfTestListInject implements UserService {

    private List<String> list = new ArrayList<>();

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

    private List<UserDao> daoList = new ArrayList<>();

    public void setDaoList(List<UserDao> daoList) {
        this.daoList = daoList;
    }

	// 打印
    public void show() {
        System.out.println(list);
        System.out.println(daoList);
    }
}
<?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.yao.service.impl.UserServiceImplOfTestListInject">
        <property name="list">
            <list>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </list>
        </property>

        <property name="daoList">
            <list>
                <!-- 也可以通过外部bean配置,然后这里ref引入 -->
                <bean id="userDao1" class="com.yao.dao.impl.UserDaoImpl"></bean>
                <bean id="userDao2" class="com.yao.dao.impl.UserDaoImpl"></bean>
                <bean id="userDao3" class="com.yao.dao.impl.UserDaoImpl"></bean>
            </list>
        </property>
    </bean>

</beans>
    /**
     * 测试集合注入
     */
    @Test
    public void testListInject() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("listBeans.xml");
        // 获取对象
        UserServiceImplOfTestListInject impl = (UserServiceImplOfTestListInject) context.getBean("userService");
        // 打印
        impl.show();
    }

② Set集合仅仅只是将<List>换成<Set>即可

③ Map集合

<property name="map">
	<map>
		<!-- 如果是普通类型,就是value="xxx" -->
		<entry key="d1" value-ref="userDao1">
		<entry key="d2" value-ref="userDao2">
	</map>
</property>

3.4 自动装配

如果被注入的属性是Bean引用的话,可以在<bean>标签中使用autowire属性去配置自动注入的方式:

  • byName:通过属性名自动装配,即去匹配setXxxid="xxx"或者name="xxx"是否一致
  • byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时会报错
<bean id="userService" class="com.yao.service.impl.UserServiceImpl" autowire="byType">
<!--自动装配进去的bean需要配置!!!-->
<bean id="userDao" class="com.yao.dao.impl.UserDaoImpl">

注意自动装配的前提是该自动装配所需要的bean已在容器中配置

3.5 其他标签

Spring的xml标签大体上分为两类:

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

1、引入自定义标签:①导入坐标依赖 ②引入命名空间 ③引入schema路径 ④使用标签

<?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></context:property-placeholder>

    <!--
        xmlns:xml namespace
        schemaLocation: schema文件的地址(看似是网址,实际上是映射到本地的某些jar包中的schema)
    -->
</beans>

2、默认标签:

beans

    <beans profile="test">
        <bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
        <bean id="dao" class="com.yao.dao.impl.UserDaoImpl"></bean>
    </beans>

    <beans profile="dev">
        <bean id="dao" class="com.yao.dao.impl.UserDaoImpl"></bean>
    </beans>
    /**
     * 测试beans的切换
     */
    @Test
    public void testBeans() {

        System.setProperty("spring.profiles.active", "dev");

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("otherTag.xml");
        // 获取对象
        UserDao dao = (UserDao) context.getBean("dao");

        System.out.println(dao);

        context.close();
    }

import

<!-- 通过import引入其他模块的配置文件 -->
<import resource="classpath:applicationContext-user.xml"></import>
<import resource="classpath:applicationContext-orders.xml"></import>

alias

<!-- userDao是在容器中配置的bean的id -->
<alias alias="aaa" name="userDao"></alias>

3.6 配置非自定义的Bean

到目前为止,前面介绍的bean的配置都属于自定义的bean的配置。但是在实际开发中,有些功能类并不是我们自己定义的,而是使用第三方jar包中的。那么,这些bean想要让Spring进行管理,也需要对其进行配置。

配置非自定义的Bean需要考虑如下两个问题(看第三方jar包中的方式来决定):

  • 被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂还是实例工厂
  • 被配置的Bean是否需要注入必要属性

比如:

① 导入坐标依赖

        <!--druid数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

② bean的配置

    <!--配置数据源信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?ServerTime=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--配置Connection-->
    <bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
        <constructor-arg name="url" value="jdbc:mysql://localhost:3306/test?ServerTime=UTC"></constructor-arg>
        <constructor-arg name="user" value="root"></constructor-arg>
        <constructor-arg name="password" value="root"></constructor-arg>
    </bean>
    <!--配置日期对象-->
    <bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
        <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
    </bean>
    <bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
        <constructor-arg name="source" value="2023-7-21 12:00:00"></constructor-arg>
    </bean>

4、Spring的后处理器

Spring的后处理器是Spring开发的重要扩展点,它允许我们介入到Bean的整个实例化流程中来。从而能够实现动态注册BeanDefinition, 动态修改BeanDefinition,以及动态修改Bean等功能需求。Spring主要有两种后处理器:

  • BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行(执行1次)
  • BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行(每个bean创建完成后都会执行1次)

4.1 BeanFactoryPostProcessor

一个接口规范,只要交给Spring容器管理,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能

1、修改

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("beanDefinitionMap填充完毕后执行");
        // 在这里做点手脚,修改某个BeanDefinition
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
        beanDefinition.setBeanClassName("com.yao.dao.impl.UserDaoImpl");
    }
    <bean id="userService" class="com.yao.service.impl.CommonUserServiceImpl"></bean>
    <!--配置Spring后处理器-->
    <bean class="com.yao.process.MyBeanFactoryPostProcessor"></bean>

当获取userServicebean的时候,会返回UserDaoImpl的实例

2、注册

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("beanDefinitionMap填充完毕后执行");
//        // 修改某个BeanDefinition
//        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
//        beanDefinition.setBeanClassName("com.yao.dao.impl.UserDaoImpl");

        // 动态注册BeanDefinition进入BeanDefinitionMap集合中
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.yao.dao.impl.OrderDaoImpl");
        // 这里, ConfigurableListableBeanFactory没有注册方法, 但是DefaultListableBeanFactory有, 所以这里先强转
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        defaultListableBeanFactory.registerBeanDefinition("orderDao", beanDefinition);
    }
}

在bean的配置文件中不用配置bean,而是通过这种方式动态注册

    /**
     * 测试Spring后处理器-BeanFactoryPostProcessor-注册
     */
    @Test
    public void testPostProcess_reg() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
        Object impl = context.getBean("orderDao");
        System.out.println(impl);
    }
beanDefinitionMap填充完毕后执行
com.yao.dao.impl.OrderDaoImpl@28ac3dc3

Spring还提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作,如果之后我们只是想要去动态注册BeanDefinition,可以直接实现BeanDefinitionRegistryPostProcessor

public class MyBeanDefinitionRegisterProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("MyBeanDefinitionRegisterProcessor的postProcessBeanDefinitionRegistry被执行!");
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.yao.dao.impl.OrderDaoImpl");
        beanDefinitionRegistry.registerBeanDefinition("orderDao", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("MyBeanDefinitionRegisterProcessor的postProcessBeanFactory被执行!!");
    }
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("beanDefinitionMap填充完毕后执行!!!");
    }
}
    /**
     * 测试Spring后处理器-BeanFactoryPostProcessor-注册
     */
    @Test
    public void testPostProcess_reg() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
        Object impl = context.getBean("orderDao");
        System.out.println(impl);
    }
}
<?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.yao.service.impl.CommonUserServiceImpl"></bean>
    <!--配置BeanFactoryPostProcessor-->
    <bean class="com.yao.process.MyBeanFactoryPostProcessor"></bean>
    <!--配置BeanDefinitionRegistryPostProcessor-->
    <bean class="com.yao.process.MyBeanDefinitionRegisterProcessor"></bean>
</beans>
MyBeanDefinitionRegisterProcessor的postProcessBeanDefinitionRegistry被执行!
MyBeanDefinitionRegisterProcessor的postProcessBeanFactory被执行!!
beanDefinitionMap填充完毕后执行!!!
com.yao.dao.impl.OrderDaoImpl@1d371b2d

通过上面的演示,我们还可以知道各方法之间的执行顺序

4.2 BeanPostProcessor

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

public class OrderDaoImpl implements OrderDao {

    private String orderName;

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    public String getOrderName() {
        return orderName;
    }

    public OrderDaoImpl() {
        System.out.println("OrderDaoImpl实例化");
    }
}
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + ":postProcessBeforeInitialization");
        if(bean instanceof OrderDaoImpl) {
            OrderDaoImpl impl = (OrderDaoImpl) bean;
            impl.setOrderName("密西西比口味烧饼!");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + ":postProcessAfterInitialization");
        return bean;
    }
}
    <bean id="orderDao" class="com.yao.dao.impl.OrderDaoImpl"></bean>
    <bean class="com.yao.process.MyBeanPostProcessor"></bean>
    /**
     * 测试Spring后处理器-BeanPostProcessor
     */
    @Test
    public void testPostProcess_bean() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("commonBeans.xml");
        OrderDaoImpl impl = (OrderDaoImpl) context.getBean("orderDao");
        System.out.println(impl + "  订单名字是:" + impl.getOrderName());
    }
}
OrderDaoImpl实例化
orderDao:postProcessBeforeInitialization
orderDao:postProcessAfterInitialization
com.yao.dao.impl.OrderDaoImpl@8e24743  订单名字是:密西西比口味烧饼!

经过上面的演示,我们知道了Bean后处理器可以让我们在Bean存入Map集合之前对Bean做一些处理

5、 Spring Bean的生命周期

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

  • Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的作用范围是否是singleton,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化
  • Bean的初始化阶段:Bean创建之后还仅仅是个“半成品”,还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段:AOP增强功能,Spring的注解功能等、Bean的循环引用问题都是在这个阶段体现的
  • Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期

这里重点是介绍Bean的初始化阶段

Bean的初始化过程涉及如下几个过程:
	第1步 => Bean实例的属性填充
		
	第2步 => Aware接口属性注入
	
	第3步 => BeanPostProcessor的before()方法回调
	
	第4步 => InitializingBean接口的初始化方法回调
	
	第5步 => 自定义初始化方法init回调
	
	第6步 => BeanPostProcessor的after()方法回调

5.1 Bean实例的属性填充

BeanDefinition中有对当前Bean实体的注入信息通过属性propertyValues进行存储,Spring在进行属性注入时,会分为以下几种情况:

  1. 注入普通属性。String、int或存储基本类型的集合时,直接通过set方法的反射 注入
  2. 注入单向引用属性的bean对象。从容器中getBean获取后通过set方法反射 注入;如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作
  3. 注入双向引用属性的bean对象。这种情况就比较复杂了,涉及到了循环引用 / 循环依赖的问题

5.2 循环依赖

多个实体之间相互依赖并形成闭环的情况就叫做循环依赖 / 循环引用

如这样一种情况:

=> 在xml中配置了userService和userDao的Bean,两者内部的属性都是对方,即循环依赖;

=> 当getBean("userService")时会先去实例化Service,然后初始化Service去填充userDao属性,但是发现容器中没有,则会去创建userDao;

=> 创建Dao又会去实例化,然后初始化填充userService的属性,发现容器中没有 (因为userService还不是完整bean,在单例池中还不存在) ,又会去创建userService,然后又会进入一个循环。

如何解决这个问题?只要当我们实例化一个bean,就把这个还未完整的bean存到另一个集合当中。当我们需要找bean时,先去单例池中找,单例池中没有,再去这个集合中找

Spring提供了三级缓存来解决循环依赖问题

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);
}

那么现在的流程:

=> 在xml中配置了userService和userDao的Bean,两者内部的属性都是对方,即循环依赖;

=> 当getBean("userService")时会先去实例化userService,并将该未完整的bean放入三级缓存中,并用objectFactory 对其进行封装【只要实例化后的bean,都会被包装成objectFactory,然后存入到三级缓存中】,然后初始化后的userService需要填充userDao属性,但是发现单例池(一级缓存) 中没有中没有;则会去早期Bean单例池(二级缓存) 中找,这也没有userDao;又去单例Bean工厂池(三级缓存) 中找,也没有;

=> 最后决定去创建userDao的bean,实例化后,在初始化中为userDao注入userService属性,重复上述的查找顺序,最后在三级缓存中找到了userService,由于内部维护着对于该userService的引用,所以userService进入了二级缓存。最后完成userDao的初始化,将userDao放入单例池中 (一级缓存),并且删除二三级缓存;

=> 既然有了userDao,那么userService也会初始化完毕,结束后将userService放入单例池中 (一级缓存),并且删除二三级缓存,整个过程结束。

5.3 Aware接口

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

常用Aware接口

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

5.4 完善的Bean实例化流程图

SpringBean实例化流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring-MyBatis整合是一种常见的开发方式,其中spring-mybatis.xml文件是用来配置Spring和MyBatis框架集成的配置文件。在这个文件中,我们会定义数据源、事务管理器、扫描Mapper接口等配置信息。同时,我们还需要将MyBatis的SqlSessionFactory注入到Spring容器中,以便其他组件可以使用它来执行数据库操作。以下是一个简单的spring-mybatis.xml文件示例: ```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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="typeAliasesPackage" value="com.example.entity" /> <property name="mapperLocations" value="classpath*:com/example/mapper/*.xml" /> </bean> <!-- 配置Mapper扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper" /> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启Spring的注解功能 --> <context:annotation-config /> <!-- 开启Spring的AOP功能 --> <aop:aspectj-autoproxy /> <!-- 开启Spring的事务管理功能 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans> ``` 在这个配置文件中,我们使用了Spring的注解功能、AOP功能和事务管理功能,以及MyBatis的Mapper扫描和SqlSessionFactory配置。其中,数据源、事务管理器和Mapper扫描的配置信息需要根据实际情况进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值