Spring 简介
Spring 的优势在哪?
简化程序开发:IoC + AOP(事务处理)
框架整合:MyBatis + MyBatis-plus + Structs + Structs2 + ……
初识 Spring
Spring 是一个开发生态圈,提供了若干种具体技术,每一种技术用于完成特定的功能。
所以基于 Spring 全家桶,可以构建各类完整的项目,如果微服务,分布式,web APP 等
Spring 的各种技术有哪些?
- Spring Framework:Spring 各种技术的基础框架
- Spring Boot:在 Spring 简化开发的基础上,加速开发
- Spring Cloud:应用于分布式系统
Spring Framework 系统架构
Spring Framework 的架构从 Spring1.0 一直到 Spring4.0 架构在变化。现在趋于稳定了
- Data Access: 数据访问 Data Integration:数据集成
- Web:用于 web 开发
- AOP:面向切面编程(一种编程思想,在不动原有对象的基础上新增功能)
- Aspects:AOP 思想的实现
- Core Container:核心容器,管理 Java 对象
- Test:单元测试与集成测试
Spring Framework 学习路线
- 核心容器:核心概念(IoC/DI) + 容器基本操作
- 整合:整合数据层技术 MyBatis
- AOP:核心概念 + 基础操作 + 实用开发
- 事务:实用开发
- Spring 家族:SpringMVC + SpringBoot + SpringCloud
Spring Framework 核心容器
IoC(Inversion of Control)控制反转
IoC 是怎么提出的
代码书写现状:
//业务层实现
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
bookDao.save();
}
}
//数据层实现
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
如果我要使用BookDaoImpl2的数据实现逻辑,那我的业务层bookDao的新建对象就为BookDaoImpl2。
这就造成了代码耦合度高的问题,核心原因其实就是业务层使用的对象在代码中强制定义好了。
解决思路就是:程序中不要主动使用 new 产生对象,而是要转换为外部提供对象,也就是 IoC(对象的创建控制权由程序转移到外部)
Spring 如何实现 IoC
Spring 提供了 Core Container,称为 IoC 容器,用来充当 IoC 思想的”外部”。
IoC 容器负责对象的创建,初始化等一系列工作,被创建或管理的对象在 IoC 中称为 Bean
//业务层实现
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
bookDao.save();
}
}
//数据层实现
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
// IoC容器
bookDao bookService对象都可以放在这个容器中
DI(Dependency Injection)依赖注入
在容器中建立 Bean 和 Bean 之间的依赖关系的过程称为依赖注入
比如上面示例代码中的 bookService 对象依赖于 bookDao 对象,那么就要建立这个依赖关系
IoC + DI 实现了什么
充分解耦代码:
使用 IoC 容器管理 Bean
在 IoC 容器中将有依赖的 Bean 进行绑定
实现使用对象时,不仅可以直接从 IoC 容器中获取,并且获取到的对象已经完成了所有绑定的依赖关系
IoC + DI 入门案例
IoC 使用过程思路分析
IoC 管理什么(Service 和 Dao)
如何将被管理的对象告诉 IoC 容器(通过配置文件)
被管理的对象交给 IoC 容器后,怎么获取 IoC 容器(Spring 提供的接口)
拿到 IoC 容器后,如何从容器中获取 Bean(接口方法)
使用 Spring 导入哪些坐标(pom.xml)
DI 使用过程思路分析
- 基于 IoC 管理 Bean
- Service 中使用 new 创建 Dao 对象不保留
- Service 中需要的 Dao 对象如何进入 Service 中(提供方法)
- Service 与 Dao 间关系如何描述(配置文件)
代码项目:spring_01_quickstart
项目地址:https://gitee.com/zou-xinyu6/ssm-study/tree/master/spring_01_quickstart
bean 的相关知识
bean 基础配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans |
功能 | 定义 Spring 核心容器管理的对象 |
格式 | |
属性列表 | id:bean 的 id,容器通过 id 获取唯一的 bean class:bean 的类型,即配置的 bean 的全路径类名 name:定义 bean 的别名,可以有多个,用空格分隔 scrope:作用范围,确定是单例还是非单例(拿到的 bean 对象是同一个还是多个),两个取值:singleton(默认) / prototype |
范例 |
为什么 bean 默认为单例
我们通过 bean 获取的对象是用来执行方法的,那么同一个对象执行多个方法更省内存
适合交给容器管理的 bean:
- 表现层(UI)对象:主要是指与用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数据。
- 业务层(BLL)对象:UI 层和 DAL 层之间的桥梁。实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。
- 数据层(DAL)对象:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。
- 工具对象:工具类是为了提供一些通用的、某一非业务领域内的公共方法,不需要配套的成员变量,仅仅是作为工具方法被使用。所以将它做成静态方法最合适,不需要实例化,能够获取到方法的定义并调用就行。
不适合交给容器管理的 bean:
- 封装实体的域对象:有状态,每个对象不一样
实例化 bean 的方式
bean 从代码上看是写在 Spring 的配置文件中的,但是当代码运行时,bean 要构造出真正的对象才能返回进行使用。
bean 实例化方法 1:构造函数
bean 通过找到 class 定义的类,执行它的无参构造方法(自己不写,Java 编译自动生成),返回一个实例对象。这种方法必须有无参构造函数,如果显式声明一个有参构造函数,就会报错。
public class BookDaoImpl implements BookDao {
private/public BookDaoImpl() {
System.out.println("book constructor is running");
}
public void save() {
System.out.println("book dao save ...");
}
}
输出:
book constructor is running
book dao save ...
bean 实例化方法 2:静态工厂
静态工厂
public class OrderDaoFactory {
public static OrderDao getOrderDao() {
System.out.println("factory set ...");
return new OrderDaoImpl();
}
}
配置文件
<bean id="orderDao" factory-method="getOrderDao" class="com.sysu.factory.OrderDaoFactory"/>
bean 实例化方法 3:实例工厂
实例工厂
public class UserDaoFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
配置文件
<bean id="userDaoFactory" class="com.sysu.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userDaoFactory"/>
bean 实例化方法 4:factoryBean
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
public boolean isSingleton() {
return false; //不是单例
}
}
<bean id="userDao" class="com.sysu.factory.UserDaoFactoryBean" />
创建对象的代码都是:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
bean 的生命周期
- 生命周期:从创建到消亡的完整过程
- bean 生命周期:bean 从创建到销毁的整体过程
- bean 生命周期控制:从 bean 创建后到销毁做的一些事情
执行顺序:
初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set 操作)
- 执行 bean 的初始化方法
使用 bean
- 执行业务操作
关闭/销毁容器
- 执行 bean 的销毁方法
通过配置文件进行控制
public class BookDaoImpl implements BookDao {
public void save() {
sout("book dao save ...");
}
public void init() { //bean对象创建后执行的函数
sout("book init ...");
}
public void destory() { //bean对象销毁时执行的函数
sout("book destory ...");
}
}
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
主函数:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook(); //挂载容器销毁钩子,确保容器销毁前,bean的destroy函数会执行
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
// ctx.close()这个就直接销毁容器了,之后的代码容器就无法使用
使用接口控制
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
public void save() {
sout("book dao save ...");
}
public void afterPropertiesSet() throws Exception {
sout("afterPropertiesSet");
}
public void destroy() throws Exception {
sout("destroy");
}
}
配置文件
<bean id="bookService" class="com.sysu.service.impl.BookServiceImpl" />
DI 相关知识
依赖注入的方式
往类中传递数据有几种方式?
- 普通方法(set 方法)
- 构造函数
如果 bean 运行的是简单数字或者字符串,那么 bean 与 bean 之间怎么建立依赖?
- 引用类型
- 简单类型
依赖注入的方式
- setter 注入:简单类型 + 引用类型
- 构造器注入:简单类型 + 引用类型
setter 注入
引用类型
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
<bean id="bookService" class="com.sysu.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl"/>
简单类型
public class BookDaoImpl implements BookDao {
private int connectionNumber;
public void setConnectionNumbero(int connectionNumber) {
this.connectionNumber = connectionNumber;
}
}
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl">
<property name="connectionNumber" value="10"/>
</bean>
构造器注入(存在紧耦合问题)
构造函数的形参名称对应 constructor-arg 的 name 属性
引用类型
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
}
<bean id="bookService" class="com.sysu.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl"/>
简单类型
public class BookDaoImpl implements BookDao {
private int connectionNumber;
public void BookDaoImpl(int connectionNumber) {
this.connectionNumber = connectionNumber;
}
}
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl">
<constructor-arg name="connectionNumber" value="10"/>
</bean>
setter 和构造器怎么选择
自己写的模块,全部使用 setter 注入。实在不行再用构造器
集合注入
BookDaoImpl.java
public class BookDaoImpl implements BookDaO {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void setArray(int[] array) { this.array = array; }
public void setList(List<String> list) { this.list = listy; }
public void setSet(Set<String> set) { this.set = sety; }
public void setMap(Map<String, String> map) { this.map = map; }
public void setProperties(Properties properties) { this.properties = properties; }
public void save() {
sout("book dao save ...");
}
}
<bean id="bookDao" class="com.sysu.dao.impl.BookDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
</array>
</property>
<property name="list">
<list>
<value>sysu</value>
<value>nju</value>
</list>
</property>
<property name="set"> //自动去重
<set>
<value>sysu</value>
<value>nju</value>
<value>nju</value>
</set>
</property>
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="guangdong"/>
</map>
</property>
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="provincce">guangdong</prop>
</props>
</property>
</bean>
第三方数据源管理
之前在配置文件中都是书写自己项目里面定义的对象,如果是第三方的对象该如何管理呢?
比如使用 druid 进行数据库管理
思路:
-
先在 pom.xml 中导入数据源的坐标
-
然后在 applicationContext.xml 中使用 DI(这里就要自己去看源代码/搜索引擎找需要管理的内容)
Spring 加载 properties 文件形式
1.开启context命名空间
<?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"> //新增
2.使用context命名空间,加载指定properties文件
<context:property-placeholder location="jdbc.properties"/>
3.使用${}读取要加载的属性值
<property name="username" value="${jdbc.username}"/>
</bean>
不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
加载properties标准格式
<context:property-placeholder location="classpath:*.properties" />
从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties" />
注解开发
注解开发是一种开发模式,其主要思想是通过在类上直接写注解来定义各种配置信息,提高开发效率
注解开发定义 bean
1.使用@Component定义bean
@Component("bookDao") //写beanId
public class BookDaoImpl implments BookDao {
...
}
2.核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.sysu"/>
Spring 提供了@Component 注解的三个衍生注解
- @Controller:用于表现层定义 bean
- @Service:用于业务层定义 bean
- @Repository:用于数据层定义 bean
这三作用和@Component 一样,只是有各种名字,区分更明显
纯注解开发模式
Spring3.0 开启纯注解开发模式,直接使用 Java 类替代配置文件。也就是说,配置文件 applicationContext.xml 可以不要了
//SpringConfig.java
@Configuration //用于设定当前类为配置类
@ComponentScan({"com.sysu.service", "com.sysu.dao"}) // 用于设定扫描路径,只能添加一次
public class SpringConfig {
}
主函数加载的方式变了
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
注解开发中 bean 作用范围和生命周期管理
@Repository
@Scope("singleton") //定义作用范围
public class BookDaoImpl implements BookDao {
public void BookDaoImpl() {
sout("book dao construct");
}
@PostConstruct //定义初始化函数
public void init() {
sout("book init");
}
@PreDestroy
public void destroy() { //定义消耗函数
sout("book destroy");
}
}
注解开发依赖注入
注解开发的 DI 是使用自动装配模式
@Service
public class BookServiceImpl implments BookService {
@Autowired(按类型自动注入)
@Qualifier("bookDao") (按名称注入,需配合Autowired)
private BookDao bookDao;
//无需setter方法
public void save() {
sout("bookService save ...");
bookDao.save();
}
}
自动装配基于反射设计创建对象,并暴力反射应对私有属性,所以无需setter方法
自动装配建议使用无参构造方法创建对象。
@Repository("bookDao")
public class BookDaoImpl implments BookDao {
@Value("100") //简单类型的依赖注入
private String connectionNum;
}
如果依赖注入涉及到加载 properties 文件
//SpringConfig.java
@Configuration //用于设定当前类为配置类
@ComponentScan({"com.sysu.service", "com.sysu.dao"}) // 用于设定扫描路径,只能添加一次
@PropertySource({"classpath:jdbc.properties", ...})
public class SpringConfig {
}
//路径不允许使用通配符
注解开发中第三方 bean 管理
使用@Bean配置第三方bean
config/SpringConfig.java
@Configuration
@Import({JdbcConfig.class, ...}) //手动加载配置类到核心配置
public class SpringConfig {
}
使用独立配置类管理第三方bean
config/JdbcConfig.java
public class JdbcConfig {
//定义一个方法获得要管理的对象
//添加@Bean,表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc.mysql://localhost:3306/databaseName");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
在我定义第三方 bean 时,如果需要注入信息怎么办?
config/JdbcConfig.java
public class JdbcConfig {
@Value("${配置文件变量名}")
private String driver;
@Value("${配置文件变量名}")
private String Url;
@Value("${配置文件变量名}")
private String userName;
@Value("${配置文件变量名}")
private String password;
@Bean
public DataSource dataSource(BookDao bookDao) {
sout(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(Url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}