Spring

2024-06-12

Spring_想要登顶的菜鸟的博客-CSDN 博客

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 架构在变化。现在趋于稳定了

img

  • Data Access: 数据访问 Data Integration:数据集成
  • Web:用于 web 开发
  • AOP:面向切面编程(一种编程思想,在不动原有对象的基础上新增功能)
  • Aspects:AOP 思想的实现
  • Core Container:核心容器,管理 Java 对象
  • Test:单元测试与集成测试

Spring Framework 学习路线

  1. 核心容器:核心概念(IoC/DI) + 容器基本操作
  2. 整合:整合数据层技术 MyBatis
  3. AOP:核心概念 + 基础操作 + 实用开发
  4. 事务:实用开发
  5. 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 使用过程思路分析

  1. 基于 IoC 管理 Bean
  2. Service 中使用 new 创建 Dao 对象不保留
  3. Service 中需要的 Dao 对象如何进入 Service 中(提供方法)
  4. 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;
	}
}