Spring5 笔记
Chr 1、Spring5 框架概述
一、Spring5 框架的概述
1、Spring是轻量级的开源的JavaEE框架
2、Spring可以解决企业应用开发的复杂性
3、Spring有两个核心部分: IOC 和 Aop
- IOC:控制反转,把创建对象过程交给 Spring 进行管理
- Aop:面向切面,不修改源代码进行功能增强
4、Spring 特点
- 方便解耦,简化开发
- Aop 编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作降低API开发难度
5、现在课程中,选取Spring版本5.x
Spring5入门案例
1、下载Spring5
-
使用Spring最新稳定版本5.2.6
-
下载地址:https://repo.spring.io/release/org/springframework/spring/
2、打开idea工具,创建普通Java工程
3、导入Spring5相关jar包
4、创建普通类,在这个类创建普通方法
public class User {
public void add() {
System.out.println("add......");
}
}
5、创建 Spring 配置文件,在配置文件配置创建的对象
- Spring 配置文件使用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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置User对象创建-->
<bean id="user" class="ltd.worldiwiu.dao.User"></bean>
</beans>
6、进行测试代码编写
@Test
public void testAdd() {
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2、获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
// 运行结果:ltd.worldiwiu.dao.User@4abdb505
IOC(控制反转)
IOC(概念和原理)
1、什么是IOC
-
控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
-
使用 IOC 目:为了耦合度降低
-
做入门案例就是 IOC 实现
2、IOC底层原理
- xml 解析、工厂模式、反射
3、画图讲解IOC底层原理
IOC(BeanFactory 接口)
1、IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
2、Spring 提供 IOC 容器实现两种方式:(两个接口)
-
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
-
ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
加载配置文件时候就会把在配置文件对象进行创建
3、ApplicationContext 接口有实现类
IOC 操作 Bean 管理(概念)
1、什么是Bean管理
-
Bean管理指的是两个操作
-
Spring创建对象
-
Spirng注入属性
2、Bean管理操作有两种方式
- 基于xml配置文件方式实现
- 基于注解方式实现
IOC 操作 Bean 管理(基于 xml 方式)
1、基于 xml 方式创建对象
-
在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
-
在 bean 标签有很多属性,介绍常用的属性
-
id 属性:唯一标识
-
class 属性:类全路径(包类路径)
-
-
创建对象时候,默认也是执行无参数构造方法完成对象创建
2、基于 xml 方式注入属性
- DI:依赖注入,就是注入属性
3、第一种注入方式:使用 set 方法进行注入
个人感觉:'set方法' 比 '有参构造' 权限高; 它们不能同时出现, 也不能只出现一部分!
① 创建类,定义属性和对应的 set 方法
/**
* 演示使用set方法进行注入属性
* 默认调用无参构造方法
*/
public class Book {
// 创建属性
private String bname;
private String bauthor;
// 无参构造
public Book(){}
// 创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public void show(){
System.out.println(bname + " => " + bauthor);
}
}
② 在 spring 配置文件配置对象创建,配置属性注入
<!--set方法注入属性-->
<bean id="book" class="ltd.worldiwiu.dao.Book">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="朝花夕拾"></property>
<property name="bauthor" value="鲁迅"></property>
</bean>
③ 测试方法
@Test
public void testBook() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// new FileSystemXmlApplicationContext("E:\\Technological Learning\\JavaWeb Frame\\Spring\\spring_test_01\\src\\main\\resources\\beans.xml");
// 2. 获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.show();
}
④ 运行结果
ltd.worldiwiu.dao.Book@24aed80c
朝花夕拾 => 鲁迅
4、第二种注入方式:使用有参数构造进行注入
① 创建类,定义属性,创建属性对应有参数构造方法
/**
* 使用有参数构造注入
*/
public class Orders {
//属性
private String bname;
private String bauthor;
//有参数构造
public Book(String bname, String bauthor){
this.bname = bname;
this.bauthor = bauthor;
}
public void show(){
System.out.println(bname + " => " + bauthor);
}
}
② 在 spring 配置文件中进行配置
<bean id="book" class="ltd.worldiwiu.dao.Book">
<!-- 使用 constructor-arg 完成属性注入
name: 类里面属性名称
value: 向属性注入的值
-->
<!-- 有参构造进行注入 -->
<constructor-arg name="bname" value="仿徨"></constructor-arg>
<constructor-arg name="bauthor" value="鲁迅"></constructor-arg>
</bean>
③ 测试方法
@Test
public void testBook() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// new FileSystemXmlApplicationContext("E:\\Technological Learning\\JavaWeb Frame\\Spring\\spring_test_01\\src\\main\\resources\\beans.xml");
// 2. 获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.show();
}
④ 运行结果
ltd.worldiwiu.dao.Book@305b7c14
仿徨 => 鲁迅
5、p 名称空间注入(了解)
- 使用 p 名称空间注入,可以简化基于 xml 配置方式
- 第一步 添加 p 名称空间在配置文件中
- 第二步 进行属性注入,在 bean 标签里面进行操作
① 在 spring 配置文件中进行配置
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
>
<!-- p名称空间注入,默认调用的是无参构造 [相当于set注入] -->
<bean id="book" class="ltd.worldiwiu.dao.Book" p:bname="野草" p:bauthor="鲁迅"></bean>
</beans>
② 运行结果
ltd.worldiwiu.dao.Book@b2c9a9c
野草 => 鲁迅
IOC 操作 Bean 管理(xml 注入其他类型属性)
1、字面量
① null 值
<!--null值-->
<property name="address">
<null/>
</property>
运行结果:
null
② 属性值包含特殊符号
<!--属性值包含特殊符号
1 把<>进行转义 < >
2 把带特殊符号内容写到CDATA
-->
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
运行结果:
<<南京>>
2、注入属性-外部 bean
① 创建两个类 service 类和 dao 类
public interface BookDao {
public void update();
}
public class BookDaoImpl implements BookDao {
@Override
public void update() {
System.out.println("BookDaoImpl 更新方法");
}
}
② 在 service 调用 dao 里面的方法
import ltd.worldiwiu.dao.BookDao;
public class BookService {
// 创建BookDao类型属性,生成set方法
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void add(){
System.out.println("service add...");
bookDao.update();
}
}
③ 在 spring 配置文件中进行配置
<!-- 外部注入 -->
<!-- service和dao对象创建 -->
<bean id="bookService" class="ltd.worldiwiu.service.BookService">
<!-- 注入BookDao对象
name属性:类(Bookservice)里面属性名称
ref属性:创建BookDao对象bean标签id值
-->
<property name="bookDao" ref="userDaoImpl"></property>
</bean>
<!-- 外部注入 -> 创建BookDao对象 -->
<bean id="userDaoImpl" class="ltd.worldiwiu.dao.BookDaoImpl"></bean>
④ 测试方法
@Test
public void testBookService() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
BookService bookService = context.getBean("bookService", BookService.class);
System.out.println(bookService);
bookService.add();
}
⑤ 运行结果
ltd.worldiwiu.service.BookService@305b7c14
service add...
BookDaoImpl 更新方法
3、注入属性-内部 bean
-
一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部
部门是一,员工是多
-
在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
① 创建部门 类( Dept ) 和员工类( Emp )
// 部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
// 员工类
public class Emp {
private String ename;
private String gender;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add() {
System.out.println(ename + "=>" + gender + "=>" + dept);
}
}
② 在 spring 配置文件中进行配置
<!-- 内部bean -->
<bean id="emp1" class="ltd.worldiwiu.dao.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Star丶Java"></property>
<property name="gender" value="男"></property>
<!-- 设置对象类型属性 -->
<property name="dept">
<bean id="dept1" class="ltd.worldiwiu.dao.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
③ 测试方法
@Test
public void testEmp() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Emp emp = context.getBean("emp1", Emp.class);
System.out.println(emp);
emp.add();
}
④ 运行结果
ltd.worldiwiu.dao.Emp@24aed80c
Star丶Java=>男=>Dept{dname='安保部'}
4、注入属性-级联赋值
① 第一种写法
<!-- 级联赋值 -->
<bean id="emp2" class="ltd.worldiwiu.dao.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Star丶Java"></property>
<property name="gender" value="男"></property>
<!-- 级联赋值 -->
<property name="dept" ref="dept2"></property>
</bean>
<bean id="dept2" class="ltd.worldiwiu.dao.Dept">
<property name="dname" value="财务部"></property>
</bean>
// 测试方法
@Test
public void testEmp() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Emp emp = context.getBean("emp2", Emp.class);
System.out.println(emp);
emp.add();
}
运行结果:
ltd.worldiwiu.dao.Emp@24aed80c
Star丶Java=>男=>Dept{dname='财务部'}
② 第二种写法
// 在 Emp 类中添加 Dept 对象
// 员工属于某一个部门,使用对象形式表示
private Dept dept;
// 生成dept的get方法
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
<!--级联赋值-->
<bean id="emp3" class="ltd.worldiwiu.dao.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Star丶Java"></property>
<property name="gender" value="男"></property>
<!-- 级联赋值 -->
<property name="dept" ref="dept3"></property> <!-- 为了连接下面的类地址 -->
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept3" class="ltd.worldiwiu.dao.Dept"></bean>
// 测试方法
@Test
public void testEmp() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Emp emp = context.getBean("emp3", Emp.class);
System.out.println(emp);
emp.add();
}
运行结果:
ltd.worldiwiu.dao.Emp@24aed80c
Star丶Java=>男=>Dept{dname='技术部'}
IOC 操作 Bean 管理(xml 注入集合属性)
1、注入数组类型属性
2、注入 List 集合类型属性
3、注入 Map 集合类型属性
4、注入 Set集合类型属性
① 创建类,定义数组、list、map、set 类型属性,生成对应 set 方法
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Stu {
// 1. 数组类型的对象
private String[] courses;
// 2. list类型的对象
private List<String> lists;
// 3. map类型的对象
private Map<String,String> maps;
// 4. set类型的对象
private Set<String> sets;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setLists(List<String> lists) {
this.lists = lists;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void show(){
System.out.println(Arrays.toString(courses));
System.out.println(lists);
System.out.println(maps);
System.out.println(sets);
}
}
② 在 spring 配置文件进行配置
<!-- xml 注入集合属性 -->
<bean id="stu" class="ltd.worldiwiu.dao.Stu">
<!-- array类型属性注入 -->
<property name="courses">
<array>
<value>Java</value>
<value>Python</value>
</array>
</property>
<!-- list类型属性注入 -->
<property name="lists">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!-- map类型属性注入 -->
<property name="maps">
<map>
<entry key="Java" value="java"></entry>
<entry key="Python" value="python"></entry>
</map>
</property>
<!-- set类型属性注入 -->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
③ 测试方法
@Test
public void testStu() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Stu stu = context.getBean("stu", Stu.class);
stu.show();
}
④ 运行结果
[Java, Python]
[张三, 小三]
{Java=java, Python=python}
[MySQL, Redis]
5、在集合里面设置对象类型值
① 在 Stu 类中添加信息
// 学生所学的多门课程
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public void show(){
System.out.println(courseList);
}
② 新建课程类
// 课程类
public class Course {
private String cname; // 课程名
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cname='" + cname + '\'' +
'}';
}
}
③ 在 spring 配置文件进行配置
<!-- xml 注入多个集合对象 -->
<bean id="stu" class="ltd.worldiwiu.dao.Stu">
<!-- 注入list集合创建多个Course对象 -->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
<ref bean="course3"></ref>
</list>
</property>
</bean>
<!--创建多个Course对象 -->
<bean id="course1" class="ltd.worldiwiu.dao.Course">
<property name="cname" value="Struts框架"></property>
</bean>
<bean id="course2" class="ltd.worldiwiu.dao.Course">
<property name="cname" value="Hibernate框架"></property>
</bean>
<bean id="course3" class="ltd.worldiwiu.dao.Course">
<property name="cname" value="Spring框架"></property>
</bean>
④ 测试方法
@Test
public void testStu() {
// 1. 加载 spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Stu stu = context.getBean("stu", Stu.class);
stu.show();
}
⑤ 运行结果
[Course{cname='Struts框架'}, Course{cname='Hibernate框架'}, Course{cname='Spring框架'}]
6、把集合注入部分提取出来
① 在 Stu 类中添加信息
// list 类型的对象
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
public void show(){
System.out.println(list);
}
② 在 spring 配置文件中引入名称空间 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:p="http://www.springframework.org/schema/p"
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">
③ 使用 util 标签完成 list 集合注入提取
<bean id="stu" class="ltd.worldiwiu.dao.Stu">
<!-- 提取 list 集合类型属性注入使用 -->
<property name="list" ref="courseList"></property>
</bean>
<!-- 提取 list 集合类型属性注入 -->
<util:list id="courseList">
<value>MyBatis框架</value>
<value>Spring5框架</value>
<value>SpringMVC框架</value>
</util:list>
④ 测试方法
@Test
public void testStu() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Stu stu = context.getBean("stu", Stu.class);
stu.show();
}
⑤ 运行结果
[MyBatis框架, Spring5框架, SpringMVC框架]
IOC 操作 Bean 管理(FactoryBean)
1、Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
(1)普通 bean:在配置文件中定义 bean 类型就是返回类型
(2)工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
① 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
② 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
2、演示代码
① Stu 类
private String[] courses;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void show(){
System.out.println(Arrays.toString(courses));
}
@Override
public String toString() {
return "Stu{" +
"courses=" + Arrays.toString(courses) +
'}';
}
② Factory 类实现 FactoryBean 接口
import org.springframework.beans.factory.FactoryBean;
public class Factory implements FactoryBean<Stu> {
/**
* 回由 FactoryBean 创建的 bean 实例,如果 isSingleton() 返回 true ,则该实例会放到 Spring 容器中单实例缓存池中
* @return
* @throws Exception
*/
@Override
public Stu getObject() throws Exception {
Stu stu = new Stu();
String array[] = {"Java1", "Python1"};
stu.setCourses(array);
return stu;
}
/**
* 返回 FactoryBean 创建的 bean 类型
* @return
*/
@Override
public Class<Stu> getObjectType() {
return Stu.class;
}
/**
* 返回由 FactoryBean 创建的 bean 实例的作用域是 singleton 还是 prototype
* @return
*/
@Override
public boolean isSingleton() {
return false;
}
}
③ 在 spring 配置文件进行配置
<!-- 工厂Bean -->
<bean id="myBean" class="ltd.worldiwiu.dao.Factory"></bean>
④ 测试方法
@Test
public void testFactory() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Stu stu = context.getBean("myBean", Stu.class);
System.out.println(stu);
}
⑤ 运行结果
ltd.worldiwiu.dao.Stu@24aed80c
IOC 操作 Bean 管理(bean 作用域)
1、在 Spring 里面,设置创建 bean 实例是单实例还是多实例
2、在 Spring 里面,默认情况下,bean 是单实例对象
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2;
}
运行显示:
ltd.worldiwiu.dao Book@5d11346a
ltd.worldiwiu.dao Book@5d11346a
3、如何设置单实例还是多实例
① 在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
② scope 属性值
- singleton:表示是单实例对象(默认值),容器只创建一个对象
- prototype:表示是多实例对象,容器创建多个对象,但容器不会去逐个销毁,默认是单例对象
- request:web项目中spring创建一个bean对象,将对象存到request域中
- session:web项目中将对象存到session域中
4、演示代码
① 在 spring 配置文件中进行配置
<bean id="book" class="com.atguigu.spring5.factorybean.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>
② 运行结果
ltd.worldiwiu.dao Book@5d11346a
ltd.worldiwiu.dao Book@7a36aefa
5、singleton 和 prototype 区别
① singleton 单实例,prototype 多实例
② 设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象
设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建对象,在调用getBean方法时候创建多实例对象
IOC 操作 Bean 管理(bean 生命周期)
1、生命周期
- 从对象创建到对象销毁的过程
2、bean 生命周期
① 通过构造器创建 bean 实例(无参数构造)
② 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
③ 调用 bean 的初始化的方法(需要进行配置初始化的方法)
④ bean 可以使用了(对象获取到了)
⑤ 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
3、演示 bean 生命周期
① 新建 Orders 类
public class Orders {
// 无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建 bean 实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用 set 方法设置属性值");
}
// 创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
// 创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
② 在 spring 配置文件中进行配置
<bean id="orders" class="com.atguigu.spring5.bean.Orders" initmethod="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
③ 测试方法
@Test
public void testBean3() {
// ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
}
④ 运行结果
第一步 执行无参数构造创建 bean 实例
第二步 调用 set 方法设置属性值
第三步 执行初始化的方法
第四步 获取创建 bean 实例对象
com.atguigu.spring5.bean.Orders@192d3247
第五步 执行销毁的方法
4、bean 的后置处理器,bean 生命周期有七步
① 通过构造器创建 bean 实例(无参构造)
② 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
③ 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
④ 调用 bean 的初始化的方法(需要进行配置初始化的方法)
⑤ 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
⑥ bean 可以使用了(对象获取到了)
⑦ 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
5、演示添加后置处理器效果
① 创建后置处理器(添加类 MyBeanPost,实现接口 BeanPostProcessor)
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
② 在 spring 配置文件中进行配置
<!--配置后置处理器-->
<bean id="myBeanPost" class="ltd.worldiwiu.dao.MyBeanPost"></bean>
③ 测试方法
@Test
public void testBean3() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
// 手动让 bean 实例销毁
((ClassPathXmlApplicationContext)context).close();
}
④ 运行结果
运行结果:
第一步 执行无参数构造创建 bean 实例
第二步 调用 set 方法设置属性值
在初始化之前执行的方法
第三步 执行初始化的方法
在初始化之后执行的方法
第四步 获取创建 bean 实例对象
com.atguigu.spring5.bean.Orders@192d3247
第五步 执行销毁的方法
IOC 操作 Bean 管理(xml 自动装配)
1、什么是自动装配
- 根据指定装配规则(属性名称或者属性类型),spring 自动将匹配的属性值进行注入
2、演示自动装配过程
① 创建 Dept 类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
② 创建 Emp 类
public class Emp {
private String ename;
private String gender;
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public Dept getDept() {
return dept;
}
@Override
public String toString() {
return "Emp{" +
"ename='" + ename + '\'' +
", gender='" + gender + '\'' +
", dept=" + dept +
'}';
}
}
③ 在 spring 配置文件中进行配置
- 根据属性名称自动注入
<!-- 自动装配
autowire(自动装配):
byName: 根据属性名称注入, 注入值 bean 的 id 值和类属性名称一样
byType: 根据属性类型注入
-->
<bean id="emp_z" class="ltd.worldiwiu.dao.Emp" autowire="byName">
<!-- <property name="dept" ref="dept_z"></property> -->
</bean>
<bean id="dept_z" class="ltd.worldiwiu.dao.Dept"></bean>
- 根据属性类型自动注入
<!-- 自动装配
autowire(自动装配):
byName: 根据属性名称注入, 注入值 bean 的 id 值和类属性名称一样
byType: 根据属性类型注入
-->
<bean id="emp_z" class="ltd.worldiwiu.dao.Emp" autowire="byType">
<!-- <property name="dept" ref="dept_z"></property> -->
</bean>
<bean id="dept_z" class="ltd.worldiwiu.dao.Dept"></bean>
④ 测试方法
@Test
public void testEmp_z() {
// 1. 加载spring配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
Emp emp = context.getBean("emp_z", Emp.class);
System.out.println(emp);
}
⑤ 运行结果
Emp{ename='null', gender='null', dept=Dept{dname='null'}}
IOC 操作 Bean 管理(外部属性文件)
1、直接配置数据库信息
(1)配置德鲁伊连接池信息
(2)引入德鲁伊连接池依赖 jar 包 [ druid-1.1.9.jar ]
<!--meven 项目里的 pom.xml 中添加包属性-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!-- 直接配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
2、引入外部属性文件配置数据库连接池
(1)创建外部属性文件,properties 格式文件,写数据库连接信息
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.userName=root
prop.password=0000
(2)把外部 properties 属性文件引入到 spring 配置文件中
① 引入 context 名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
② 在 spring 配置文件使用标签引入外部属性文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
IOC 操作 Bean 管理(基于注解方式)
1、什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值...)
(2)使用注解,注解作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml 配置
2、Spring 针对 Bean管理中创建对象提供注解
(1)@Component [ 所有层均可用 ]
(2)@Service [ Service层、业务逻辑层 ]
(3)@Controller [ Web层 ]0
(4)@Reposotory [ DAO层 / 持久层 ]
上面四个注解工能是一样的,都可以来创建 bean 实例
3、基于注解方式实现对象创建
(1)引入依赖 => [ spring.-aop-5.2.6.RELEASE.jar ]
(2)开启组件扫描
<!-- 开启组件扫描
如果扫描多个包,包名之间可以用逗号隔开
扫描包上层目录
-->
<context:component-scan base-package="ltd.worldiwiu"></context:component-scan>
(3)创建类,在类上面添加创建对象注解
import org.springframework.stereotype.Component;
// 在注解里面 value 属性值可以省略不写,
// 默认值是类名称,首字母小写
// UserService -> userService
@Component(value = "userService") // <bean id="userService" class=".."/>
@Component // 这样写也行
public class UserService {
public void add(){
System.out.println("service add...");
}
}
// 测试方法
@Test
public void testBookService() {
// 1. 加载spring配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
BookService bookService = context.getBean("bookService", BookService.class);
System.out.println(bookService);
bookService.add();
}
运行结果:
ltd.worldiwiu.service.BookService@4f2b503c
service add...
4、开启组件扫描细节配置
<!-- 示例1
如果扫描多个包,包名之间用逗号隔开
扫描包的上层目录
base-package: 需要扫描的包
use-default-filters: true > 扫描全部组件
false > 只扫描部分组件
-->
<context:component-scan base-package="ltd.worldiwiu" use-default-filters="false">
<!--
include-filter: 设置扫描指定内容类的包
exclude-filter: 设置不扫描指定内容类的包
type: annotation > 按照注解进行扫描, 标注了指定注解的组件不要扫描
assignable > 指定排除某个具体的类, 按照类排除
expression: 注解的全类名
-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
5、基于注解方式实现属性注入
xml 文件中只添加扫描即可
<!-- 开启组件扫描
如果扫描多个包,包名之间用逗号隔开
扫描包的上层目录
-->
<context:component-scan base-package="ltd.worldiwiu"></context:component-scan>
(1)@Autowired: 根据属性类型进行自动装配
① 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
② 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
演示代码
// BookDao 接口
public interface BookDao {
public void update();
}
// 实现 BookDao 接口
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void update() {
System.out.println("BookDao > BookDaoImpl...");
}
}
// BookService 类
import ltd.worldiwiu.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
// 定义 dao 类型属性
// 不需要添加 set 方法
// 添加注入属性注解
@Autowired // 根据属性类型进行自动装配
private BookDao bookDao;
public void add(){
System.out.println("service > add...");
bookDao.update();
}
}
<!-- 开启组件扫描
如果扫描多个包,包名之间用逗号隔开
扫描包的上层目录
-->
<context:component-scan base-package="ltd.worldiwiu"></context:component-scan>
// 测试方法
@Test
public void testBookService() {
// 1. 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 2. 获取配置创建的对象
BookService bookService = context.getBean("bookService", BookService.class);
System.out.println(bookService);
bookService.add();
}
运行结果:
ltd.worldiwiu.service.BookService@36916eb0
service > add...
BookDao > BookDaoImpl...
(2)@Qualifier: 根据名称进行注入
这个 @Qualifier 注解的使用,和上面 @Autowired 一起使用
演示代码
// 添加 @Repository 注解的名称
// 实现 BookDao 接口
import org.springframework.stereotype.Repository;
@Repository(value = "bookDaoImpl_")
public class BookDaoImpl implements BookDao {
@Override
public void update() {
System.out.println("BookDao > BookDaoImpl...");
}
}
// 添加 @Qualifier 注解
// BookService 类
import ltd.worldiwiu.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookService {
// 定义 dao 类型属性
// 不需要添加 set 方法
// 添加注入属性注解
@Autowired // 根据属性类型进行自动装配
@Qualifier(value = "bookDaoImpl_") // 根据名称进行注入
private BookDao bookDao;
public void add(){
System.out.println("service > add...");
bookDao.update();
}
}
运行结果:
ltd.worldiwiu.service.BookService@12aba8be
service > add...
BookDao > BookDaoImpl...
(3)@Resource: 可以根据类型注入,可以根据名称注入
演示代码
import ltd.worldiwiu.dao.BookDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class BookService {
// @Resource // 直接写的话,就直接根据类型进行注入
// 根据名称注入,跟 BookDaoImpl 类的注解 【@Repository(value = "bookDaoImpl_")】的名称一样
@Resource(name = "bookDaoImpl_")
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void add(){
System.out.println("service > add...");
bookDao.update();
}
}
运行结果:
ltd.worldiwiu.service.BookService@53ce1329
service > add...
BookDao > BookDaoImpl...
(4)@Value: 注入普通类型属性
import ltd.worldiwiu.dao.BookDao;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;
@Service
public class BookService {
// @Resource // 直接写的话,就直接根据类型进行注入
// 根据名称注入,跟 BookDaoImpl 类的注解 【@Repository(value = "bookDaoImpl_")】的名称一样
@Resource(name = "bookDaoImpl_")
private BookDao bookDao;
@Value("abc")
private String name;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void add(){
System.out.println("service > add...");
System.out.println("name => " + name);
bookDao.update();
}
}
运行结果:
ltd.worldiwiu.service.BookService@437da279
service > add...
name => abc
BookDao > BookDaoImpl...
6、完成注解开发
(1)创建配置类,替代 xml 配置文件
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 作为配置类,替代 xml 配置文件
// 替换 xml文件中 > context:component-scan base-package="ltd.worldiwiu"></context:component-scan>
@ComponentScan(basePackages = {"ltd.worldiwiu"})
public class SpringConfig {}
(2)编写测试类
@Test
public void testBookService() {
// 1. 加载spring配置文件
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 获取配置创建的对象
BookService bookService = context.getBean("bookService", BookService.class);
System.out.println(bookService);
bookService.add();
}
(3)运行结果
ltd.worldiwiu.service.BookService@306e95ec
service > add...
name => abc
BookDao > BookDaoImpl...
AOP(面向切面编程)
AOP(基本概念)
什么是AOP
(1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
(3)使用登录例子说明 AOP
AOP(底层原理)
AOP底层使用动态代理
(1)有接口情况
- 创建接口实现类代理对象,增强类的方法
(2)没有接口情况
- 创建子类的代理对象,增强类的方法
AOP(JDK 动态代理)
1、使用 JDK 动态代理,使用 Proxy
☆ 调用 newProxyInstance 方法
static Object newProxyInstance(ClassLoader loder, 类<?>[] interface, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
① ClassLoader loder: 类加载器
② 类<?>[] interface: 增强方法所在的类,这个类实现的接口,支持多个接口
③ InvocationHandler h: 实现这个接口 InvocationHandler ,创建代理对象,写增强的部分
2、编写 JDK 动态代理代码
(1)创建接口,定义方法
public interface BookDao {
public int add(int a, int b);
public String update(String id);
}
(2)创建接口实现类,实现方法
public class BookDaoImpl implements BookDao {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public String update(String id) {
System.out.println("BookDao > BookDaoImpl...");
return id;
}
}
(3)使用 Proxy 类创建接口代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class BookProxy {
public static void main(String[] args) {
// 创建接口实现类代理对象
Class[] interfaces = {BookDao.class};
/*
Proxy.newProxyInstance(BookProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
*/
BookDaoImpl bookDaoImpl = new BookDaoImpl();
BookDao bookDao = (BookDao) Proxy.newProxyInstance(BookProxy.class.getClassLoader(), interfaces, new BookDaoProxy(bookDaoImpl));
int result = bookDao.add(8,9);
String res = bookDao.update("2018");
System.out.println("add > " + result + "\tupdate > " + res);
}
}
// 创建代理对象类
class BookDaoProxy implements InvocationHandler{
// 把需要代理对象传递过来
// 有参构造传递
private Object object;
public BookDaoProxy(Object object){
this.object = object;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.out.println("方法之前执行..." + method.getName() + "传递的参数: " + Arrays.toString(args));
// 被增强的方法执行
Object result = method.invoke(object, args);
// 方法之后
System.out.println("方法之后执行..." + object);
return result;
}
}
(4)运行结果
方法之前执行...add传递的参数: [8, 9]
BookDaoImpl > add...
方法之后执行...ltd.worldiwiu.dao.BookDaoImpl@2f92e0f4
方法之前执行...update传递的参数: [2018]
BookDaoImpl > update...
方法之后执行...ltd.worldiwiu.dao.BookDaoImpl@2f92e0f4
add > 17 update > 2018
AOP(术语)
1、连接点
类里面哪些方法可以被增强,这些称为连接点
2、切入点
实际被真正增强的方法称为切入点
3、通知(增强)
(1)实际增强的逻辑部分称为通知(增强)
(2)通知有多种类型
① 前置通知:在切入点前面执行
② 后置通知:在切入点后面执行
③ 环绕通知:在切入点前、后都执行
④ 异常通知:在切入点异常时执行
⑤ 最终通知:在最后执行(相当于 finally )
4、切面 [ 是动作操作 ]
把通知应用到切入点过程
AOP操作(准备工作)
1、Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spring框架一起使用,进行 AOP 操作
2、基于 AspectJ 实现 AOP 操作
(1)基于 xml 配置文件实现
(2)基于注解方式实现(使用)
3、在项目工程里面引入 AOP 相关依赖
<!-- meven 工程中需要在 pom.xml 文件中加入即可以 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
4、切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
① 对 ltd.worldiwiu.dao.BookDao 类里面的 add 进行增强
execution(* ltd.worldiwiu.dao.BookDao.add(..))
② 对 ltd.worldiwiu.dao.BookDao 类里面的所有的方法进行增强
execution(* ltd.worldiwiu.dao.BookDao.*(..))
③ 对 ltd.worldiwiu.dao 包里面的所有类,类里面 所有方法进行增强
execution(* ltd.worldiwiu.dao.☆.☆(..))
AOP 操作(AspectJ 注解)
1、创建类,在类里面定义方法
// 被增强的类
public class User {
public void add(){
System.out.println("User > add...");
}
}
2、创建增强类(编写增强逻辑)
- 在增强类里面,创建方法,让不同方法代表不同通知类型.
// 被增强的类
public class User {
public void add(){
System.out.println("User > add...");
}
}
// 被增强的类
public class UserProxy {
// 前置通知
public void before (){
System.out.println("UserProxy > before");
}
}
3、进行通知的配置
(1)在 Spring 配置文件中,开启 注解扫描
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="ltd.worldiwiu.aop"></context:component-scan>
</beans>
(2)使用注解创建 User 和 UserProxy 对象
import org.springframework.stereotype.Component;
// 被增强的类
@Component
public class User {
public void add(){
System.out.println("User > add...");
}
}
import org.springframework.stereotype.Component;
// 被增强的类
@Component
public class UserProxy {
// 前置通知
public void before (){
System.out.println("UserProxy > before");
}
}
(3)在增强类上面添加注解 @Aspect
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
// 被增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {
// 前置通知
public void before (){
System.out.println("UserProxy > before");
}
}
(4)在 Spring 配置文件中开启生成代理对象
<!-- 开启Aspect生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、配置不同类型的通知
- 在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 被增强的类
@Component
@Aspect
public class UserProxy {
// 前置通知
// @Before 注解表示前置通知
@Before("execution(* ltd.worldiwiu.aop.User.add(..))")
public void before (){
System.out.println("UserProxy > before");
}
// 最终通知
// @After 注解表示最终通知
@After("execution(* ltd.worldiwiu.aop.User.add(..))")
public void after (){
System.out.println("UserProxy > after");
}
// 后置通知(返回通知)
// @AfterReturning 注解表示返回值之后执行
@AfterReturning("execution(* ltd.worldiwiu.aop.User.add(..))")
public void afterReturning (){
System.out.println("UserProxy > afterReturning");
}
// 异常通知
// @AfterThrowing 注解表示异常通知
@AfterThrowing("execution(* ltd.worldiwiu.aop.User.add(..))")
public void afterThrowing (){
System.out.println("UserProxy > afterThrowing");
}
// 环绕通知
// @Around 注解表示环绕通知
@Around("execution(* ltd.worldiwiu.aop.User.add(..))")
public void around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("UserProxy > aroundBefore");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("UserProxy > aroundAfter");
}
}
运行结果:
UserProxy > aroundBefore
UserProxy > before
User > add...
UserProxy > aroundAfter
UserProxy > after
UserProxy > afterReturning
5、相同 的切入点抽取
// 相同切入点抽取
@Pointcut("execution(* ltd.worldiwiu.aop.User.add(..))")
public void pointcut (){}
// 前置通知
// @Before 注解表示前置通知
@Before("pointcut()")
public void before (){
System.out.println("UserProxy > before");
}
6、有多个增强多同一个方法进行增强,设置增强优先级
- 有多个增强多同一个方法进行增强
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class User2Proxy {
@Before("execution(* ltd.worldiwiu.aop.User.add(..))")
public void before (){
System.out.println("User2Proxy > before");
}
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class User3Proxy {
@Before("execution(* ltd.worldiwiu.aop.User.add(..))")
public void before (){
System.out.println("User3Proxy > before");
}
}
运行结果:
User2Proxy > before
User3Proxy > before
UserProxy > aroundBefore
UserProxy > before
User > add...
UserProxy > aroundAfter
UserProxy > after
UserProxy > afterReturning
- 在增强类上面添加注解 @Order(数字类型值),数字型值越小优先级越高
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(2)
public class UserProxy {
// 相同切入点抽取
@Pointcut("execution(* ltd.worldiwiu.aop.User.add(..))")
public void pointcut (){}
// 前置通知
// @Before 注解表示前置通知
@Before("pointcut())")
public void before (){
System.out.println("UserProxy > before");
}
......
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(3)
public class User2Proxy {
// 相同切入点抽取
@Pointcut("execution(* ltd.worldiwiu.aop.User.add(..))")
public void pointcut (){}
// 前置通知
// @Before 注解表示前置通知
@Before("pointcut())")
public void before (){
System.out.println("User2Proxy > before");
}
......
}
// 被增强的类
@Component
@Aspect
@Order(1)
public class User3Proxy {
// 相同切入点抽取
@Pointcut("execution(* ltd.worldiwiu.aop.User.add(..))")
public void pointcut (){}
// 前置通知
// @Before 注解表示前置通知
@Before("pointcut())")
public void before (){
System.out.println("User3Proxy > before");
}
......
}
运行结果:
User3Proxy > aroundBefore
User3Proxy > before
UserProxy > aroundBefore
UserProxy > before
User2Proxy > aroundBefore
User2Proxy > before
User > add...
User2Proxy > aroundAfter
User2Proxy > after
User2Proxy > afterReturning
UserProxy > aroundAfter
UserProxy > after
UserProxy > afterReturning
User3Proxy > aroundAfter
User3Proxy > after
User3Proxy > afterReturning
7、完全使用注解开发
- 创建配置类,不需要创建 xml 配置文件
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"ltd.worldiwiu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class UserConfig {}
AOP 操作(AspectJ 配置文件)
1、创建两个类,增强类和被增强类,创建方法
2、在 spring 配置文件中创建两个类对象
<!-- 创建对象 -->
<bean id="user" class="ltd.worldiwiu.aop.User"></bean>
<bean id="userProxy" class="ltd.worldiwiu.aop.UserProxy"></bean>
3、在 spring 配置文件中配置切入点
<!-- 创建对象 -->
<bean id="user" class="ltd.worldiwiu.aop.User"></bean>
<bean id="userProxy" class="ltd.worldiwiu.aop.UserProxy"></bean>
<!-- 配置aop增强 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="p" expression="execution(* ltd.worldiwiu.aop.User.add(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="userProxy">
<!-- 增强作用在具体的方法上 -->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
JdbcTemplate
JdbcTemplate(概念和准备)
1、什么的 JdbcTemplate
- Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
2、准备工作
(1)引入相关 jar 包
(2)在 spring 配置文件配置数据库连接池
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.userName=root
prop.password=0000
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
(3)配置 JdbcTemplate 对象,注入 DataSource
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入 dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
(4)创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象
- xml 配置文件
<!-- 组件扫描 -->
<context:component-scan base-package="ltd.worldiwiu"></context:component-scan>
- Service
import ltd.worldiwiu.template.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
// 定义 dao 类型属性
// 不需要添加 set 方法
// 添加注入属性注解
@Autowired // 根据类型进行注入
private BookDao bookDao;
}
- Dao
// BookDao 接口
public interface BookDao {
}
// BookDaoImpl 类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
// 注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
JdbcTemplate 操作数据库
1、对应数据库创建实体类
public class Book {
// 表里的属性
private String id;
private String title;
private String version;
private String author;
private String status;
private String price;
private String house;
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getVersion() {
return version;
}
public String getAuthor() {
return author;
}
public String getStatus() {
return status;
}
public String getPrice() {
return price;
}
public String getHouse() {
return house;
}
public void setId(String id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setVersion(String version) {
this.version = version;
}
public void setAuthor(String author) {
this.author = author;
}
public void setStatus(String status) {
this.status = status;
}
public void setPrice(String price) {
this.price = price;
}
public void setHouse(String house) {
this.house = house;
}
}
2、JdbcTemplate 操作数据库(添加)
- 编写 service 和 dao
(1)在 dao 进行数据库添加操作
(2)调用 JdbcTemplate 对象里面 update 方法实现添加操作
import ltd.worldiwiu.template.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
// 注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 添加的方法
@Override
public void add(Book book) {
// 创建sql语句
String sql = "insert into user values(?, ?, ?, ?, ?)";
// 调用方法实现
Object[] args = {book.getId(), book.getTitle(), book.getAuthor(), book.getStatus(), book.getHouse()};
int update = jdbcTemplate.update(sql, args);
System.out.println("影响" + update + "行数");
}
}
(3)测试类
import ltd.worldiwiu.template.entity.Book;
import ltd.worldiwiu.template.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBook {
@Test
public void testJdbcTemplate (){
ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setId("ISBN 987-7-04-039661-4");
book.setTitle("《工程数学·线性代数(同济)》");
book.setVersion("VI");
book.setAuthor("同济大学数学系");
book.setStatus("在库");
book.setPrice("22.20");
book.setHouse("高等教育出版社");
bookService.addBook(book);
}
}
3、JdbcTemplate 操作数据库(修改)
// 修改方法
@Override
public void editBook(Book book, String str) {
// 创建sql语句
String sql = "update book set id=?, title=?, version=?, author=?, status=?, price=?, house=? where id=? or title=?";
// 调用方法实现
Object[] args = {book.getId(), book.getTitle(), book.getVersion(), book.getAuthor(), book.getStatus(), book.getPrice(), book.getHouse(), str, str};
int update = jdbcTemplate.update(sql, args);
System.out.println("影响" + update + "行数");
}
4、JdbcTemplate 操作数据库(删除)
// 删除方法
@Override
public void deleteBook(String str) {
// 创建sql语句
String sql = "delete from book where id=? or title=?";
// 调用方法实现
Object[] args = {str, str};
int update = jdbcTemplate.update(sql, args);
System.out.println("影响" + update + "行数");
}
5、JdbcTemplate 操作数据库(查询表记录数)
// 查询表记录数方法
@Override
public int selectCountBook() {
// 创建sql语句
String sql = "select count(*) from book";
// 调用方法实现
/**
* queryForObject(String sql, Class<T> requiredType)
* 第一个参数:sql 语句
* 第二个参数:返回类型 Class
*/
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
6、JdbcTemplate 操作数据库(查询某条记录)
// 查询返回对象
@Override
public Book findBookInfo(String str) {
// 创建sql语句
String sql = "select * from book where id=? or title=?";
// 调用方法实现
/**
* queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
* 第一个参数:sql 语句
* 第二个参数:RowMapper为接口,返回不同类型数据,使用这个接口里面实现类完成数据封装
* 第三个参数:sql语句值
*/
Object[] args = {str, str};
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), args);
return book;
}
7、JdbcTemplate 操作数据库(查询返回集合 / 批量查询)
// 查询返回集合
@Override
public List<Book> findAllBook() {
// 创建sql语句
String sql = "select * from book";
// 调用方法实现
/**
* query(String sql, RowMapper<T> rowMapper, Object... args)
* 第一个参数:sql 语句
* 第二个参数:RowMapper为接口,返回不同类型数据,使用这个接口里面实现类完成数据封装
* 第三个参数:sql语句值
*/
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
8、JdbcTemplate 操作数据库(批量添加)
// 批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
// 创建sql语句
String sql = "insert into book values(?, ?, ?, ?, ?, ?, ?)";
// 调用方法实现
/**
* batchUpdate(String sql, List<Object[]> batchArgs)
* 第一个参数:sql 语句
* 第二个参数:List集合,添加多条记录数据
*/
int[] batcharg = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(batcharg));
}
// 批量添加测试语句
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {"ISBN 987-7-04-039661-5", "《工程数学·线性代数(同济)5》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社"};
Object[] arg2 = {"ISBN 987-7-04-039661-6", "《工程数学·线性代数(同济)6》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社"};
Object[] arg3 = {"ISBN 987-7-04-039661-7", "《工程数学·线性代数(同济)7》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社"};
batchArgs.add(arg1);
batchArgs.add(arg2);
batchArgs.add(arg3);
// 批量添加
bookService.batchAddBook(batchArgs);
9、JdbcTemplate 操作数据库(批量修改)
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
// 创建sql语句
String sql = "delete from book where id=? or title=?";
// 调用方法实现
/**
* batchUpdate(String sql, List<Object[]> batchArgs)
* 第一个参数:sql 语句
* 第二个参数:List集合,添加多条记录数据
*/
int[] batcharg = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(batcharg));
}
// 批量修改测试语句
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {"ISBN 987-7-04-039661-51", "《工程数学·线性代数(同济)51》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社", "《工程数学·线性代数(同济)5》", "《工程数学·线性代数(同济)5》"};
Object[] arg2 = {"ISBN 987-7-04-039661-61", "《工程数学·线性代数(同济)61》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社", "ISBN 987-7-04-039661-6", "ISBN 987-7-04-039661-6"};
Object[] arg3 = {"ISBN 987-7-04-039661-71", "《工程数学·线性代数(同济)17》", "VI", "同济大学数学系", "在库", "22.20", "高等教育出版社", "《工程数学·线性代数(同济)7》", "《工程数学·线性代数(同济)7》"};
batchArgs.add(arg1);
batchArgs.add(arg2);
batchArgs.add(arg3);
// 批量修改
bookService.batchEditBook(batchArgs);
10、JdbcTemplate 操作数据库(批量删除)
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
// 创建sql语句
String sql = "delete from book where id=? or title=?";
// 调用方法实现
/**
* batchUpdate(String sql, List<Object[]> batchArgs)
* 第一个参数:sql 语句
* 第二个参数:List集合,添加多条记录数据
*/
int[] batcharg = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(batcharg));
}
// 批量删除测试语句
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {"ISBN 987-7-04-039661-51", "《工程数学·线性代数(同济)51》"};
Object[] arg2 = {"《工程数学·线性代数(同济)61》", "《工程数学·线性代数(同济)61》"};
Object[] arg3 = {"ISBN 987-7-04-039661-71", "《工程数学·线性代数(同济)17》"};
batchArgs.add(arg1);
batchArgs.add(arg2);
batchArgs.add(arg3);
// 批量删除
bookService.batchDeleteBook(batchArgs);
事务操作
事务概念
1、什么是事务
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
(2)典型场景:银行转账
-
lucy 转账 100 元 给 mary
-
lucy 少 100,mary 多 100
2、事务四个特性(ACID)
(1)原子性 (2)一致性 (3)隔离性 (4)持久性
事务操作(搭建事务操作环境)
1、创建数据库表,添加记录
2、创建 service,搭建 dao,完成对象创建和注入关系
import ltd.worldiwiu.template.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
3、在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
① UserDaoImpl 类
import ltd.worldiwiu.template.entity.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// 多钱
@Override
public void addMoney(double money, String s_user, String z_user) {
// 创建sql语句
String sql = "update user set money = money + ? where username = ?";
// 调用方法实现
Object[] args = {money, s_user};
int update = jdbcTemplate.update(sql, args);
System.out.println(z_user + "向你转账" + money + "元!");
}
// 少钱
@Override
public void reduceMoney(double money, String user, String pwd) {
String pwd_md5 = null;
try {
// MD5 加密
pwd_md5 = DigestUtils.md5Hex(pwd.getBytes("UTF-8"));
}catch (Exception e){
System.err.println("密码加密失败" + e);
}
String pwdselsql = "select password from user where username = ?";
Object[] pwdselargs = {user};
// 支付方用户
User pwdselud = jdbcTemplate.queryForObject(pwdselsql, new BeanPropertyRowMapper<User>(User.class), pwdselargs);
// System.out.println("pwdselud > " + pwdselud.getPassword());
// System.out.println("pwd_md5 > " + pwd_md5);
if(pwdselud.getPassword().equalsIgnoreCase(pwd_md5)){
// 创建sql语句
String sql = "update user set money = money - ? where username = ?";
// 调用方法实现
Object[] args = {money, user};
int update = jdbcTemplate.update(sql, args);
System.out.println("成功转账" + money + "元!");
} else {
System.err.println("密码错误!");
}
}
}
② UserService 类
import ltd.worldiwiu.template.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void transfer (double money, String z_user, String s_user, String pwd){
if(money > 0){
try {
userDao.reduceMoney(money, z_user, pwd);
userDao.addMoney(money, s_user, z_user);
System.err.println("转账成功!");
} catch (Exception e) {
e.printStackTrace();
System.err.println("转账失败!");
}
} else {
System.err.println("转账失败!");
}
}
}
③ TestUser 类
import ltd.worldiwiu.template.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUser {
@Test
public void texTransfer (){
ApplicationContext context =
new ClassPathXmlApplicationContext("bean-jdbc.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.transfer(999.9, "猪八戒", "唐僧", "123456");
}
}
④ 运行结果
成功转账999.9元!
猪八戒向你转账999.9元!
转账成功!
4、上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
5、MD5 加密测试
import org.apache.commons.codec.digest.DigestUtils;
public class TestMD5 {
public static void md5(String str){
try {
String md5 = DigestUtils.md5Hex(str.getBytes("UTF-8"));
System.out.println(md5);
}catch (Exception e){
System.err.println("密码加密失败" + e);
}
}
public static void main(String[] args) {
md5("123456");
}
}
运行结果: MD5 32位
e10adc3949ba59abbe56e057f20f883e
事务操作(Spring 事务管理介绍)
1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
- 有两种方式:编程式事务管理和声明式事务管理(使用)
3、声明式事务管理
(1)基于注解方式(使用)
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
事务操作(注解声明式事务管理)
1、在 Spring 配置文件配置事务管理器
<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在 Spring 配置文件,开启事务注解
(1)在 Spring 配置文件引入名称空间 tx
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
(2)开启事务注解
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在 Service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果在类上面,这个类里面所有的方法都添加事务
(3)如家在方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
...
}
事务操作(声明式事务管理参数配置)
1、在 service 类上面添加注解 @Transactional,在这个注解可以配置事务相关参数
2、propagation:事务传播行为
(1)多事务方法直接进行调用,这个过程中事务 是如何进行管理的
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {
...
}
3、ioslation:事务隔离级别
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
(3)脏读:一个未提交事务读取到另一个未提交事务的数据
(4)不可重复读:一个未提交事务读取到另一提交事务修改数据
(5)虚读:一个未提交事务读取到另一提交事务添加数据
(6)解决:通过设置事务隔离级别,解决读问题
@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class UserService {
...
}
4、timeout:超时时间
(1)事务需要在一点时间内进行提交,如果不提交进行回滚
(2)默认值是-1,设置时间以秒单位进行计算
5、readOnly:是否只读
(1)读:查询操作;写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,只能查询
6、rollbackFor:回滚
设置出现哪些异常进行事务回滚
7、noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
事务操作(XML 事务管理)
- 在 Spring 配置文件中进行配置
① 配置事务管理器
② 配置通知
③ 配置切入点和切面
<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txadvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定哪种规则的方法上面添加事务 -->
<tx:method name="accountMoney" propagation="REQUIRED" />
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))" />
<!-- 配置切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
事务操作(完全注解声明式事务管理)
1、创建配置类,使用配置类替代 xml 配置文件
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration // 作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"ltd.worldiwiu.template"}) // 替换 xml文件中 > <context:component-scan base-package="ltd.worldiwiu.template"></context:component-scan>
// @PropertySource("classpath:jdbc.properties")// 路径不对 // 替换 xml文件中 > <context:property-placeholder location="classpath:jdbc.properties"/>
@EnableTransactionManagement // 开启事务 替换 xml文件中 > <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
public class SpringConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("0000");
// dataSource.setDriverClassName("prop.driverClass");
// dataSource.setUrl("prop.url");
// dataSource.setUsername("prop.userName");
// dataSource.setPassword("prop.password");
return dataSource;
}
// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
// 到IOC容器中根据类型找到DataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入DataSource对象
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
2、测试方法
@Test
public void textTransfer (){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.transfer(888.88, "唐僧", "猪八戒","123456");
}
3、运行结果
成功转账888.88元!
唐僧向你转账888.88元!
转账成功!
Spring5 框架新功能
Spring5 框架新功能
1、整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除
2、Spring 5.0 框架自带了通用的日志封装
(1)Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
(2)Spring5 框架整合 Log4j
① pom.xml 进行配置
<!-- log4j 依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
② 创建 log4j2.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL -->
<!--Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,
当设置成 trace 时,可以看到 log4j2 内部各种详细输出--> <configuration status="INFO">
<!--先定义所有的 appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义 logger,只有定义 logger 并引入的 appender,appender 才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为
默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
(3)Spring5 框架整合 Log4j2
① 引入 jar 包
② pom.xml 进行配置
<!-- log4j2 依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
③ 创建 log4j2.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- status : 指定log4j本身的打印日志的级别.ALL< Trace < DEBUG < INFO < WARN < ERROR
< FATAL < OFF。 monitorInterval : 用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s. -->
<Configuration status="WARN" monitorInterval="30">
<Properties>
<!-- 配置日志文件输出目录 ${sys:user.home} -->
<Property name="LOG_HOME">D:/logs</Property>
<property name="ERROR_LOG_FILE_NAME">D:/logs</property>
<property name="WARN_LOG_FILE_NAME">D:/logs</property>
<property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t-%L] %-5level %logger{36} - %msg%n</property>
</Properties>
<Appenders>
<!--这个输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="trace" onMatch="ACCEPT"
onMismatch="DENY" />
<!-- 输出日志的格式 -->
<!--
%d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间
%p : 日志输出格式
%c : logger的名称
%m : 日志内容,即 logger.info("message")
%n : 换行符
%C : Java类名
%L : 日志输出所在行数
%M : 日志输出所在方法名
hostName : 本地机器名
hostAddress : 本地ip地址 -->
<PatternLayout
pattern="${PATTERN}" />
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 -->
<!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
<File name="log" fileName="logs/test.log" append="false">
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。 modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am. -->
<!-- 关键点在于 filePattern后的日期格式,以及TimeBasedTriggeringPolicy的interval,
日期格式精确到哪一位,interval也精确到哪一个单位 -->
<!-- log4j2的按天分日志文件 : info-%d{yyyy-MM-dd}-%i.log-->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<!-- SizeBasedTriggeringPolicy:Policies子节点, 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小. -->
<!-- <SizeBasedTriggeringPolicy size="2 kB" /> -->
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${WARN_LOG_FILE_NAME}/warn.log"
filePattern="${WARN_LOG_FILE_NAME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="2 kB" />
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20" />
</RollingFile>
<RollingFile name="RollingFileError" fileName="${ERROR_LOG_FILE_NAME}/error.log"
filePattern="${ERROR_LOG_FILE_NAME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd-HH-mm}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<!-- log4j2的按分钟 分日志文件 : warn-%d{yyyy-MM-dd-HH-mm}-%i.log-->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<!-- <SizeBasedTriggeringPolicy size="10 MB" /> -->
</Policies>
</RollingFile>
</Appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<Loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!-- 第三方日志系统 -->
<logger name="org.springframework.core" level="info" />
<logger name="org.springframework.beans" level="info" />
<logger name="org.springframework.context" level="info" />
<logger name="org.springframework.web" level="info" />
<logger name="org.jboss.netty" level="warn" />
<logger name="org.apache.http" level="warn" />
<!-- 配置日志的根节点 -->
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</Loggers>
</Configuration>
private static final Logger logger=LogManager.getLogger(AmSignSrvController.class);
3、Spring5 框架核心容器支持 @Nullable 注解
(1)@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
(2)注解用在方法上面,方法返回值可以为空
@Nullable
String getId();
(3)注解使用在方法参数里面,方法参数可以为空
(4)注解使用在属性上面,属性值可以为空
@Nullable
private String bookName;
4、Spring5 核心容器支持函数式风格 GenericApplicationContext
@Test
public void testGenericApplicationContext() {
//1 创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用 context 的方法对象注册
context.refresh();
context.registerBean("user1", User.class, () -> new User());
//3 获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
5、Spring5 支持整合 JUnit5
(1)整合 JUnit4
① 引入 Spring 相关针对测试依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
② 创建测试类,使用注解方法完成
import ltd.worldiwiu.template.config.Spring5Config;
import ltd.worldiwiu.template.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) // 单元测试版本
/**
* 1. 加载spring配置文件
* ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
* ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
*/
//@ProfileValueSourceConfiguration(Spring5Config.class) // 不知道还需要在哪配置
@ContextConfiguration("classpath:bean-jdbc.xml") // 加载配置文件
public class JTest4 {
/**
* 2. 获取配置创建的对象
* UserService userService = context.getBean("userService", UserService.class);
*/
@Autowired
private UserService userService;
@Test
public void testJunit4 (){
userService.transfer(666.66, "猪八戒", "孙悟空","123456");
}
}
(2)Spring5 整合 JUnit5
① 引入 JUnit5 的jar包
org.junit.jupiter:junit-jupiter:5.7.0
② 创建测试类,使用注解完成
import ltd.worldiwiu.template.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class) // 单元测试版本
/**
* 1. 加载spring配置文件
* ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
* ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
*/
@ContextConfiguration("classpath:bean-jdbc.xml") // 加载配置文件
public class JTest5 {
/**
* 2. 获取配置创建的对象
* UserService userService = context.getBean("userService", UserService.class);
*/
@Autowired
private UserService userService;
@Test
public void testJunit4 (){
userService.transfer(666.66, "猪八戒", "孙悟空","123456");
}
}
(3)使用一个复合注解替代上面两个注解完成整合
Spring5 框架心功能(Webflux)
1、SpringWebflux 介绍
(1)是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
(2)使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
(3)解释什么是异步非阻塞
-
异步和同步
-
非阻塞和阻塞
-
上面都是针对对象不一样
-
异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步
-
阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞
(4)Webflux 特点:
① 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
② 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
(5)比较 SpringMVC
① 两个框架都可以使用注解方式,都运行在 Tomet 等容器中
② SpringMVC 采用命令式编程,Webflux 采用异步响应式编程
2、响应式编程(Java 实现)
(1)什么是响应式编程
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
(2)Java8 及其之前版本
- 提供的观察者模式两个类 Observer 和 Observable
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
//添加观察者
observer.addObserver((o,arg)->{
System.out.println("发生变化");
});
observer.addObserver((o,arg)->{
System.out.println("手动被观察者通知,准备改变");
});
observer.setChanged(); //数据变化
observer.notifyObservers(); //通知
}
}
3、响应式编程(Reactor 实现)
(1)响应式编程操作中,Reactor 是满足 Reactive 规范框架
(2)Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素
(3)Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号。
错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
(4)代码演示 Flux 和 Mono
① 引入依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.5.RELEASE</version>
</dependency>
② 编程代码
public static void main(String[] args) {
//just 方法直接声明
Flux.just(1,2,3,4);
Mono.just(1);
//其他的方法
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);
}
(5)三种信号特点
- 错误信号和完成信号都是终止信号,不能共存的
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示是无限数据流
(6)调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
// just方法直接声明
Flux.just(1, 2, 3, 4).subscribe(System.out::print);
Mono.just(1).subscribe(System.ouy::print);
(7)操作符
对数据流进行一道道操作,成为操作符,比如工厂流水线
① map 元素映射为新元素
② flatMap 元素映射为流
- 把每个元素转换流,把转换之后多个流合并成大的流
4、SpringWebflux 执行流程和核心 API
SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,异步非阻
塞的框架
(1)Netty
- BIO
- NIO
(2)SpringWebflux 执行过程和 SpringMVC 相似的
- 接口 WebHandler 有一个方法
- SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler
- WebHandler 接口下方法的作用:
DispatchHandler: 核心的控制器
WebHandlerDecorator: 装饰器,用装饰模式来做一些扩展功能
ResourvceWebHandler: 用于处理一些静态资源
RouterFunctionWebHandler in RouterFunctions: 做的是一些路由相关的处理
(3)SpringWebflux 里面 DispatcherHandler,负责请求的处理
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责请求处理
- HandlerResultHandler:响应结果处理
(4)SpringWebflux 实现函数式编程,两个接口:RouterFunction(路由处理)和 HandlerFunction(处理函数)
5、SpringWebflux(基于注解编程模型)
SpringWebflux 实现方式有两种:注解编程模型和函数式编程模型
使用注解编程模型方式,和之前 SpringMVC 使用相似的,只需要把相关依赖配置到项目中,
SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
① 创建 SpringBoot 工程,引入 Webflux 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
② 配置启动端口号
// application.properties
server.prot=8081
③ 创建包和相关类
- 实体类
public class User{
private String name;
private String gender;
private String age;
public User(String name, String gender, Integer age){
this.name = name;
this.gender = gender;
this.age = age;
}
...(生成set/get方法)
}
- 创建接口定义操作的方法
// 用户操作接口
public interface UserService {
// 根据 id 查询用户
Mono<User> getUserById(int id);
// 查询所有用户
Flux<User> getAllUser();
// 添加用户
Mono<Void> saveUserInfo(Mono<User> user);
}
- 接口实现类
public class UserServiceImpl implements UserService {
// 创建 map 集合存储数据
private final Map<Integer,User> users = new HashMap<>();
public UserServiceImpl() {
this.users.put(1,new User("lucy","nan",20));
this.users.put(2,new User("mary","nv",30));
this.users.put(3,new User("jack","nv",50));
}
// 根据 id 查询
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(this.users.get(id));
}
// 查询多个用户
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(this.users.values());
}
// 添加用户
@Override
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(person -> {
// 向 map 集合里面放值
int id = users.size()+1;
users.put(id,person);
}).thenEmpty(Mono.empty());
}
}
- 创建 controller
@RestController
public class UserController {
// 注入 service
@Autowired
private UserService userService;
// id 查询
@GetMapping("/user/{id}")
public Mono<User> geetUserId(@PathVariable int id) {
return userService.getUserById(id);
}
// 查询所有
@GetMapping("/user")
public Flux<User> getUsers() {
return userService.getAllUser();
}
// 添加
@PostMapping("/saveuser")
public Mono<Void> saveUser(@RequestBody User user) {
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}
- 说明
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC + Servlet + Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux + Reactor + Netty
6、SpringWebflux(基于函数式编程模型)
(1)在使用函数式编程模型操作时候,需要自己初始化服务器
(2)基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发
给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数
式接口的实现并且启动需要的服务器。
(3)SpringWebflux 请 求 和 响 应 不 再 是 ServletRequest 和 ServletResponse ,而是 ServerRequest 和 ServerResponse
① 把注解编程模型工程复制一份 ,保留 entity 和 service 内容
② 创建 Handler(具体实现方法)
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
// 根据 id 查询
public Mono<ServerResponse> getUserById(ServerRequest request) {
// 获取 id 值
int userId = Integer.valueOf(request.pathVariable("id"));
// 空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
// 调用 service 方法得到数据
Mono<User> userMono = this.userService.getUserById(userId);
// 把 userMono 进行转换返回
// 使用 Reactor 操作符 flatMap
return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person))).switchIfEmpty(notFound);
}
// 查询所有
public Mono<ServerResponse> getAllUsers() {
// 调用 service 得到结果
Flux<User> users = this.userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
}
// 添加
public Mono<ServerResponse> saveUser(ServerRequest request) {
// 得到 user 对象
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
③ 初始化服务器,编写 Router
- 创建路由的方法
// 创建 Router 路由
public RouterFunction<ServerResponse> routingFunction() {
// 创建 hanler 对象
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler(userService);
// 设置路由
return RouterFunctions.route(GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById).andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}
- 创建服务器完成适配
// 创建服务器完成适配
public void createReactorServer() {
// 路由和 handler 适配
RouterFunction<ServerResponse> route = routingFunction();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
// 创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
- 最终调用
public static void main(String[] args) throws Exception{
Server server = new Server();
server.createReactorServer();
System.out.println("enter to exit");
System.in.read();
}
(4)使用 WebClient 调用
public class Client {
public static void main(String[] args) {
// 调用服务器地址
WebClient webClient = WebClient.create("http://127.0.0.1:5794");
// 根据 id 查询
String id = "1";
User userresult = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
System.out.println(userresult.getName());
// 查询所有
Flux<User> results = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
results.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
}
}
******************** 结束啦 ********************
本文作者:笔兴洽谈室 哔哩哔哩:笔兴洽谈室 GitHub:StarJava1024 Gitee:StarJava1024
本文链接:https://www.cnblogs.com/CrayonXiaoxing/articles/17146555.html
原创文章仅用于学习,不得修改原作品,不得再创作。若本文侵犯某版权,请私信联系删除!如需转载,请私信!