Spring 是个轻量级开源框架,主要以 IoC(Inverse Of Control: 控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)为内核的容器框架。作为业界使用框架中的基础框架,我一直只是简单应用而未曾有过深入挖掘,这次有些时间就做一个学习总结, 对 Spring 内部原理进行剖析并分享出来~
分享内容大致分为:IoC 的基本使用,IoC 的配置技巧,IoC 基本原理剖析
一、IoC 的基本使用
Spring
主要是以IoC
和AOP
为内核的容器框架,那么IoC
是干什么用的,AOP
又是干什么用的呢?
在讲Spring
的IoC
和AOP
之前我们先介绍下“耦合”这个概念,所谓程序的耦合就是程序之间的依赖关系,例如类与类之间的依赖、方法与方法之间的依赖等。而耦合严重会使开发效率低下、后续维护困难,造成部分功能变更影响到所有功能的改动,简直是一改毁所有,所以我们需要解耦来降低程序之间的依赖关系。
那么哪种情况算是耦合?我们又如何解耦呢?首先我们应避免使用new
来创建对象,推荐使用反射来创建对象;其次在代码中避免将一些参数配置写死,而推荐使用读取配置文件的方式来加载参数。这里我们可以通过工厂模式来实例化对象,搭配单例模式还可避免消耗过多的资源,这样一波骚操作后我们降低了程序之间的依赖关系达到了解耦的目的,而让我们为每个对象去编写这样太麻烦了,所以Spring
的IoC
控制反转就闪亮登场了。
IoC
控制反转的作用正是削减程序间的耦合,解除代码中的依赖关系。它可以理解为是一个工厂类,内部有一个Map
存放着对象,使用时直接获取即可。为什么管它叫控制反转呢?因为原来我们获取对象时都是采用new
的方式,是主动的;现在获取对象时跟工厂要,由工厂为我们查找或创建对象,是被动的;这种被动接收的方式获取对象的思想就叫做控制反转。下面我们来看看IoC
是如何使用的~
1. 新建工程并导入jar包
为了简单方便,我们通过Maven
来管理jar
包。spring-context
中包含了spring-core
、spring-beans
、spring-expression
和spring-aop
,因此仅配置spring-context
即可
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
2. 编写相关接口及实现
持久层接口。随便写个save
方法作为测试方法
/**
* 持久层接口
*
* @author Wenx
* @date 2021/3/27
*/
public interface ISpringDao {
/**
* 保存数据
*/
void save();
}
持久层接口实现类。通常情况下此实现类将省略不会编写,一般会使用MyBatis
的XML
映射文件配置SQL
,或者Hibernate
自动生成SQL
,这里通过打印结果来模拟访问数据库
/**
* 持久层接口实现
*
* @author Wenx
* @date 2021/3/27
*/
public class SpringDaoImpl implements ISpringDao {
public SpringDaoImpl() {
System.out.println("SpringDaoImpl对象实例化了……");
}
public void save() {
System.out.println("保存成功!");
}
}
业务层接口。为调用持久层方法写个同名方法(最烦起名字……)
/**
* 业务层接口
*
* @author Wenx
* @date 2021/3/27
*/
public interface ISpringService {
/**
* 保存数据
*/
void save();
}
业务层接口实现类。我们应避免使用new
来创建对象,这里使用Spring
的构造函数注入来为成员变量赋值,还可以使用set
方法注入、@autowired
注解注入等方式,其他方式在后面会有相应介绍
/**
* 业务层接口实现
*
* @author Wenx
* @date 2021/3/27
*/
public class SpringServiceImpl implements ISpringService {
private ISpringDao springDao;
/**
* 构造函数注入(依赖注入)
*
* @param springDao 持久层接口实现
*/
public SpringServiceImpl(ISpringDao springDao) {
this.springDao = springDao;
System.out.println("SpringServiceImpl对象实例化了……");
}
public void save() {
springDao.save();
}
}
3. 配置文件的相关设置
配置文件SpringConfig.xml
(自定义名称)。用来设置Bean
所在的全限定类名和相应变量的依赖注入,DI(Dependency Injection: 依赖注入)它是Spring
框架核心IoC
的具体实现,它具有多种注入方式我们在后面会进行相应介绍
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 无成员变量赋值可使用默认无参构造函数实例化Bean,仅需配置id和class属性(bean所在的全限定类名) -->
<bean id="springDao" class="com.wenx.demo.dao.impl.SpringDaoImpl"></bean>
<!-- 有成员变量赋值还需配置依赖注入,通过spring框架来赋值 -->
<bean id="springService" class="com.wenx.demo.service.impl.SpringServiceImpl">
<!-- 构造函数注入 -->
<constructor-arg name="springDao" ref="springDao"></constructor-arg>
</bean>
</beans>
4. 编写IoC测试代码
我们应避免使用new
来创建对象,所以通过Spring
来管理Bean
对象。使用时通过IoC
容器来获取Bean
对象,这样做的目的是为了削减程序间的耦合,解除代码中的依赖关系
/**
* @author Wenx
* @date 2021/3/27
*/
public class SpringDemo {
public static void main(String[] args) {
// 1.获取IoC核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("SpringConfig.xml");
// 2.获取bean对象
ISpringService ss = ac.getBean("springService", ISpringService.class);
// 3.调用对象方法
ss.save();
System.out.println(ss);
}
}
二、IoC 的配置技巧
IoC
有两种配置方式,分别是基于XML
配置和基于注解配置。XML
配置文件像个目录让Bean
之间的关系一目了然但配置稍繁琐,注解方式配置较简便但Bean
之间的关系展现不清晰,使用XML
配置还是注解配置需要根据实际情况选择
1. 基于XML的Bean配置
Spring
通过XML
配置文件来描述Bean
与Bean
之间的依赖关系,了解XML
配置中的标签及属性有利于深入理解IoC
运行方式,有助于熟练掌握IoC
使用技巧
1). 导入XML配置约束
XML
配置约束可以从官网找到,然后直接在XML
文件中导入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">
</beans>
2). 实例化Bean的三种方式
创建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:指定bean的id,用于从容器中获取对象
class:指定类的全限定类名,用于反射创建对象,默认调用无参构造函数
scope:指定对象的作用范围。singleton:单例(默认);prototype:多例
lazy-init:是否懒加载(延迟加载),默认立即加载
init-method:指定类中的初始化方法
destroy-method:指定类中销毁方法
-->
<bean id="beanService"
class="com.wenx.demo.service.impl.BeanServiceImpl"
scope="singleton"
lazy-init="true"
init-method="init"
destroy-method="destroy">
</bean>
<!-- 实例化Bean的三种方式 -->
<!-- 1.使用默认无参构造函数。
bean标签仅配置id和class属性,但未配置其他属性和标签时,使用默认构造函数创建bean对象。
如果类中没有默认构造函数,则对象创建失败。
-->
<bean id="bean1Service" class="com.wenx.demo.service.impl.BeanServiceImpl"></bean>
<!-- 2.使用静态工厂的方法创建对象(使用某个类中的静态方法创建对象,并交给spring容器管理)
id:指定bean的id,用于从容器中获取对象
class:指定静态工厂的全限定类名
factory-method:指定生产对象的静态方法
-->
<bean id="bean2Service" class="com.wenx.demo.factory.StaticFactory" factory-method="getBeanService"></bean>
<!-- 3.使用实例工厂的方法创建对象(使用某个类中的方法创建对象,并交给spring容器管理)
先创建工厂交给spring容器管理,然后调用工厂里面的方法来创建bean对象
id:指定bean的id,用于从容器中获取对象
class:指定静态工厂的全限定类名
factory-bean:指定实例工厂bean的id
factory-method:指定实例工厂中创建对象的方法
-->
<bean id="instanceFactory" class="com.wenx.demo.factory.InstanceFactory"></bean>
<bean id="bean3Service" factory-bean="instanceFactory" factory-method="getBeanService"></bean>
</beans>
业务层接口。使用之前的测试接口即可
/**
* 业务层接口
*
* @author Wenx
* @date 2021/3/27
*/
public interface ISpringService {
/**
* 保存数据
*/
void save();
}
业务层接口实现类。我们为不同生命周期编写了触发方法
/**
* @author Wenx
* @date 2021/3/31
*/
public class BeanServiceImpl implements ISpringService {
public BeanServiceImpl() {
System.out.println("BeanServiceImpl对象实例化了……");
}
public void save() {
System.out.println("BeanServiceImpl的save方法执行了……");
}
public void init() {
System.out.println("BeanServiceImpl对象初始化了……");
}
public void destroy() {
System.out.println("BeanServiceImpl对象销毁了……");
}
}
静态工厂类。通过使用静态工厂的方式创建对象
/**
* 模拟一个静态工厂,创建业务层实现类(部分类可能存在jar包中无默认构造函数)
*
* @author Wenx
* @date 2021/3/31
*/
public class StaticFactory {
public static ISpringService getBeanService() {
return new BeanServiceImpl();
}
}
实例工厂类。通过使用实例工厂的方式创建对象
/**
* 模拟一个实例工厂,创建业务层实现类(部分类可能存在jar包中无默认构造函数)
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*
* @author Wenx
* @date 2021/3/31
*/
public class InstanceFactory {
public ISpringService getBeanService() {
return new BeanServiceImpl();
}
}
测试代码
/**
* @author Wenx
* @date 2021/3/31
*/
public class BeanDemo {
public static void main(String[] args) {
// ApplicationContext没有close方法,为触发对象的销毁方法我们使用ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("BeanConfig.xml");
// bean配置中lazy-init置为true开启延时加载,此处可断点测试
ISpringService ss1 = (ISpringService) ac.getBean("beanService");
// 调用对象方法
ss1.save();
System.out.println(ss1);
// 再次获取bean检测是否单例
ISpringService ss2 = ac.getBean("beanService", ISpringService.class);
System.out.println(ss2);
// 单例对象随容器的销毁而消亡,多例对象由Java的垃圾回收器回收
ac.close();
}
}
3). spring的依赖注入
DI(Dependency Injection: 依赖注入)它是Spring
框架核心IoC
的具体实现,它有三种注入方式:构造函数注入、set
方法注入、注解方式注入(后面会有相应介绍)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
<!-- 依赖注入的三种方式 -->
<!-- 1.构造函数注入,使用类中的构造函数给成员变量赋值,通过配置的方式让spring框架来为我们注入。
标签:constructor-arg
其属性:
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称
value:指定参数的值,可以是基本数据类型和String类型
ref:指定参数的值,可以是其他配置的bean等引用类型
-->
<bean id="di1Service" class="com.wenx.demo.service.impl.DI1ServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
<!-- 2.set方法注入,使用set方法给类中的成员变量赋值,通过配置的方式让spring框架来为我们注入。
标签:property
其属性:
name:指定类中set方法后面的部分
value:指定参数的值,可以是基本数据类型和String类型
ref:指定参数的值,可以是其他配置的bean等引用类型
-->
<bean id="di21Service" class="com.wenx.demo.service.impl.DI2ServiceImpl">
<property name="name" value="张三"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 使用名称空间注入数据,本质还是set方法注入 -->
<bean id="di22Service"
class="com.wenx.demo.service.impl.DI2ServiceImpl"
p:name="张三" p:age="18" p:birthday-ref="now">
</bean>
<!-- 注入集合属性,本质还是set方法注入 -->
<bean id="di3Service" class="com.wenx.demo.service.impl.DI3ServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="strings">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入list集合数据 -->
<property name="lists">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入set集合数据 -->
<property name="sets">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入Map数据 -->
<property name="maps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
<!-- 注入properties数据 -->
<property name="props">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
</bean>
<!-- 3.使用注解方式注入,后面会有相应介绍。 -->
</beans>
业务层接口。使用之前的测试接口即可
/**
* 业务层接口
*
* @author Wenx
* @date 2021/3/27
*/
public interface ISpringService {
/**
* 保存数据
*/
void save();
}
构造函数注入。通过编写有参构造函数的方式进行注入
/**
* @author Wenx
* @date 2021/3/31
*/
public class DI1ServiceImpl implements ISpringService {
private String name;
private Integer age;
private Date birthday;
/**
* 构造函数注入(依赖注入)
*
* @param name 姓名
* @param age 年龄
* @param birthday 生日
*/
public DI1ServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void save() {
System.out.println("DI1ServiceImpl的save方法执行了……\r\n" +
"姓名:" + name + ",年龄:" + age + ",生日:" + birthday);
}
}
set
方法注入。通过编写set
方法的方式进行注入
/**
* @author Wenx
* @date 2021/3/31
*/
public class DI2ServiceImpl implements ISpringService {
private String name;
private Integer age;
private Date birthday;
/**
* set方法注入(依赖注入)
*
* @param name 姓名
*/
public void setName(String name) {
this.name = name;
}
/**
* set方法注入(依赖注入)
*
* @param age 年龄
*/
public void setAge(Integer age) {
this.age = age;
}
/**
* set方法注入(依赖注入)
*
* @param birthday 生日
*/
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void save() {
System.out.println("DI2ServiceImpl的save方法执行了……\r\n" +
"姓名:" + name + ",年龄:" + age + ",生日:" + birthday);
}
}
复杂类型注入,也称集合类型注入。本质上还是set
方法注入,通过编写set
方法的方式进行注入
/**
* @author Wenx
* @date 2021/3/31
*/
public class DI3ServiceImpl implements ISpringService {
private String[] strings;
private List<String> lists;
private Set<String> sets;
private Map<String, String> maps;
private Properties props;
public void setStrings(String[] strings) {
this.strings = strings;
}
public void setLists(List<String> lists) {
this.lists = lists;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setProps(Properties props) {
this.props = props;
}
public void save() {
System.out.println("DI3ServiceImpl的save方法执行了……");
System.out.println("strings:" + Arrays.toString(strings));
System.out.println("lists:" + lists);
System.out.println("sets:" + sets);
System.out.println("maps:" + maps);
System.out.println("props:" + props);
}
}
依赖注入测试代码
/**
* @author Wenx
* @date 2021/3/31
*/
public class DIDemo {
public static void main(String[] args) {
// 1.获取IoC核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("DIConfig.xml");
// 2.获取bean对象
ISpringService ss = ac.getBean("di1Service", ISpringService.class);
// 3.调用对象方法
ss.save();
System.out.println(ss);
}
}
2. 基于注解的IoC配置
基于注解配置是通过使用注解的方式来达到使用XML
文件配置相同的效果,相比基于XML
配置来说基于注解的IoC
配置更为简便,所以对于厌烦了通过XML
文件进行配置的同学来讲基于注解配置也是个不错的选择
1). 配置Spring扫描包的范围
基于注解配置是通过Spring
自动扫描包中带注解的类来实例化Bean
对象,虽然不需要向基于XML
配置那样手动配置Bean
,但还是要在XML
文件中指定下Spring
扫描包的范围。那么有同学就说了这不还是使用XML
文件了吗?全部用注解进行配置脱离XML
文件的方式后面会有相应介绍
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!-- 指定Spring在创建Bean时要扫描的包 -->
<context:component-scan base-package="com.wenx.demo"></context:component-scan>
</beans>
2). 用于创建对象的注解
@Component
:用于把当前类对象交给Spring
来管理,相当于<bean id="" class="">
@Component
注解的value
属性用于指定bean
的id
,不指定时默认就是当前类名,且首字母变小写。
为了明确三层结构Spring
框架还提供了@Controller
、@Service
和@Repository
这三个注解,他们与@Component
的使用方法及作用完全一样只是名称各有不同,主要用来区分表现层、业务层和持久层。
/**
* 表现层
*
* @author Wenx
* @date 2021/4/2
*/
@Controller
public class AnnotationController {
public void save() {
System.out.println("AnnotationController的save方法执行了……");
}
}
/**
* 业务层
*
* @author Wenx
* @date 2021/4/2
*/
@Service("annotationService")
public class AnnotationServiceImpl implements ISpringService {
public void save() {
System.out.println("AnnotationServiceImpl的save方法执行了……");
}
}
/**
* 持久层
*
* @author Wenx
* @date 2021/4/2
*/
@Repository("annotationDao")
public class AnnotationDaoImpl implements ISpringDao {
public void save() {
System.out.println("AnnotationDaoImpl的save方法执行了……");
}
}
3). 用于注入数据的注解
@Autowired
:用于按照变量类型匹配bean
对象,若匹配结果唯一则注入成功,否则注入失败。相当于<property name="" ref="">
或<property name="" value="">
,但不需要编写set
方法。
@Qualifier
:用于在按照变量类型基础上再按照变量名称匹配bean
对象并自动注入,@Qualifier
注解的value
属性用于指定bean
的id
。
@Resource
:用于直接指定bean
的id
注入,其属性为的name
属性。
@Value
:用于注入基本数据类型和String
类型,前三个注解只能注入bean
对象,而集合类型的注入只能使用XML
配置。@Value
注解的value
属性用于指定数据的值,它还可以使用SpEL表达式(写法:${表达式})
/**
* 业务层
*
* @author Wenx
* @date 2021/4/2
*/
@Service("annotationService")
public class AnnotationServiceImpl implements ISpringService {
@Autowired
private ISpringDao annotationDao1;
@Autowired
@Qualifier("annotationDao")
private ISpringDao annotationDao2;
@Resource(name = "annotationDao")
private ISpringDao annotationDao3;
@Value("root")
//@Value("${jdbc.username}")
private String username;
public void save() {
System.out.println("AnnotationServiceImpl的save方法执行了……");
annotationDao1.save();
annotationDao2.save();
annotationDao3.save();
System.out.println(username);
}
}
4). 用于改变作用范围的注解
@Scope
:用于指定bean
的作用范围,@Scope
注解的value
属性包含: singleton
单例(默认)、prototype
多例,相当于<bean id="" class="" scope="">
/**
* 业务层
*
* @author Wenx
* @date 2021/4/2
*/
@Service("annotationService")
@Scope("singleton")
public class AnnotationServiceImpl implements ISpringService {
public void save() {
System.out.println("AnnotationServiceImpl的save方法执行了……");
}
}
5). 和生命周期相关的注解
@PostConstruct
:用于指定类中的初始化方法, 相当于<bean id="" class="" init-method="" />
@PreDestroy
:用于指定类中销毁方法, 相当于<bean id="" class="" destroy-method="" />
/**
* 业务层
*
* @author Wenx
* @date 2021/4/2
*/
@Service("annotationService")
public class AnnotationServiceImpl implements ISpringService {
public void save() {
System.out.println("AnnotationServiceImpl的save方法执行了……");
}
@PostConstruct
public void init() {
System.out.println("AnnotationServiceImpl对象初始化了……");
}
@PreDestroy
public void destroy() {
System.out.println("AnnotationServiceImpl对象销毁了……");
}
}
6). 脱离XML文件的注解配置
在上面配置Spring
扫描包的范围时我们还是要在XML
文件中指定,这违背了我们选择注解方式配置的初衷,我既然使用了注解配置就不想使用XML
文件了,那我该怎么弄呢?我们可以使用Spring
的注解配置类。
@Configuration
:用于指定当前类是一个配置类,当配置类作为AnnotationConfigApplicationContext
对象创建的参数时该注解可以不写,获取容器时需要使用AnnotationApplicationContext(有@Configuration注解的类.class)
@ComponentScan
:用于指定Spring
在初始化容器时要扫描的包,@ComponentScan
注解的value
属性用于指定要扫描的包,相当于<context:component-scan base-package="com.xxxx"></context:component-scan>
@PropertySource
:用于加载.properties
文件中的配置,@PropertySource
注解的value
属性用于指定properties
文件位置,若在类路径下需要加上classpath:
前缀。
@Import
:用于导入其他的配置类,@Import
注解的value
属性用于指定其他配置类的字节码,被引入的其他配置类可以不用再写@Configuration
注解。
@Bean
:用于把当前方法的返回值作为bean
对象存入Spring
的IoC
容器中,@Bean
注解的name
属性用于指定bean
的id
,不指定时默认就是当前方法名称。
/**
* @author Wenx
* @date 2021/4/2
*/
public class AnnotationDemo {
public static void main(String[] args) {
// ApplicationContext没有close方法,为触发对象的销毁方法我们使用AnnotationConfigApplicationContext
//ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("AnnotationConfig.xml");
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 获取bean对象
ISpringService ss1 = (ISpringService) ac.getBean("annotationService");
// 调用对象方法
ss1.save();
System.out.println(ss1);
// 再次获取bean检测是否单例
ISpringService ss2 = ac.getBean("annotationService", ISpringService.class);
System.out.println(ss2);
// 单例对象随容器的销毁而消亡,多例对象由Java的垃圾回收器回收
ac.close();
}
}
SpringConfiguration配置类
/**
* Spring配置类
*
* @author Wenx
* @date 2021/4/2
*/
@Configuration
@ComponentScan("com.wenx.demo")
@PropertySource("classpath:SpringConfig.properties")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
JdbcConfig子配置类
/**
* @author Wenx
* @date 2021/4/2
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "createBean")
public String createBean() {
return "AAA";
}
}
SpringConfig.properties配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/study_demo
jdbc.username=root
jdbc.password=1234
7). Spring整合Junit4的单元测试
在Spring
整合Junit4
进行单元测试时还会用到几个注解,这里也简单介绍一下。
@RunWith
:用于指定启动时的运行器,替换成Spring
提供的运行器,在启动时运行Spring
容器从而支持IoC
相关操作。
@ContextConfiguration
:用于指定启动时配置文件,locations
属性:指定xml
文件的位置,在类路径下需加上classpath:
前缀;classes
属性:指定注解类的字节码。
@Before
:指定执行测试之前运行的方法。
@After
:指定执行测试之后运行的方法。
@Test
:指定执行的测试方法。
注:先导入Junit
的jar
包,使用spring 5.x
版本的时候,要求Junit
的jar
必须是4.12及以上
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
单元测试代码
/**
* Spring整合Junit4进行单元测试
*
* @author Wenx
* @date 2021/4/2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AnnotationServiceImplTest {
@Autowired
private ISpringService annotationService;
@Before
public void setUp() throws Exception {
System.out.println("执行测试方法之前运行……");
}
@After
public void tearDown() throws Exception {
System.out.println("执行测试方法之后运行……");
}
@Test
public void save() {
annotationService.save();
}
}
三、IoC 基本原理剖析
在上面我们讲了Spring
中IoC
的基本使用和IoC
的配置技巧,这里我们将对IoC
的基本原理进行剖析。Spring
通过配置文件来描述Bean
与Bean
之间的依赖关系,利用反射实例化Bean
对象,并使用IoC
容器来管理。下面通过对源码断点跟踪进行简要分析,从而了解其内部的基本运行原理
1. IoC核心容器初始化
通常我们使用ApplicationContext
来获取Bean
对象,查看类图发现它是由BeanFactory
接口派生而来,而BeanFactory
就是我们所说的IoC
容器。ApplicationContext
接口的实现排除Abstract
前缀的抽象类,常用的有ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
、AnnotationConfigApplicationContext
这三种。通过对源码的简要分析发现可以将BeanFactory
理解为是一个使用Map
存放Bean
对象的工厂,调用getBean
方法可从Map
中获取Bean
对象
// 1.通过加载类路径下的配置文件,获取IoC核心容器对象
ApplicationContext ac1 = new ClassPathXmlApplicationContext("SpringConfig.xml");
// 2.通过加载磁盘路径下的配置文件(需访问权限),获取IoC核心容器对象
ApplicationContext ac2 = new FileSystemXmlApplicationContext("D:\\SpringConfig.xml");
// 3.通过读取注解配置类,获取IoC核心容器对象
ApplicationContext ac3 = new AnnotationConfigApplicationContext(SpringConfiguration.class);
2. ClassPathXmlApplicationContext实现类
本次主要分析ClassPathXmlApplicationContext
类这种实现方式,按住ctrl
键对着ClassPathXmlApplicationContext
一路连点,发现super(parent)
中没什么有用的,而起作用的是this.refresh()
这行,此方法在AbstractApplicationContext
抽象类中
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
3. AbstractApplicationContext抽象类
在AbstractApplicationContext
类的refresh
方法中对ApplicationContext
容器进行了逐步初始化,在this.finishBeanFactoryInitialization(beanFactory)
方法中发现是利用反射将配置中的全限定类名实例化对象并存放到Map
中
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
在AbstractApplicationContext
类的finishBeanFactoryInitialization
方法中beanFactory.preInstantiateSingletons()
这行内部发现是通过单例模式来实例化对象。Spring
默认是单例模式并立即加载对象,可通过配置文件进行相关设置
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {
beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver((strVal) -> {
return this.getEnvironment().resolvePlaceholders(strVal);
});
}
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
String[] var3 = weaverAwareNames;
int var4 = weaverAwareNames.length;
for(int var5 = 0; var5 < var4; ++var5) {
String weaverAwareName = var3[var5];
this.getBean(weaverAwareName);
}
beanFactory.setTempClassLoader((ClassLoader)null);
beanFactory.freezeConfiguration();
beanFactory.preInstantiateSingletons();
}
4. DefaultListableBeanFactory类
在DefaultListableBeanFactory
类的preInstantiateSingletons
方法中,先通过Bean
定义尝试获取Bean
对象,若获取为null
则实例化对象并存放到Map
中
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Pre-instantiating singletons in " + this);
}
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
Iterator var2 = beanNames.iterator();
while(true) {
String beanName;
Object bean;
// 省略部分代码……
this.getBean(beanName);
// 省略部分代码……
}
}
5. AbstractBeanFactory抽象类
在AbstractBeanFactory
类的doGetBean
方法中,通过this.getSingleton(beanName)
方法从Map
中获取对象,若获取为null
则使用this.createBean(beanName, mbd, args)
创建Bean
对象并存放到Map
中,createBean
内部就是实例化对象这里不再过多分析
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
Object beanInstance;
if (sharedInstance != null && args == null) {
// 省略部分代码……
} else {
// 省略部分代码……
try {
// 省略部分代码……
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// 省略部分代码……
} else
// 省略部分代码……
}
} catch (BeansException var32) {
// 省略部分代码……
} finally {
// 省略部分代码……
}
}
return this.adaptBeanInstance(name, beanInstance, requiredType);
}
6. DefaultSingletonBeanRegistry类
在DefaultSingletonBeanRegistry
类的getSingleton
方法中,singletonFactory.getObject()
这行就是在用工厂方法实例化对象,最后通过addSingleton
方法添加到Map
中
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 省略部分代码……
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
// 省略部分代码……
} catch (BeanCreationException var17) {
// 省略部分代码……
} finally {
// 省略部分代码……
}
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
在DefaultSingletonBeanRegistry
类的addSingleton
方法中,可以看到this.singletonObjects.put(beanName, singletonObject)
这行在进行put
操作,查看singletonObjects
的定义发现它其实就是个Map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
7. 获取Bean对象
回到最外层的测试代码,通过ApplicationContext
的getBean
方法可获取Bean
对象,其内部就是从Map
中获取对象
// 通过加载类路径下的配置文件,获取IoC核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("SpringConfig.xml");
// 获取bean对象
ISpringService ss = ac.getBean("springService", ISpringService.class);
// 调用对象方法
ss.save();
总结: 我们本次由浅入深的讲解了IoC
的基本使用、配置技巧和基本原理剖析,当然还有更深度的原理及使用技巧等着大家在使用中去自行发现。在此感谢大家能看将本篇文章看完,后面还有篇关于Spring
的AOP
的文章还正在酝酿中,敬请期待~