Test 模块:支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试。
Spring 各模块的依赖关系如下图所示:
Spring 的优缺点
?? Spring 框架的优点:
方便解耦,简化开发
通过 Spring 提供的 lOC 容器,可以将对象的创建和依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合,也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP 编程的支持
Spring 提供面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现,例如对程序进行权限拦截、运行监控。
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程。
方便程序的测试
Spring 对 Junit4/Junit5 进行了支持,可以通过注解方便的测试 Spring 程序。
方便集成各种优秀框架
Spring 内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis 等)。
降低 JavaEE API 的使用难度
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)提供了封装,使这些API 应用难度大大降低。
?? Spring 框架的缺点:
Spring 依赖反射,反射影响程序执行性能。
IoC & DI 的理解
?? IoC 是什么?
IoC——Inversion of Control,即“控制反转”,不是一种技术,是一种设计思想。
所谓的“控制”,指的是对对象的控制,即对象的创建、初始化、销毁等生命周期的操作;所谓的“控制反转”,指的是对对象的控制权发生反转,即对象的创建、初始化、销毁等操作交由 IoC 容器管理,而不是传统的在你的对象内部直接控制。
在传统的 Java SE 程序设计中,我们的程序是对象的直接控制者,负责对象的创建、初始化和销毁;而在 Spring 中,对象的创建、初始化和销毁交由 IoC 容器负责,我们的程序无法决定对象生命周期的任何一个阶段,最多是借助于 Spring 的扩展机制做一些微小的动作。
创建对象:原来是 new 一个对象,现在由 IoC 容器负责创建;
初始化对象:比如 A 依赖 B,原来通过构造器或者 setter 方法赋值,现在由 IoC 容器查找及注入依赖对象;
销毁对象:原本直接将对象赋值 null 或者做一些销毁操作,现在由 IoC 容器管理生命周期负责销毁。
也就是说,我们由对象的直接控制者变成了 IoC 容器的被动接受者,对象生命周期的控制权发生了反转。
?? IoC 能做什么?
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们设计出松耦合、更优良的程序。把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。
?? DI 是什么?
DI——Dependency Injection,即“依赖注入”,是 IoC 的一种重要实现,从 IoC 到 DI,就是从理论到了实践。
一个对象的创建往往会涉及到其它对象的创建,比如一个对象 A 的成员变量持有着另一个对象 B 的引用,这就是依赖,A 依赖于 B。IoC 机制既然负责了对象的创建,那么这个依赖关系也就必须由 IoC 容器负责起来,而负责的方式就是 DI——依赖注入。通过将依赖关系写入配置文件,然后在创建有依赖关系的对象时,由 IoC 容器负责注入依赖的对象,如在创建 A 时,检查到有依赖关系,IoC 容器就把 A 依赖的对象 B 创建后注入到 A 中(组装,通过反射机制实现),然后把 A 返回给对象请求者,完成工作。
IoC 和 DI 是同一个概念的不同角度描述,但实际上又是有区别的。IoC 强调的是容器和对象的控制权发生了反转,而 DI 强调的是对象的依赖由容器进行注入,大部分情况下说两者相同也不算错。但是广义上 IoC 是一种软件开发模式,也就是说还可以通过别的方式实现,而 DI 只是其中一种,Spring 选择了 DI 从而使 DI 在 Java 开发中深入人心。
核心接口介绍
BeanFactory 和 ApplicationContext
BeanFactory 和 ApplicationContext 是 Spring 中的两大核心接口,都可以把他们当做 IoC 容器。其中,ApplicationContext 扩展了 BeanFactory 接口,是其子接口。
?? 依赖关系
BeanFactory 是 IoC 容器的最底层接口,提供了完整的 IoC 服务支持。简单来说,BeanFactory 实例就是一个管理 Bean 的工厂,负责初始化各种 Bean,控制 Bean 的生命周期,维护 Bean 之间的依赖关系。
需要注意,@Autowired 注解只能注入其它 bean 类型。该注解首先会自动按照类型注入,当 IoC 容器中有多个 bean 匹配时,会查找 id 与要注入的属性名称相同的 bean。具体使用如下:
?? 编写 Mapper 层
package com.example.mapper.impl;
import org.springframework.stereotype.Repository;
@Repository // 默认将 "userMapper" 作为 bean 的 id
public class UserMapper {
public void say() {
System.out.println("hello world");
}
}
?? 编写 Service 层
package com.example.service.impl;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 默认将 "userService" 作为 bean 的 id
public class UserService {
@Autowired
private UserMapper userMapper; // 会将 UserMapper 类型的 bean 注入
@Override
public void say() {
userMapper.say();
}
}
?? 测试
@Test
public void test() {
// 加载 Spring 配置文件,初始化 IoC 容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)applicationContext.getBean("userService");
userService.say(); // hello
}
@Qualifier 和 @Resource
@Qualifier 注解是在自动按照类型注入的基础之上,指定 bean 的 id 注入,它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。
前面也提到,@Autowire 注解首先会根据类型进行注入,如果 IoC 容器中有多个 bean 匹配时,会查找 id 与要注入的属性名称相同的 bean,而 @Qualifier 的 value 属性就是指定要查找的 bean 的 id。具体使用如下:
package com.example.service.impl;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service // 默认将 "userService" 作为 bean 的 id
public class UserService {
/**
* 首先会自动按照类型注入,即寻找 UserMapper 类型的 bean;
* 如果 IoC 容器中有多个 bean 匹配,会寻找 id 为 "userMapper" 的 bean 注入
*/
@Autowired
@Qualifier("userMapper")
private UserMapper userMapper;
@Override
public void say() {
userMapper.say();
}
}
@Resource 注解相当于@Autowired + @Quali?e的组合,该注解有一个 name 属性用于指定 bean 的 id。具体使用如下:
package com.example.service.impl;
import com.example.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service // 默认将 "userService" 作为 bean 的 id
public class UserService {
@Resource(name = "userMapper")
private UserMapper userMapper;
@Override
public void say() {
userMapper.say();
}
}
@Value
前面三个注解是注入其它 bean 类型的,而 @Value 注解是用来注入基本数据类型和 String 类型的,相当于 XML 配置的:
该注解有一个 value 属性用来指定要注入的属性值,该值可以使用 SpEL 表达式,写法为#{表达式}。下面会介绍相关的用法。
?? 在 resource 目录下创建 userInfo.properties 文件
# 自定义配置属性
person.name=Bob
person.age=20
?? 在 Spring 配置文件中添加配置
?? 使用 @Value 注解将属性注入
package com.example;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Value("1") // 普通用法,直接写值
private int id;
@Value("${person.name}") // 读取 properties 配置文件中自定义属性
private String name;
@Value("${person.age}") // 读取 properties 配置文件中自定义属性
private int age;
@Value("#{100 * 5 - 10}") // 使用 SpEL 表达式
private int num;
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", num=" + num +
'}';
}
}
@Test
public void test() {
// 加载 Spring 配置文件,初始化 IoC 容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person)applicationContext.getBean("person");
System.out.println(person);
// Person{id=1, name='Bob', age=20, num=490}
}
作用范围和生命周期
@Scope
该注解是指定 bean 的作用范围的,相当于 XML 配置中的 scope 属性:
该注解使用 value 属性来指定作用范围,常用取值为 singleton(默认值)和 prototype。
@Component
@Scope("prototype") // 多例
public class Bean {
public void say() {
System.out.println("hello world");
}
}
可以发现该注解本身就是 @Component 的衍生注解,所以 @Con?guration 修饰的 Java 类在 IoC 容器会注册为 bean。具体使用如下:
@Configuration // 表明当前类是一个 Spring 配置类
public class SpringConfig1 {
// 注册各种 bean
}
注意:前面的配置类没有使用 @Con?guration,是因为该配置类被 AnnotationConfigApplicationContext 实现类指定为 Spring 配置类,所以在 IoC 容器中会自动进行注册。如果需要多个 Spring 配置类,除了被加载的主配置类外,其它配置类都需要使用 @Con?guration 修饰,并且还要被 @ComponentScan 注解扫描到。
其中,name 属性和 value 属性互为别名,都是用于指定 bean 的 id。如果该注解没有指定 name 和 value 属性,默认情况下将方法名作为 bean 的 id。具体使用如下:
@Configuration // 表明当前类是一个 Spring 配置类
@ComponentScan("com.example") // 指定 IoC 容器注解扫描的包路径
@PropertySource("classpath:userInfo.properties") // 加载指定路径下的 properties 文件
public class SpringConfig1 {
/**
* 将方法返回对象注册为 IoC 容器中 bean。默认情况下,bean 的 id 为方法名
*/
@Bean
public Person person() {
return new Person();
}
}