Spring框架的基本概念
Spring框架概述
- Spring是轻量级的开源的JavaEE框架
- 解决企业应用开发的复杂性
- Spring有两个核心部分:IOC和AOP
- IOC:控制反转, 把创建对象的过程交给spring进行管理
- AOP:面向切面,不修改源代码的情况下进行功能增强
- Spring特点
- 方便解耦,简化开发
- AOP编程支持
- 方便程序的测试
- 方便集成各种优秀框架
- 方便进行事务操作
- 降低API开发难度
- Spring5.x
IOC容器
IOC底层原理
IOC是什么?
IOC是控制反转,是面向对象编程中的一种设计原则,可以用来降低计算机代码中的耦合度
把对象的创建和对象之间的调用过程,交给Spring管理
底层原理
(1)xml解析
(2)工厂模式
(3)反射:得到类的字节码文件
过程
- 配置xml文件,配置创建的对象
- 创建工厂类
- xml解析
- 通过反射创建对象返回
IOC接口(BeanFactory)
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
-
Spring提供IOC容器实现两种方式:(两个接口)
- BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员使用
- 加载配置文件的时候,不会创建对象,在使用对象的时候才去创建对象
- ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
- 加载配置文件的时候就会把配置文件对象进行创建
- BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员使用
-
ApplicationContext实现类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLiAdfBS-1617946922003)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210330213935809.png)]
IOC操作Bean管理(基于xml)
-
什么是Bean管理
-
Spring创建对象
<!--配置user对象创建对象--> <bean id="User" class="com.hxq.spring5.User"></bean>
id:给对象取个别名
class:创建对象所属类的全路径
name:与id类似,可以用特殊符合
-
Spring注入属性
-
基于set方法进行注入
- 创建属性和set方法
private String bName; private String bAuthor; public void setbName(String bName) { this.bName = bName; } public void setbAuthor(String bAuthor) { this.bAuthor = bAuthor; }
- 在xml文件中配置
<!--基于set方法注入属性--> <bean id="book" class="com.hxq.spring5.Book"> <property name="bName" value="葵花宝典" /> <property name="bAuthor" value="教主" /> </bean>
- 测试
@Test public void testAdd() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Book book = context.getBean("book", Book.class); System.out.println(book); }
-
基于构造函数进行注入
-
创建属性和对应的构造方法
private String bName; private String bAuthor; public Book(String bName, String bAuthor) { this.bName = bName; this.bAuthor = bAuthor; }
-
在xml文件中配置
<!--基于构造函数注入属性--> <bean id="book" class="com.hxq.spring5.Book"> <constructor-arg name="bName" value="葵花宝典" /> <constructor-arg name="bAuthor" value="教主" /> </bean>
-
测试
-
-
p命名空间注入
-
是set方法的简化,本质上是用set方法注入
-
配置
<!--基于p命名空间注入,相当于property--> <bean id="book" class="com.hxq.spring5.Book" p:bAuthor="教主" p:bName="葵花宝典" />
-
-
注入空值和特殊符号
-
null值
<!-- 设置空值 --> <bean id="book" class="com.hxq.spring5.Book"> <property name="bAuthor"> <null/> </property> <property name="bName"> <null/> </property> </bean>
-
特殊符号
-
把特殊符号进行转义, 例如:"<", “>”
- <![CDATA[ ... ]]>
<!-- 属性值中包含特殊符号 --> <bean id="book" class="com.hxq.spring5.Book"> <property name="bName"> <value><![CDATA[<<书名>>]]></value> </property> </bean>
-
-
-
注入外部Bean和级联赋值
<!--1 service和dao对象创建--> <bean id="userDao" class="com.hxq.spring5.dao.UserDaoImpl" /> <bean id="userService" class="com.hxq.spring5.service.UserService"> <property name="userDao" ref="userDao" /> </bean>
-
注入内部Bean
-
一对多关系:部门和员工
-
在实体类之间表示一对多关系
//员工类 public class Emp { //员工属于某一个部门,使用对象形式表示 private Dept dept; }
-
在spring配置文件中进行配置
<!-- 内部bean--> <bean id="emp" class="com.hxq.spring5.bean.Emp"> <!--设置普通属性--> <property name="eName" value="Lucy"/> <property name="gender" value="女"/> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.hxq.spring5.bean.Dept"> <property name="dName" value="保安部"/> </bean> </property> </bean>
-
测试
-
-
注入集合类型
-
数组类型
-
List集合
-
Map集合
-
set集合
<!--集合类型属性注入--> <bean id="stu" class="com.hxq.spring5.collectiontype.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="map"> <map> <entry key="Java" value="java"/> <entry key="C" value="c"/> </map> </property> <!--set类型属性注入--> <property name="set"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </bean>
-
在集合里面设置对象类型
<!--注入list集合类型, 值是对象--> <property name="courseList"> <list> <ref bean="course1"/> <ref bean="course2"/> </list> </property>
-
把集合注入部分提取出来
-
引入util命名空间
-
-
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> ``` 2. 使用util标签完成list集合的提取(map, set等集合类似) ```xml <!--提取list集合类型属性注入--> <util:list id="bookList"> <value>易筋经</value> <value>九阴真经</value> <value>九阳神功</value> </util:list> <!--提取list集合类型注入使用--> <bean id="book" class="com.hxq.spring5.collectiontype.Book"> <property name="list" ref="bookList"/> </bean> ```
-
-
工厂Bean (FactoryBean)
-
普通bean:定义什么类型返回什么类型
-
工厂bean:在配置文件中定义得bean类型可以和返回类型可以不一样
-
创建类,让这个类作为工厂bean,实现接口FactoryBean
-
实现接口里面得方法,在实现的方法中定义返回的bean类型
public class MyBean implements FactoryBean<Course> { //定义返回bean对象 public Course getObject() throws Exception { Course course = new Course(); course.setCnmame("abc"); return course; } public Class<?> getObjectType() { return null; } public boolean isSingleton() { return false; } }
-
测试
@Test public void myBeanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml"); Course myBean = context.getBean("myBean", Course.class); System.out.println(myBean); }
-
-
-
bean的作用域
在spring里面,可以设置bean实例是单实例还是多实例,默认情况下是单实例对象
scope属性值:第一个值默认值,singleton,表示单实例对象,在加载配置文件时创建实例对象
第二个值prototype, 表示是多实例对象,在运行时才创建实例对象
request,session基本不用,要用的话,就是在web项目中,把对象放到request或session中
-
bean的生命周期(!!!)
对象从创建到销毁的过程
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean的引用(调用set方法)
(3)把bean实例传递bean后置处理器的方法(当加入了后置处理器后才有)postProcessBeforeInitialization方法
(4)调用bean的初始化的方法(需要进行配置) init-method
(5)把bean实例传递bean后置处理器的方法(当加入了后置处理器后才有)postProcessAfterInitialization方法
(6)bean可以使用了(对象获取到了)
(7)当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
<bean id="orders" class="com.hxq.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手机" /> </bean>
public class Orders { public Orders() { System.out.println("第一步无参构造函数执行..........."); } 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("第五步, 执行销毁的方法。。。。。"); } }
@Test public void OrdersTest() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 获取实例对象。。。。。"); System.out.println(orders); context.close(); }
- ApplicationContext没有close方法,需要强转为子接口CPX,或者直接返回CPX
加入后置处理器后:
(1)创建类,实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之前执行的方法" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之后执行的方法" + bean); return bean; } }
(2)配置后置处理器
<!--配置后置处理器--> <bean id="myBeanPost" class="com.hxq.spring5.bean.MyBeanPost" />
-
自动装配
根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
bean标签中属性autowire,配置自动装配
常用两个值:
(1)byName根据属性名称注入, 属性名称需要与bean的id相同
(2)byType根据属性类型注入,相同类型的bean不能有多个,否则找不到要使用哪个bean
<!--自动装配--> <!--<bean id="emp" class="com.hxq.spring5.autowire.Emp" autowire="byName" />--> <bean id="emp" class="com.hxq.spring5.autowire.Emp" autowire="byType" /> <bean id="dept" class="com.hxq.spring5.autowire.Dept" />
-
引入外部属性文件
(1)直接配置数据库信息
-
引入相关依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.5</version> </dependency>
-
配置德鲁伊连接池
<!--直接配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/userDb" /> <property name="username" value="root" /> <property name="password" value="hexiqi123" /> </bean>
(2)引入外部属性文件配置数据库连接池
-
创建外部属性文件,properties格式的文件,写数据库信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0YX76xr-1617946922004)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210402133856926.png)]
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.username=root prop.password=hexiqi123
-
把外部properties属性文件引入到spring配置文件中
-
引入context命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
-
在spring配置文件使用标签引入外部属性文件
<context:property-placeholder location="jdbc.properties" />
-
配置连接池
<!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}" /> <property name="url" value="${prop.url}" /> <property name="username" value="${prop.username}" /> <property name="password" value="${prop.password}" /> </bean>
-
-
-
IOC操作Bean管理(基于注解)
Spring针对Bean管理中创建对象提供的注解
- @Component
- @Service:业务层
- @Controller:web层
- @Repository:持久层dao层
- 四个注解功能一样,都可用来创建bean实例
基于注解方式实现对象的创建
-
引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.5</version> </dependency>
-
开启组件扫描
(1)引入context命名空间
(2)开启组件扫描
- 如果扫描多个包,多个包使用逗号隔开
- 扫描包的上层目录
<!--开启组件扫描--> <context:component-scan base-package="com.hxq.spring5" />
(3)创建类,添加注解
@Service(value = "userService") //相当于<bean id="userService" class="..."/> public class UserService { public void add() { System.out.println("service add........."); } }
- 在注解里面value属性值可以省略不写,默认是类名称,首字母小写
组件扫描中的细节配置
<!--示例1-->
<context:component-scan base-package="com.hxq" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
-
use-default-filters=“false” :表示现在不使用默认的filter,而使用自己配置的filter
-
context:include-filter:扫描哪些内容
<!--示例2-->
<context:component-scan base-package="com.hxq">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- context:exclude-filter:不扫描哪些内容
基于注解的属性注入
-
@AutoWired:根据属性类型进行自动装配
-
@Qualifier:根据属性名称进行注入,需要和**@AutoWired**一起使用
@Service(value = "userService") public class UserService { //不需要添加set方法 //根据类型进行注入,如果接口有多个实现类,需要配合@Qualifier使用 @Autowired //根据名称进行注入 @Qualifier(value = "userDaoImpl1") private UserDao userDao; public void add() { System.out.println("service add........."); userDao.add(); } }
-
@Resource:可以根据类型注入,也可以根据名称注入
// @Resource//根据类型注入 @Resource(name = "userDaoImpl1") //根据名称进行注入 private UserDao userDao;
- 注意:@Resource是javax拓展包中的注解,不是Spring中的注解
-
@Value:注入普通类型属性
@Value("abc") private String name;
完全注解开发
-
创建配置类,替代xml配置文件
@Configuration @ComponentScan(basePackages = {"com.hxq.spring5"}) public class SpringConfig { }
-
编写测试类
@Test public void testService2() { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.add(); }
- AnnotationConfigApplicationContext
AOP
-
AOP是面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
-
不通过修改源代码方式,在主干功能上添加新的功能
-
底层使用动态代理
- 有两种情况的动态代理
- 有接口:使用JDK动态代理:创建实现类代理对象,增强类的方法
- 没有接口:使用CGLIB动态代理:创建子类代理对象,增强类的方法
- 有两种情况的动态代理
JDK动态代理
(1)Proxy类
-
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)方法返回接口代理类对象
(1)第一个参数,类加载器
(2)第二个参数,增强方法所在类,这个类实现的接口,支持多个接口
(3)实现这个接口InvocationHandler,创建代理对象,写增强的方法
(2)代码实现
-
创建接口,定义方法
public interface UserDao { public int add(int a, int b); }
-
创建接口实现类,实现方法
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { System.out.println("add方法执行了,,,"); return a + b; } }
-
创建处理生成代理对象的处理类,实现InvocationHandler接口
class UserDaoProxy implements InvocationHandler { private Object obj; //1 把创建的谁的代理对象,把谁传递过来 //有参构造传递 public UserDaoProxy(Object o) { this.obj = o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在方法之前 System.out.println("方法之前执行。。。。" + method.getName() + ":传递的参数..." + Arrays.toString(args)); //被增强的方法 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后执行, "+ obj); return res; } }
-
使用Proxy类创建接口代理对象
Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
AOP主要相关术语
(1)连接点:类里面的哪些方法可以被增强,这些方法就称为连接点
(2)切入点:实际被真正增强的方法,称为切入点
(3)通知(增强):实际增强的逻辑部分称为通知(增强)
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
(4)切面:是一个过程,把通知应用到切入点的过程
AOP操作
- Spring框架一般都是基于AspectJ实现AOP操作
- AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
准备工作:
-
在项目工程中引入AOP相关依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.5</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.5</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
-
切入点表达式
(1)作用:知道对哪个类里面的那个方法进行增强
(2)语法结构:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
基于AspectJ实现AOP操作
基于xml配置文件实现(了解即可)
-
创建类,在类中定义方法
public class BookProxy { public void before() { System.out.println("before.............."); } }
public class Book { public void buy() { System.out.println("buy.............."); } }
-
在spring配置文件中创建两个类对象
<!--创建对象--> <bean id="book" class="com.hxq.spring5.aopxml.Book" /> <bean id="bookProxy" class="com.hxq.spring5.aopxml.BookProxy" />
-
在spring配置文件中配置切入点
<!--配置aop增强--> <aop:config> <!--切入点--> <aop:pointcut id="p" expression="execution(* com.hxq.spring5.aopxml.Book.buy())"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--增强作用在具体的方法上--> <aop:before method="before" pointcut-ref="p" /> </aop:aspect> </aop:config>
基于注解方式实现
-
创建类,在类里面定义方法
public class User { public void add() { System.out.println("add............."); } }
-
创建增强类(编写增强逻辑)
(1)在增强类里面创建方法,让不同方法代表不同通知类型
public class UserProxy { public void before() { System.out.println("before............."); } }
-
进行通知的配置
(1)在spring配置文件中,开启注解扫描
<!--开启注解扫描--> <context:component-scan base-package="com.hxq.spring5.aopanno" />
(2)使用注解创建User和UserProxy对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8McgB4Ds-1617946922005)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403161847528.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oTIl0GgC-1617946922006)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403161902370.png)]
(3)在增强类上面添加注解**@Aspect**
(4)在spring配置文件中开启生成代理对象
<!--开启AspectJ生成代理对象--> <aop:aspectj-autoproxy />
-
配置不同类型的通知
//前置通知 @Before(value = "execution(* com.hxq.spring5.aopanno.User.add())") //最终通知 @After(value = "execution(* com.hxq.spring5.aopanno.User.add())") //后置通知(返回通知) @AfterReturning(value = "execution(* com.hxq.spring5.aopanno.User.add())") //异常通知 @AfterThrowing(value = "execution(* com.hxq.spring5.aopanno.User.add())") //环绕通知 @Around(value = "execution(* com.hxq.spring5.aopanno.User.add())") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前............."); //被增强的方法 proceedingJoinPoint.proceed(); System.out.println("环绕之后。。。。。。"); }
-
其中环绕通知带参数ProceedingJoinPoint类,方法proceed()表示方法执行
-
最终通知不管有没有异常,都会执行,相当于finally
-
执行结果:
环绕之前…
before…
add…
AfterReturning…
after…
环绕之后。。。。。。
-
-
相同的切入点抽取
//相同切入点抽取 @Pointcut(value = "execution(* com.hxq.spring5.aopanno.User.add())") public void pointdemo() {}
- 之后只需在注解中调用方法即可
-
有多个增强类对同一个方法进行增强,设置增强类优先级
- 在增强类上添加注解@Order(数字类型值),数字类型值越小优先级越高
-
完全使用注解开发
-
创建配置类,不需要创建xml文件
-
@Configuration @ComponentScan(basePackages = {"com.hxq.spring5"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
-
JdbcTemplate
-
Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库的操作
-
引入相关依赖
-
<!-- 德鲁伊连接池依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.5</version> </dependency> <!-- 数据库连接的依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!-- spring封装了jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.5</version> </dependency> <!-- 针对事务的相关操作的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.5</version> </dependency> <!--整合其他框架需要用到的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.5</version> </dependency>
-
-
在spring配置文件中配置数据库连接池
-
<!--数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true" /> <property name="username" value="root" /> <property name="password" value="hexiqi123" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean>
-
-
配置JdbcTemplate对象,注入DataSource
-
<!--JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入dataSource对象--> <property name="dataSource" ref="dataSource" /> </bean>
-
-
创建service类,创建dao类,在dao注入jdbcTemplate对象
-
配置文件
-
<!--组件扫描--> <context:component-scan base-package="com.hxq.spring5" />
-
-
service
-
@Service public class BookService { //注入dao @Autowired private BookDao bookDao; }
-
-
dao
-
@Repository public class BookDaoImpl implements BookDao { //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }
-
-
JdbcTemplate操作数据库
(1)添加
-
对应数据库创建实体类
-
编写service和dao
-
在dao中进行数据库操作
-
调用JdbcTemplate对象里面update方法实现添加操作
- 第一个参数:sql语句
- 第二个参数,可变参数,设置sql语句值
-
@Repository public class BookDaoImpl implements BookDao { //注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //添加的方法 @Override public void add(Book book) { //1 创建sql语句 String sql = "insert into t_book values(?,?,?)"; //2 调用方法实现 Object[] args = {book.getUserId(), book.getUserName(), book.getUstatus()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } }
-
(2)修改和删除
//修改的方法
@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);
}
//删除的方法
@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);
}
(3)查询
-
返回值
(1)查询表里面有多少条记录,返回是某个值
(2)使用JdbcTemplate的queryForObject方法实现查询返回某个值代码
//查询表中的记录数 @Override public int selectCount() { String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
- 参数1:sql语句
- 参数2:返回类型的class
-
返回对象
(1)场景:查询图书的详情页面,返回查询图书的信息
(2)JdbcTemplate中的queryForObject方法实现查询返回对象
@Override public Book findBookInfo(String id) { String sql = "select * from t_book where user_id=?"; Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; }
- 第一个参数:sql语句
- 第二个参数:RowMapper,是一个接口,返回不同类型数据,使用这个接口里面的实现类完成数据封装
- 第三个参数:sql语句值
-
返回集合
(1)场景:查询图书列表分页…
(2)调用JdbcTemplate中的query方法实现查询返回集合
//查询返回集合 @Override public List<Book> findAllBook() { String sql = "select * from t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; }
(4)批量添加功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zygOWbjR-1617946922007)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403220232359.png)]
- 第一个参数:sql语句
- 第二个参数:List集合,添加多条记录数据
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
- 修改删除类似
事务管理
- 事务是数据库操作的基本单元,逻辑上的一组操作,要么都成功,如果有一个操作失败,则所有操作都失败
- 典型场景:银行转账
- 四大特性(ACID):
- 原子性:所有操作,要么全成功,要么全部失败
- 一致性:一个事务执行之前和执行之后都必须处于一致性状态
- 隔离性:多个用户同时访问数据库,每个用户之间的操作互相隔离,互不影响
- 持久性:一个事务一旦被提交了,就会修改数据库,而且这个修改是永久性的
搭建事务操作环境
- web层:显示到web端的数据
- service层:主要编写业务逻辑代码
- dao层:主要编写数据操作的代码
-
创建数据库表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4Asdj6g-1617946922008)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404145310331.png)]
-
创建service,搭建dao,完成对象的创建和注入关系
(1)service注入dao,在dao里面注入JdbcTemplate,在JdbcTemplate中注入dataSource
-
在dao里面定义方法,在service里面定义方法
- 中间出现异常,会导致数据不一致,需要使用事务进行处理
-
事务的操作过程
(1)开启事务
(2)进行业务操作
(3)没有发生异常,则提交事务
(4)出现异常,则捕获异常,进行事务回滚
事务管理
-
事务添加到JavaEE三层结构中的Service层中(业务逻辑层)
-
在Spring中进行事务管理操作
(1)编程式事务管理
(2)声明式事务管理
-
声明式事务管理
(1)基于注解方式
(2)基于xml配置文件方式
-
在Spring进行声明式事务管理,底层使用AOP
-
Spring事务管理API
(1)提供一个接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNwNprBU-1617946922009)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404153041893.png)]
(2)PlatformTransactionManager是一个事务管理器接口,如果使用jdbc模板,则使用DataSourceTransactionManager实现类
spring声明式事务管理(注解方式)
-
在spring配置文件中配置事务管理器
<!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
-
在Spring配置文件中开启事务注解
(1)在Spring配置文件中引入名称空间tx
(2)开启事务注解,transaction-manager指定事务管理器
<!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager" />
-
在service类上面或方法上添加事务注解**@Transactional**
(1)注解可以添加到类上面,也可以添加到方法上
(2)如果把这个注解添加到类上面,这个类中的所有方法都添加事务
(3)如果把这个注解添加到某个方法上面,那么只为这个方法添加事务
@Service @Transactional public class UserService {
事务参数
在service类上添加注解@Transactional,在这个注解里可以配置事务相关参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bul3KXzB-1617946922010)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404224703399.png)]
(1)propagation: 事务的传播行为
(2)isolation: 事务的隔离级别
(3)timeout: 超时时间
(4)readOnly: 是否只读
(5)rollbackFor: 回滚
(6)noRollbackFor: 不会滚
-
传播行为(propagation)
(1)多事务方法直接进行调用,这个过程中事务是如何进行管理的
(2)事务方法:对数据库表数据进行变化的操作
(3)Spring框架事务传播行为有7种
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVngoAg4-1617946922011)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404230245564.png)]
- REQUIRED: 如果add方法本身有事务,则调用update之后,update使用当前的add方法里的事务,如果add方法本身没有事务,则调用update之后,创建新的事务
- REQUIRES_NEW: 使用add方法调用update方法,add无论是否有事务,都创建新的事务
- SUPPORTS
- NOT_SUPPORTS
- MANDATORY
- NEVER
- NESTED
@Service @Transactional(propagation = Propagation.REQUIRES_NEW) public class UserService {
-
隔离级别(isolation)
(1)事务里面有一个特性称为隔离性,在多事务操作之间不会产生影响。不考虑隔离性会产生很多问题。
(2)有三个读问题:脏读,不可重复读,虚读(幻读)。
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交的事务读取到另一个提交事务修改数据
- 虚读(幻读):一个未提交事务读取到另一个已提交事务添加的数据
(3)通过设置事务的隔离性解决读问题
脏读 不可重复读 幻读 READ UNCOMMITTED(读未提交) 有 有 有 READ COMMITTED(读已提交) 无 有 有 REPEATABLE READ(可重复读)(mysql默认) 无 无 有 SERIALIZABLE(串行化) 无 无 无
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
public class UserService {
-
超时时间(timeout)
(1)事务需要在一定时间内进行提交,如果不提交则进行回滚
(2)默认值:-1,设置时间以秒为单位进行计算
-
是否只读(readOnly)
(1)读:查询操作,写:添加修改删除操作
(2)readOnly默认值:false, 可以查询也可以添加修改删除
(3)设置readOnly值为true,只能进行查询操作
-
回滚(rollbackFor)
(1)设置出现哪些异常进行事务回滚
-
不回滚(noRollbackFor)
(1)设置出现哪些异常不进行事务回滚
spring声明事务管理(xml方式)
-
在spring配置文件中,进行配置
-
配置事务管理器
-
配置通知
<!--配置通知--> <tx:advice id="txadvice"> <!--配置事务的相关参数--> <tx:attributes> <!--指定哪种规则的方法上添加事务--> <tx:method name="account*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
-
配置切入点切面
<!-- 配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* com.hxq.spring5.service.UserService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt" /> </aop:config>
-
声明式事务管理(完全注解开发)
-
创建配置类,使用配置类代替xml配置文件
@Configuration //配置类 @ComponentScan(basePackages = "com.hxq.spring5") //组件扫描 @EnableTransactionManagement //开启事务 public class TxConfig { //创建数据库的连接池 @Bean public DruidDataSource getDruidDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true"); dataSource.setUsername("root"); dataSource.setPassword("hexiqi123"); return dataSource; } //创建JdbcTemplate对象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { //到ico容器中根据类型找到dataSource JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //创建事务管理器对象 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
Spring5新特性
- 整个Spring5框架的代码基于jdk8,运行时兼容jdk9,许多不建议使用的类和方法在代码库中删除
spring5框架自带通用的日志封装
(1)spring5已经移除了Log4jConfigListener,官方建议使用Log4j2
(2)Spring5整合Log4j2
-
引入相关依赖
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency>
-
创建log4j2.xml配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration status="DEBUG"> <appenders> <console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:ss:SSS} [%t] %-51level %logger{36} - %msg%n" /> </console> </appenders> <loggers> <root level="info"> <appender-ref ref="Console" /> </root> </loggers> </configuration>
Spring5框架核心容器支持@Nullable注解
(1)@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
(2)注解用在方法上面,表示方法返回值可以为空
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4Fif1th-1617946922012)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184030155.png)]
(3)注解使用在参数上,表示参数可以为空
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1LLcs6sT-1617946922013)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184147207.png)]
(4)注解使用在属性上,表示属性值可以为空
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncgdUhS0-1617946922015)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184343892.png)]
spring5核心容器支持函数式风格GenericApplicationContext
//函数式风格创建对象,交给spring管理
@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("user1");
System.out.println(user);
}
- 如果其中未指定bean的名字,则获取bean时,可以使用类的全路径来获取
spring5支持整合JUnit5框架
-
整合JUnit4
(1)引入Spring相关针对测试的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.5</version> <scope>test</scope> </dependency>
(2)创建测试类,使用注解完成
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架 @ContextConfiguration("classpath:bean2.xml") //加载配置文件 public class JTest4 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
- @RunWith(SpringJUnit4ClassRunner.class)
-
spring5整合JUnit5
(1)引入JUnit5的相关依赖
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.0-M1</version> <scope>test</scope> </dependency>
(2)创建测试类,使用注解完成
@ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean2.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
- @ExtendWith(SpringExtension.class)
- 或者直接使用复合注解**@SpringJUnitConfig(locations = “classpath:bean2.xml”)**
SpringWebFlux
介绍
SpringWebFlux是Spring5添加的新的模块,用于web开发,功能SpringMVC类似的,Webflux使用当前一种比较流行响应式编程出现的框架。
使用传统web框架,比如SpringMVC,这些基于Servlet容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
- 异步非阻塞
- 异步和同步:针对调用者,调用者发送请求后,异步不会等着请求返回后执行其他操作,同步需等待请求返回后再去执行其他操作
- 非阻塞和阻塞:针对被调用者,被调用者收到请求后,做完请求任务之后才给出反馈,这个叫阻塞,收到请求直接给出反馈,再去执行请求的任务,这是非阻塞
Webflux特点
- 异步非阻塞:在有限的资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
- 函数式编程:Spring5框架基于java8,Webflux使用java8函数式编程方式实现路由请求
比较springMVC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veMwCRcd-1617946922016)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406125654954.png)]
(1)两个框架都可以使用注解方式,都运行在Tomcat等容器中
(2)SpringMVC采用命令式编程,WebFlux采用异步响应式编程
响应式编程
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型回自动将变化的值通过数据流进行传播。
例如:Excel中的表格如果填的一个函数,那个格子的值会根据函数中的参数改变而改变
-
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(); //通知 } }
-
-
Reactor实现
(1)响应式编程操作中,Reactor满足Reactive规范框架
(2)Reactor有两个核心类,Mono和Flux,这两个类实现接口Publisher,提供丰富的操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或者1个元素。
(3)Flux 和 Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
(4)代码演示:
-
引入依赖
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core --> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.4.4</version> </dependency>
-
编写代码
//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流 Stream<Integer> stream = list.stream(); Flux.fromStream(stream);
-
订阅需要用到subscribe()方法
Flux.just(1,2,3,4).subscribe(System.out::print); Mono.just(1).subscribe(System.out::print);
-
(5)三种信号特点
- 错误信号和完成信号都是终止信号,不能同时共存
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示无限数据流
(6)调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅声明都不会发生。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kGgKdNY-1617946922017)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406134730630.png)]
(7)操作符
-
对数据流进行一道道操作,就称为操作符,比如工厂流水线对产品进行加工
-
map 元素映射为新元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXHRy0ON-1617946922018)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406135119337.png)]
-
flatmap 元素映射为流
- 把每个元素转换流,把转换之后多个流合并大的流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ajm0JmT-1617946922019)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406135212444.png)]
-
执行流程和核心API
SpringWebFlux基于Reator,默认使用的容器是Netty,Netty是高性能的NIO框架,异步非阻塞框架
-
Netty
-
BIO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zyvkxapr-1617946922020)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406150807475.png)]
-
NIO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lg7yJpDe-1617946922022)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406150914068.png)]
-
-
SpringWebFlux执行过程和SpringMVC相似的
-
SpringWebFlux核心控制器DispatchHandler,实现接口WebHandler
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WlDh4WJs-1617946922023)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406151412639.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBujsvPu-1617946922023)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406151730898.png)]
-
-
SpringWebFlux里面DispatcherHandler,负责请求的处理
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责处理请求
- HandlerResultHandler:相应结果处理
-
SpringWebFlux实现函数式编程,两个接口:RouterFunction(路由处理) 和 HandleFunction(处理函数)
-
SpringWebFlux(基于注解编程模型)
-
SpringWebFlux实现方式有两种:注解编程模型和函数式编程模型
-
使用注解编程模型方式,和之前SpringMVC使用相似的,只需把相关的依赖配置到项目中,SpringBoot自动配置相关运行容器,默认使用Netty服务器
(1)创建SpringBoot工程,引入WebFlux依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMnTY3Ik-1617946922024)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210408195916482.png)]
(2)配置启动端口号
server.port=8081
(3)创建包和相关类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfYLrUyu-1617946922025)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210408200248193.png)]
(4)创建实体类
@Data public class User { private String name; private String gender; private Integer age; }
(5)创建了接口,定义操作的方法
public interface UserService { //根据id查询用户 Mono<User> getUserById(String id); //查询所有用户 Flux<User> getAllUser(); //添加用户 Mono<Void> saveUserInfo(Mono<User> user); }
(6)接口的实现类
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",20)); this.users.put(3, new User("jack","nv",20)); } @Override public Mono<User> getUserById(Integer 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 -> { users.put(users.size() + 1, person); }).thenEmpty(Mono.empty()); } }
(6)创建controller
@RestController public class UserController { @Autowired private UserService userService; //id查询 @GetMapping("/user/{id}") public Mono<User> getUserById(@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
-
-
SpringWebFlux(基于函数式编程模型)
(1)在使用函数式编程模型操作时候,需要自己初始化服务器
(2)基于函数式编程模型实现,有两个核心接口:RouteFunction(实现路由功能,请求转发给对应的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> notFount = ServerResponse.notFound().build(); Mono<User> userMono = this.userService.getUserById(userId); //把userMono进行转换返回 //使用Reactor操作符flatMap return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person))).switchIfEmpty(notFount); } //查询素有 public Mono<ServerResponse> getAllUsers() { Flux<User> users = this.userService.getAllUser(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class); } //添加 public Mono<ServerResponse> saveUser(ServerRequest request) { Mono<User> userMono = request.bodyToMono(User.class); return ServerResponse.ok().build(this.userService.saveUserInfo(userMono)); } }
-
初始化服务器,编写Router
public class Server { //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 static void main(String[] args) throws IOException { Server server = new Server(); server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); }
-
使用WebClient调用
public class Client { public static void main(String[] args) { //调用服务器地址 WebClient webClient = WebClient.create("http://127.0.0.1:51095"); //根据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(); } }
-
Response> 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 static void main(String[] args) throws IOException { Server server = new Server(); server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); }
-
使用WebClient调用
public class Client { public static void main(String[] args) { //调用服务器地址 WebClient webClient = WebClient.create("http://127.0.0.1:51095"); //根据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(); } }
spring学习笔记–尚硅谷
此文是在学习尚硅谷视频时手打的笔记。
另附上尚硅谷spring5视频链接:尚硅谷Spring5