Spring学习笔记(Ico部分)

Spring学习笔记(Ico部分)

  • 一.传统Javaweb开发的困惑
  • 二.IoC、DI和AOP思想提出
      • 1.IoC思想(Inversion of Control)
      • 2.DI思想(Dependency Injection)
      • 3.AOP (Aspect Oriented Programming)
      • 4.框架基本特点
      • 5.Java中常见的框架
  • 三.Spring框架的诞生
      • 1.Spring 框架概述
      • 2.Spring 框架的历史(了解)
      • 3.Spring Framework技术栈图示
      • 4.BeanFactory 快速入门
      • 5.ApplicationContext快速入门
      • 6.BeanFactory与ApplicationContext的关系
      • 7.BeanFactory的继承体系
      • 8.ApplicationContext的继承体系
  • 四.基于xml的Spring应用
      • 1.SpringBean 的配置详解
        • (1)Spring开发中主要是对Bean的配置,Bean的常用配置一览如下:
        • (2)Bean的依赖注入有两种方式:
        • (3)Spring的其他配置标签
      • 2.Spring 的get方法
      • 3.Spring 配置非自定义Bean
        • 实例:(这些实例不用记,理解就好,实际开发一般不这么用)
          • 1)配置 Druid 数据源交由Spring管理(无参构造,需要注入四个)
          • 2)配置Connection交由Spring管理
          • 3)配置日期对象交由Spring管理
          • 4)配置MyBatis的SqlSessionFactory交由Spring管理
      • 4.Bean 实例化的基本流程(4-8都是源码讲解)
      • 5.Spring的后处理器(重点)
          • 1)BeanFactoryPostProcessor
          • 2)BeanFactoryProcessor
      • 6.Spring Bean的生命周期
          • Bean的初始化阶段
      • 7.Spring IoC 整体流程总结
      • 8.Spring xml方式整合第三方框架
  • 五.基于注解的Spring应用
      • 1.Bean基本注解开发
      • 2.Bean依赖注入注解开发
      • 3.非自定义Bean注解开发
      • 4.Bean配置类的注解开发
      • 5.Spring 配置其他注解
      • 6.Spring注解的解析原理
      • 7.Spring注解方式整合第三方框架

一.传统Javaweb开发的困惑

  • 困惑一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起
    解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象(不在业务里new对象)
  • 困惑二:通用的事务功能耦合在业务代码中,通用的日志功能耦合在业务代码中
    解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象的代理对象(BeanProxy),代理对象内部动态结合业务和通用功能(不在业务里写通用功能)

IoC和DI思想主要是解决困惑一,AOP解决困惑二

二.IoC、DI和AOP思想提出

1.IoC思想(Inversion of Control)

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

谁去充当第三方角色呢?工厂设计模式,BeanFactory
BeanFactory怎么知道产生哪些Bean实例呢?使用配置文件配置Bean的基本信息

2.DI思想(Dependency Injection)

依赖注入,某个Bean的完整创建依赖于其他Bean(或普通参数)的注入
例如:将UserDao在BeanFactory内部设置给UserService的过程叫做“注入”,而UserService需要依赖UserDao的注入才能正常工作,这个过程叫做“依赖注入”

面试题:IoC 和 DI 的关系?
首先,先回答IoC和DI的是什么
其次,回答IoC和DI的关系:
第一种观点:IoC强调的是Bean创建权的反转,而DI强调的是Bean的依赖关系,认为不是一回事
第二种观点:IoC强调的是Bean创建权的反转,而DI强调的是通过注入的方式反转Bean的创建权,认为DI是IoC的其中一种实现方式

3.AOP (Aspect Oriented Programming)

面向切面编程,用横向抽取方法(属性、对象等)思想,组装成一个功能性切面。(简单理解就是增强对象的)

4.框架基本特点

  1. 是基于基础技术之上通用的解决方案;
  2. 是一个半成品,提高开发效率
  3. 使用大量的设计模式、算法、底层代码操作技术,如反射、内省、xml解析、注解解析等;
  4. 具备扩展性;
  5. 将精力尽可能的投入在纯业务开发

5.Java中常见的框架

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

三.Spring框架的诞生

1.Spring 框架概述

开源、轻量级Java开发应用框架
生态及其完善,不管是Spring哪个领域的解决方案都是依附于SpringFramework基础框架的
Spring的官网:www.spring.io

2.Spring 框架的历史(了解)

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

3.Spring Framework技术栈图示

Spring学习笔记(Ico部分)_第1张图片

  • Data Access:数据访问
  • Data Integration:数据集成
  • Web:Web开发
  • AOP:面向切面编程
  • Aspects:AOP思想实现
  • Core Container:核心容器
  • Test:单元测试与集成测试

<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.7version>
dependency>

4.BeanFactory 快速入门

开发时不会接触到,但是是Spring里最核心的对象

程序代码
BeanFactory
配置文件
UserServiceImpl
UserDaoImpl

只要在配置文件里配置了对象,BeanFactory 就可以帮我们创建
只要在配置文件里对象配置引用子标签,对象中有Set方法,BeanFactory 就可以帮我们创建并注入

  1. 创建BeanFactory
  2. 创建读取器
  3. 加载配置文件
  4. 获取UserService实例对象

5.ApplicationContext快速入门

只是简化了开发代码,配置文件还是一样
原理: ApplicationContext实现了BeanFactory接口,在调用ApplicationContext时,实际还是在底层调用BeanFactory,ApplicationContext里面维护了一个BeanFactory,BeanFactory里面维护了一个map,里面存放了Bean

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

  1. 创建ApplicationContext,加载配置文件,实例化容器
  2. 根据beanName获得容器中的Bean实例

6.BeanFactory与ApplicationContext的关系

面试题: 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。

7.BeanFactory的继承体系

BeanFactory的具体实现类是DefaultListableBeanFactory。
DefaultListableBeanFactory里面有一个Map叫beanDefinitionMap这里面维护的是配置文件里配置的Bean

8.ApplicationContext的继承体系

ApplicationContext具体实现类有三个:

  • ClassPathXmlApplicationContext:加载类路径下的xml配置的ApplicationContext
  • FileSystemXmlApplicationContext:加载磁盘路径下的xml配置的ApplicationContext
  • AnnotationConfigApplicationContext:加载注解配置类的ApplicationContext

注意:这是只导入了spring-context坐标的结构,如果还导入了spring-web坐标,实现类变成XmlWebApplicationContext,GroovyApplicationContext,AnnotatinConfigWebApplicationContext这些实现类还整合了其他技术。

四.基于xml的Spring应用

开发一般用注解,但是xml更能看清spring原理

1.SpringBean 的配置详解

(1)Spring开发中主要是对Bean的配置,Bean的常用配置一览如下:
Xml配置方式 功能描述
< bean id=“” class=“” > Bean的id和全限定名配置 注:id在容器中会转化成Bean的name,getBean()时传的参数spring只会在配置文件找id或name匹配的类,如果没有赋值id,getBean的参数应该是全限定名
< bean name=“”> 通过name设置Bean的别名(小名),通过别名也能直接获取到Bean实例 注:如果匹配到的是name,spring还会根据name找到id(ApplicationContext的BeanFactory里面维护了别名映射map)将id作为BeanName,如果没有配置id,则用name作为BeanName
< 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 注:手动注入需要配置了子类,如果自动注入则不需要配置,spring会根据set方法自己去找想要的Bean,注意名称对应才能找到。
< bean constructor-arg=“” > 有参构造方法实例化Bean 注:此标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过< constructor-arg >标签完成,如果静态工厂方法实例化Bean所传递的参数也是要通过< constructor-arg >进行传递的
< bean factory-bean=“” /> 指定哪个工厂Bean的哪个方法完成Bean的创建 注:原来是创建class里的对象(Spring会自己找无参构造进行创建),命名为id,现在是找到class里的对象中factory-bean规定的方法,用这个方法的返回值创建对象,对象名为id。

注:一般在实际开发中配置id,不配置name

面试题:scope的取值:
在单纯的spring环境中:

  • singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
  • prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
  • 在spring-webmvc环境中:
    还有requestsession两个取值,可以放在request和session域中

面试题:Spring的实例化方式?

  • 构造方式实例化:底层通过构造方法对Bean进行实例化
    • 无参构造方法实例化
    • 有参构造方法实例化
  • 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
    • 静态工厂方法实例化Bean:定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其静态方法配置给Spring即可。 应用:在创建Bean之前可以写一些其他业务代码;导入的jar中也有别人写好的需要静态构造的Bean,配置factory-method构造。
    • 实例工厂方法实例化Bean:也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,再配置目标Bean。
    • 实现FactoryBean规范延迟实例化Bean:和前两种可以达到相同的目的,只不过不用自定义方法了,实现接口规范即可。这种方法在底层的实现上是懒加载的,spring容器创建时不会创建Bean,用到的时候才会创建,最后把Bean放入缓存池中。(正常开发不会用到,但是spring的源码底层经常用)
//静态工厂方法实例化Bean
public class UserDaoFactoryBean {//工厂类
	//静态工厂方法
	public static UserDao getUserDao(String name){
		//可以在此编写一些其他逻辑代码
		return new UserDaoImpl();
	}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean" factory-method="getUserDao">
//实例工厂方法实例化Bean
public class UserDaoFactoryBean2 {
	//非静态工厂方法
	public UserDao getUserDao(String name){
		//可以在此编写一些其他逻辑代码
		return new UserDaoImpl();
	}
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.itheima.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDao" factory-bean="userDaoFactoryBean2" factory-method="getUserDao">
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {
	public UserDao getObject() throws Exception {
		return new UserDaoImpl();
	}
	public Class<?> getObjectType() {
		return UserDao.class;
	}
}
//配置FactoryBean交由Spring管理即可
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean3"/>
(2)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 用于注入普通属性值。
依赖注入的数据类型有如下三种:

  • 普通数据类型,例如:String、int、boolean等,通过value属性指定
  • 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定
  • 集合数据类型,例如:List、Map、Properties等。通过标签嵌套
<!--List<String>普通数据类型-->
<property name="strList">             
	<list>
		<value>haohao</value>
		<value>miaomiao</value>
	</list>
</property>
<!--List<String>引用数据类型-->
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<property name="objList">
	<list>
		<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
		<ref bean="userDao3"></ref>
	</list>
</property>
<!-- 注入泛型为字符串的Set集合 -->
<property name="valueSet">
	<set>
		<value>muzi</value>
		<value>muran</value>
	</set>
</property>
<!-- 注入泛型为对象的Set集合 -->
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<property name="objSet">
	<set>
		<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
		<ref bean="userDao3"></ref>
	</set>
</property>

注:后面将省略注册UserDao,只写引用方式的注入,另一种就不写了

<!--注入值为字符串的Map集合-->
<property name="valueMap">
	<map>
		<entry key="aaa" value="AAA" />
		<entry key="bbb" value="BBB" />
	</map>
</property>
<!--注入值为对象的Map集合-->
<property name="objMap">
	<map>
		<entry key="ud" value-ref="userDao"/>
		<entry key="ud2" value-ref="userDao2"/>
		<entry key="ud3" value-ref="userDao3"/>
</map>
</property>
 <property name="properties">
	<props>
		<prop key="xxx">XXX</prop>
		<prop key="yyy">YYY</prop>
	</props>
</property>

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

  • byName:通过属性名自动装配,即去匹配 setXxx 与 id=“xxx”(name=“xxx”)是否一致
  • byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
(3)Spring的其他配置标签

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

  • 默认标签:就是不用额外导入其他命名空间约束的标签,例如 < bean> 标签
  • 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 < context:property-placeholder/> 标签
默认标签 作用
< beans> 一般作为 xml 配置根标签,其他标签都是该标签的子标签
< bean> Bean的配置标签,上面已经详解了,此处不再阐述
< import> 外部资源导入标签
< alias> 指定Bean的别名标签,使用较少
  1. < beans>标签:除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
</beans>

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

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

  • 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
  • 使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,“test”)
    注:如果没有设置环境变量,只有公共部分配置(xml)生效,如果设置了环境变量,则公共部分和环境内的配置(xml)都生效
  1. < import>标签:用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过< import>标签导入到一个主配置文件(applicationContext.xml)中,项目加载主配置文件就连同< import> 导入的文件一并加载了
<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
  1. < 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专门映射别名和id的关系

自定义标签:需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring xml扩展点方式之一,在《Spring整合其他框架》章节进行详细介绍

<!--默认标签-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--自定义标签-->
<context:property-placeholder/>
<mvc:annotation-driven/>
<dubbo:application name="application"/>

注:1.引用坐标 2.引入命名空间和地址映射 3.使用标签

2.Spring 的get方法

方法定义 返回值和参数
Object getBean (String beanName) 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转
T getBean (Class type) 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转(一般开发中,一个类型只配置一个Bean)
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);

3.Spring 配置非自定义Bean

在实际开发中有些功能类是使用的第三方jar包中的,那么这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:

  • 被配置的Bean的实例化方式是什么?(看源码)无参构造、有参构造、静态工厂方式还是实例工厂方式;(先要搞明白要用的这个类是怎么实例化的)
  • 被配置的Bean是否需要注入必要属性。
实例:(这些实例不用记,理解就好,实际开发一般不这么用)
1)配置 Druid 数据源交由Spring管理(无参构造,需要注入四个)
  1. 导入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>
  1. 配置 DruidDataSource
<!--配置 DruidDataSource数据源(无参构造)-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
	<!--配置必要属性(必须注入四个属性)-->
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc://localhost:3306/mybatis"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
</bean>
2)配置Connection交由Spring管理

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

<bean 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>

实际开发中Connection被DataSource封装了,不这么配,这里只是应用一下

3)配置日期对象交由Spring管理

产生一个指定日期格式的对象,原始代码按如下:

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>

实际开发中没有必要用spring帮我们产生日期对象,只是在这演示一下实例工厂方式

4)配置MyBatis的SqlSessionFactory交由Spring管理
  1. 导入MyBatis的相关坐标:
<!--mybatis框架-->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.49</version>
</dependency>
  1. MyBatis原始获得SqlSessionFactory的方式:
//加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream in = Resources.getResourceAsStream(“mybatis-conifg.xml”);
//创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(in);
  1. 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="sqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
<!--实例工厂方式产生Bean实例-->
<bean id="sqlSessionFactory" factory-bean="sqlSessionFactoryBuilder" factory-method="build">
	<constructor-arg name="inputStream" ref="inputStream"/>
</bean>

4.Bean 实例化的基本流程(4-8都是源码讲解)

Bean信息定义对象-BeanDefinition:

<bean id="" class="" name="" lazyinit="" 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();
//..... 省略部分属性和方法
}

Bean 实例化的基本流程:

  1. 加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
  2. 将BeanDefinition存储在一个名为beanDefinitionMap的Map中;
  3. ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
  4. 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map中;
  5. 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
    Spring学习笔记(Ico部分)_第2张图片

5.Spring的后处理器(重点)

Spring的后处理器是Spring对外开发的重要扩展点(框架帮我们弄好了,但是我们要实现一些需求要和已经弄好的不一样,要介入框架,就需要通过扩展点实现),允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。开发中使用AOP增强,就是基于这两个扩展点实现的。
Spring主要有两种后处理器:

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

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

Spring学习笔记(Ico部分)_第3张图片

1)BeanFactoryPostProcessor

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

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
		System.out.println("MyBeanFactoryPostProcessor执行了...");
	}
}
//配置BeanFactoryPostProcessor
<bean class="com.itheima.processor.MyBeanFactoryPostProcessor"/>

postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的BeanDefinition进行操作了(但是为了安全,我们是不能拿到整个Map的,我们可以通过BeanName操作) 。

举例:

  1. 对UserDaoImpl的BeanDefinition进行修改操作
/*快速入门*/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition userDaoBD = beanFactory.getBeanDefinition(“userDao”);//获得UserDao定义对象
		userDaoBD.setBeanClassName("com.itheima.dao.impl.UserDaoImpl2"); //修改class
		//userDaoBD.setInitMethodName(methodName); //修改初始化方法
		//userDaoBD.setLazyInit(true); //修改是否懒加载
		//... 省略其他的设置方式 ...
	}
}
  1. 对BeanDefiition进行注册操作
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
		//强转成子类DefaultListableBeanFactory
		if(configurableListableBeanFactory instanceof DefaultListableBeanFactory){
			DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
			//postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,DefaultListableBeanFactory里面才有注册方法。
			BeanDefinition beanDefinition = new RootBeanDefinition();
			beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
			//进行注册操作
			beanFactory.registerBeanDefinition("userDao2",beanDefinition);
		}
	}
}

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

public class MyBeanFactoryPostProcessor2 implements BeanDefinitionRegistryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
		BeanDefinition beanDefinition = new RootBeanDefinition();
		beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
		beanDefinitionRegistry.registerBeanDefinition("userDao2",beanDefinition);
	}
}

执行顺序:
BeanDefinitionRegistryPostProcessor 的postProcessBeanDefinitionRegistry方法
BeanDefinitionRegistryPostProcessor 的postProcessBeanFactory方法
BeanFactoryPostProcessor 的postProcessBeanFactory方法

2)BeanFactoryProcessor

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

/*快速入门*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
返回值:当前Bean实例对象 */
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("BeanPostProcessor的before方法...");
		return bean;
	}
/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
返回值:当前Bean实例对象 */
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("BeanPostProcessor的after方法...");
		return bean;
	}
}
//配置MyBeanPostProcessor
<bean class="com.itheima.processors.MyBeanPostProcessor"></bean>

执行顺序:
Bean创建
BeanPostProcessor的before方法…
Bean属性填充…
Bean初始化init方法执行…
BeanPostProcessor的after方法…

举例:

对Bean方法进行执行时间日志增强要求如下:

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

分析:

  • 对方法进行增强主要就是代理设计模式和包装设计模式;
  • 由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
  • 在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean
/*编写BeanPostProcessor,增强逻辑编写在 after方法中,这里只写被覆盖的方法代码*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	//对Bean进行动态代理,返回的是Proxy代理对象,进而存储到单例池中
	Object proxyBean = 	Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(Object proxy, Method method, Object[] 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 proxyBean;
}

6.Spring Bean的生命周期

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

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

  • Bean的初始化阶段该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的。 Bean创建之后还仅仅是个"半成品",还需要:

    • Bean实例的属性填充
    • Aware接口属性注入
    • BeanPostProcessor的before()方法回调
    • InitializingBean接口的初始化方法回调
    • 自定义初始化方法init回调
    • BeanPostProcessor的after()方法回调。
  • Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。

Bean的初始化阶段

最重要的阶段,后四点前面已经讲过,所以在这讲解前两点:

  1. Bean实例属性填充
    当前Bean实体的注入信息存储在:ApplicationContext->beanFactory->beanDefinitionMap ->propertyValues。在创建对象时Spring会查看有无需要注入的信息,进行操作。

Spring在进行属性注入时,会分为如下几种情况:

  • 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;比如:UserService里注入String
  • 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;比如:UserService里注入UserDao
  • 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖问题,下面会详细阐述解决方案。比如:UserService里注入UserDao,UserDao里注入UserService,(开发几乎不会遇到这种需求,但是是面试的高频考题,记住原理就行,不用看源码)

问题:都是半成品谁都没法向下执行。
Spring学习笔记(Ico部分)_第4张图片
解决方案:Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
1、最终存储单例Bean 成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map singletonObjects = new ConcurrentHashMap(256);
2、早期Bean单例池,缓存 半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map earlySingletonObjects = new ConcurrentHashMap(16);
3、单例Bean的工厂池,缓存半成品对象对象未被引用,使用时在通过工厂创建Bean,称为"三级缓存"
Map> singletonFactories = new HashMap(16);

面试题:如何解决循环引用问题?
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下

  1. UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
  2. UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
  3. UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
  4. UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
  5. UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
  6. UserService 注入UserDao;
  7. UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
  1. Aware接口属性注入
    Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。(就是说我们自己写的普通类里面想用人家框架里的对象,我们只要让我们自己的实现这些接口,框架就把对应的对象注入给我们的对象了)

常用的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对象

7.Spring IoC 整体流程总结

Spring学习笔记(Ico部分)_第5张图片

8.Spring xml方式整合第三方框架

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

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

举例:
1. Spring整合MyBatis(不需要自定义名空间)
之前已经在Spring中简单的配置了SqlSessionFactory(使用静态工厂等方式的内容),但是这不是正规的整合方式,MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。

  • Spring整合MyBatis的步骤如下:
    (1)导入MyBatis整合Spring的相关坐标;(spring-jdbc;mybatis-spring)
    (2)编写Mapper和Mapper.xml;
    (3)配置SqlSessionFactoryBean和MapperScannerConfigurer;
    (4)编写测试代码
    整合前后的区别:以前需要先配置UserMapper,再配置UserMapper注入UserService,整合后只需要配置UserMapper注入UserService,UserMapper会被包扫描自动扫描配置

  • Spring整合MyBatis的原理剖析:
    整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
    (1)SqlSessionFactoryBean: 需要进行配置,作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
    (2)MapperScannerConfigurer: 需要进行配置,作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
    (3)MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
    (4)ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。PS:autowireMode取值:1是根据名称自动装配,2是根据类型自动装配

ApplicationContext.xml配置SqlSessionFactoryBean和MapperScannerConfigurer:

<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<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包扫描,作用:扫描指定的包,产生Mapper对象存到spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.itheima.dao"></property>
</bean>

编写Mapper和Mapper.xml

public interface UserMapper {
List<User> findAll();
}
<?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.dao.UserMapper">
<select id="findAll" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>

2.spring整合context(需要引入第三方框架命名空间)
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文件,将键值对存储在Spring容器中

jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

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

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">

	<context:property-placeholder location="classpath:jdbc.properties" />
	
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
<beans>

外部命名空间标签的执行流程,如下:

  • 将自定义标签的约束 与 物理约束文件与网络约束名称的约束 以键值对形式存储到一个spring.schemas文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
  • 将自定义命名空间的名称 与 自定义命名空间的处理器映射关系 以键值对形式存在到一个叫spring.handlers文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
  • 准备好NamespaceHandler,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NamespaceHandler的parse方法时在分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)。

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

步骤分析:

  1. 确定命名空间名称、schema虚拟路径、标签名称;
  2. 编写schema约束文件haohao-annotation.xsd
  3. 在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
  4. 编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
  5. 编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
  6. 编写HaohaoBeanPostProcessor
    -------------------以上五步是框架开发者写的,以下是框架使用者写的------------------------
  7. 在applicationContext.xml配置文件中引入命名空间
  8. 在applicationContext.xml配置文件中使用自定义的标签

*编写schema约束文件haohao-annotation.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.itheima.com/haohao"
		xmlns:xsd="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.itheima.com/haohao">
	<xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>

*在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
Spring学习笔记(Ico部分)_第6张图片
*编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser

public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		this.registerBeanDefinitionParser("annotation-driven",new HaohaoBeanDefinitionParser());
	}
}

*编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注HaohaoBeanPostProcessor

public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//创建HaohaoBeanPostProcessor的BeanDefinition
		RootBeanDefinition beanDefinition = new RootBeanDefinition();
		beanDefinition.setBeanClass(HaohaoBeanPostProcessor.class);
		//注册HaohaoBeanPostProcessor
		parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor",beanDefinition);
		return beanDefinition;
}}

*编写HaohaoBeanPostProcessor

public class HaohaoBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("自动注入HaohaoBeanPostProcessor成功");
		return bean;
	}
}

五.基于注解的Spring应用

1.Bean基本注解开发

核心:用注解替代标签,用核心配置类替代xml文件
替代< bean>标签:

xml配置 注解 描述
< bean id=“” class=“”> @Component 被该注解标识的类,会在指定扫描范围内被Spring加载并实例化。注:注解的value属性指定当前Bean实例的beanName,也可以省略不写,不写的情况下为当前类名首字母小写
< bean scope=“”> @Scope 在类上或使用了@Bean标注的方法上,标注Bean的作用范围,取值为singleton或prototype
< bean lazy-init=“”> @Lazy 在类上或使用了@Bean标注的方法上,标注Bean是否延迟加载,取值为true和false
< bean init-method=“”> @PostConstruct 在方法上使用,标注Bean的实例化后执行的方法
< bean destroy-method=“”> @PreDestroy 在方法上使用,标注Bean的销毁前执行方法

由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解:

@Component衍生注解 描述
@Repository 在Dao层类上使用
@Service 在Service层类上使用
@Controller 在Web层类上使用

如果单单只使用注解是没用的,需要告诉Spring去哪找这些Bean,要配置组件扫描路径:

<!-- 告知Spring框架去itheima包及其子包下去扫描使用了注解的类 -->
<context:component-scan base-package="com.itheima"/>

扫描到了注解,知道了那些Bean需要实例化,那具体将Bean放入容器的事情是谁干的?通过Bean后处理器完成

2.Bean依赖注入注解开发

替代< property> 标签:

属性注入注解 描述
@Value 使用在字段或方法上,用于注入普通数据
@Autowired 使用在字段或方法上,用于根据类型(byType)注入引用数据
@Qualifier 使用在字段或方法上,结合@Autowired,根据名称注入
@Resource 使用在字段或方法上,既可以根据类型注入,也可以根据名称注入,无参就是根据类型注入,有参数就是根据名称注入 PS:@Resource注解存在与 javax.annotation 包中,Spring对其进行了解析

举例:

  1. 通过@Value 直接注入普通属性:意义不大,普通写死的和直接赋值是一样的
@Value("haohao")//也可以用于set方法
private String username;
  1. 通过@Value 注入properties文件中的属性
@Value("${jdbc.username}")
private String username;
<context:property-placeholder location="classpath:jdbc.properties"/>

ps:注解和xml不同的是无须提供set方法

  1. @Autowired注解,根据类型进行注入
@Repository("userDao")//匹配
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{}

当容器中同一类型的Bean实例有多个时,会尝试自动根据名字进行匹配,名字与被注入Bean名称不匹配时会报错。 上述代码中的名字是userDao,下面两个相同类型的Bean中第一个的beanName与之匹配,所以选择第一个注入,不会报错,如果没有匹配的则会报错。

@Autowired
private UserDao userDao;//UserDaoImpl 

扩展:@Autowired不仅可以用在set方法上,还可以加在一般方法上,根据类型自动匹配参数

@Autowired//先匹配类型,有多个就匹配名称
public void xxx(UserDao userDao){//UserDaoImpl 
	System.out.println(userDao);
}
@Autowired//匹配类型,因为参数是集合,所以只要类型匹配,都放进去
public void yyy(List<UserDao > userDaoList){//UserDaoImpl ,UserDaoImpl2 
	System.out.println(userDaoList);
}
  1. @Qualifier配合@Autowired可以完成根据名称注入Bean实例,使用@Qualifier指定名称
@Autowired
@Qualifier("userDao2")
private UserDao userDao;

3.非自定义Bean注解开发

非自定义Bean不能像自定义Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化,使用@Bean标注方法即可,@Bean的属性为beanName,如不指定beanName,为当前工厂方法名称
PS:工厂方法所在类必须要被Spring管理 (只用@Bean是不管用的),可以在类上加@Component,或者在配置类上用@import引用该类。

将方法返回值Bean实例以@Bean注解指定的名称存储到Spring容器中:

@Bean("dataSource")
public DataSource dataSource(){
	DruidDataSource dataSource = new DruidDataSource();
	dataSource.setDriverClassName("com.mysql.jdbc.Driver");
	dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
	dataSource.setUsername("root");
	dataSource.setPassword("root");
	return dataSource;
}

如果@Bean工厂方法需要参数的话,则有如下几种注入方式:
1.使用@Autowired 根据类型自动进行Bean的匹配,@Autowired可以省略 ;
2.使用@Qualifier 根据名称进行Bean的匹配;(注入属性需要和@Autowired一起使用,注入参数无须加@Autowired)
3.使用@Value 根据名称进行普通数据类型匹配。

@Bean
@Autowired //根据类型匹配参数
public Object objectDemo01(UserDao userDao){
	System.out.println(userDao);
	return new Object();
}
@Bean
public Object objectDemo02(@Qualifier("userDao") UserDao userDao,@Value("${jdbc.username}") String username){
	System.out.println(userDao);
	System.out.println(username);
	return new Object();
}

4.Bean配置类的注解开发

上面讲的注解作用都是替代了< bean>标签,但是像< import>、< context:componentScan> 等非< bean> 标签怎样去使用注解替代呢?
定义一个配置类替代原有的xml配置文件,< bean>标签以外的标签,一般都是在配置类上使用注解完成的

配置类的注解 作用
@Configuration 标识该类是一个配置类,替代xml文件+@Component作用
@ComponentScan 组件扫描配置,替代< context:component-scan base-package=“”/>
PS:指定一个或多个包名:扫描指定包及其子包下使用注解的类;
         不配置包名:扫描当前@componentScan注解配置类所在包及其子包下的类
@PropertySource 加载外部properties资源配置,替代< context:property-placeholder location=“”/>
@Import 加载其他配置类,替代< import resource=“classpath:beans.xml”/>
@MapperScan Mapper包扫描,替代< bean class=“org.mybatis.spring.mapper.MapperScannerConfigurer”>

原来的配置文件:

<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 组件扫描 -->
<context:component-scan base-package="com.itheima"/>
<!-- 引入其他xml文件 -->
<import resource="classpath:beans.xml"/>

配置类代替xml:

@Configuration
@ComponentScan
@PropertySource("classpath:jdbc.properties")
@Import(OtherConfig.class)
public class ApplicationContextConfig {}

5.Spring 配置其他注解

注解 作用
@Primary 相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时,会选用优先级更高的
@Profile 替代< beans profile=“test”>@Profile 标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才能被注册到Spring容器里。不指定环境的Bean,任何环境下都能注册到Spring容器里

可以使用以下两种方式指定被激活的环境:(前面学过)

  • 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
  • 使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,“test”);

6.Spring注解的解析原理

只要将Bean对应的BeanDefinition注册到beanDefinitionMap中,就可以经历整个SpringBean的生命周期,最终实例化进入单例池中xml是直接在spring容器创建时就扫描,将Bean注册到被beanDefinitionMap中去。
Spring学习笔记(Ico部分)_第7张图片

7.Spring注解方式整合第三方框架

这个部分着重讲解整合第三方框架时xml到注解方式的过渡,具体的整合原理在前面xml整合第三方框架中已经讲解。

  • 整合不需要自定义名空间:无非就是将xml标签替换为注解,将xml配置文件替换为配置类而已
  • 整合需要引入第三方框架命名空间:Spring与MyBatis注解方式整合有个重要的技术点就是@Import,第三方框架与Spring整合xml方式很多是凭借自定义标签完成的,而第三方框架与Spring整合注解方式很多是靠@Import注解完成的。
    @Import可以导入如下三种类:
    • 普通的配置类
    • 实现ImportSelector接口的类
    • 实现ImportBeanDefinitionRegistrar接口的类
      PS:开发人员用第一种比较多,如果后期需要自定义框架,第二种第三种用的比较多。
  1. 整合MyBatis

原有xml方式整合配置如下:

<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
	<property name="username" value="root"></property>
	<property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper包扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.itheima.dao"></property>
</bean>

注解整合:

@Configuration//代替配置文件
@ComponentScan("com.itheima")//代替包扫描
@MapperScan("com.itheima.mapper")//代替Mapper包扫描
public class ApplicationContextConfig {
@Bean//代替配置数据源
public DataSource dataSource(
		@Value("${jdbc.driver}") String dirver,
		@Value("${jdbc.url}") String url,
		@Value("${jdbc.username}") String username,
		@Value("${jdbc.password}") String password,
){
	DruidDataSource dataSource = new DruidDataSource();
	dataSource.setDriverClassName(dirver);
	dataSource.setUrl(url);
	dataSource.setUsername(username);
	dataSource.setPassword(password);
	return dataSource;
}
@Bean//代替SqlSessionFactoryBean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
	SqlSessionFactoryBean sqlSessionFactoryBean = new 	SqlSessionFactoryBean();
	sqlSessionFactoryBean.setDataSource(dataSource);
	return sqlSessionFactoryBean;
}}
  1. @Import导入实现了ImportSelector接口的类

开发者使用第三方框架:

@Import导入实现了ImportSelector接口的类
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}

框架开发者开发时的代码:

public class MyImportSelector implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
	//参数annotationMetadata叫做注解媒体数组,该对象内部封装的是当前使用了@Import注解的类上的其他注解的元信息,也就是通过annotationMetadata我可以获取到@ComponentScan里面的值是com.itheima
	//返回要进行注册的Bean的全限定名数组
		return new String[]{User2.class.getName()};
	}
}

MyImportSelector 干了什么?将User2注册到容器中,即不通过配置的方式,用代码的方式将类注册到容器中。
有啥用?框架开发者肯定没法操作使用框架人的代码,但是需要把框架里的Bean交给spring容器去管理,就可以在开发框架的时候写好代码,后面使用者只需要加上@Import({MyImportSelector.class})注解,就可以把里面我想要注册的Bean注册到spring容器中去。

  1. @Import导入实现ImportBeanDefinitionRegistrar接口的类
    实现了该接口的类的registerBeanDefinitions方法会被自动调用,在该方法内可以注册BeanDefinition(和@Import导入实现了ImportSelector接口的类作用差不多)

开发者使用第三方框架:

@Import导入实现了ImportSelector接口的类
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportBeanDefinitionRegistrar .class})
public class ApplicationContextConfig {
}

框架开发者开发时的代码:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinition
		BeanDefinition beanDefinition = new RootBeanDefinition();
		beanDefinition.setBeanClassName("com.itheima.pojo.User2");
		registry.registerBeanDefinition("user2",beanDefinition);
	}
}

扩展:可以像@Mapper一样再封装一层,隐藏底层代码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({MyImportBeanDefinitionRegistrar .class})
public @interface MyMapperScan(){
}

开发者在开发时就不需要写@Import({MyImportBeanDefinitionRegistrar .class}),直接写@MyMapperScan即可

你可能感兴趣的:(springboot,spring,学习,java)