spring入门1.0
- 一 小黑子对spring基础进行概述
- 1.1 spring导论
- 1.2 传统Javaweb开发困惑及解决方法
- 1.3 三大的思想提出
- 1.3.1 IOC入门案例
- 1.3.2 DI入门案例
- 1.4 框架概念
- 1.5 初识spring
- 1.5.1 Spring Framework
- 1.6 BeanFactory快速入门
- 1.7 ApplicationContext快速入门
- 1.8 BeanFactory与ApplicationContext的关系
- 1.9 BeanFactory的继承体系
- 1.10 ApplicationContext的继承体系
- 二 小黑子基于xml的SpringBean配置详解
- 2.1 Bean的配置概述
- 2.2 beanName和别名配置
- 2.3 Bean的作用范围scope配置
- 2.4 Bean的延迟加载
- 2.5 Bean的初始化方法和销毁方法
- 2.6 InitializingBean方式
- 2.7 实例化Bean的方式-构造方法方式
- 2.7.1 spring bean异常的看法
- 2.7.2 静态工厂方法该方式去实例化Bean
- 2.7.3 实例工厂方法该方式去实例化Bean
- 2.7.4 有参数的静态工厂和实例工厂方法
- 2.7.5 实现FactoryBean规范延迟实例化Bean
- 2.8 Bean的注入
- 2.8.1 Bean的注入方式
- 2.8.2 Bean的注入数据类型
- 2.8.3 自动装配
- 2.9 命名空间的种类
- 2.9.1 beans的profile属性切换环境
- 2.9.2 import标签
- 2.9.3 alias标签
- 2.9.4 自定义命名空间的使用步骤
- 三 小黑子Spring的get方法
- 3.1 spring常用的三种getBean的API
- 四 小黑子配置Spring非自定义的Bean
- 4.1 DruidDatasource
- 4.2 Connection
- 4.3 Date
- 4.4 SqlSessionFactory
- 五 小黑子Bean实例化的基本流程
- 5.1 BeanDefinition
- 5.2 单例池和流程总结
- 六 小黑子Spring的Bean工厂后处理器
- 6.1 入门
- 6.2 注册BeanDefinition
- 6.3 BeanDefinitionRegistryPostProcessor
- 6.4 完善实例化流程图
- 6.5 自定义@Component
- 七 Spring的Bean后处理器
- 7.1 BeanPostProcessor
- 7.2 before和after方法的执行时机
- 7.3 案例-对Bean方法执行日志功能增强
- 7.4 再次完善实例化基本流程图
- 八 小黑子Spring Bean的生命周期
- 8.1 概述
- 8.2 初始化阶段执行步骤
- 8.3 初始化阶段注入属性信息封装
- 8.4 属性注入的三种情况
- 8.5 注入单向对象的代码验证
- 8.6 循环依赖概念及解决方案
- 8.7 三级缓存的设计原理
- 8.8 循环依赖源码流程剖析
- 8.9 Aware接口
- 九 小黑子对Spring IoC整体流程总结
- 十 小黑子进行Spring xml方式整合第三方框架
- 10.1 Mybatis整合Spring实现
- 10.2 Mybatis整合Spring源码解析
- 10.3 加载外部properties文件
- 10.4 自定义空间步骤
- 10.5 自定义命名空间案例
一 小黑子对spring基础进行概述
1.1 spring导论
1.2 传统Javaweb开发困惑及解决方法
问题一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起
- 解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象
问题二:通用的事务功能耦合在业务代码中,通用的日志功能耦合在业务代码中
- 解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象的代理对象
1.3 三大的思想提出
现今代码书写现状:
- 耦合度偏高:在项目上线部署之后,项目要更新代码就要更新,假如说数据层要换个实现,那么业务层也要跟着换个实现对象,然后重新测试、部署、发布,成本高。
- 解决方案:使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象,这种对象创建控制器由程序转移到外部——称为IoC
-
IoC思想
-
lnversion of Control,控制反转,强调的是原来在程序中创建Bean的权利反转给第三方。使用对象时,由主动new产生对象转换为由
外部
提供对象,此过程对象创建控制权由程序转移到外部,该思想叫做控制反转。 -
spring提供了一个容器——IoC容器(spring容器),用来充当思想中的
外部
-
IoC容器负责对象的创建、初始化等一系列工作,呗创建或被管理的对象在IoC容器中统称为
Bean
-
-
DI思想
- Dependency Injection,
依赖注入
,强调的Bean之间关系,这种关系第三方负责去设置。 - 在容器中建立
bean与bean之间的依赖关系
的整个过程
,被称为依赖注入
- Dependency Injection,
-
AOP思想
- Aspect Oriented Programming,面向切面编程,功能的横向抽取,主要的实现方式就是Proxy
1.3.1 IOC入门案例
IoC入门案例思路分析
- 管理什么?( Service与Dao )
- 如何将被管理的对象告知IoC容器?(配置)
- 被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
- IoC容器得到后,如何从容器中获取bean ?(接口方法)
- 使用Spring导入哪些坐标?( pom.xml )
- 在IDEA下创建maven项目,在resources文件下:导入Spring坐标
- 发现没有spring这种结构选,这就需要导入包
- 发现没有spring这种结构选,这就需要导入包
- 在pom文件里面的导包
之后就出现spring的配置文件
目录:
-
定义spring管理的类(接口)
-
创建spring配置文件,配置对应类作为spring管理的bean
-
初始化IoC容器(spring核心容器/spring容器),通过容器获取bean
1.3.2 DI入门案例
DI入门案例思路分析
- 基于IoC管理bean
- Service中使用new形式创建的Dao对象是否保留?(否)
- Service中需要的Dao对象如何进入到service中?(提供方法)
- Service与Dao间的关系如何描述?(配置)
-
删除使用new的形式创建对象的代码
-
提供依赖对象对应的setter方法
-
配置service与dao之间的关系
1.4 框架概念
- 框架(Framework),是基于基础技术之上,从众多业务中抽取出的通用解决方案;
- 框架是一个半成品,使用框架规定的语法开发可以提高开发效率可以用简单的代码就能完成复杂的基础业务;
- 框架内部使用
在这里插入代码片
大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等; - 框架一般都具备扩展性;
- 有了框架,我们可以将精力尽可能的投入在纯业务开发上而不用去费心技术实现以及一些辅助业务。
Java中常用的框架:
不同语言,不同领域都有属于自己的框架,使用框架开发是作为程序员的最基础的底线。Java语言中的框架,可以分为基础框架和服务框架:
- 基础框架:完成基本业务操作的框架,如MyBatis、Spring、SpringMVC、Struts2、Hibernate等
- 服务框架:特定领域的框架,一般还可以对外提供服务框架,如MQ、ES、Nacos等
1.5 初识spring
spring是一个开源的轻量级Java开发应用框架,可以简化企业级应用开发。Spring解决了开发者在JavaEE开发中遇到的许多常见的问题,提供了功能强大IOC
、AOP
及Web MVC
等功能。是当前企业中Java开发几乎不能缺少的框架之一。Spring的生态及其完善,不管是Spring哪个领域的解决方案都是依附于在Spring Framework基础框架的。
spring官网
Spring发展到今天已经形成了一种开发的生态圈,spring提供了若干给项目,每个项目用于完成特定的功能
Spring框架的历史
- Jsp 默默扛下所有;
- MVC+三层架构分工明确,但开发成本及其高;
- EJB重量级框架出现,走出一个困境,有进入另一个困境;
- Spring春天来到,随之,
- SSH风生水起、称霸武林;
- Spring 稳住江湖大哥位置,SSM开始上位;、
- Spring本着“拿来主义”的思维快速发展,生态不断健全;
- SpringBoot 又一里程碑崛起,把“约定大于配置“思想玩儿的炉火纯青;
- SpringCloud打包了微服务众多解决方案,应对互联网项目更加easy!
-
Spring Boot
使用spring boot技术可以在简化开发的基础上加速开发,它是用来提高开发速度的,会让原先的spring开发变得更加简单,代码写得更少 -
Spring cloud
分布式开发的相关技术
1.5.1 Spring Framework
这是spring里面的第一门技术,也是最早出现的,在整个全家桶的地位即其他的所有技术都是依赖它执行的。它是Spring生态圈中最基础的项目,是其他项目的根基
Spring Framework技术栈图示
- 第一:要学习的内容是Core Container核心容器部分,它是管理对象的。所有的一切都是基于对象的,一定要先学这spring核心部分的内容
- 第二:学数据访问已集成的部分,可以整合mybatis。还有一个东西需要重点学习——Transactions事务,开发效率非常高的事务控制方案
- 第三:学习spring中的第二个核心东西,AOP技术——其也是一种编程的思想,其具体是可以在不惊动原始程序的基础上,给它增强功能。而Aspects也是对AOP思想进行了实现
1.6 BeanFactory快速入门
根据下图,分析一下Spring的BeanFactory的开发步骤:
- 在pom文件中导入Spring的jar包或Maven坐标;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.7</version>
</dependency>
- 定义UserService接口及其UserServiceImpl实现类;
public interface UserService {
}
public class UserServiceImpl implements UserService {
}
- 创建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">
<!-- 配置UserServiceImpl-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
</beans>
- 编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象
//创建工厂对象
DefaultListableBeanFactory BeanFactory = new DefaultListableBeanFactory();
//创建一个读取器(XML文件)
XmlBeanDefinitionReader Reader = new XmlBeanDefinitionReader(BeanFactory);
//读取配置文件给工厂
Reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean实例对象
UserService userService = (UserService)BeanFactory.getBean("UserService");
能够打印出其对象
实现DI依赖注入
-
在
dao包
下定义UserDao接口及其UserDaolmpl实现类; -
修改UserServicelmpl代码,添加一个setUserDao(UserDao userDao)用于接收注入的对象;
public class UserServiceImpl implements UserService {
private UserDao userDao;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory去调用该方法获得userDao设置到此处:"+userDao);
this.userDao = userDao;
}
}
- 修改beans.xml配置文件,在UserDaolmpl的
<bean>
中嵌入<property>
,其name和UserServicelmpl里面的创建变量的userDao
,该属性名是相互对应的,其ref同引入的哪个bean的id相互对应,配置注入;
<bean id="UserService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<bean id="UserDao" class="com.Smulll.Dao.Impl.UserDaoImpl"></bean>
- 修改测试代码,获得UserService时,setUserService方法执行了注入操作
//创建工厂对象
DefaultListableBeanFactory BeanFactory = new DefaultListableBeanFactory();
//创建一个读取器(XML文件)
XmlBeanDefinitionReader Reader = new XmlBeanDefinitionReader(BeanFactory);
//读取配置文件给工厂
Reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean实例对象
UserService userService = (UserService)BeanFactory.getBean("UserService");
1.7 ApplicationContext快速入门
ApplicationContext 称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用ApplicationContext进行开发时,xml配置文件的名称习惯写成applicationContext.xml
//创建ApplicationContext,加载配置文件,实例化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//根据beanName获得容器中的Bean实例
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService);
1.8 BeanFactory与ApplicationContext的关系
- BeanFactory是Spring的早期接口,称为Spring的Bean工厂,
ApplicationContext是后期更高级接口,称之为Spring容器; - ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。
BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装; - Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
- Bean的初始化时机不同,原始BeanFactory是在首次调用
getBean
时才进行Bean的创建,
ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。
ApplicationContext除了继承了BeanFactory外,还继承了ApplicationEventPublisher(事件发布器)、ResouresPatternResolver(资源解析器)、MessageSource(消息资源)等。但是ApplicationContext的核心功能还是BeanFactory。
1.9 BeanFactory的继承体系
BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory
,而ApplicationContext内部维护的Beanfactory的实现类也是它
1.10 ApplicationContext的继承体系
只在Spring基础环境下,即只导入spring-context坐标时,此时ApplicationContext的继承体系
只在Spring基础环境下,常用的三个ApplicationContext作用如下:
实现类 | 功能描述 |
---|---|
ClassPathXmlApplicationContext | 加载类路径下的xml配置的ApplicationContext |
FileSystemXmlApplicationContext | 加载磁盘路径下的xml配置的ApplicationContext |
AnnotationConfigApplicationContext | 加载注解配置类的ApplicationContext |
如果Spring基础环境中加入了其他组件解决方案,如web层解决方案,即导入spring-web坐标,此时ApplicationContext的继承体系
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.7</version>
</dependency>
只在Spring的Web环境下,常用的两个ApplicationContext作用如下:
实现类 | 功能描述 |
---|---|
XmlWebApplicationContext | web环境下,加载类路径下的xml配置的ApplicationContext |
AnnotationConfigWebApplicationContext | web环境下,加载磁盘路径下的xml配置的ApplicationContext |
二 小黑子基于xml的SpringBean配置详解
2.1 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.2 beanName和别名配置
一、Bean的基础配置
例如:配置UserDaolmpl由Spring容器负责管理
<bean id="userDao" class="com.itheima.dao .impl.UserDaoImp! " />
此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaolmpl对象,可以根据beanName获取Bean实例
applicationContext.getBean("userDao");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean("com.itheima.dao.impl.UserDaoImpl");
二、Bean的别名配置
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可定义多个,使用逗号, 分号; 空格 分隔 |
范例 |
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.itheima.dao.impl.UserDaoImp1" />
此时多个名称都可以获得UserDaolmpl实例对象
applicationContext.getBean("userDao");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");
注意:
获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常
NoSuchBeanDefinitionException
NoSuchBeanDefinitionException: No bean named ‘bookServiceImpl’ available
所以抛出这个异常,检查id或者name有没有对上即可
2.3 Bean的作用范围scope配置
默认情况下,单纯的Spring环境Bean的作用范围有两个: Singleton和Prototype
- singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
- prototype:非单例,Spring容器初始化时不会创建Bean实例当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
为什么bean默认为单例?
- 为适合交给spring容器进行管理的bean
- 表现层对象
- 业务层对象serivce
- 数据层对象dao
- 工具对象utils
- 不适合交给容器进行管理的bean
- 封装实体的域对象
2.4 Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的
<bean id="userDao" class=" com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
2.5 Bean的初始化方法和销毁方法
- 格式一:
- 格式二:
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" init-method="init" destroy-method="destroy">
<property name="userDao" ref="userDao"></property>
</bean>
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
public class UserServiceImpl implements UserService {
public void init(){ System.out.println("初始化方法...");}
public void destroy(){ System.out.println("销毁方法...");}
private UserDao userDao;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory去调用该方法获得userDao设置到此处:"+userDao);
this.userDao = userDao;
}
}
- 测试
@Test
public void BeanTest3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService);
applicationContext.close();
}
bean的消耗和bean销毁的方法的调用是两回事,有的时候容器已经没有了,对应内部维护的哪些bean对象也没有了,这时方法没有被调用的画——是因为spring没有执行到调用销毁方法那个步骤,这个容器就挂掉了
步骤:
- bean销毁时机
2.6 InitializingBean方式
扩展:除此之外,我们还可以通过实现InitializingBean
接口,完成一些Bean的初始化操作,如下:
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.beans.factory.InitializingBean;
public class UserServiceImpl implements UserService, InitializingBean {
public void init(){ System.out.println("初始化方法...");}
public void destroy(){ System.out.println("销毁方法...");}
private UserDao userDao;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory去调用该方法获得userDao设置到此处:"+userDao);
this.userDao = userDao;
}
//执行时机早于init-method配置的方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(" afterPropertiesSet方法执行...");
}
}
2.7 实例化Bean的方式-构造方法方式
-
提供可访问的构造方法
-
配置
Bean的实例化配置
Spring的实例化方式主要如下两种:
- 构造方式实例化: 底层通过构造方法对Bean进行实例化
- 工厂方式实例化: 底层通过调用自定义的工厂方法对Bean进行实例化
- 测试
@Test
public void BeanTest3(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService);
}
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的<bean>
几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean
public class UserServiceImpl implements UserService {
public UserServiceImpl(){
System.out.println("UserServiceImpl无参构造方法执行");
}
public UserServiceImpl(String name,int age){
System.out.println("UserServiceImpl有参构造方法执行");
}
private UserDao userDao;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
}
有参构造在实例化Bean时,需要参数的注入,通过<constructor-arg>
标签,其name为有参构造里面的形参名字,value值随便写。该嵌入在<bean>
标签内部提供构造参数,如下:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<constructor-arg name="name" value="haohao"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<property name="userDao" ref="userDao"></property>
</bean>
大部分的情况下用的都是无参构造
- 无参构造方法如果不存在,将会抛出异常
BeanCreationException
2.7.1 spring bean异常的看法
报错异常建议从最下面开始看起解决,看不懂再到上一层,逐层递增
大部分的情况下解决最后一个异常就解决问题了
2.7.2 静态工厂方法该方式去实例化Bean
工厂方式实例化Bean,又分为如下三种:
- 静态工厂方法实例化Bean
- 实例工厂方法实例化Bean
- 实现FactoryBean规范延迟实例化Bean
使用该静态工厂方法的优点:
- 可以执行一些创建对象之前其他的逻辑操作
- 可以生成一些其他工具类或jar包使用静态方法创造的对象
public class MyBeanFactory1 {
public static UserDao userDao(){
//Bean创建之前可以进行一些其他业务逻辑操作
return new UserDaoImpl();
}
}
- bean标签中有了factory-method,就不是把MyBeanFactory1当做一个对象,而是找它内部的这个userDao方法,之后把该方法的返回值当做对象。再以你指定的id作为nam存储到spring容器当中
<bean id="userDao1" class="com.itheima.factory.MyBeanFactory1" factory-method="userDao"></bean>
- 测试
@Test
public void BeanTest3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao1 = applicationContext.getBean("userDao1");
System.out.println(userDao1);
}
2.7.3 实例工厂方法该方式去实例化Bean
区别静态工厂就是不加static
使用实例工厂方法的优点:
- 可以执行一些创建对象之前其他的逻辑操作
- 可以生成一些其他工具类或jar包通过方法来创造的对象
public class MyBeanFactory2 {
public UserDao userDao(){
//Bean创建之前可以进行一些其他业务逻辑操作
return new UserDaoImpl();
}
}
实例工厂就要调用对象
<!--配置实例工厂方法-->
<!--先设置工厂对象-->
<bean id="myBeanFactory2" class="com.itheima.factory.MyBeanFactory2"></bean>
<!--在设置工厂方法-->
<bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao"></bean>
如果是静态工厂就不用传递对象,直接调用方法即可
- 测试
@Test
public void BeanTest3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao2 = applicationContext.getBean("userDao2");
System.out.println(userDao2);
}
2.7.4 有参数的静态工厂和实例工厂方法
<bean id="factoryUser" factory-bean="factoryDemo2" factory-method="CreateUserService">
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="age" value="18"/>
</bean>
constructor-arg该参数是工厂方法构造Bean方法的参数
2.7.5 实现FactoryBean规范延迟实例化Bean
使工厂类继承FactoryBean<UserDao>
通过getObject()
方法返回对象
public class MyBeanFactory3 implements FactoryBean<UserDao> {
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
该bean获取的不是MyBeanFactory3,而是MyBeanFactory3内部的getObject()
返回的这个对象
<bean id="userDao3" class="com.itheima.factory.MyBeanFactory3"></bean>
该方法有延迟功能,只有调用类时,才会将getObject()
方法返回的类储存到FactoryBean缓存当中
- 测试
@Test
public void BeanTest3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao3 = applicationContext.getBean("userDao3");
System.out.println(userDao3);
}
2.8 Bean的注入
2.8.1 Bean的注入方式
Bean的依赖注入又两种方式:
注入方式 | 配置方式 |
---|---|
通过Bean的set方法(又叫setter)注入 | <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用于注入普通属性值。
2.8.2 Bean的注入数据类型
依赖注入的数据类型有如下三种:
-
普通数据类型,例如: String、int、 boolean等,通过value属性指定
-
引用数据类型,例如: UserDaolmpl、DataSource等,通过ref属性指定。
-
集合数据类型,例如: List、Map、Properties等。
注:property和constructor-arg的name属性和参数的名字必须要一样
public class UserServiceImpl implements UserService {
//注入List
private List<String> stringList;
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
private List<UserDao> userDaoList;
public void setUserDaoList(List<UserDao> userDaoList) {
this.userDaoList = userDaoList;
}
private Set<String> strSet;
public void setStrSet(Set<String> strSet){
this.strSet = strSet;
}
private Set<UserDao> userDaoSet;
public void setUserDaoSet(Set<UserDao> userDaoSet){
this.userDaoSet = userDaoSet;
}
private Map<String,UserDao> map;
public void setMap(Map<String, UserDao> map) {
this.map = map;
}
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void show(){
System.out.println(stringList);
System.out.println(userDaoList);
System.out.println(userDaoSet);
System.out.println(strSet);
System.out.println(map);
System.out.println(properties);
}
private UserDao userDao;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
}
- bean容器
<property name="stringList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userDaoList">
<list>
<ref bean="userDao1"></ref>
<ref bean="userDao2"></ref>
<ref bean="userDao3"></ref>
</list>
</property>
<property name="strSet">
<set>
<value>xxxx</value>
<value>yyyy</value>
</set>
</property>
<property name="userDaoSet">
<set>
<ref bean="userDao1"></ref>
<ref bean="userDao2"></ref>
<ref bean="userDao3"></ref>
</set>
</property>
<property name="map">
<map>
<entry key="d1" value-ref="userDao1"></entry>
<entry key="d2" value-ref="userDao2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">pp1</prop>
<prop key="p2">pp2</prop>
</props>
</property>
</bean>
<bean id="userDao1" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userDao2" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"></bean>
- 接口的方法创建
public interface UserService {
public void show();
}
- 测试
@Test
public void BeanTest3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.show();
}
2.8.3 自动装配
扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在<bean>
标签中使用autowire
属性去配置自动注入方式,属
性值有两个:
- byName:通过属性名自动装配,即去匹配
setXxx
与id="xxx" (name="xxx")
是否一致;
public void show(){
System.out.println(userDao);
}
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
- byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
2.9 命名空间的种类
Spring的xml标签大体上分为两类,一种是默认标签,一种是自定义标签
- 默认标签:就是不用额外导入其他命名空间约束的标签,例如
<bean>
标签 - 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如
<context:property-placeholder/>
标签
Spring的默认标签用到的是Spring的默认命名空间
<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http: //www.w3.org/2001/xMschema-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.9.1 beans的profile属性切换环境
<beans>
标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!--配置测试环境下,需要加载的Bean实例-->
<beans profile="test">
</beans>
<!--配置开发环境下,需要加载的Bean实例-->
<beans profile="dev">
</beans>
可以使用以下两种方式指定被激活的环境:
- 使用命令行动态参数,虚拟机参数位置加载
-Dspring.profiles.active=test
- 使用代码的方式设置环境变量
System.setProperty("spring.profiles.active","test")
案例:
<beans profile="dev">
<bean id="userService1" class="com.itheima.service.impl.UserServiceImpl"></bean>
</beans>
<beans profile="test">
<bean id="userDao1" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</beans>
@Test
public void BeanTest4(){
//指定环境
System.setProperty("spring.profiles.active","test");
//System.setProperty("spring.profiles.active","dev");
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// UserService userService1 = (UserService) applicationContext.getBean("userService1");
UserDao userDao1 = (UserDao) applicationContext.getBean("userDao1");
// System.out.println(userService1);
System.out.println(userDao1);
}
2.9.2 import标签
<import>
标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过<import>
标签导入到一个主配置文件中,项目加载主配置文件就连同<import>
导入的文件一并加载了
<!--导入用户模块配置文件-->
<import resource="classpath:applicationContext-user.xml"></import>
<import resource="classpath:applicationContext-orders.xml"></import>
案例:
- user的bean
<?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.itheima.service.impl.UserServiceImpl"></bean>
</beans>
- orders的bean
<?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="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</beans>
- 测试
@Test
public void BeanTest4(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
System.out.println(userService);
System.out.println(userDao);
}
2.9.3 alias标签
<alias>
标签是为某个Bean添加别名,与在<bean>
标签上使用name属性添加别名的方式一样,我们为UserServicelmpl指定四个别名: aaa、bbb、xxx、yyy
格式:<alias alias="自定义名字" name="bean的id">
<!--配置UserService-->
<bean id="userService" name="aaa,bbb" class="com.itheima.service.imp1.UserserviceImp1">
<property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userDao" alias="yyy"/>
测试
@Test
public void BeanTest4(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("xxx");
// UserDao userDao = (UserDao) applicationContext.getBean("userDao");
System.out.println(userService);
// System.out.println(userDao);
}
2.9.4 自定义命名空间的使用步骤
Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用<前缀:标签>
形式的标签,称之为自定义标签,自定义标签的解析流程也是Spring xml扩展点方式之一
-
第一步:先再pom文件里面引入坐标
-
第二步:去找或者抄对应的这个命名空间xmlns,例如:
xmlns:mvc="http://www.springframework.org/schema/mvc"
-
第三步:去找或者抄对应的xsi这个schema的位置,例如:
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
-
第四步:通过前缀来引入自定义标签
<!--默认标签-->
<bean id="userDao" class="com.itheima. dao .imp1.userDaoImpl" />
<!--自定义标签-->
<mvc:annotation-driven></mvc:annotation-driven>
三 小黑子Spring的get方法
3.1 spring常用的三种getBean的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类型实例,无需强转 |
//根据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都是自己定义的,例如: UserDaolmpl,UserServicelmpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:
- 被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例化工厂方式;
- 被配置的Bean是否需要注入必要属性
4.1 DruidDatasource
- 配置Druid数据源交由Spring管理
导入Druid坐标
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupid>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
- 配置数据源信息
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="你的数据库名字"></property>
<property name="password" value="你的数据库密码"></property>
</bean>
- 测试
@Test
public void BeanTest5(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object dataSource = applicationContext.getBean("dataSource");
System.out.println(dataSource);
}
4.2 Connection
Connection的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置
<bean id="clazz" class="java.lang.Class" factory-method="forName">
<constructor-arg name="className" value="com.mysql.jdbc.Driver" />
</bean>
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
<constructor-arg name="url" value="jdbc:mysql:///mybatis" />
<constructor-arg name="user" value="root"/>
<constructor-arg name="password" value="root"/>
</bean>
- 测试
@Test
public void BeanTest5(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object connection = applicationContext.getBean("connection");
System.out.println(connection);
}
4.3 Date
产生一个指定日期格式的对象,原始代码如下
String currentTimeStr = "2023-08-27 07:20:00" ;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(currentTimeStr);
可以看成是实例工厂方式,使用Spring配置方式产生Date实例
<!--配置日期对象-->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss" />
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-08-27 07:20:00"/>
</bean>
- 测试
@Test
public void BeanTest5(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object date = applicationContext.getBean("date");
System.out.println(date);
}
4.4 SqlSessionFactory
mybatis官方文档
配置SqlSessionFactory交由Spring管理
导入Mybatis相关坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
- 配置bean参考的原代码
//静态工厂方法方式
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//无参构造实例化
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//实例工厂方法
SqlSessionFactory sqlSessionFactory = builder.build(is);
<!-- class路径是复制对象的路径 id可以是原本命名的id-->
<!-- 工厂对象--><!--静态工厂-->
<bean id="is" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
<constructor-arg name="resource" value="mybatis-config.xml">
<!-- 该name使用ctrl+点击左键查看getResourceAsStream方法的形参可知-->
</constructor-arg>
</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="is"></constructor-arg>
</bean>
- 测试
@Test
public void BeanTest5() throws {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
System.out.println(sqlSessionFactory);
}
在拿到一个bean时去配置让spring去管,要分清它是属于哪种方式产生的,是属于无参构造,还是静态工厂方法,还是实例工厂方法。确定之后,再看需不需要参数
五 小黑子Bean实例化的基本流程
5.1 BeanDefinition
- Spring容器在进行初始化时,会将xml配置的
<bean>
的信息封装成一个BeanDefinition
对象; - 所有的
BeanDefinition
存储到一个名为beanDefinitionMap
的Map集合中去; - Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为
singletonObjects
的Map集合中; - 当调用
getBean
方法时则最终从该Map集合中取出Bean实例对象返回。
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的实例化操作
5.2 单例池和流程总结
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> singleton0bjects = new ConcurrentHashMap(256)
基本流程:
- 加载xml配置文件,解析获取配置中的每个
<bean>
的信息,封装成一个个的BeanDefinition
对象;- 将
BeanDefinition
存储在一个名为beanDefinitionMap
的Map<String,BeanDefinition>
中;- ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
- 创建好的Bean实例对象,被存储到一个名为
singletonObjects
的Map<String,Object>
中;- 当执行
applicationContext.getBean(beanName)
时,从singletonObjects
去匹配Bean实例返回。
六 小黑子Spring的Bean工厂后处理器
6.1 入门
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
BeanFactoryPostProcessor: Bean
工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;BeanPostProcessor: Bean
后处理器,一般在每个Bean实例化之后,填充到单例池singletonObjects之前执行。
Bean工厂后处理器——BeanFactoryPostProcessor
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
BeanFactoryPostProcessor定义如下:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
6.2 注册BeanDefinition
使用这种方法,可以不用在spring容器内再创建一个类的<bean>
标签
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory){
System.out.println("beandefinitionMap填充完毕后回调该方法");
//1.注册一个beandefinition 创建一个RootBeanDefinition()对象
BeanDefinition BeanDefinition = new RootBeanDefinition();
BeanDefinition.setBeanClassName("com.itheima.dao.impl.PersonDaoImpl");
//2.将beanFactory强转成DefaultListableBeanFactory类型
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
defaultListableBeanFactory.registerBeanDefinition("personDao",BeanDefinition);
}
}
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
PersonDao personDao = applicationContext.getBean(PersonDao.class);
System.out.println(personDao);
}
<bean class="com.itheima.processor.MyBeanFactoryPostProcessor"></bean>
6.3 BeanDefinitionRegistryPostProcessor
Spring提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法");
//创建一个RootBeanDefinition()对象
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.impl.PersonDaoImpl");
//不需要强转就可以创建一个Bean
beanDefinitionRegistry.registerBeanDefinition("personDao",beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("MyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法");
}
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory){
System.out.println("MyBeanFactoryPostProcessor的postProcessBeanFactory方法");
// //1.注册一个beandefinition 创建一个RootBeanDefinition()对象
// BeanDefinition BeanDefinition = new RootBeanDefinition();
// BeanDefinition.setBeanClassName("com.itheima.dao.impl.PersonDaoImpl");
// //2.将beanFactory强转成DefaultListableBeanFactory类型
// DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
// defaultListableBeanFactory.registerBeanDefinition("personDao",BeanDefinition);
}
}
<bean class="com.itheima.processor.MyBeanFactoryPostProcessor"></bean>
<bean class="com.itheima.processor.MyBeanDefinitionRegistryPostProcessor"></bean>
- 测试
可见执行顺序
6.4 完善实例化流程图
BeanFactoryPostProcessor
在SpringBean的实例化过程中的体现
记图非常重要
6.5 自定义@Component
要求:
- 自定义@MyComponent注解,使用在类上;
- 使用资料中提供好的包扫描器工具BaseClassScanUtils完成指定包的类扫描;
- 自定义BeanFactoryPostProcessor完成注解@MyComponent的解析,解析后最终被Spring管理。
utils包下BaseClassScanUtils
package com.itheima.utils;
import com.itheima.anno.MyComponent;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.util.HashMap;
import java.util.Map;
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.itheima");
System.out.println(stringClassMap);
}
}
anno包下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
String value();
}
beans包下
@MyComponent("otherBean")
public class OtherBean {
}
bean容器
<bean class="com.itheima.processor.MyBeanDefinitionRegistryPostProcessor"></bean>
processor包下的MyComponentBeanFactoryPostProcessor
public class MyComponentBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
//通过扫描工具去扫描指定包及其子包下的所有雷,手机使用@Mycomponent的注解的类
Map<String, Class> myComponentAnnotationMap = BaseClassScanUtils.scanMyComponentAnnotation("com.itheima");
//遍历Map 组装BeanDefinition进行注册
myComponentAnnotationMap.forEach((beanName,clazz)->{
//获得beanClassName
String beanClassName = clazz.getName();//com.itheima.beans.OtherBean
//创建BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName(beanClassName);
//注册
beanDefinitionRegistry.registerBeanDefinition(beanClassName,beanDefinition);
});
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
- 测试
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
OtherBean otherBean = applicationContext.getBean(OtherBean.class);
System.out.println(otherBean);
}
七 Spring的Bean后处理器
7.1 BeanPostProcessor
Bean被实例化后,到最终缓存到名为singletonObjects
单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor
,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor
,会在流程节点上被Spring自动调用。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName+":postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName+":postProcessAfterInitialization");
return bean;
}
}
public class UserDaoImpl implements UserDao {
private String username;
public void setUsername(String username) {
this.username = username;
}
public UserDaoImpl(){
System.out.println("userDao实例化");
}
}
<!-- <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<!-- <bean class="com.itheima.processor.MyBeanFactoryPostProcessor"></bean>-->
<!-- <bean class="com.itheima.processor.MyBeanDefinitionRegistryPostProcessor"></bean>-->
<!-- <bean class="com.itheima.processor.MyComponentBeanFactoryPostProcessor"></bean>-->
<bean class="com.itheima.processor.MyBeanPostProcessor"></bean>
- 测试
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
}
postProcessBeforeInitialization
方法和postProcessAfterInitialization
方法需要进行手动创建,接口中实现的是null返回值的方法- 两者方法在bena创建之后执行
7.2 before和after方法的执行时机
- 先执行bean的构造方法
- 执行before方法
- 执行InitializingBean接口中的afterPropertiesSet()方法
- 执行在xml文件中设置的Bean的init-method方法
- 执行after方法
public class UserDaoImpl implements UserDao, InitializingBean {
private String username;
public void setUsername(String username) {
this.username = username;
}
public UserDaoImpl(){
System.out.println("userDao实例化");
}
public void init(){
System.out.println("init初始化方法执行。。。");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("属性在设置之后执行。。。");
}
}
<!-- <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="init"></bean>
<!-- <bean class="com.itheima.processor.MyBeanFactoryPostProcessor"></bean>-->
<!-- <bean class="com.itheima.processor.MyBeanDefinitionRegistryPostProcessor"></bean>-->
<!-- <bean class="com.itheima.processor.MyComponentBeanFactoryPostProcessor"></bean>-->
<bean class="com.itheima.processor.MyBeanPostProcessor"></bean>
- 测试
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao = applicationContext.getBean("userDao");
}
7.3 案例-对Bean方法执行日志功能增强
- Processor类
public class TimeLogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//使用动态代理对目标Bean进行增强,返回proxy对象,进而存储到singletonObjects中
Object beanProxy = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy,method,args)->{
//1.输出开始时间
System.out.println("方法:"+method.getName()+"-开始时间:"+new Date());
//2.执行目标方法
Object result = method.invoke(bean, args);
//3.输出结束时间
System.out.println("方法:"+method.getName()+"-结束时间:"+new Date());
return result;
}
);
return beanProxy;
}
}
- 接口实现类
public interface UserDao
{
void show();
}
public class UserDaoImpl implements UserDao{
@Override
public void show() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show");
}
}
- xml文件
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.processor.TimeLogBeanPostProcessor"></bean>
- 测试
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.show();
}
7.4 再次完善实例化基本流程图
BeanPostProcessor在 SpringBean的实例化过程中的体现
八 小黑子Spring Bean的生命周期
8.1 概述
Spring Bean的生命周期是从Bean 实例化之后,即通过反射创建出对象之后,导Bean成为一个完整对象,最终储存到单例池中,这个过程被称为SpringBean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
- Bean的实例化阶段: Spring框架会取出
BeanDefinition
的信息进行判断当前Bean的范围是否是singleton
的,是否不是延迟加载的,是否不是FactoryBean
等,最终将一个普通的singleton
的Bean通过反射进行实例化 - Bean的初始化阶段∶Beane创建之后还仅仅是个”半成品“,还需要对Bean实例的属性进行填充、执行一些
Aware
接口方法、执行BeanPostProcessor
方法、执行InitializingBean
接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的 - Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池
singletonObjects
中去了,即完成了Spring Bean的整个生命周期。
8.2 初始化阶段执行步骤
由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
-
Bean实例的属性填充
-
Aware
接口属性注入 -
BeanPostProcessor
的before()方法回调 -
lnitializingBean
接口的初始化方法回调 -
自定义初始化方法init回调
-
BeanPostProcessor
的after()方法回调
8.3 初始化阶段注入属性信息封装
BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
<property name="username" value="haohao"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
例如UserService的属性信息:
Bean实例的属性填充
8.4 属性注入的三种情况
Spring在进行属性注入时,会分为如下几种情况:
- 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
- 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作
- 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。
8.5 注入单向对象的代码验证
public class UserServiceImpl implements UserService {
public UserServiceImpl(){
System.out.println("userService创建");
}
private UserDao userDao;
private String username;
//BeanFactory去调用该反复 从容器中获的userDao设置到此处
public void setUserDao(UserDao userDao){
System.out.println("userService执行注入userDao的操作:setUserDao方法执行");
this.userDao = userDao;
}
public void setUsername(String username) {
this.username = username;
}
}
public class UserDaoImpl implements UserDao{
public UserDaoImpl(){
System.out.println("userDao创建");
}
@Override
public void show() {
System.out.println("show");
}
}
1、user的<bean>
在userService的前面
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
<property name="username" value="haohao"></property>
</bean>
- 测试
@Test
public void BeanTest5() throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService bean = applicationContext.getBean(UserService.class);
}
2、user的<bean>
在userService的后面时
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
<property name="username" value="haohao"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
8.6 循环依赖概念及解决方案
多个实体之间相互依赖并形成闭环的情况就叫做 “循环依赖”,也叫做 “循环引用”
8.7 三级缓存的设计原理
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);
}
8.8 循环依赖源码流程剖析
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
- UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
- UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
- UserDao 实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
- UserDao 属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
- UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
- UserService注入UserDao;
- UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
1.
2.
3.
8.9 Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |
九 小黑子对Spring IoC整体流程总结
十 小黑子进行Spring xml方式整合第三方框架
xml整合第三方框架有两种整合方案:
不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如: MyBatis;
需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo
10.1 Mybatis整合Spring实现
Spring整合MyBatis,之前已经在Spring中简单的配置了SqlSessionFactory,但是这不是正规的整合方式,MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
Spring整合MyBatis的步骤如下:
- 导入MyBatis整合Spring的相关坐标;
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
- 编写Mapper和Mapper.xml;
- 配置
SqlSessionFactoryBean
和MapperScannerConfigurer
;
<!-- 配置数据源信息-->
<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/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置SqlSessionFactoryBean,作用将SqlSessionFactory储存导spring容器中-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 作用扫描指定的包,产生Mapper对象储存导spring容器当中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"></property>
</bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"></property>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
- 编写测试代码
public class UserServiceImpl implements UserService {
//需要Mapper
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public void show() {
List<User> all = userMapper.selectAll();
for(User user : all){
System.out.println(user);
}
}
}
package com.itheima.pojo;
public class User {
private Long id;
private String username;
private String password;
private String gender;
private String addr;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
package com.itheima.mapper;
import com.itheima.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectAll();
}
<?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.itheima.mapper.UserMapper">
<select id="selectAll" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.mybatis"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.itheima.mapper"/>
</mappers>
</configuration>
@Test
public void BeanTest6(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = applicationContext.getBean(UserService.class);
userService.show();
}
10.2 Mybatis整合Spring源码解析
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
SqlSessionFactoryBean
:需要进行配置,用于提供SqlSessionFactory;MapperScannerConfigurer
:"需要进行配置,用于扫描指定mapper注册BeanDefinition;MapperFactoryBean
::Mapper的FactoryBean,获得指定Mapper时调用getObject方法;ClassPathMapperScanner
:definition.setAutowireMode(2)修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。
MapperScanerConfiguer源码分析
10.3 加载外部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应用信息 -->
<dubbo:application name="your-application-name" />
<!-- 配置注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 配置服务提供者 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.example.YourServiceInterface" ref="yourServiceBean" />
<!-- 配置其他Bean -->
<!-- 消费者配置 -->
<dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>
通过配置context文件来加载外部properties文件
- jdbc.properties
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis
jdbc.username = root
jdbc.password = root
- bean.xml
<!-- 加载properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
自定义命名空间解析原理
10.4 自定义空间步骤
- 将自定义标签的约束与物理约束文件与网络约束名称的约束以键值对形式存储到一个
spring.schemas
文件里,该文件存储在类加载路径的META-INF里,Spring会自动加载到; - 将自定义命名空间的名称与自定义命名空间的处理器映射关系以键值对形式存在到一个叫
spring.handlers
文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到; - 准备好
NamespaceHandler
,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition
。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser
,在执行NamespaceHandler
的parse方法时在分流给不同的
BeanDefinitionParser
进行解析(重写doParse方法即可)。
10.5 自定义命名空间案例
步骤分析:
- 确定命名空间名称、schema虚拟路径、标签名称;
- 编写schema约束文件haohao-annotation.xsd
- 在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
- 编写命名空间处理器HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
- 编写标签的解析器HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
- 编写HaohaoBeanPostProcessor
============= 以上五步是框架开发者写的,以下是框架使用者写的 ====================
- 在applicationContext.xml配置文件中引入命名空间
- 在applicationContext.xml配置文件中使用自定义的标签