Spring 5 笔记
课程内容介绍
尚硅谷 Spring 5
视频地址:https://www.bilibili.com/video/BV1Vf4y127N5
-
Spring 概念
-
IoC 容器
-
IoC 底层原理
-
IoC 接口(BeanFactory)
-
IoC 操作 Bean 管理(基于xml)
-
IoC 操作 Bean 管理(基于注解)
-
-
AOP
-
JdbcTemplate
-
事务管理
-
Spring 5 新特性
Spring 框架概述
-
Spring 是轻量级的开源的 JavaEE 框架
-
Spring 可以解决企业应用开发的复杂性
-
两个核心部分:IoC、AOP
- IoC:Inversion of Control,控制反转。把创建对象过程交给 Spring 进行管理
- AOP:Aspect Oriented Programming,面向切面编程。不修改源代码进行功能增强
-
Spring 特点
- 方便解耦,简化开发
- AOP 编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低 API 开发难度
-
本课程选取 Spring 5.x
IoC
概念和原理
1、什么是 IoC ?
控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
使用 IoC 目的:降低耦合度
入门案例就是 IoC 实现
2、IoC 底层原理
xml 解析、工厂模式、反射
接口
1、IoC 思想基于 IoC 容器完成,IoC 容器底层就是对象工厂
2、Spring 提供 IoC 容器实现两种方式(两个接口):
(1) BeanFactory
:IoC 容器基本实现,是 Spring 内部使用接口,一般不提供开发人员使用
加载配置文件时不会创建对象,在获取(使用)对象时才创建
(2) ApplicationContext
: BeanFactory
接口的子接口,提供更多更强大的功能,一般由开发人员使用
加载配置文件时就会将配置的对象创建
3、 ApplicationContext
接口有实现类
IoC 操作 Bean 管理
1、什么是 Bean 管理
Bean 管理指的是两个操作:Spring 创建对象,Spring 注入属性
2、Bean 管理操作有两种方式
-
基于 xml 配置文件方式实现
-
基于注解方式实现
基于 xml 方式
基于 xml 方式创建对象
<bean id="alice" class="com.yin.spring5.bean.User"></bean>
-
在 Spring 配置文件中,使用 bean 标签,标签中添加对应属性,就可以实现对象创建
-
bean 标签有很多属性,常用属性:
id
:唯一标识class
:全类名
-
创建对象的时候,默认执行无参构造器
基于 xml 方式注入属性
DI:Dependency Injection,依赖注入,是 IoC 的一种实现方式
-
第一种注入方式:set 方法注入
<!--set 方法注入--> <bean id="alice" class="com.yin.spring5.bean.User"> <property name="name" value="Alice"/> <property name="age" value="20"/> </bean>
-
第二种注入方式:有参构造器注入
<!--有参构造器注入--> <bean id="bob" class="com.yin.spring5.bean.User"> <constructor-arg name="name" value="Bob"/> <constructor-arg name="age" value="20"/> </bean>
-
p 名称空间注入(了解即可)
添加约束:
xmlns:p="http://www.springframework.org/schema/p"
<!--p命名空间--> <bean id="cindy" class="com.yin.spring5.bean.User" p:name="Cindy" p:age="18"> </bean>
-
c 名称空间注入(了解即可)
添加约束:
xmlns:c="http://www.springframework.org/schema/c"
<!--c命名空间--> <bean id="david" class="com.yin.spring5.bean.User" c:name="David" c:age="18"> </bean>
xml 注入其他类型
-
字面量
-
注入
null
<!--注入null--> <bean id="eva" class="com.yin.spring5.bean.User"> <property name="name" value="Eva"/> <property name="age"> <null/> </property> </bean>
-
属性值包含特殊字符
-
将特殊字符进行转义
<!--属性值包含特殊字符--> <bean id="special" class="com.yin.spring5.bean.User"> <!--将特殊字符进行转义--> <property name="name" value="<<TRUMP>>"/> <property name="age" value="18"/> </bean>
输出:
User{name='<<TRUMP>>', age=18}
-
使用 CDATA
<!--属性值包含特殊字符--> <bean id="special" class="com.yin.spring5.bean.User"> <!--使用 CDATA--> <property name="name"> <value><![CDATA[<<BIDEN>>]]></value> </property> <property name="age" value="18"/> </bean>
输出:
User{name='<<BIDEN>>', age=18}
-
-
-
外部 bean
public class UserDao { public void delete() { System.out.println("UserDao#delete()..."); } }
public class UserService { UserDao dao; public void setDao(UserDao dao) { this.dao = dao; } public void deleteUser() { System.out.println("UserService#deleteUser()..."); dao.delete(); } }
<bean id="userDao" class="com.yin.spring5.dao.UserDao"> </bean> <bean id="userService" class="com.yin.spring5.service.UserService"> <!--注入外部 bean--> <property name="dao" ref="userDao"/> </bean>
-
内部 bean
public class Department { private String departName; // getter setter toString 等方法在此省略 }
public class Employee { private String empName; private String email; private Department dept; // getter setter toString 等方法在此省略 }
<bean id="employee" class="com.yin.spring5.bean.Employee"> <property name="empName" value="杰克"/> <property name="email" value="jack@jack.com"/> <!--内部 bean--> <property name="dept"> <bean class="com.yin.spring5.bean.Department"> <property name="departName" value="开发部"/> </bean> </property> </bean>
输出:
Employee{empName='杰克', email='jack@jack.com', dept=Department{departName='开发部'}}
-
级联赋值
写法一:
<bean id="employee2" class="com.yin.spring5.bean.Employee"> <property name="empName" value="Pony"/> <property name="email" value="pony@pony.com"/> <property name="dept" ref="department"/> </bean> <bean id="department" class="com.yin.spring5.bean.Department"> <property name="departName" value="运维部"/> </bean>
输出:
Employee{empName='Pony', email='pony@pony.com', dept=Department{departName='运维部'}}
写法二:
<bean id="employee2" class="com.yin.spring5.bean.Employee"> <property name="empName" value="Pony"/> <property name="email" value="pony@pony.com"/> <property name="dept" ref="department"/> <!--注意这里--> <property name="dept.departName" value="开发部"/> </bean> <bean id="department" class="com.yin.spring5.bean.Department"> <property name="departName" value="运维部"/> </bean>
输出:
Employee{empName='Pony', email='pony@pony.com', dept=Department{departName='开发部'}}
xml 注入集合属性
-
注入数组类型属性
<!--数组类型注入--> <property name="array"> <array> <value>array1</value> <value>array2</value> <value>array3</value> </array> </property>
-
注入 List 集合类型属性
<!--List 类型注入--> <property name="list"> <list> <value>list1</value> <value>list2</value> <value>list3</value> </list> </property>
-
注入 Map 集合类型属性
<!--Map 类型注入--> <property name="map"> <map> <entry key="key1" value="value1"/> <entry key="key2" value="value2"/> <entry key="key3" value="value3"/> </map> </property>
-
注入 Set 集合类型属性
<!--Set 类型注入--> <property name="set"> <set> <value>set1</value> <value>set2</value> <value>set3</value> </set> </property>
输出:
Student{stuName='Pony', array=[array1, array2, array3], list=[list1, list2, list3], map={key1=value1, key2=value2, key3=value3}, set=[set1, set2, set3]}
-
在集合里面设置对象类型
<bean id="student" class="com.yin.spring5.bean.Student"> <!--省略部分代码,只看有关的--> <!--在集合里面设置对象类型--> <property name="courses"> <list> <ref bean="course1"/> <ref bean="course2"/> </list> </property> </bean> <bean id="course1" class="com.yin.spring5.bean.Course"> <property name="courseName" value="数据结构"/> <property name="credit" value="5"/> </bean> <bean id="course2" class="com.yin.spring5.bean.Course"> <property name="courseName" value="操作系统"/> <property name="credit" value="4"/> </bean>
输出:
[Course{courseName='数据结构', credit=5}, Course{courseName='操作系统', credit=4}]
-
把集合注入部分提取出来
a. 在配置文件中引入
util
名称空间<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> </beans>
b. 使用
util
标签完成 List 集合注入提取public class Book { private List<String> list; // getter setter toString 等方法在此省略 }
<bean id="book" class="com.yin.spring5.bean.Book"> <property name="list" ref="bookList"/> </bean> <!--List 集合注入提取--> <util:list id="bookList"> <value>Java编程思想</value> <value>算法导论</value> <value>Effective Java</value> </util:list>
输出:
[Java编程思想, 算法导论, Effective Java]
FactoryBean
普通 bean:配置文件中定义的 bean 类型就是返回类型
工厂 bean:配置文件中定义的 bean 类型可以和返回类型不一样
- 创建类,实现
FactoryBean
接口 - 实现接口的方法
/**
* MyFactoryBean 工厂Bean
*/
public class MyFactoryBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
return new Course();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="myFactoryBean" class="com.yin.spring5.bean.MyFactoryBean">
</bean>
@Test
public void test3() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("spring3.xml");
Course course = ioc.getBean("myFactoryBean", Course.class);
course.setCourseName("数据结构");
course.setCredit(5);
System.out.println(course);
}
测试:
Course{courseName='数据结构', credit=5}
Bean 作用域
在 Spring 中,设置创建 bean 实例是单实例还是多实例(默认单实例)
@Test
public void test4() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("spring2.xml");
Book book1 = ioc.getBean("book", Book.class);
Book book2 = ioc.getBean("book", Book.class);
// 这里打印对象地址,需要把其 toString() 方法注释掉
System.out.println(book1);
System.out.println(book2);
System.out.println(book1==book2);
}
输出:
com.yin.spring5.bean.Book@433defed
com.yin.spring5.bean.Book@433defed
true
如何设置单实例/多实例?bean 标签中 scope
属性设置作用域
scope
属性值:
singleton
:单实例prototype
:多实例
将 scope 属性值设置为 `` 后,再进行上面测试:
<bean id="book" class="com.yin.spring5.bean.Book" scope="prototype">
输出:
com.yin.spring5.bean.Book@548a24a
com.yin.spring5.bean.Book@433defed
false
singleton
和 prototype
的区别:
-
singleton
是单实例,prototype
是多实例 -
singleton
在 Spring 加载配置文件时就会创建单实例对象prototype
在获取对象时才会进行创建
Bean 生命周期
生命周期:从对象创建到销毁的过程
bean 生命周期:
- 通过构造器创建 bean 实例(无参构造)
- 为 bean 注入属性和对其他 bean 的引用(set 方法)
- 调用 bean 的初始化方法(需要进行配置初始化方法)
- 获取并使用 bean
- 当容器关闭时,调用 bean 的销毁方法(需要进行配置销毁方法)
代码验证:
public class User {
private String name;
private Integer age;
private Character gender;
public User() {
System.out.println("User 无参构造器执行。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("User.setName 执行。。。");
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
System.out.println("User.setAge 执行。。。");
this.age = age;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
System.out.println("User.setGender 执行。。。");
this.gender = gender;
}
public void init() {
System.out.println("自定义 User.init 执行。。。");
}
public void destroy() {
System.out.println("自定义 User.destroy 执行。。。");
}
}
<bean id="user" class="com.yin.spring5.bean.User"
init-method="init" destroy-method="destroy">
<property name="name" value="Alice"/>
<property name="gender" value="F"/>
<property name="age" value="20"/>
</bean>
@Test
public void test5() {
ClassPathXmlApplicationContext ioc =
new ClassPathXmlApplicationContext("spring4.xml");
User user = ioc.getBean("user", User.class);
System.out.println(user);
// 关闭容器
ioc.close();
}
输出:
User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。
自定义 User.init 执行。。。
com.yin.spring5.bean.User@16eb3ea3
自定义 User.destroy 执行。。。
其实完整来讲,bean 的声明周期有 7 步。在初始化方法执行前后,分别有 postProcessBeforeInitialization
和 postProcessAfterInitialization
方法执行。
在上述代码的基础上,再添加以下内容:
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
System.out.println(" postProcessBeforeInitialization 执行。。。");
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
System.out.println(" postProcessAfterInitialization 执行。。。");
return null;
}
}
<bean id="myBeanPost" class="com.yin.spring5.bean.MyBeanPost"></bean>
再次进行测试,输出如下:
User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。
postProcessBeforeInitialization 执行。。。
自定义 User.init 执行。。。
postProcessAfterInitialization 执行。。。
com.yin.spring5.bean.User@33bc72d1
自定义 User.destroy 执行。。。
综上,bean 生命周期完整表述如下:
- 通过构造器创建 bean 实例(无参构造)
- 为 bean 注入属性和对其他 bean 的引用(set 方法)
- 把 bean 实例传递给初始化前的后置处理
postProcessBeforeInitialization
- 调用 bean 的初始化方法(需要进行配置初始化方法)
- 把 bean 实例传递给初始化后的后置处理
postProcessAfterInitialization
- 获取并使用 bean
- 当容器关闭时,调用 bean 的销毁方法(需要进行配置销毁方法)
xml 自动装配
什么是自动装配?
根据指定装配规则(属性名称或属性类型),Spring 自动将匹配的属性值进行注入
bean 标签中的 autowire
属性可以配置自动装配,autowire
常用属性值:
byName
:根据名称装配(注入 bean 的 id 要与被注入 bean 的 set 方法一致)byType
:根据类型装配(有多个同类型的 bean 时,此属性值无法使用)
代码演示:
public class Department {
// 此处略去 get set toString 等方法
private String deptName;
}
public class Employee {
// 此处略去 get set toString 等方法
private String empName;
private Department dept;
}
<bean id="dept" class="com.yin.spring5.autowire.Department">
<property name="deptName" value="开发部"/>
</bean>
<bean id="dept2" class="com.yin.spring5.autowire.Department">
<property name="deptName" value="测试部"/>
</bean>
<bean id="emp" class="com.yin.spring5.autowire.Employee" autowire="byName">
<property name="empName" value="Pony"/>
</bean>
输出:
Employee{empName='Pony', dept=Department{deptName='开发部'}}
若将 Employee
类中默认生成的 setDept
方法名修改为 setDept2
,则输出变为:
Employee{empName='Pony', dept=Department{deptName='测试部'}}
由此证实:byName
的自动注入是根据 set 方法判断的,而不是类中的属性名。
外部属性文件
以数据库连接池为例:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
将数据库配置抽取到 properties 文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
则 Spring 配置文件改写如下,注意需要引入 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">
<!--引入外部 properties 文件-->
<context:property-placeholder location="classpath:db-config.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--取出 properties 文件中的值-->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
基于注解方式
创建 bean
Spring 创建 bean 的四个注解:@Component
,@Controller
,@Service
,@Repository
。
<!--开启组件扫描,将会扫描 base-package 及其子包下所有组件-->
<context:component-scan base-package="com.yin.spring5"/>
排除某些组件,以排除 @Controller
组件扫描为例:
<!--开启组件扫描-->
<context:component-scan base-package="com.yin.spring5">
<!--排除 Controller 组件扫描-->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
context:component-scan
标签部分属性:
base-package
:要扫描组件的基础包,默认将会扫描 base-package 及其子包下所有组件use-default-filters
:是否使用默认的扫描规则
context:component-scan
标签的子标签:
context:exclude-filter
:指定排除规则context:include-filter
:指定包含规则
属性注入
@Autowired
:根据属性类型自动装配@Qualifier
:根据属性名称注入@Resource
:可以根据类型注入,可以根据名称注入(这是 javax 包下的注解,已移除)@Value
:注入普通类型属性
AOP
AOP,Aspect Oriented Programming,面向切面编程
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
底层原理
AOP 底层使用动态代理。有两种情况:
-
有接口,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法
-
没有接口,使用 CGLIB 动态代理
创建子类的代理对象,增强类的方法
JDK 动态代理
使用 JDK 动态代理,使用 java.lang.reflect.Proxy
类里面的方法创建代理对象
代码演示:
-
创建接口,定义方法
public interface UserDao { int add(int a, int b); String update(String id); }
-
创建接口实现类,实现方法
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { System.out.println("add 方法执行"); return a + b; } @Override public String update(String id) { System.out.println("update 方法执行"); return id; } }
-
使用
java.lang.reflect.Proxy
类创建接口代理对象public class JdkProxy { public static void main(String[] args) { // 创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; UserDao userDao = new UserDaoImpl(); // 被代理对象 UserDao proxyInstance = (UserDao) Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); System.out.println(proxyInstance.add(2, 3)); System.out.println(proxyInstance.update("abc")); } } // 创建代理对象代码 class UserDaoProxy implements InvocationHandler { private Object obj; // 把被代理对象传递过来 public UserDaoProxy(Object obj) { this.obj = obj; } // 增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法之前 System.out.println(method.getName() + " 方法之前执行,参数:" + Arrays.toString(args)); // 被增强的方法执行 Object returnValue = method.invoke(obj, args); // 方法之后 System.out.println(method.getName() + " 方法之后执行"); return returnValue; } }
输出:
add 方法之前执行,参数:[2, 3]
add 方法执行
add 方法之后执行
5
update 方法之前执行,参数:[abc]
update 方法执行
update 方法之后执行
abc
AOP 术语
- 连接点:类中可以被增强的方法称为连接点
- 切入点:实际被真正增强的方法称为切入点
- 通知(增强):实际增强的逻辑部分称为通知(增强)
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 切面:把通知应用到切入点的过程
AOP 操作
Spring 框架一般基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,而是独立的 AOP 框架,但是一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作
Spring 基于 AspectJ 实现 AOP 操作有两种方式:xml 配置文件方式、注解方式。
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
切入点表达式
切入点表达式作用:指明对哪个类的哪个方法进行增强
语法结构:
execution([权限修饰符][返回类型][全类名][方法名]([参数列表]))
例 1:对 com.yin.spring5.UserDao#add
方法进行增强
execution(* com.yin.spring5.UserDao.add(..)) // 权限修饰符省略
例 2:对 com.yin.spring5.UserDao
类中所有方法增强
execution(* com.yin.spring5.UserDao.*(..)) // 权限修饰符省略
例 2:对 com.yin.spring5
包下所有类中所有方法增强
execution(* com.yin.spring5.*.*(..)) // 权限修饰符省略
基于注解
-
创建类,在类中定义方法
public class User { public void add() { System.out.println("User.add 方法执行"); } }
-
创建增强类,编写增强逻辑
在增强类中,创建方法,让不同的方法代表不同的通知
public class UserProxy { // 前置通知 public void before() { System.out.println("UserProxy.before 方法执行"); } ...... }
-
进行通知的配置
-
配置文件开启注解扫描
<context:component-scan base-package="com.yin.spring5.anno"/>
-
使用注解创建 User 和 UserProxy 对象
@Component public class User {...}
@Component public class UserProxy {...}
-
在增强类上面添加注解
@Aspect
@Component @Aspect public class UserProxy {...}
-
配置文件开启生成代理对象
<!--开启 Aspect 生成代理对象,要引入aop名称空间--> <aop:aspectj-autoproxy/>
-
-
配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
@Component @Aspect public class UserProxy { // 前置通知 @Before("execution(* com.yin.spring5.anno.User.add(..))") public void before() { System.out.println("UserProxy.before 方法执行"); } // 最终通知 @After("execution(* com.yin.spring5.anno.User.add(..))") public void after() { System.out.println("UserProxy.after 方法执行"); } // 后置通知(返回通知) @AfterReturning("execution(* com.yin.spring5.anno.User.add(..))") public void afterReturning() { System.out.println("UserProxy.afterReturning 方法执行"); } // 异常通知 @AfterThrowing("execution(* com.yin.spring5.anno.User.add(..))") public void afterThrowing() { System.out.println("UserProxy.afterThrowing 方法执行"); } // 环绕通知 @Around("execution(* com.yin.spring5.anno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前。。。"); // 被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后。。。"); } }
测试:
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring1.xml");
User user = context.getBean("user", User.class);
user.add();
}
输出:
环绕之前。。。
UserProxy.before 方法执行
User.add 方法执行
UserProxy.afterReturning 方法执行
UserProxy.after 方法执行
环绕之后。。。
抽取相同的切入点
@Component
@Aspect
public class UserProxy {
// 抽取相同的切入点
@Pointcut("execution(* com.yin.spring5.anno.User.add(..))")
public void pointDemo() {
}
// 前置通知
@Before("pointDemo()")
public void before() {
System.out.println("UserProxy.before 方法执行");
}
...
}
有多个增强类对同一个方法进行增强,设置增强类优先级
在增强类上添加注解 @Order(value = )
,数字越小,优先级越高
完全使用注解
创建配置类,不需要创建 xml 配置文件
@Configuration
@ComponentScan(basePackages = "com.yin.spring5.anno")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
基于 xml 配置文件
JdbcTemplate
什么是 JdbcTemplate ?
Spring 对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
环境配置
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
</dependencies>
配置数据库连接池
<!--引入外部数据库配置文件-->
<context:property-placeholder location="classpath:db-config.properties"/>
<!--Druid 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
配置 JdbcTemplate
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
编写类文件
编写 Book 实体类,创建对应数据表
public class Book {
private Integer id;
private String title;
private String author;
private BigDecimal price;
// 此处省略 getter setter toString 等方法
}
环境一览
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--注意:此处文件头等信息略去-->
<!--组件扫描-->
<context:component-scan base-package="com.yin.spring5"/>
<!--引入外部数据库配置文件-->
<context:property-placeholder location="classpath:db-config.properties"/>
<!--Druid 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
public interface BookDao {
}
@Repository
public class BookDaoImpl implements BookDao {
JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
@Service
public class BookService {
BookDao bookDao;
@Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
操作数据库
增
@Override
public int insert(Book book) {
String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";
return jdbcTemplate.update(sql, book.getTitle(), book.getAuthor(), book.getPrice());
}
改
@Override
public int update(Book book) {
String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";
return jdbcTemplate.update(sql, book.getTitle(), book.getAuthor(),
book.getPrice(), book.getId());
}
删
@Override
public int delete(Integer id) {
String sql = "DELETE FROM book WHERE id=?";
return jdbcTemplate.update(sql, id);
}
查
某个值
@Override
public int recordCount() {
String sql = "SELECT COUNT(*) FROM book";
return jdbcTemplate.queryForObject(sql, Integer.TYPE);
}
对象
@Override
public Book getOneById(Integer id) {
String sql = "SELECT id, title, author, price FROM book WHERE id=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
集合
@Override
public List<Book> getAll() {
String sql = "SELECT * FROM book";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
}
批量操作
批量添加
@Override
public int[] batchInsert(List<Object[]> bookList) {
String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";
return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchAdd() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> bookList = new ArrayList<>();
// 按占位符顺序写参数
Object[] book1 = {"城南旧事", "林海音", 29.00};
Object[] book2 = {"追风筝的人", "忘了", 29.00};
Object[] book3 = {"小王子", "安东尼", 19.00};
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
bookService.batchAddBook(bookList);
}
批量修改
@Override
public int[] batchUpdate(List<Object[]> bookList) {
String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";
return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchUpdate() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> bookList = new ArrayList<>();
// 按占位符顺序写参数
Object[] book1 = {"城南旧事", "林海音(台湾)", 26.00, 24};
Object[] book2 = {"追风筝的人", "还是没想起来", 29.00, 25};
Object[] book3 = {"小王子", "安东尼奥", 20.00, 26};
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
bookService.batchUpdateBook(bookList);
}
批量删除
@Override
public int[] batchDelete(List<Object[]> idList) {
String sql = "DELETE FROM book WHERE id=?";
return jdbcTemplate.batchUpdate(sql, idList);
}
@Test
public void testBatchDelete() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> idList = new ArrayList<>();
// 按占位符顺序写参数
Object[] id1 = {19};
Object[] id2 = {20};
Object[] id3 = {21};
Object[] id4 = {25};
idList.add(id1);
idList.add(id2);
idList.add(id3);
idList.add(id4);
bookService.batchDeleteBook(idList);
}
事务
概念
-
什么是事务?
事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个操作失败,所有操作都失败
典型场景:银行转账
-
事务四个特性 ACID
-
原子性 Atomicity
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功
-
一致性 Consistency
事务必须使数据库从一个一致性状态变换到另一个一致性状态
-
隔离性 Isolation
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
-
持久性 Durability
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
-
环境搭建
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
</dependencies>
创建数据库表
use `jdbc_template`;
create table `account` (
`id` int primary key auto_increment comment 'id',
`name` varchar(50) comment '姓名',
`balance` int comment '余额'
);
insert into `account`(`name`, `balance`)
VALUES ('亚索', 1000),
('永恩', 1000);
编写实体类、DAO、Service 等
public class Account {
private Integer id;
private String name;
private Integer balance;
// 此处省略 getter setter toString 等方法
}
public interface AccountDao {
/**
* 增加金额
*
* @param id 用户id
* @param money 金额数目
*/
void addMoney(int id, int money);
/**
* 扣除金额
*
* @param id 用户id
* @param money 金额数目
*/
void deductMoney(int id, int money);
}
@Repository
public class AccountDaoImpl implements AccountDao {
final JdbcTemplate jdbcTemplate;
@Autowired
public AccountDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void addMoney(int id, int money) {
String sql = "UPDATE account SET balance=balance+? WHERE id=?";
jdbcTemplate.update(sql, money, id);
}
@Override
public void deductMoney(int id, int money) {
String sql = "UPDATE account SET balance=balance-? WHERE id=?";
jdbcTemplate.update(sql, money, id);
}
}
public class AccountService {
final AccountDao accountDao;
@Autowired
public AccountService(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账业务
*
* @param deductId 扣除金额方id
* @param addId 增加金额方id
* @param money 转账金额数目
*/
public void transferAccount(int deductId, int addId, int money) {
accountDao.deductMoney(deductId, money);
accountDao.addMoney(addId, money);
}
}
上面的代码,如果正常执行是没有问题的,但是如果代码执行过程中出现异常,会有问题
比如:
public void transferAccount(int deductId, int addId, int money) {
accountDao.deductMoney(deductId, money);
// 模拟异常
int i = 10 / 0;
accountDao.addMoney(addId, money);
}
为避免诸如上述问题,就需要事务。
事务操作流程:
- 开启事务
- 业务操作
- 若无异常,提交事务
- 出现异常,回滚事务
Spring 事务管理
-
事务一般添加到 Service 层
-
Spring 进行事务管理有两种方式:编程式事务管理 和 声明式事务管理。一般都使用声明式事务管理。
-
声明式事务管理有两种方式:基于注解,基于 xml 配置文件。
-
Spring 进行声明式事务管理,底层使用 AOP 原理。
-
Spring 事务管理 API
提供
PlatformTransactionManager
接口,代表事务管理器,这个接口针对不同框架提供不同的实现类
基于注解的声明式事务管理
配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
上面讲到,事务一般添加到 Service 层
在 Service 类上(或者类中的方法上)添加事务注解 @Transactional
事务注解 @Transactional
既可以添加到类上,也可以添加到方法上
- 如果添加到类上,则该类中的所有方法都会添加事务
- 如果添加到方法上,则只会给该方法添加事务
@Service
@Transactional
public class AccountService {...}
事务注解 @Transactional
部分参数:
-
propagation
:事务传播类型(默认REQUIRED
)多事务方法直接进行调用,这个过程中事务是如何进行管理的
-
isolation
:事务隔离级别事务特性之一。不考虑隔离性会产生很多问题,三个读问题:脏读、不可重复读、幻读(虚读)。
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交事务读取到另一个提交事务修改数据
- 幻读:一个未提交事务读取到另一个提交事务添加数据
-
timeout
:事务超时时间(秒,默认 -1 即永不超时)事务需要在指定时间内进行提交,如果不提交就会回滚
-
readOnly
:事务是否只读(默认false
) -
rollbackFor
:指明哪些异常类型进行事务回滚 -
noRollbackFor
:指明哪些异常类型不进行事务回滚
事务方法:对数据库表数据进行变化的操作
事务的传播行为可以由传播类型指定,Spring 定义了 7 种传播类型
传播类型 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行;否则,就启动一个新的事务,并在自己的事务内运行 |
REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行;如果有事务正在运行,应该将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行;否则,就启动一个新的事务,并在它自己的事务内运行 |
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED(读未提交) | 有 | 有 | 有 |
READ_COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE_READ(可重复读,MySQL默认) | 无 | 无 | 有 |
SERIALIZABLE(串行化) | 无 | 无 | 无 |
基于 XML 的声明式事务管理
第一步,配置事务管理器;第二步,配置通知;第三步,配置切入点和切面
<!--基于 XML 的事务-->
<!--1.配置事务管理器-->
<bean id="transactionManager2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource2"/>
</bean>
<!--2.配置通知-->
<tx:advice id="txAdvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上添加事务-->
<tx:method name="transferAccount" propagation="REQUIRED"
isolation="REPEATABLE_READ"/>
</tx:attributes>
</tx:advice>
<!--3.配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPointCut"
expression="execution(* com.yin.spring5.service.AccountService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointCut"/>
</aop:config>
完全注解
创建配置类,代替 xml 配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "com.yin.spring5") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc_template" +
"?serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager =
new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
Spring 5 新功能
-
Spring 5 基于 Java 8,运行时兼容 Java 9,移除了不建议使用的类和方法。
-
Spring 5 自带了通用的日志封装
Spring 5 移除了
Log4jConfigListener
,官方建议使用 Log4j2 -
Spring 5 核心注解支持
@Nullable
注解@Nullable
注解可以用在方法上、属性上、参数上,表示方法返回值、属性、参数可以为空 -
支持函数式风格 GenericApplicationContext / AnnotationConfigApplicationContext
@Test public void test4() { GenericApplicationContext context = new GenericApplicationContext(); context.refresh(); // 注册 bean context.registerBean("acct", Account.class, Account::new); // 获取注册的对象 Account acct = context.getBean("acct", Account.class); System.out.println(acct); }
-
Spring 5 支持整合 JUnit 5
Spring 5 整合 Log4j2
- 引入依赖
- 创建 log4j2.xml 配置文件(文件名固定)
Spring WebFlux
Spring WebFlux 介绍
Spring WebFlux 是 Spring 5 新增的模块,用于 Web 开发,功能与 Spring MVC 类似,使用当前比较流行的响应式编程
使用传统 Web 框架,比如 Spring MVC,这些基于 Servlet 容器。WebFlux 是一种异步非阻塞式的框架,异步非阻塞的框架在 Servlet 3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
什么是异步非阻塞?
-
同步与异步
异步和同步针对调用者。调用者发送请求,如果等着对方回应之后才去左其他事就是同步;如果发送请求后不等着对方回应就去做其他事就是异步。
-
阻塞与非阻塞
阻塞和非阻塞针对被调用者。被调用者收到请求之后,做完请求任务之后才给出反馈就是阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。
WebFlux 特点
- 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
- 函数式编程:使用 Java 8 函数式编程方式实现路由请求
对比 Spring MVC
- 两种方式都可以使用注解方式,都运行在 Tomcat 等容器中
- SpringMVC 采用命令式编程,WebFlux 采用异步响应式编程
响应式编程
什么是响应式编程?
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例如,在命令式编程环境中,a=b+c
表示将表达式的结果赋给 a,而之后改变 b 或 c 的值不会影响 a。但在响应式编程中,a 的值会随着 b 或 c 的更新而更新。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似 "=B1+C1" 的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
Java 8 及之前版本
提供的观察者模式两个类:Observer 接口,Observable 类(两者从 Java 9 开始被遗弃)
响应式编程(Reactor 实现)
- 响应式编程操作中,Reactor 是满足 Reactive 规范框架
- Reactor 有两个核心类,Mono 和 Flux,这两个类实现 Publisher 接口,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或 1 个元素
- Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值、错误信号、完成信号。错误信号和完成信号都表示终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
- 三种信号特点:
- 错误信号和完成信号都是终止信号,二者不能共存
- 如果没有发送任何元素值,而是直接发送终止信号,表示是空数据流
- 如果没有终止信号,表示是无限数据流
代码演示 Flux 和 Mono
引入依赖
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.0</version>
</dependency>
调用 just 方法或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生。
// just 方法直接声明
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(5).subscribe(System.out::println);
// 其他的方法
Integer[] array = {1,2,3,4};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
操作符
对数据流进行一道道操作,称为操作符,比如工厂流水线
-
map:将元素映射为新元素
-
flatMap:将元素映射为流。把每个元素转换流,把转换之后多个流合并为大的流
WebFlux执行流程和核心 API
Spring WebFlux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能 NIO(Non-blocking IO,非阻塞IO) 框架。(与 NIO 相对的是 BIO,即 Blocking IO)
BIO 的通信方式:
NIO 的通信方式:
Spring WebFlux 执行过程与 Spring MVC 相似
Spring WebFlux 核心控制器 DispatchHandler
,实现接口 WebHandler
。
DispatcherHandler.handle
方法:
在 Spring WebFlux 中,DispatcherHandler 负责请求的处理
以下三个都是接口:
-
HandlerMapping
:请求查询到处理的方法。Interface to be implemented by objects that define a mapping between requests and handler objects.
由定义请求和处理程序对象之间的映射关系的对象实现的接口。
-
HandlerAdapter
:真正负责请求处理。Contract that decouples the {@link DispatcherHandler} from the details of invoking a handler and makes it possible to support any handler type.
使 DispatcherHandler 与调用处理程序的详细信息分离的契约,并且可以支持任何处理程序类型。
-
HandlerResultHandler
:响应结果处理。Process the {@link HandlerResult}, usually returned by an {@link HandlerAdapter}.
处理 HandlerResult,通常由 HandlerAdapter 返回。
Spring WebFlux 实现函数式编程,两个接口:
RouterFunction
:Represents a function that routes to a {@linkplain HandlerFunction handler function}.HandlerFunction
:Represents a function that handles a {@linkplain ServerRequest request}.
基于注解编程模型
使用注解方式,与之前 Spring MVC 使用相似,只需要引入相关依赖,Spring Boot 自动配置相关运行容器,默认情况下使用 Netty 服务器。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
编写代码
public interface UserService {
/**
* 根据 id 查询 User
*
* @param id 要查询的 id
* @return 查询的结果
*/
Mono<User> getUserById(int id);
/**
* 查询所有 User
*
* @return 查询的结果
*/
Flux<User> getAllUsers();
/**
* 添加 User
*
* @param user 要添加的 User
* @return 。
*/
Mono<Void> saveUser(Mono<User> user);
}
@Service
public class UserServiceImpl implements UserService {
// 为了方便,这里使用 Map 代替数据库存储数据
private final Map<Integer, User> map = new HashMap<>();
public UserServiceImpl() {
this.map.put(1, new User(1, "Alice", 'F', 18));
this.map.put(2, new User(2, "Bella", 'F', 20));
this.map.put(3, new User(3, "Cindy", 'F', 18));
this.map.put(4, new User(4, "Diana", 'F', 20));
this.map.put(5, new User(5, "Emily", 'F', 18));
}
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(this.map.get(id));
}
@Override
public Flux<User> getAllUsers() {
return Flux.fromIterable(this.map.values());
}
@Override
public Mono<Void> saveUser(Mono<User> userMono) {
return userMono.doOnNext(user -> map.put(user.getId(), user))
.thenEmpty(Mono.empty());
}
}
@RestController
public class UserController {
UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@GetMapping("/user/{id}")
public Mono<User> getUserById(@PathVariable("id") int id) {
return userService.getUserById(id);
}
@GetMapping("/users")
public Flux<User> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping("/user")
public Mono<Void> saveUser(User user) {
Mono<User> userMono = Mono.just(user);
return userService.saveUser(userMono);
}
}
- Spring MVC 方式,同步阻塞的方式,基于 Spring MVC + Servlet + Tomcat;
- Spring WebFlux 方式,异步非阻塞的方式,基于 Spring WebFlux + Reactor + Netty。
基于函数式编程模型
- 使用函数式编程模型,需要自己初始化服务器
- 两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
- Spring WebFlux 的请求和响应不再是 ServletRequest 和 ServletResponse,而是 ServerRequest 和 ServerResponse。
-
把基于注解的复制一份,删去 controller 部分
-
创建 Handler
public class UserHandler { private final UserService userService; public UserHandler(UserService userService) { this.userService = userService; } public Mono<ServerResponse> getUserById(ServerRequest request) { // 获取 id 值 int id = Integer.parseInt(request.pathVariable("id")); // 空值处理 Mono<ServerResponse> notFound = ServerResponse.notFound().build(); // 调用 service 得到数据 Mono<User> userMono = userService.getUserById(id); // 把 userMono 进行转换返回,使用 Reactor 操作符 flatMap return userMono.flatMap(user -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(user))) .switchIfEmpty(notFound); } public Mono<ServerResponse> getAllUsers() { Flux<User> allUsers = userService.getAllUsers(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(allUsers, User.class); } public Mono<ServerResponse> saveUser(ServerRequest request) { Mono<User> userMono = request.bodyToMono(User.class); return ServerResponse.ok().build(userService.saveUser(userMono)); } }
-
初始化服务器,编写 Router
- 创建路由的方法
- 创建服务器完成适配
- 最终调用
public class Server { public static void main(String[] args) throws IOException { Server server = new Server(); server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); } // 1. 创建 Router 路由 public RouterFunction<ServerResponse> routingFunction() { // 创建 handler 对象 UserService userService = new UserServiceImpl(); UserHandler userHandler = new UserHandler(userService); // 设置路由 return RouterFunctions.route(GET("/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::getUserById) .andRoute(GET("/users").and(accept(MediaType.APPLICATION_JSON)), userHandler::getAllUsers); } // 2. 创建服务器完成适配 public void createReactorServer() { // 路由和 handler 适配 RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); // 创建服务器 HttpServer httpServer = HttpServer.create(); httpServer.handle(handlerAdapter).bindNow(); } }
-
使用 WebClient 调用
public class Client { public static void main(String[] args) { WebClient webClient = WebClient.create("http://localhost:8235"); User user = webClient.get().uri("/user/{id}", 1) .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class) .block(); System.out.println(user); Flux<User> users = webClient.get().uri("/users") .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class); users.map(User::toString).buffer().doOnNext(System.out::println).blockFirst(); } }
课程总结
-
Spring 框架概述
轻量级开源 JavaEE 框架,核心 IoC 和 AOP
-
IoC 容器
- IoC 底层原理(工厂、反射等)
- IoC 接口(BeanFactory)
- IoC 操作 Bean 管理(基于 xml)
- IoC 操作 Bean 管理(基于注解)
-
AOP
- AOP 底层原理:动态代理,有接口(JDK 动态代理),没有接口(CGLIB 动态代理)
- 术语:切入点、增强(通知)、切面
- 基于 AspectJ 实现 AOP 操作
-
JdbcTemplate
- 使用 JdbcTemplate 实现 CRUD 操作
- 使用 JdbcTemplate 实现批量操作
-
事务管理
- 事务概念
- 重要概念(传播行为、隔离级别)
- 基于注解实现声明式事务管理
- 完全注解方式
-
Spring 5 新特性
- 整合日志框架
@Nullable
注解- 函数式注册对象
- 整合 JUnit 5 单元测试框架
- Spring WebFlux