赞
踩
Spring框架是一个轻量级的开源的JavaEE框架。
Spring可以解决企业应用开发的复杂性。
Spring有两个核心部分:IOC和AOP
Spring特点
入门案例
第一步,下载jar包(GA表示稳定版本)
下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/
第二步,创建普通的java项目
第三步、导入相关jar包
第四步,创建普通类
第五步,创建spring配置文件,在配置文件配置对象的创建
第六步,测试
@Test
public void testAdd() {
//1 加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2 获取配置创建的对象
User user = context.getBean("user", User.class); //参数"user"是配置文件中的id
System.out.println(user);
user.add();
}
控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理。
使用IOC目的,是为了降低耦合度。
工厂模式:解耦合
IOC过程解耦合,通过 xml解析 + 反射 实现
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
Spring提供IOC容器两种实现方式:(两个接口)
ApplicationContext接口有两个实现类,区别是ClassPathXmlApplicationContext的参数写xml的文件名,而FileSystemXmlApplicationContext的参数是写磁盘路径。
Bean管理指的是两个操作:
Bean管理操作有两种方式:
基于xml方式创建对象
在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建。
在bean标签有很多属性,常用属性:
创建对象的时候,默认也是执行无参数构造方法完成对象创建。
基于xml方式注入属性
DI:依赖注入,就是注入属性
IOC和DI有什么区别:DI是IOC中的具体实现,DI表示依赖注入或注入属性,注入属性要在创建对象的基础之上完成。
public class Book {
private String bookName;
private String bAuthor;
// set方法注入
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setbAuthor(String bAuthor) {
this.bAuthor = bAuthor;
}
}
<!--set方法注入属性-->
<bean id="book" class="com.company.spring5.Book">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bookName" value="白鹿原"></property>
<property name="bAuthor" value="陈忠实"></property>
</bean>
public class Order {
private String name;
private String address;
public Order(String name, String address) {
this.name = name;
this.address = address;
}
public void test() {
System.out.println(name + "--" + address);
}
}
<!--有参构造注入属性-->
<bean id="order" class="com.company.spring5.Order">
<constructor-arg name="name" value="电脑"></constructor-arg>
<constructor-arg name="address" value="中国"></constructor-arg>
</bean>
<bean id="book" class="com.company.spring5.Book" p:bookName="平凡的世界" p:bAuthor="路遥">
</bean>
1、xml注入其他类型属性
注入null值和特殊符号
<bean id="book" class="com.company.spring5.Book">
<!--设置属性为空-->
<property name="bookName">
<null/>
</property>
<!-- 属性值包含特殊符号
1. 把<>进行转义 < >
2. 把带特殊符号内容写到CDATA
-->
<property name="bAuthor">
<value><![CDATA[<<南京>>]]></value>
</property>
</bean>
2、注入属性-外部bean
(1)创建两个类service类和dao类
(2)在service调用dao里面的方法
(3)在spring配置文件中进行配置
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("add..............");
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("update...........");
}
}
3、注入属性-内部bean和级联赋值
(1)一对多关系:部门和员工。一个部门有多个员工。
(2)在实体类之间表示一对多关系。
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
//员工类 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 void test() { System.out.println(ename+"::"+gender+"::"+dept); } }
内部bean注入
级联赋值注入
4、注入集合类型属性
(1)注入数组类型属性
(2)注入List集合类型属性
(3)注入Map集合类型属性
public class Student {
// 数组类型
private String[] courses;
//list集合类型
private List<String> list;
//map集合类型
private Map<String, String> maps;
//编写set方法
}
list集合中存放对象类型
public class Student {
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
}
把集合注入部分提取出来,让别人也能使用这个集合
(1)在spring配置文件中引入名称空间util
(2)使用util标签完成list集合注入提取
Spring有两种类型bean,一种普通bean,另一种工厂bean
普通bean:在配置文件中定义bean类型就是返回类型
工厂bean:在配置文件定义bean类型可以和返回类型不一样
第一步,创建类,这个类作为工厂bean,实现接口FactoryBean
第二步,实现接口里面的方法,在实现的方法中定义返回的bean类型
在Spring中,设置创建bean实例默认是单实例对象。
在spring配置文件bean标签里面有属性scope用于设置单实例还是多实例。
scope属性值
singleton,表示单实例对象。
prototype,表示多实例对象。
singleton和prototype的区别
生命周期:从对象创建到对象销毁的过程。
bean的生命周期:
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)调用bean的初始化方法(配置初始化方法)
(4)使用bean(对象获取到了)
(5)当容器关闭,调用bean的销毁方法(需要配置销毁的方法)
如果加上后置处理器,有七个步骤:
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)把bean实例传递bean后置处理器的方法
(4)调用bean的初始化方法(配置初始化方法)
(5)把bean实例传递bean后置处理器的方法
(6)使用bean(对象获取到了)
(7)当容器关闭,调用bean的销毁方法(需要配置销毁的方法)
实现:创建类实现接口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;
}
}
配置好后,会为所有对象配置上后置处理器,分别在调用bean初始化之前和初始化之后执行。
自动装配:指 Spring 容器在不使用 和 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。
注意,使用byType注入,相同类型的bean不能定义多个
配置数据库信息
(1)配置德鲁伊连接池,引入德鲁伊依赖jar包
(2)引入外部属性文件配置数据库连接池
(3)把外部properties属性文件引入到Spring配置文件
引入名称空间
引入外部属性文件,用标签赋值
Spring针对Bean管理中创建对象提供注解
(1)@Component
(2)@Service
(3)@Controller
(4)@Repository
上面四个注解功能是一样的,都可以用来创建bean实例。
基于注解方式实现对象创建
第一步,引入依赖
第二步,开启组件扫描,看哪个类上有注解,有注解就进行创建
第三步,创建类,在类上面添加创建对象注解
注解中的value属性可以省略不写,不写默认值是类名首字母小写(StudentService —— studentService)
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");
StudentService studentService = context.getBean("studentService", StudentService.class);
studentService.add();
}
组件扫描配置
扫描包下所有加了注解的类
use-default-filters:表示是否使用默认filter,false表示使用自己配置的filter
context:include-filter:设置扫描哪些内容
context:exclude-filter:表示设置哪些内容不进行扫描
基于注解方式实现属性注入
(1)@Autowired:根据属性类型进行自动装配
第一步,把service和dao对象创建,在service和dao类添加注解
第二步,在service注入dao对象,在service类添加dao类型属性,在属性上面使用注解
(2)@Qualified:根据属性名称进行注入
@Qualified 注解要和 @Autowired 一起使用。
@Qualified表示以名称注入,@Autowired表示以类型注入,如果一个接口有多个实现类,那么要用@Qualified指定名称进行注入。
(3)@Resource:可以根据类型注入,可以根据名称注入
@Resource:根据类型进行注入
@Resource(name=“userDaoImpl1”):根据名称进行注入
(4)@Value:注入普通类型属性
@Value(value="abc")
private String name;
完全注解开发
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
通俗描述:不通过修改源码方式,在主干功能里面添加新功能。
AOP底层使用动态代理。
JDK动态代理的实现:使用Proxy类里面的方法创建代理对象
现在想要增强这个add方法
调用 newProxyInstance方法,方法有三个参数:
术语:
Spring框架一般基于AspectJ实现AOP操作。AspectJ不是Spring组成部分,一般把AspectJ和Spring框架一起使用,进行AOP操作。
引入依赖
切入点表达式
利用切入点表达式,可以让Spring知道对哪个类里面的哪个方法进行增强。
语法:execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])
举例1:对 com.atguigu.dao.BookDao类里面的add进行增强
execution(* com.atguigu.dao.BookDao.add(..))
举例2:对 com.atguigu.dao.BookDao类里面所有的方法进行增强
execution(* com.atguigu.dao.BookDao.*(..))
举例3:对 com.atguigu.dao包里面所有的类,类里面所有的方法进行增强
execution(* com.atguigu.dao.*.*(..))
进行通知的配置
(1)在Spring配置文件中,开启注解扫描
(2)使用注解创建User和UserProxy对象
(3)在增强类上面添加注解@Aspect
(4)在Spring配置文件中开启生成代理对象
如果找到类上有@Aspect注解,将这个类生成代理对象。
AspectJ注解:配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置。
测试:执行被增强的方法
结果
配置其他类型通知
测试,此时没有执行异常通知
我们给被增强的方法中加一个异常
结果
相同的切入点抽取
有多个增强类对同一个方法进行增强,设置增强类的优先级
在增强类上面添加注解@Order(数字类型值)
PersonProxy优先级高,先进行增强
完全注解开发
配置文件实现AOP
<bean id="user" class="com.company.spring5.aop.User"></bean>
<bean id="userProxy" class="com.company.spring5.aop.UserProxy"></bean>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.company.spring5.aop.User.add(..))"/>
<!--配置切面-->
<aop:aspect ref="userProxy">
<!--增强作用在具体方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
JdbcTemplate:Spring框架对JDBC进行的封装,使用JdbcTemplate方便实现对数据库操作。
1. 引入依赖
2. 配置数据库连接池
3. 配置JdbcTemplate对象,注入DataSource
<!--JdbcTemplate对象-->
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4. 创建类和接口
5. 创建数据库表和对应实体类
用JdbcTemplate进行添加操作
用update方法进行添加操作,方法有两个参数:
第一个参数:sql语句
第二个参数:可变参数,sql语句的参数
dao层
@Override
public void add(Book book) {
String sql = "insert into t_book values (?,?,?)";
Object[] args = {book.getUserId(), book.getUsername(), book.getUstatus()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
service层
//添加
public void addBook(Book book) {
bookDao.add(book);
}
测试
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setUserId("1");
book.setUsername("java");
book.setUstatus("有效");
bookService.addBook(book);
}
用JdbcTemplate进行修改操作
@Override
public void updateBook(Book book) {
String sql = "update t_book set username = ?, ustatus = ? where user_id = ?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
用JdbcTemplate进行删除操作
@Override
public void deleteBook(String id) {
String sql = "delete from t_book where user_id = ?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
用JdbcTemplate进行查询操作
查询表里有多少条记录,返回某个值
第一个参数:sql语句
第二个参数:返回类型Class
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
return integer;
}
查询返回对象
有三个参数:
第一个参数:sql语句
第二个参数:RowMapper,接口,针对返回不同类型的数据,使用这个接口里面的实现类完成封装
第三个参数:sql语句的参数
@Override
public Book findBookInfo(String id) {
String sql = "select * from t_book where user_id = ?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
return book;
}
查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
List<Book> books = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
return books;
}
JdbcTemplate批量操作数据
//批量添加
@Override
public void batchAdd(List<Object[]> batchArgs) {
String sql = "insert into t_book values (?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3", "java", "a"};
Object[] o2 = {"4", "c++", "b"};
Object[] o3 = {"5", "Mysql", "c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchAdd(batchArgs);
}
事务:数据库操作最基本单元,逻辑上是一组操作,要么同时成功,要么同时失败,如果有一个失败所有操作都失败。
1. 创建数据库表,添加记录
2. 创建service,搭建dao,完成对象创建和注入关系
3. 编写dao的两个方法,加钱和减钱
//减钱
@Override
public void addMoney() {
String sql = "update t_account set money = money - ? where username = ?";
jdbcTemplate.update(sql, 100, "lucy");
}
//加钱
@Override
public void reduceMoney() {
String sql = "update t_account set money = money + ? where username = ?";
jdbcTemplate.update(sql, 100, "mary");
}
4. 在service层调用这两个方法,实现转账
//转账的方法
public void accountMoney() {
personDao.reduceMoney();
personDao.addMoney();
}
上面代码,如果正常执行没有问题,但是如果代码执行过程中出现异常,则有问题。
事务应该添加到三层结构中的Service层(业务逻辑层)
Spring进行事务管理操作有两种方式
try { //第一步,开启事务 //第二步,进行业务操作 personDao.reduceMoney(); // lucy少100 //可能出现异常 int a = 1/0; personDao.addMoney(); // mary多100 //第三步,没有发生异常,提交事务 } catch (Exception e) { //第四步,出现异常,事务回滚 }
Spring的声明式事务管理,底层使用了AOP原理。
Spring事务管理提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。
Spring声明式事务注解实现
1、在Spring配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在spring配置文件中,开启事务注解
(1)在Spring配置文件引入名称空间tx
(2)开启事务的注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在service类上面添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法到方法上
(2)如果把这个注解添加在类上,这个类所有的方法都添加事务
(3)如果在方法上添加注解,只是给这个方法添加事务
声明式事务管理参数配置
1、propagation:事务传播行为
当一个事务方法被另外一个事务方法调用的时候,这个事务方法如何进行。
2、isolation:事务隔离级别
有三个读问题:脏读、不可重复读、幻读。
解决:通过设置事务隔离级别,解决读问题。
3、其他参数
事务操作(XML声明式事务管理)
第一步,配置事务管理器
第二步,配置通知
第三步,配置切入点和切面
<!--1 创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--2 配置通知--> <tx:advice id="txadvice"> <!--配置事务参数--> <tx:attributes> <!--指定哪种规则的方法上面添加事务--> <tx:method name="accountMoney" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--3 配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* com.company.spring5.service.PersonService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> </aop:config>
完全注解配置
@Configuration //配置类 @ComponentScan(basePackages = "com.company") //组件扫描 @EnableTransactionManagement //开启事务 public class TxConfig { //创建数据库连接池 @Bean public DruidDataSource getDruidDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/db4?useSSL=false&characterEncoding=utf-8"); dataSource.setUsername("root"); dataSource.setPassword("qwer`123"); 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 transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
Spring5框架自带了通用的日志封装。
Spring5移除了Log4jConfigListener,官方建议使用Log4j2.
第一步,引入依赖
第二步,创建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>
@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
(1)注解用在方法上面,方法返回值可以为空。
(2)注解用在方法参数里面,方法参数可以为空。
(3)注解使用在属性上面,属性值可以为空。
函数式风格GenericApplicationContext
Spring5核心容器支持函数式风格创建对象,交给Spring进行管理。
@Test
public void test5() {
//1 创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用context的方法对象注册
context.refresh();
context.registerBean("user1", User.class, ()->new User());
//3 获取在spring注册的对象
User user = (User) context.getBean("user1");
System.out.println(user);
}
Spring5整合Junit5
第一步,添加依赖
第二步,测试
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean7.xml")
public class Test5 {
@Autowired
private PersonService personService;
@Test
public void test() {
personService.accountMoney();
}
}
这样省去了之前创建ApplicationContext对象和getBean的操作。
简化,使用一个复合注解替代上面两个注解
1. SpringWebflux介绍
(1)WebFlux是Spring5添加的新模块,用于web开发的,功能和SpringMVC类似的,WebFlux使用当前一种比较流行的响应式编程出现的框架。
(2)使用传统web框架,比如SpringMVC,这些基于是Servlet容器的,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
(3)Webflux的特点:异步非阻塞,在有限资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程;函数式编程,Spring5框架基于Java8,Webflux使用java8函数式编程方式实现路由请求。
(4)比较SpringMVC:两个框架都可以使用注解方式,都运行在Tomcat等容器;SpringMVC采用命令式编程,Webflux采用异步响应式编程。
2. 响应式编程
什么是响应式编程:
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例如,对于 a=b+c 这个表达式的处理,在命令式编程中,会先计算 b+c 的结果,再把此结果赋值给 变量a,因此 b,c 两值的变化不会对 变量a 产生影响。但在响应式编程中,变量a 的值会随时跟随 b,c 的变化而变化。
Java实现响应式编程(观察者模式)
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(); //通知
}
}
没有最后两句代码,不会打印结果
响应式编程(Reactor实现)
(1)响应式编程操作中,Reactor是满足Reactive规范框架
(2)Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富的操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或1个元素。
(3)Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
public class TestReactor { 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); } }
三种信号特点
调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生。
调用subscribe进行订阅
操作符
对数据流进行一道道操作,称为操作符,比如工厂流水线。
1、map元素映射为新元素
2、flatMap元素映射为流。把每个元素转换为流,把转换后多个流合并成大的流。
3. WebFlux执行流程和核心API
SpringWebFlux是基于Reactor,默认使用容器是Netty,Netty是高性能的NIO框架,异步非阻塞的框架。
Netty
BIO
NIO
SpringWebflux执行过程和SpringMVC相似:
SpringWebflux核心控制器DispatcherHandler,实现接口WebHandler,接口WebHandler有一个方法。
SpringWebflux里面DispatcherHandler,负责请求的处理。
HandlerMapping:请求查询到处理的方法
HandlerAdapter:真正负责请求处理
HandlerResultHandler:响应结果处理
SpringWebflux实现函数式编程,两个接口:RouterFunction(路由处理)和HandlerFunction(处理函数)
4. SpringWebflux(基于注解编程)
第一步,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
第二步,创建包和实体类
第三步,编写service接口和实现接口
//用户操作接口
public interface UserService {
//根据id查询用户
Mono<User> getUserById(int id);
//查询所有用户
Flux<User> getAllUser();
//添加用户
Mono<Void> saveUserInfo(Mono<User> user);
}
@Repository 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> user) { return user.doOnNext(person -> { //向集合里面放值 int id = users.size() + 1; users.put(id, person); }).thenEmpty(Mono.empty()); } }
第四步,编写controller
@RestController public class UserController { @Autowired private UserService userService; //id查询 @GetMapping("/user/{id}") public Mono<User> getUserId(@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
5. SpringWebflux(基于函数式编程)
(1)在使用函数式编程操作的时候,需要自己初始化服务器。
(2)有两个核心接口:RouterFunction(实现路由功能,请求转发给对应的handler)和HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
(3)SpringWebflux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。
第一步,创建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,创建适配完成服务,完成最终调用
public class Server { public static void main(String[] args) throws Exception { Server server = new Server(); //调用 server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); } //1 创建Router路由 public RouterFunction<ServerResponse> routingFunction() { 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); } //2 创建服务器完成适配 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 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); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。