概述:
https://cntofu.com/book/95/33-what-new-in-the-spring-framework.md
这个不错。
轻量级javaee框架。
针对于bean的生命周期进行管理。
解决企业应用开发的复杂性。
核心:
IOC:控制反转,把创建对象的过程交给spring管理。
AOP:面向切面,不修改源代码进行功能增强。
特点:
1、方便解耦,简化开发;
2、AOP编程的支持;
3、方便程序的测试;
4、方便整合其他框架;
5、对JDBC的API进行了封装,方便事务的操作;
6、源码经典;
https://repo.spring.io/ui/native/libs-release/org/springframework/spring/5.2.6.RELEASE/
bean的初使用:
1、导入jar包
这里边所有的包都是必须导入的。common-logging不能落下;导入包之后,才能配置下边的spring的xml配置文件,否则是没有的。
![image-20230109094601361](../../project/spring5/image-20230109094601361.png)
2、配置jar包
3、代码:
<?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">
<bean id="user" class="com.pshdhx.spring5.User" ></bean>
</beans>
package com.pshdhx.spring5;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Auther: pshdhx
* @Date: 2023/01/09/9:43
* @Description:
*/
public class TestSpring5 {
@Test
public void testSpring5(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
IOC容器
容器:说白了就是创建对象的工厂。
1、IOC底层原理
概念:控制反转,把对象的创建以及对象之间的调用过程,交给spring容器进行管理,降低了耦合度;
底层原理:
1、xml解析(IOC过程)
1、配置xml文件文件,配置要创建的对象
<bean id="dao" class="com.pshdhx.spring5.UserDao" ></bean>
2、第二步,有Service类和dao类,创建工厂类
class UserFactory{
public static UserDao getDao(){
String classValue = class属性值; //xml解析
Class clazz = Class.forName(classValue);//通过反射创建对象
return (UserDao)clazz.newInstance();
}
}
我们只需要修改配置文件,耦合度进一步降低。
1、IOC思想,基于IOC容器完成,IOC容器底层就是对象工厂。
2、Spring提供IOC容器实现两种方式:两个接口。
BeanFactory:IOC容器的基本实现接口,是Spring内部的使用接口,不提供开发人员使用。
加载配置文件的时候看,不会创建对象,只有在获取对象或者是使用对象的时候,才会去创建对象。所以不应该在web项目中使用。因为我们希望在启动web项目的时候,就自动创建配置里边的对象,这些耗时、耗资源的过程在启动时就应该完成。
ApplicationContext接口:BeanFactory接口的子接口,提供更过更强大的功能,一般由开发人员进行使用。
加载配置文件时,就会把配置文件中的对象进行创建。
FileSystem:是盘符路径。
ClassPath:是src下的项目路径。
2、Bean管理
1、由Spring进行创建对象;
基于xml方式创建对象
public class User {
private String userName;
public User(String userName) {
this.userName = userName;
}
public void add(){
System.out.println("add..");
}
}
//org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [bean1.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.pshdhx.spring5.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.pshdhx.spring5.User.<init>()
创建对象时,默认是执行的无参构造,如果没有无参构造,报错。此时此刻,无参构造被有参构造代替了,所以报错。
基于xml方式注入属性
DI:依赖注入,就是注入属性;
set方法注入
<bean id="user" class="com.pshdhx.spring5.User" >
<property name="userName" value="pshdhx" />
</bean>
public class User {
private String userName;
public void setUserName(String userName) {
this.userName = userName;
}
public void add(){
System.out.println("add..");
System.out.println(this.userName);
}
}
有参构造方法注入
<bean id="user" class="com.pshdhx.spring5.User" >
<!-- <property name="userName" value="pshdhx" /> 无参构造 注入 -->
<constructor-arg name="userName" value="pshdhx" />
</bean>
<bean id="user" class="com.pshdhx.spring5.User" >
<!-- <property name="userName" value="pshdhx" /> 无参构造 注入 -->
<!-- <constructor-arg name="userName" value="pshdhx" />-->
<constructor-arg index="0" value="pshdhx2" />
</bean>
public class User {
private String userName;
public User(String userName) {
this.userName = userName;
}
public void add(){
System.out.println("add..");
System.out.println(this.userName);
}
}
p名称空间方式注入
<bean id="user" class="com.pshdhx.spring5.User" p:userName="pshdhx"></bean>
public class User {
private String userName;
public void setUserName(String userName) {
this.userName = userName;
}
public void add(){
System.out.println("add..");
System.out.println(this.userName);
}
}
xml方式注入其他类型
设置字面量,可以null
<bean id="user" class="com.pshdhx.spring5.User">
<property name="userName">
<null></null>
</property>
</bean>
属性值包含特殊符号
<bean id="user" class="com.pshdhx.spring5.User">
<property name="userName">
<!-- <null></null>-->
<value><![CDATA[<<pshdhx>>]]></value>
</property>
</bean>
注入外部bean
1、注册两个bean,service和dao;
2、service层要有dao的属性,然后设置set方法;
3、一个bean的属性是另外一个bean,ref指向其id;
<property name="userName" ref="serviceImpl" />
注入内部bean和级联赋值
1、内部bean
例如员工和部门,在员工实体类中添加部门对象;
<bean id="Emp" class="com.pshdhx.spring5.emp">
<property name="dept">
<bean id="dept" class="com.pshdhx.spring5.Dept">
<property name="dname" value="部门名称1"></property>
</bean>
</property>
</bean>
注入属性级联赋值
<bean id="Emp" class="com.pshdhx.spring5.emp">
<property name="dept" ref="dept" />
</bean>
<bean id="dept" class="com.pshdhx.spring5.Dept">
<property name="dname" value="部门名称1"></property>
</bean>
或者是:
<bean id="Emp" class="com.pshdhx.spring5.vo.Emp">
<property name="ename" value="ename-pshdhx" />
<property name="dept" ref="dept" />
<property name="dept.dname" value="dept.dname.pshdhx" />
</bean>
<bean id="dept" class="com.pshdhx.spring5.vo.Dept">
</bean>
public class Emp {
private String ename;
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public Dept getDept() {
return dept;
}
public void add(){
System.out.println("emp-add"+ename+"::"+dept);
}
}
此时要注意:dept.name 要在Emp类中设置getDept的方法;
注入集合属性
注入map list set arr
<bean id="stu" class="com.pshdhx.spring5.vo.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<property name="courseList">
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</property>
</bean>
集合属性提取到外边
<!--第一步:在 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:util="http://www.springframework.org/schema/util" <!--添加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名称空间-->
<!--第二步:使用 util 标签完成 list 集合注入提取-->
<!--把集合注入部分提取出来-->
<!--1 提取list集合类型属性注入-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!--2 提取list集合类型属性注入使用-->
<bean id="book" class="com.pshdhx.spring5.vo.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>
3、工厂模式降低耦合度:
4、Bean类型
一种是普通Bean:在配置文件中定义bean类型就是返回类型;
一种是factoryBean:在配置文件中定义bean类型和返回类型不一致;
factoryBean的代码如下,是根据泛型返回的。
public class MyBean implements FactoryBean<Course> {
//定义返回bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
}
<bean id="myBean" class="com.pshdhx.spring5.vo.MyBean">
</bean>
@Test
public void test3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
//xml里定义的是MyBean类型的,而返回值是Course类型的
Course course = context.getBean("myBean", Course.class);//返回值类型可以不是定义的bean类型!
System.out.println(course);
}
5、Bean的scope属性
单实例和多实例,spring5 只剩下了这两个了。
<bean id="stu" class="com.pshdhx.spring5.vo.Stu" scope="prototype">
<bean id="stu" class="com.pshdhx.spring5.vo.Stu" scope="singleton">
6、Bean的生命周期
创建到销毁的过程:
1、通过构造器进行创建(无参构造)
2、对其他Bean的引用和赋值,设置set方法的过程-property。
3、调用bean的初始化的方法(初始化前后把bean实例传递前置处理器和后置处理器)
4、bean可以获取到使用了
5、当容器在关闭的时候,调用Bean销毁的方法。
<bean id="stu" class="com.pshdhx.spring5.vo.Stu" scope="prototype" init-method="" destroy-method="">
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property><!--这里就是通过set方式(注入属性)赋值-->
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
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;
}
}
7、基于xml方式自动装配
手动装配:就是通过ref指向或者是value值进行赋值操作。
自动装配:根据指定的装配规则,(属性名称或者是属性类型)Spring将自动匹配的属性值进行注入操作。
Bean标签属性autowire ,属性自动装配;
autowire:属性常用的有两个值
ByName:根据属性名称自动注入;和bean的标签的id一样;
ByType:根据属性值类型进行注入;和bean标签的class一样;【如果还有个teacher1的话,就报错。不知道找到那个bean了】
<bean id="stu" class="com.pshdhx.spring5.vo.Stu" scope="prototype" init-method="" destroy-method="" autowire="byName">
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.pshdhx.spring5.vo.Teacher"></bean>
<!-- <bean id="teacher1" class="com.pshdhx.spring5.vo.Teacher"></bean> byType就报错了 -->
8、引入外部属性文件
db.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/shangguigu
jdbc.username=root
jdbc.password=pshdhx
1、引入上下文的名称空间context
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="driver" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
9、基于注解管理Bean
注解:是代码中的特殊标记。格式:@注解名称(属性名称=属性值,属性名称=属性值…)
可以作用在类中,方法中,属性中。
为什么用注解:简化xml配置;
1、创建对象
@Component【普通】
@Service【Service层】
@Controller【控制层】
@Repository【Dao层】
1、如果需要做注解,需要引入AOP依赖。【spring-aop-5.2.6.RELEASE.jar】
2、开启组件扫描,需要知道那些类中有注解,进而创建bean。需要引入context名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
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">
<context:property-placeholder location="classpath:db.properties" ></context:property-placeholder>
3、扫描配置
<context:component-scan base-package="com.pshdhx.spring5" use-default-filters="false">
<!-- 只扫描Controller注解 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 不扫描Service注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
2、基于注解进行属性注入
1、@Autowired 根据属性类型进行自动装配
2、@Qualifer 根据属性名称自动注入
3、@Resource 根据属性类型或者是根据属性名称进行自动注入【啥都不写是根据类型注入。@Resource(name=“userServiceImpl1”)根据名称进行注入】
4、@Value(${abc.def}) :可以注入普通属性。@Value(“abc”)
如果根据类型进行属性注入,那么有多个实现类怎么办呢?
如果使用@Autowired,就不知道找哪个了
需要再添加上@Qualifier(“userDaoImpl”),根据名称进行注入。
或者是直接注入其实现类。不注入接口了。
@Repository
public class UserDao {
public int ins(){
System.out.println("userDao . ins ....");
return 0;
}
}
public interface UserService {
public int ins();
}
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public int ins() {
userDao.ins();
return 0;
}
}
注意:注解能否添加在接口上?不能。一般都在其实现类上添加注解;
3、完全注解开发
1、做一个配置类,替代xml文件。
@Configuration
@ComponentScan(basePackages = "com.pshdhx.spring5")
public class Spring5Config {
}
@Test
public void testSpringAnotation(){
ApplicationContext context = new AnnotationConfigApplicationContext(Spring5Config.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.ins();
// String[] beanDefinitionNames = context.getBeanDefinitionNames();
// System.out.println(Arrays.asList(beanDefinitionNames));
}
AOP
基本概念:
面向切面编程,可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率。
通俗描述:不通过修改原始代码的方式,在主干功能里边添加新功能;
1、AOP底层原理-代理
AOP的底层使用了动态代理的方式进行实现的。
1、有接口情况,使用JDK动态代理
1、创建接口实现类的代理对象(等同于Impl的代理对象)
2、没有接口情况,使用Cglib动态代理。
1、使用继承的方式。创建当前类的子类的代理对象。
public class User{
public void add(){
}
}
public class User2 extends User{
public void add(){
//...
super.add();
//...
}
}
2、jdk的动态代理
java.lang.reflect
class proxy
static object newProxyInstance(CloassLoader loader,类<?>[] interfaces,InvocationHandler h)
//返回指定接口代理类的实例,该接口的方法调用分派给指定的调用处理程序
public class TestJdkProxy {
public static void main(String[] args) {
Class interfaces[] = {UserDao.class};
JdkProxy proxyInvoke = new JdkProxy(new UserDaoImpl());
UserDao userDao = (UserDao) Proxy.newProxyInstance(TestJdkProxy.class.getClassLoader(), interfaces, proxyInvoke);
int addRes = userDao.add(3, 4);
System.out.println("finally=="+addRes);
}
}
class JdkProxy implements InvocationHandler {
private Object obj;
public JdkProxy(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行之前");
Object res = method.invoke(obj,args); //此处一定不能是proxy,陷入死循环
System.out.println("方法执行之后");
return res;
}
}
3、术语
连接点:类中那些方法可以被增强,这些方法被称为连接点。
切入点:实际被增强的方法,被称为切入点。
切面:把通知应用到切入点的过程。
通知(增强):增强的逻辑部分。通知有多重类型。
1、前置通知
2、后置通知
3、环绕通知
4、异常通知
5、最终通知
4、AOP-AspectJ包
其本身并不是spring的一部分,它是一个单独的AOP框架。
基于AspectJ的方式有两种,基于注解和基于xml的方式。
1、引入依赖
spring-aspects-5.2.6.RELEASE.jar
cglib-3.0.jar
aspectjweaver-1.9.7.jar
aopalliance-1.0.jar
2、语法结构
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.. (…))
5、基于注解配置AOP
<?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="com.pshdhx.spring5.aop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Component
public class User {
public void add() {
System.out.println("add.......");
}
}
//4、配置不同类型的通知
@Component
@Aspect //生成代理对象
public class UserProxy {
@Before("execution(* com.pshdhx.spring5.aop.User.add(..))")
public void before() {//前置通知
System.out.println("before......");
}
@Around("execution(* com.pshdhx.spring5.aop.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//环绕通知
System.out.println("around环绕之前...");
proceedingJoinPoint.proceed();
System.out.println("around环绕之后...");
}
@AfterReturning("execution(* com.pshdhx.spring5.aop.User.add(..))")
public void afterReturning() {//后置返回通知
System.out.println("afterReturning......");
}
@AfterThrowing("execution(* com.pshdhx.spring5.aop.User.add(..))")
public void afterTrowing() {//异常通知
System.out.println("afterTrowing......");
}
@After("execution(* com.pshdhx.spring5.aop.User.add(..))")
public void after() {//后置通知
System.out.println("after......");
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
结果:
around环绕之前…
before…
add…
around环绕之后…
after…
afterReturning…
【正常情况下没有throwing。】
com.pshdhx.spring5.aop.User@5ab9e72c
around环绕之前…
before…
after…
afterTrowing…
【一旦出现throwing,则会没有afterReturning和环绕之后around】
抽取相同的切入点
//相同切入点抽取
@Pointcut(value = "execution(* com.pshdhx.spring5.aop.User.add(..))")
public void pointdemo() {
}
@Before("pointdemo()")
public void before() {//前置通知
System.out.println("before......");
}
通知
@Order(1) 在代理类中添加注解,值越小,优先级越高。
6、基于xml配置AOP
public class User {
public void buy(){
System.out.println("buy...");
}
}
public class UserProxy {
public void befor(){
System.out.println("buy ...before...");
}
}
<bean id="user" class="com.pshdhx.spring5.aopxmlconfig.User"></bean>
<bean id="userProxy" class="com.pshdhx.spring5.aopxmlconfig.UserProxy"></bean>
<aop:config>
<aop:pointcut id="p" expression="execution(* com.pshdhx.spring5.aopxmlconfig.User.buy(..))"/>
<aop:aspect ref="userProxy">
<aop:before method="befor" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
不用实现任何接口,例如BeforeAdvice等接口。接口是不引入aspectj的实现方式。详情可见spring4的那一篇文章。
7、完全基于注解
@Configuration
@ComponentScan(basePackages = "com.pshdhx.spring5")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring5Config {
}
jdbcTemplate
概述
Spring框架对JDBC进行了封装,使用JdbcTemplate方便实现对数据库的操作。
新增操作
引入相关的jar包;
druid-1.1.9.jar
mysql-connector-java-5.1.49.jar
spring-jdbc-5.2.6.RELEASE.jar
spring-orm-5.2.6.RELEASE.jar
spring-tx-5.2.6.RELEASE.jar
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<context:component-scan base-package="com.pshdhx.spring5.jdbctemplate"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
public interface BookDao {
int add(Book book);
}
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int add(Book book) {
String sql = "insert into book values (?,?,?)";
Object obj[] = {book.getBookId(), book.getBookName(), book.getPrice()};
int update = jdbcTemplate.update(sql, obj);//影响的行数
return update;
}
}
public class Book {
private String bookId;
private String bookName;
private Double price;
public Book(String bookId, String bookName, Double price) {
this.bookId = bookId;
this.bookName = bookName;
this.price = price;
}
public String getBookId() {
return bookId;
}
public void setBookId(String bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public void addBook(Book book){
bookDao.add(book);
System.out.println("插入完成");
}
}
CREATE TABLE `book` (
`book_id` varchar(255) NOT NULL,
`book_name` varchar(255) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--jdbc.url=jdbc:mysql://localhost:3306/shangguigu?useUnicode=true&characterEncoding=utf-8&useSSL=false
新增,删除和修改,如上所示,都用update。
查询count(*)
public int selectCount() {
String sql = "select count(*) from t_book";
//queryForObject方法中:第一个参数代表--sql语句;第二个参数代表--返回类型class
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
查询返回对象
public List<Book> findAllBook() {
String sql = "select * from t_book";
//调用方法
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
查询返回集合
public List<Book> findAllBook() {
String sql = "select * from t_book";
//调用方法
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
批量添加
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
//batchUpdate方法 第一个参数:sql语句 第二个参数:List集合,添加多条记录数据
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
批量修改和删除
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
spring的事务
事务:是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操 作都失败
事务特性acid
原子性:要么都成功,要么都失败。
一致性:操作之前和操作之后,总量不变。例如:银行卡之间转账,减多少,就得加多少,钱的总量不变。
隔离性:多事务操作的时候,不会产生影响。
持久性:事务提交之后,不会发生变化了。
搭建事务操作环境
基于xml实现事务
<!-- 事务相关配置 -->
<!-- 创建事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置声明式事务,要想其生效,就必须有切点(基于通知)。真正控制那些方法实现事务,则必须控制具体的方法 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="userInsert" propagation="REQUIRED" isolation="SERIALIZABLE" rollback-for="java.lang.Exception"/>
<tx:method name="ins*"/>
<tx:method name="del*"/>
<tx:method name="upd*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.pshdhx.service.impl.*.*(..))"/>
<!-- 声明式事务相当于通知,多饶了一个圈 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint" />
</aop:config>
基于注解实现事务
1、引入tx名称空间
<?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"
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/aop/spring-tx.xsd">
2、创建事务管理器,给数据源绑定事务
<!-- 事务相关配置 -->
<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
3、使用注解方式开启事务
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
4、给方法或者类 添加注解
@Service
@Transactional
public class BookService {
}
事务的传播行为
当一个事务方法被另一个事务方法调用的时候,这个事务方法如何进行处理。
propagation:
required:方法a调用方法b,如果方法a有事务,则方法b在方法a的事务中运行。如果方法a没有事务,则方法a新开一个事务,方法b在方法a的事务中运行。
所以方法a使用都有事务,不管方法b是否有事务,都在方法a的事务中运行。
required_new:方法a调用方法b,调用方法b时,必须启动新的事务,新的事务对方法b有效。方法a的事务则进行挂起操作。如果此时,方法a调用方法b出现了 问题,则方法b根据其事务进行提交或者是回滚,但是方法a的事务不会进行回滚。换句话说:方法a的事务和方法b的事务没有任何关系。
supports:如果方法b运行的环境中存在事务,则方法B就按照环境中的事务进行;如果没有,自己搞自己的。
外层有事务,按照外层来。没有事务,按照自己的来。
隔离级别
多事务之间操作不会产生影响。
脏读
一个事务A读取到另一个未提交事务B的数据。事务B未提交的数据可能会发生改变,事务A读取到的数据为脏数据。
幻读(表锁)
事务A按照特定条件查询出结果,事务B去新增了一条符合条件的数据。
事务A中查出的数据和数据库中的数据不一致,事务A好像出现了幻觉,此时加表锁。
特点:
针对于的操作是**新增和删除**数据;
两次事务的结果。
不可重复读(行读)
当事务A在第一次读取数据后,事务B对事务A读取的数据进行了修改,事务A中再次读取的数据和之前读取的数据不一致,该过程为不可重复读。
特点:
主要是针对于**某行数据,或者是行中的某一列**。
主要是针对的是**修改操作**。
两次读取在同一个事务内。
场景:张三去取钱,发现余额为20000,想要取出15000。与此同时,他老婆取出了15000,张三再取钱时,发现钱不够了。所以要在读取到20000余额时,对该数据加上行锁。
解决读问题
通过设置事务隔离性,解决读问题。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
read uncommitted(读未提交)–级别最低,效率最高,问题最大。 | 有 | 有 | 有 |
read committed(读已提交) --解决脏读 | 无 | 有 | 有 |
repeatable read(可重复度)–解决脏读和不可重复读(解决表锁问题) | 无 | 无 | 有 |
serializable(串行化)–事务之间排队-- 解决行锁问题。 | 无 | 无 | 无 |
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class BookService {
}
mysql默认的隔离级别是【可重复读】
oracle和SQL server 都是【读已提交】
timeout:事务在一定时间内要提交,不能一直占据事务,要不然回滚。
默认值是-1,时间单位是秒。
readonly:是否只读。
默认值:false,可以查询,可以增删改操作。
设置为true之后,只可以做读操作,不可以做增删改操作。
rollbackFor:回滚。
设置哪些出现的异常可以进行回滚。
noRollback:不会滚。
设置那些出现的异常不进行回滚。
完全注解方式
@Configuration //配置类
@ComponentScan(basePackages = {"com.pshdhx.spring5"}) //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
/**
* 德鲁伊数据源
* 创建数据库连接池
* @return {@link DruidDataSource}
*/
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3307/user_db");
dataSource.setUsername("root");
dataSource.setPassword("lcj6445254");
return dataSource;
}
/**
* 获得jdbc模板
* 创建JdbcTemplate对象
* @param dataSource 数据源
* @return {@link JdbcTemplate}
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//到IOC容器中根据类型找DataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入DataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
/**
* 事务管理器获取数据源
* 创建事务管理器
* @param dataSource 数据源
* @return {@link DataSourceTransactionManager}
*/
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
spring新特性
https://cntofu.com/book/95/33-what-new-in-the-spring-framework.md
spring5只能整合log4j2-基于jdk8
log4j-api-2.11.2.jar
log4j-core-2.11.2.jar
log4j-slf4j-impl-2.11.2.jar
slf4j-api-1.7.30.jar
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级顺序 off > fatal > error > warn >info >debug > trace > all-->
<!--configuration后边的status 用于设置log4j2自身内部的信息输出 可以不设置,当设置成true时,可以看到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才会生效-->
<loggers>
<!-- root:用于指定项目的根日志,如果没有单独指定logger,则会使用root作为默认的日志输出-->
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
private static final Logger log = LoggerFactory.getLogger(TestSpring5.class);
@Test
public void testLog4j2(){
log.info("log4j2 ...");
log.warn("log4j2 ...");
log.error("log4j2 ...");
log.debug("log4j2 ...");
}
@Nullable注解
可以使用在方法上边,属性上边,参数上边。表示方法的返回值可以为空,属性值可以为空,参数值可以为空。
函数式风格创建对象
//函数式风格创建对象
@Test
public void testGenericApplicationContext(){
//1、创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//2、调用context方法进行对象的注册
context.refresh();
context.registerBean(User.class,()->new User());
User user = (User) context.getBean("com.pshdhx.spring5.aopannotation.User");
System.out.println(user);
}
整个JUnit5单元测试框架
1、引入依赖包
spring-test-5.2.6.RELEASE.jar
package com.pshdhx.spring5;
import com.pshdhx.spring5.jdbctemplate.entity.Book;
import com.pshdhx.spring5.jdbctemplate.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Auther: pshdhx
* @Date: 2023/01/11/15:03
* @Description:
*/
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架版本
@ContextConfiguration("classpath:jdbctemplate.xml")//加载外部配置文件
public class Junit4Test {
@Autowired
private BookService bookService;
@Test
public void test1(){
Book book = new Book("8848", "book_name1", 8848.99);
bookService.addBook(book);
}
}
external library里边有了junit4的依赖。
2、根据@Test注解,导入junit5的包,之后使用junit5的注解。
package com.pshdhx.spring5;
import com.pshdhx.spring5.jdbctemplate.entity.Book;
import com.pshdhx.spring5.jdbctemplate.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Auther: pshdhx
* @Date: 2023/01/11/15:03
* @Description:
*/
//@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架版本
//@ContextConfiguration("classpath:jdbctemplate.xml")//加载外部配置文件
@SpringJUnitConfig(locations = "classpath:jdbctemplate.xml")
public class Junit4Test {
@Autowired
private BookService bookService;
@Test
public void test1(){
Book book = new Book("8849", "book_name1", 8848.99);
bookService.addBook(book);
}
}
Webflux-函数式编程模型
前置知识:
SpringMVC
SpringBoot
Maven
java8新特性 lamda表达式,stream流。
简介:
1、是Spring5添加的新的模块,用于web开发的。功能和SpringMVC类似的,WebFlux使用当前一种比较流行的响应式编程出现的框架。
2、使用传统的web框架,比如SpringMVC,这些基于Servlet容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
异步和同步:
针对调用者。
阻塞和非阻塞:
针对被调用者。A调用B,B给了反馈,就是非阻塞。没有给反馈,就是阻塞。
阻塞需要等待反馈,非阻塞则不需要。
webflux优势:
1、异步非阻塞:在不扩充机器的情况下,可以提高系统吞吐量;
与SpringMVC的区别
![image-20230111155349009](../../project/spring5/webflux与SpringMVC区别.png)
springMVC采用的是命令式编程,spring webflux采用的是响应式编程。
响应式编程:excel求和,数据变化,结果变化。
public class Observer extends Observable {
public static void main(String[] args) {
Observer observer = new Observer();
observer.addObserver((item,arg)->{
System.out.println("add observer 01 ...");
});
observer.addObserver((item,arg)->{
System.out.println("add observer 02 ...");
});
observer.setChanged();
observer.notifyObservers();
}
}
响应式编程:
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.1.5.RELEASE</version>
</dependency>
Flux.just(1,2,3,4);
Flux.just(1);
Integer []arr = {1,2,3};
Flux.fromArray(arr);
List<Integer> list = Arrays.asList(arr);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
三种信号特点:
1、错误信号和完成信号都是终止信号,不能共存的。
2、如果没有发送任何元素值,而是直接发送错误或者是完成信号,表示空数据流。
3、如果没有错误信号,没有完成信号,表示是无限数据流。
//订阅之后才能输出
Flux.just(1222).subscribe(System.out::println);
Flux.just(3344).subscribe(System.out::println);
声明:调用just或者是其他方法知识声明数据流,数据流并没有发出,只有订阅之后,才会触发数据流,不订阅什么都不会发生的。
操作符
map
flatmap
把里边的若干个元素变为若干个流,然后进行流的合并,成为一个大流。
Netty的NIO
高性能,异步,非阻塞框架。
核心API
SpringWebFlux核心控制器DisPatchHandler,实现接口WebHandler。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
package org.springframework.web.server;
import reactor.core.publisher.Mono;
public interface WebHandler {
Mono<Void> handle(ServerWebExchange var1);
}
public Mono<Void> handle(ServerWebExchange exchange) {//放http请求信息
return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
return mapping.getHandler(exchange);//根据请求地址,获取对应的mapping
}).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
return this.invokeHandler(exchange, handler);//调用具体的业务方法
}).flatMap((result) -> {
return this.handleResult(exchange, result);//返回处理结果
});
}
SpringWebflux里边的DispatchHandler,负责请求的处理
HandlerMapping:请求查询到处理的方法。
HandlerAdapter:真正负责请求处理。
HandlerResultHander:响应结果处理。
函数式编程的接口:
RouterFunction:路由处理
HandlerFunction:具体方法处理
注解编程模型:
SpringMVC实现:同步阻塞方式,基于SpringMVC+Servlet+Tomcat
SpringWebFlux实现:异步非阻塞方式,基于SpringWebflux+reactor+Netty
代码-已验证
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
@Data
@AllArgsConstructor
public class Book {
private Integer bookId;
private String bookName;
private Double price;
}
public interface BookService {
Mono<Book> queryBookById(int id);
Flux<Book> queryBookAll();
Mono<Void> addBook(Mono<Book> monoBook);
}
@Service
public class BookServiceImpl implements BookService {
private Map<String,Book> map = new HashMap<>();
public BookServiceImpl(){
map.put("1",new Book(1,"book1",1.1));
map.put("2",new Book(2,"book2",1.2));
map.put("3",new Book(3,"book3",1.3));
map.put("4",new Book(4,"book4",1.4));
}
@Override
public Mono<Book> queryBookById(int id) {
return Mono.justOrEmpty(this.map.get(id+""));
}
@Override
public Flux<Book> queryBookAll() {
return Flux.fromIterable(this.map.values());
}
@Override
public Mono<Void> addBook(Mono<Book> monoBook) {
return monoBook.doOnNext(item->{
int id = map.size()+1;
map.put(id+"",item);
}).thenEmpty(Mono.empty());
}
}
@RestController
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/book/{id}")
public Mono<Book> getBookById(@PathVariable int id){
Mono<Book> bookMono = bookService.queryBookById(id);
return bookMono;
}
@GetMapping("/book/queryAll")
public Flux<Book> getAllBooks(){
Flux<Book> bookFlux = bookService.queryBookAll();
return bookFlux;
}
@PostMapping("/book/addBook")
public Mono<Void> addBook(@RequestBody Book bookMono){
Mono<Void> voidMono = bookService.addBook(Mono.just(bookMono));
return voidMono;
}
}
函数式编程模型:
1、在使用函数式编程模型操作的时候,需要自己初始化服务器;
2、基于函数式编程模型时候,有两个核心接口:
RouterFunction:实现路由功能,请求转发给对应的handler
HandlerFunction:处理请求生成响应的函数。
核心任务时定义两个函数式接口的实现,且需要启动需要的服务器。
SpringWebflux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。
代码-已验证
package com.pshdhx.reactor3.webflux.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @Auther: pshdhx
* @Date: 2023/01/12/9:40
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Book {
private Integer bookId;
private String bookName;
private Double price;
}
package com.pshdhx.reactor3.webflux.handler;
import com.pshdhx.reactor3.webflux.entity.Book;
import com.pshdhx.reactor3.webflux.service.BookService;
import com.pshdhx.reactor3.webflux.service.impl.BookServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* @Auther: pshdhx
* @Date: 2023/01/12/10:41
* @Description:
*/
public class BookHandler {
BookService bookService = new BookServiceImpl();
public Mono<ServerResponse> getBookById(ServerRequest request){
String id = request.pathVariable("id");
//空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Book> bookMono = this.bookService.queryBookById(Integer.parseInt(id));
return bookMono.flatMap(item->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(fromObject(item)))
.switchIfEmpty(notFound);
}
public Mono<ServerResponse> getAllBook(ServerRequest request){
Flux<Book> bookFlux = this.bookService.queryBookAll();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(bookFlux,Book.class);
}
public Mono<ServerResponse> addBook(ServerRequest request){
Mono<Book> bookMono = request.bodyToMono(Book.class);
return ServerResponse.ok().build(this.bookService.addBook(bookMono));
}
}
package com.pshdhx.reactor3.webflux.server;
import com.pshdhx.reactor3.webflux.entity.Book;
import com.pshdhx.reactor3.webflux.handler.BookHandler;
import com.pshdhx.reactor3.webflux.service.BookService;
import com.pshdhx.reactor3.webflux.service.impl.BookServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
/**
* @Auther: pshdhx
* @Date: 2023/01/12/10:55
* @Description:
*/
public class BookServer {
//创建router路由
public RouterFunction<ServerResponse> routingFunction() {
BookService bookService = new BookServiceImpl();
BookHandler bookHandler = new BookHandler();
return RouterFunctions.route(
GET("/books/{id}")
.and(accept(APPLICATION_JSON)), bookHandler::getBookById)
.andRoute(GET("/books").and(accept(APPLICATION_JSON)), bookHandler::getAllBook);
}
//创建服务器,完成适配
public void createReactorServer() {
// 路由和handler适配
RouterFunction<ServerResponse> router = routingFunction();
HttpHandler httpHandler = toHttpHandler(router);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
//调用
public static void main(String[] args) throws IOException {
BookServer server = new BookServer();
server.createReactorServer();
System.out.println("enter to exit");
int read = System.in.read();
}
}
WebClient调用
public class BookClient {
public static void main(String[] args) {
WebClient webClient = WebClient.create("http://127.0.0.1:1475");
String id = "1";
Book book = webClient.get().uri("/books/{id}", id)
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(Book.class)
.block();
System.out.println(book.toString());
Flux<Book> bookFlux = webClient.get().uri("/books").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(Book.class);
bookFlux.map(itme->itme.getBookName()).buffer().doOnNext(System.out::println).blockFirst();
}
}