Spring
Spring
ref:
-
官网: Spring | Home
-
下载:https://repospring.iofrelease/org/springframework/spring/
-
Github:GitHub - spring-projects/spring-framework: Spring Framework
-
核心
- IoC:控制反转,把创建对象交给Spring来管理(不用手动new实例对象),降低耦合度
- 原理:xml解析,工厂模式。反射
- Bean管理:Spring创建对象,Spring注入属性(DI:依赖注入)
- 方式一:xml配置
- 方式二:注解
- Aop:面向切面,不修改源代码进行增强
- IoC:控制反转,把创建对象交给Spring来管理(不用手动new实例对象),降低耦合度
-
BeanFactory与ApplicationContext,前者使用对象时才会创建,后者加载时就创建,默认创建无参构造方法
初始化项目
-
创建空项目
-
创建对应文件夹
-
导入jar包到libs
-
项目结构-模块-选中模块-依赖-添加libs下的jar包
-
项目结构-项目-设置SDK、Java版本以及输出目录
-
配置文件bean1.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.learn.bean.User"> <!--DI 属性注入 需要有set方法--> <property name="name" value="zhangsan"/> </bean> </beans> -
User
public class User { private String name; public void add(){ System.out.println("add..."); } public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } -
测试
public class SpringTests { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); User user = context.getBean(User.class); System.out.println(user.getName());// zhangsan user.add();// add... } }
Bean管理
XML方式
属性注入
<bean id="user" class="com.learn.bean.User"> <!--DI 属性注入 需要有set方法--> <property name="name" value="zhangsan"/> </bean>
有参构造创建对象
<bean id="user" class="com.learn.bean.User" > <constructor-arg name="name" value="zhangsan"/> <constructor-arg name="age" value="16"/> </bean>
命名空间
配置文件给beans添加属性
xmlns:p="http://www.springframework.org/schema/p"
需要set方法
<bean id="user" class="com.learn.bean.User" p:name="zhangsan" p:age="18"/>
设置空值
<bean id="user" class="com.learn.bean.User"> <property name="name"> <null /> </property> </bean>
value含特殊字符
一:使用<代替
<bean id="user" class="com.learn.bean.User"> <property name="name" value="<<zhangsan"/> <!--<<zhangsan--> </bean>
二:使用CDATA
<bean id="user" class="com.learn.bean.User"> <property name="name"> <value><![CDATA[<<南京>>]]></value> </property> </bean>
外部bean
同样需要有set方法(setUserService()),在UserDao中
<bean id="userService" class="com.learn.service.impl.UserServiceImpl"/> <bean id="userDao" class="com.learn.dao.UserDao"> <property name="userService" ref="userService"/> </bean>
内部bean
<bean id="user" class="com.learn.bean.User"> <property name="name" value="John"/> <property name="age" value="18"/> <property name="role"> <bean class="com.learn.bean.Role"> <property name="roleId" value="1"/> <property name="roleName" value="普通用户"/> </bean> </property> </bean>
级联赋值
roleName需要有get方法
<bean id="user" class="com.learn.bean.User"> <property name="name" value="John"/> <property name="age" value="18"/> <property name="role" ref="role"/> <property name="role.roleName" value="游客"/> </bean> <bean id="role" class="com.learn.bean.Role"> <property name="roleId" value="1"/> <property name="roleName" value="普通用户"/> </bean>
注入集合1
<bean id="user" class="com.learn.bean.User"> <property name="hobbies"> <!-- list或者array都可以支持数组对象注入--> <!-- String[]--> <array> <value>跑步</value> <value>游泳</value> </array> </property> <!-- List<E>--> <property name="pets"> <list> <value>小猫</value> <value>小狗</value> </list> </property> <!-- Map<K,V>--> <property name="cars"> <map> <entry key="car1" value="car1"/> <entry key="car2" value="car2"/> </map> </property> <!-- Set<E>--> <property name="titles"> <set> <value>title1</value> <value>title2</value> </set> </property> </bean>
User{name='null', age='null', role=null, hobbies=[跑步, 游泳, 吉他], pets=[小猫, 小狗], cars={car1=car1, car2=car2}, titles=[title1, title2]}
注入集合2
<property name="pets"> <list> <ref bean="pet1"/> <ref bean="pet2"/> </list> </property>
提取
需要引入命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:list id="pets"> <value>123</value> <value>456</value> <value>789</value> </util:list> <bean id="user" class="com.learn.bean.User"> <property name="pets" ref="pets"/> </bean> </beans>
FactoryBean
返回值类型不是类本身
bean.xml
<bean id="myBean" class="com.learn.bean.MyBean"/>
MyBean
public class MyBean implements FactoryBean<User> { // 定义返回对象 @Override public User getObject() { User user = new User(); user.setName("MyBean"); return user; } @Override public Class<?> getObjectType() { return null; } }
测试
User bean = context.getBean("myBean", User.class);// MyBean
bean单/多实例
单实例 scope="singleton"(默认)
<bean id="myBean" class="com.learn.bean.MyBean" scope="singleton"/>
多实例
<bean id="myBean" class="com.learn.bean.MyBean" scope="prototype"/>
区别:单实例加载配置文件时就会创建一个公共实例,每次用是它。多实例用的时候(getBean)创建,每个实例都是独立的,地址不同。
bean生命周期
-
构造器构造bean实例
public User() { System.out.println("第一步 -->> 执行无参构造方法创建bean实例..."); } -
为bean的属性赋值和对其它bean引用(set方法)
public void setName(String name) { this.name = name; System.out.println("第二步 -->> 调用set方法设置属性值..."); } -
调用bean的初始化方法(需要配置)
public void initMethod(){ System.out.println("第三步 -->> 执行初始化方法..."); } -
把bean实例传递给后置处理器的方法(需要实现BeanPostProcessor接口 postProcessBeforeInitialization方法 并在xml文件并配置)(初始化前)
⚠️ 所有bean实例都会执行
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("第三步 -->> bean初始化前"); return null; } -
bean可以使用了(对象获取到)
User bean = context.getBean(User.class); System.out.println(bean.getName()); -
把bean实例传递给后置处理器的方法(需要实现BeanPostProcessor接口 postProcessAfterInitialization 方法 并在xml文件配置)(初始化后)
⚠️ 所有bean实例都会执行
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("第五步 -->> bean初始化后"); return null; } -
当容器关闭时,调用bean的销毁方法(需要配置)
context.close();
自动装配
autowire = default | no | byName | byType | constructor
byName :根据属性的名字
byType : 根据属性的类型
<bean id="user" class="com.learn.bean.User" autowire="byName"/> <bean id="role" class="com.learn.bean.Role"> <property name="roleId" value="1"/> <property name="roleName" value="Amy"/> </bean>
外部属性文件
项目添加 druid-1.2.14.jar 和 mysql-connector-java-8.0.27.jar 并在项目模块设置依赖
需要声明命名空间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"> <!-- 引入属性文件,需要context名称空间--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
注解方式
Spring针对bean管理中创建对象提供注解
- @Component(普通组件,@Component(value = "user"),只有value可以省略,不写默认value = 类名首字母小写 )
- @Service(Spring建议放在service层上)
- @Controller(Spring建议放在controller层上)
- Repository (Spring建议放在dao层上)
上面注解功能一样,都是创建bean实例
前置
-
使用注解操作,需要额外的AOP包
-
添加spring-aop-5.2.9.RELEASE.jar到libs,并在项目结构模块依赖添加该jar包
-
开启组件扫描:
<?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"> <context:component-scan base-package="com.learn.service" /> <context:component-scan base-package="com.learn.dao" /> <context:component-scan base-package="com.learn.controller" /> <context:component-scan base-package="com.learn.bean" /> </beans> -
给类添加注解
@Component public class Student { private String name; private String sex; // setter getter constructor toString... } @Service public class StudentServiceImpl implements StudentService { private StudentDao studentDao = new StudentDao(); public void print(){ System.out.println("StudentServiceImpl print..."); studentDao.print(); } } @Controller public class StudentController { private StudentService studentService = new StudentServiceImpl(); public void print(){ studentService.print(); System.out.println("StudentController print..."); } } @Repository public class StudentDao { public void print(){ System.out.println("StudentDao print..."); } } -
注意:开启扫描和使用注解少了任意一个都不行
public class SpringTests { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Student student = context.getBean(Student.class); student.setName("Jack"); System.out.println(student); StudentController controller = context.getBean(StudentController.class); controller.print(); } }
Student{name='Jack', sex='null'} StudentController print... StudentServiceImpl print... StudentDao print...
自定义扫描
- use-default-filters:不使用默认的filter
- 第一个context:component-scan解释:仅在该包扫描带Service注解的类
- 第二个context:component-scan解释:忽略该包带Component注解的类
<?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"> <context:component-scan base-package="com.learn.service" use-default-filters="false"> <!-- type = annotation | assignable | aspectj | regex | custom --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan> <context:component-scan base-package="com.learn.dao" use-default-filters="false"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan> <context:component-scan base-package="com.learn.controller" /> <context:component-scan base-package="com.learn.bean" /> </beans>
属性注入
- @AutoWired:根据属性类型自动注入
- Qualifier:根据属性名称注入
- @Resource:根据属性名称或类型自动注入
- @Value:诸如普通类型属性
@Controller public class StudentController { @Resource private StudentService studentService; @Value("666") private String name; public void print(){ System.out.println("StudentController print..." + name);// StudentController print...666 studentService.print(); } }
注解配置
创建配置类,替代xml配置文件
@Configuration @ComponentScan(basePackages = {"com.learn"}) public class SpringConfig { }
public class SpringTests { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); StudentController controller = context.getBean(StudentController.class); controller.print(); } }
AOP
-
面向切面编程,降低耦合度,提高重用性
-
不修改源代码修改而增强功能
-
底层使用动态代理
-
代理的两种情况
- JDK动态代理:创建UserDao接口实现类代理对象
- CGLIB动态代理:创建当前类子类的代理对象
基本术语
-
连接点:类里面可以被增强的方法
-
切入点:被增强的方法
-
通知(增强):增强的部分
- 前置通知:@Befaore 切入点 前执行
- 后置通知:@AfterReturning 切入点 返回结果后执行
- 环绕通知:@Around 切入点 前、后分别执行
- 异常通知:@AfterThrowing 切入点 出现异常时
- 最终通知:@After 切入点 后执行,不管有无异常,最终都会被执行
-
切面:把通知应用到切点的过程
JDK动态代理
下面的代码所有的方法都会被代理
- 匿名内部类
public class JDKProxy { public static void main(String[] args) { // 创建接口实现类代理对象 Class<?>[] interfaces = {AdminDao.class}; /* * arg1 类加载器 : JDKProxy的类加载器 * arg2 接口数组 : 要增强的接口 * arg3 增强逻辑 : 对目标进行增强的逻辑 * */ AdminDao adminDao =(AdminDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { /** * 增强部分,参数都是被代理对象原来的内容 * @param proxy 被代理的对象,空的,需要自己手动添加 * @param method 执行的方法 * @param args 传递给方法的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理的谁? proxy = new AdminDaoImpl(); // 在 增强方法执行 之前执行的代码 System.out.println("-------------- >>>> " + method.getName() + "之前执行 ,权限校验。传递的参数: " + Arrays.toString(args)); // method:当前方法, obj:被代理的对象, args:参数 Object res = method.invoke(proxy, args); // 在 增强方法执行 之后执行的代码 System.out.println("-------------- >>>> login之后执行,记录到日志系统"); return res; } }); // 测试 boolean login = adminDao.login("admin", "123456"); System.out.println("login result:" + login); } }
- 实现 InvocationHandler 接口
public class JDKProxy { public static void main(String[] args) { // 创建接口实现类代理对象 Class<?>[] interfaces = {AdminDao.class}; /* * arg1 类加载器 : JDKProxy的类加载器 * arg2 接口数组 : 要增强的接口 * arg3 增强逻辑 : 对目标进行增强的逻辑 * */ AdminDao adminDao =(AdminDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces,new AdminDaoProxy(new AdminDaoImpl())); // 测试 boolean login = adminDao.login("admin", "123456"); System.out.println("login result:" + login); } } class AdminDaoProxy implements InvocationHandler{ // 代理谁把谁传过来 private Object obj; public AdminDaoProxy(Object obj) {this.obj = obj;} public AdminDaoProxy() {} @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在 增强方法执行 之前执行的代码 System.out.println("-------------- >>>> " + method.getName() + "之前执行 ,权限校验。传递的参数: " + Arrays.toString(args)); // method:当前方法, obj:被代理的对象, args:参数 Object res = method.invoke(obj, args); // 在 增强方法执行 之后执行的代码 System.out.println("-------------- >>>> login之后执行,记录到日志系统"); return res; } }
- 结果
-------------- >>>> login之前执行 ,权限校验。传递的参数: [admin, 123456] AdminDaoImpl login ... 验证账号密码 ... -------------- >>>> login之后执行,记录到日志系统 login result:true
- 指定方法做处理
method.getName().equals("login")
AOP操作
准备工作
- 引入依赖
需要依赖spring-aspects以及spring-aspects所依赖的包,之后,项目结构-模块-添加依赖 这样就应用到项目中了
当前包情况
aspectjrt-1.9.6.jar(spring-aspects依赖) aspectjweaver-1.9.6.jar(spring-aspects依赖) commons-logging-1.1.1.jar druid-1.2.14.jar mysql-connector-java-8.0.27.jar spring-aop-5.2.9.RELEASE.jar spring-aspects-5.2.9.RELEASE.jar spring-beans-5.2.9.RELEASE.jar spring-context-5.2.9.RELEASE.jar spring-core-5.2.9.RELEASE.jar spring-expression-5.2.9.RELEASE.jar
切入点表达式
-
切入点表达式作用:知道对哪个类里面的哪个方法进行增强。
-
语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表])) 权限修饰符:public private 等...* 表示所有
-
例1:对com.learn.dao.UserDao类里面的add方法进行增强
execution(* com.learn.dao.UserDao.add(...)) -
例2:对com.learn.dao.UserDao类里面的get开头方法进行增强
execution(* com.learn.dao.UserDao.get*(...)) -
例3:对com.learn.dao.UserDao类里面的所有方法进行增强
execution(* com.learn.dao.UserDao.*(...)) -
例4:对com.learn.dao.UserDao类里面的所有类所有方法进行增强
execution(* com.learn.dao.*.*(...))
基于XML
示例
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <!-- 创建对象--> <bean id="book" class="com.learn.bean.Book"/> <bean id="bookProxy" class="com.learn.proxy.BookProxyXML"/> <!-- aop增强配置--> <aop:config> <!-- 切入点--> <aop:pointcut id="pcSet" expression="execution(* com.learn.bean.Book.set*(..))"/> <!-- 配置切面 把增强应用到切入点--> <aop:aspect ref="bookProxy"> <!-- 增强(method)作用在具体方法上(pointcut-ref)--> <aop:before method="before" pointcut-ref="pcSet"/> <aop:after method="after" pointcut-ref="pcSet"/> <aop:around method="around" pointcut-ref="pcSet"/> <aop:after-returning method="afterReturning" pointcut-ref="pcSet"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pcSet"/> </aop:aspect> </aop:config> </beans>
类
public class Book{ private String name; public void setName(String name) { System.out.println("setName...: " + name); this.name = name; } // setter getter... }
增强类
public class BookProxyXML { public void before(){ System.out.println("before..."); } public void after(){ System.out.println("after..."); } public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("around - before..."); Object result = joinPoint.proceed(); System.out.println("around - after..."); return result; } public void afterReturning(){ System.out.println("afterReturning..."); } public void afterThrowing(){ System.out.println("afterThrowing..."); } }
测试
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Book book = context.getBean(Book.class); book.setName("三国演义");
输出
before... around - before... setName...: 三国演义 afterReturning... around - after... after...
基于注解
示例
- 开启组件扫描 与 开启AspectJ生成代理对象
<?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.learn.proxy" /> <context:component-scan base-package="com.learn.bean" /> <aop:aspectj-autoproxy/> </beans>
- 类与增强类
@Component public class Book { private Integer id; private String name; // setter getter... }
// Book增强的类 @Component @Aspect// 表示生成代理对象 public class BookProxy { // 前置通知 public void before(){ System.out.println("before..."); } }
- 配置不同类型的通知
// Book增强的类 @Component @Aspect // 表示生成代理对象 public class BookProxy { // 前置通知 @Before("execution(* com.learn.bean.Book.getName(..))") public void before(){ System.out.println("before..."); } }
- 测试
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean10.xml"); Book book = context.getBean(Book.class); book.setName("三国演义"); System.out.println(book.getName());
before... 三国演义
- 各种通知
public void setName(String name) { System.out.println("setName: " + name); this.name = name; }
package com.learn.proxy; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; // Book增强的类 @Component @Aspect // 表示生成代理对象 public class BookProxy { // 前置通知 @Before("execution(* com.learn.bean.Book.setName(..))") public void before() { System.out.println("before..."); } // 后置通知/最终通知 方法执行完,不管有没有异常都会通知 @After("execution(* com.learn.bean.Book.setName(..))") public void after() { System.out.println("after..."); } // 异常通知 @AfterThrowing("execution(* com.learn.bean.Book.setName(..))") public void afterThrowing() { System.out.println("afterThrowing..."); } // 环绕通知 @Around("execution(* com.learn.bean.Book.setName(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("Around - before..."); Object result = pjp.proceed(); System.out.println("Around - after..."); return result; } // 方法返回值之后 @AfterReturning("execution(* com.learn.bean.Book.getName(..))") public void afterReturning() { System.out.println("AfterReturning..."); } }
执行顺序(正常)
Around - before... before... setName: 三国演义 after... Around - after...
执行顺序(异常)
Around - before... before... setName: 三国演义 afterThrowing... after... Exception in thread "main" java.lang.ArithmeticException:....
@Pointcut
在Book类以set开头的方法和Student类以set开头的方法之前通知
@Pointcut("execution(* com.learn.bean.Book.set*(..))") public void setBook(){} @Pointcut("execution(* com.learn.bean.Student.set*(..))") public void setStudent(){} // 前置通知 @Before("setStudent() || setBook()") public void before() { System.out.println("before..."); }
@Order
优先级,一个类有多个增强时,优先执行增强类
数值越小,优先级越高,@Order(1) 优先级高于 @Order(2)
// Book增强的类 @Component @Aspect // 表示生成代理对象 @Order(1) public class BookProxy {}
完全注解
Java代码替代上面的xml文件
@Configuration @ComponentScan(basePackages = {"com.learn"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AOPConfig { }
加载时
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AOPConfig.class);
JDBC Template
准备工作
- 导包
spring对jdbc的封装
需要的jar包
druid-1.2.14.jar
mysql-connector-java-8.0.27.jar
spring-jdbc-5.2.9.RELEASE.jar
spring-tx-5.2.9.RELEASE.jar
spring-orm-5.2.9.RELEASE.jar
添加并添加到项目依赖
- 配数据库连接池
<?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 https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 引入属性文件,需要context名称空间--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
- JDBC Template注入DruidDataSource
<!-- 配置jdbc template 注入 dataSource--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <property name="dataSource" ref="dataSource"/> </bean>
- 组件扫描
<!-- 组件扫描--> <context:component-scan base-package="com.learn.controller"/> <context:component-scan base-package="com.learn.service"/> <context:component-scan base-package="com.learn.dao"/> <context:component-scan base-package="com.learn.bean"/>
- 创建对应类和接口
dao
@Repository public class StudentDao { @Resource private JdbcTemplate jdbcTemplate; public void insert(Student student) { String sql = "insert into `student` values(?,?,?)"; Object[] args = {null,student.getName(), student.getSex()}; int update = jdbcTemplate.update(sql, args); System.out.println("StudentDao add --->> " + update); } }
service
@Service public class StudentServiceImpl implements StudentService { @Resource private StudentDao studentDao; @Override public void insert(Student student) { studentDao.add(student); } }
Test
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); StudentService service = context.getBean(StudentService.class); service.add(new Student("testAdd", "男"));
输出
十一月 14, 2022 11:07:14 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info 信息: {dataSource-1} inited StudentDao add --->> 1
CRUD简单示例
service
@Service public class StudentServiceImpl implements StudentService { @Resource private StudentDao studentDao; @Override public void insert(Student student) { studentDao.insert(student); } @Override public void updateById(Student student) { studentDao.updateById(student); } @Override public void deleteById(Integer id) { studentDao.deleteById(id); } @Override public void selectAll() { studentDao.selectAll(); } }
dao
@Repository public class StudentDao { @Resource private JdbcTemplate jdbcTemplate; public void insert(Student student) { String sql = "insert into `student` values(?,?,?)"; Object[] args = {null,student.getName(), student.getSex()}; int update = jdbcTemplate.update(sql, args); System.out.println("StudentDao insert --->> " + update); } public void updateById(Student student) { String sql = "update `student` set `name` = ?,`sex` = ? where `id` = ?"; Object[] args = {student.getName(), student.getSex(), student.getId()}; int update = jdbcTemplate.update(sql, args); System.out.println("StudentDao updateById --->> " + update); } public void deleteById(Integer id) { String sql = "delete from `student` where `id` = ?"; int update = jdbcTemplate.update(sql, id); System.out.println("StudentDao deleteById --->> " + update); } public void selectAll() { String sql = "select * from `student`"; List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Student>(Student.class)); System.out.println("StudentDao selectAll --->> " + studentList.toString()); } }
Test
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); StudentService service = context.getBean(StudentService.class); for (int i = 0; i < 5; i++) { service.insert(new Student("testAdd" + i, "男")); } System.out.println("----------------------------------------"); service.selectAll(); System.out.println("----------------------------------------"); service.updateById(new Student(5, "update", "女")); System.out.println("----------------------------------------"); service.deleteById(1);
输出
十一月 14, 2022 11:31:57 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info 信息: {dataSource-1} inited StudentDao insert --->> 1 StudentDao insert --->> 1 StudentDao insert --->> 1 StudentDao insert --->> 1 StudentDao insert --->> 1 ---------------------------------------- StudentDao selectAll --->> [Student{name='testAdd0', sex='男'}, Student{name='testAdd1', sex='男'}, Student{name='testAdd2', sex='男'}, Student{name='testAdd3', sex='男'}, Student{name='testAdd4', sex='男'}] ---------------------------------------- StudentDao updateById --->> 1 ---------------------------------------- StudentDao deleteById --->> 1
批量操作
public void batchAdd(List<Object[]> batchArgs){ String sql = "insert into `student` values(?,?,?)"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
public void batchUpdate(List<Object[]> batchArgs){ String sql = "update `student` set `name` = ?,`sex` = ? where `id` = ?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
public void batchUpdate(List<Object[]> batchArgs){ String sql = "delete from `student` where `id` = ?"; int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println(Arrays.toString(ints)); }
事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。
四个特性(ACID)
- 原子性:不可分割,一个失败都失败
- 一致性:不出现脏数据、幽灵数据等
- 隔离性:多对象操作同一条数据时,不相互影响
- 持久性:提交后数据发生变化
代码环境
表数据
id name money
1 "Bank_A" 1000
2 "Bank_B" 1000
dao
@Repository public class BankDao { @Resource private JdbcTemplate jdbcTemplate; // 少钱 public void addMoney(){ String sql = "update `bank` set money=money+? where `name`=?"; // A银行多100块 jdbcTemplate.update(sql, 100,"Bank_A"); } // 多钱 public void reduceMoney(){ String sql = "update `bank` set money=money-? where `name`=?"; // B银行少100块 jdbcTemplate.update(sql, 100,"Bank_B"); } }
service
@Service public class BankService { @Resource private BankDao bankDao; public void accountMoney(){ // 转账 bankDao.reduceMoney(); // 收到转账 bankDao.addMoney(); } }
场景引入
模拟异常
@Service public class BankService { @Resource private BankDao bankDao; public void bankMoney(){ // 转账 bankDao.reduceMoney(); // 模拟异常 int i = 10/0; // 收到转账 bankDao.addMoney(); } }
异常
十一月 15, 2022 12:10:10 上午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info 信息: {dataSource-1} inited Exception in thread "main" java.lang.ArithmeticException: / by zero at com.learn.service.BankService.accountMoney(BankService.java:18) at com.learn.test.TXTests.main(TXTests.java:11)
初步解决方案
public void bankMoney(){ try{ // 第一步:开启事务 // 第二步:业务操作 // 转账 bankDao.reduceMoney(); // 模拟异常 int i = 10/0; // 收到转账 bankDao.addMoney(); // 第三步:没有异常,提交事务 }catch (Exception e){ // 第四步:出现异常,事务回滚 } }
事务管理操作
一般添加到JavaEE三层结构里的Service层
- 编程式事务管理(每个Service都手写具体步骤)
- 声明式事务管理(通过配置),底层使用AOP原理
事务管理API
- PlatformTransactionManager (org.springframework.transaction)(接口)
- CallbackPreferringPlatformTransactionManager (org.springframework.transaction.support)(接口)
- WebSphereUowTransactionManager (org.springframework.transaction.jta)
- AbstractPlatformTransactionManager (org.springframework.transaction.support)
- CciLocalTransactionManager (org.springframework.jca.cci.connection)
- JpaTransactionManager (org.springframework.orm.jpa)
- DataSourceTransactionManager (org.springframework.jdbc.datasource)
- JtaTransactionManager (org.springframework.transaction.jta)
- WebLogicJtaTransactionManager (org.springframework.transaction.jta)
- WebSphereUowTransactionManager (org.springframework.transaction.jta)
- HibernateTransactionManager (org.springframework.orm.hibernate5)
- ResourceTransactionManager (org.springframework.transaction.support)(接口)
- CciLocalTransactionManager (org.springframework.jca.cci.connection)
- JpaTransactionManager (org.springframework.orm.jpa)
- DataSourceTransactionManager (org.springframework.jdbc.datasource)
- HibernateTransactionManager (org.springframework.orm.hibernate5)
- CallbackPreferringPlatformTransactionManager (org.springframework.transaction.support)(接口)
基于XML
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 组件扫描--> <context:component-scan base-package="com.learn.controller"/> <context:component-scan base-package="com.learn.service"/> <context:component-scan base-package="com.learn.dao"/> <context:component-scan base-package="com.learn.bean"/> <!-- 引入属性文件,需要context名称空间--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 配置jdbc template 注入 dataSource--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置通知--> <tx:advice id="txAdvice"> <!-- 配置事务参数--> <tx:attributes> <!-- 哪种规则的方法上面添加事务--> <!-- <tx:method name="bankMoney"/>--> <tx:method name="bank*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- aop配置--> <aop:config> <!-- 切入点--> <aop:pointcut id="pc" expression="execution(* com.learn.service.BankService.*(..))"/> <!-- 切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/> </aop:config> </beans>
@Service public class BankService { @Resource private BankDao bankDao; public void bankMoney(){ bankDao.reduceMoney(); // 模拟异常 int i = 10/0; bankDao.addMoney(); } }
@Repository public class BankDao { @Resource private JdbcTemplate jdbcTemplate; // 少钱 public void addMoney(){ String sql = "update `bank` set money=money+? where `name`=?"; jdbcTemplate.update(sql, 100,"Bank_A"); } // 多钱 public void reduceMoney(){ String sql = "update `bank` set money=money-? where `name`=?"; jdbcTemplate.update(sql, 100,"Bank_B"); } }
public class TXXMLTests { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean13.xml"); BankService bankService = context.getBean(BankService.class); bankService.bankMoney();// 出现异常,事务回滚了 } }
基于注解
1、配置事务管理
<!-- 事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
2、引入名称空间并开启事务注解
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
<!-- 开启事务注解 transaction-manager="transactionManager"r--> <tx:annotation-driven />
?:如果:
<tx:annotation-driven transaction-manager="transactionManager"/>
dea报:Redundant default attribute value assignment
3、添加注解@Transactional
@Service @Transactional public class BankService { @Resource private BankDao bankDao; public void bankMoney(){ // 转账 bankDao.reduceMoney(); // 模拟异常 int i = 10/0; // 收到转账 bankDao.addMoney(); } }
此时执行bankMoney()抛异常,但是金额没变,事务回滚了
完全注解
TXConfig
@Configuration @ComponentScan(basePackages = {"com.learn.dao","com.learn.service","com.learn.controller","com.learn.bean"})// 开启组件扫描 @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/db_learn?serverTimezone=UTC&useSSL=false&characterEncoding=utf8&useUnicode=true"); dataSource.setUsername("root"); dataSource.setPassword("0101"); 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){// 到ioc容器中找到dataSource并注入 DataSourceTransactionManager manager = new DataSourceTransactionManager(); manager.setDataSource(dataSource); return manager; } }
BankService
@Service @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ) public class BankService { @Resource private BankDao bankDao; public void bankMoney(){ // 转账 bankDao.reduceMoney(); // 模拟异常 int i = 10/0; // 收到转账 bankDao.addMoney(); } }
BankDao
@Repository public class BankDao { @Resource private JdbcTemplate jdbcTemplate; // 少钱 public void addMoney(){ String sql = "update `bank` set money=money+? where `name`=?"; // A银行多100块 jdbcTemplate.update(sql, 100,"Bank_A"); } // 多钱 public void reduceMoney(){ String sql = "update `bank` set money=money-? where `name`=?"; // B银行少100块 jdbcTemplate.update(sql, 100,"Bank_B"); } }
Test
ApplicationContext context = new AnnotationConfigApplicationContext(TXConfig.class); BankService bankService = context.getBean(BankService.class); bankService.bankMoney();
声明式事务管理参数配置
propagation
-
propagation:事务传播行为
-
REQUIRED: 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
@Transactional(propagation = Propagation.REQUIRED)// 单独执行就启动新事务,别的事务方法调用我就直接加入该事务,不开启新事务 -
REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行.如果有事务正在运行,应该将它挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)//外层事务方法A调用方法B(也是属于事务的方法,成为内层事务)后出现毛病,内层事务正常提交,外层事务回滚 -
SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行.否则它可以不运行在事务中.
@Transactional(propagation = Propagation.SUPPORTS)// 你调用我,你运行在事务中我也运行在事务中,你不是那我也就普通运行
-
propagation用法讲解:
1、PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启。
2、PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
3、PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
4、PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务存在,则将这个存在的事务挂起。
5、PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。
6、PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。
7、 PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行
摘自:https://blog.csdn.net/fight_man8866/article/details/81297929
isolation
-
事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题.有三个读问题:脏读、不可重复读、虚(幻)读。
-
脏读: -一个未提交事务读取到另一个未提交事务的数据。
-
不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
-
幻读:一个未提交事务读取到另一提交事务添加数据,
例如:目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
-
-
isolation:事务隔离级别
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ_UNCOMMITTED (读未提交) | 有 | 有 | 有 |
READ_COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE_READ(可重复,MySQL默认) | 无 | 无 | 有 |
SERIALIZABLE(串行化) | 无 | 无 | 无 |
- 示例设置
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
timeout
事务需要在一定时间内提交,否则进行超市回滚,默认单位:秒,默认值:-1
readOnly
是否只读,默认值:false
当readOnly = true,只能执行查询操作
rollbackFor
设置哪些异常进行回滚
noRollbackFor
设置哪些异常不进行回滚
Spring5新功能
-
日志
-
函数式风格操作
-
@Nullable 参数|属性等 可以为空
-
Webflux(功能与webmvc类似,响应式编程,底层与webmvc有很大区别,异步非阻塞)SpringMVC采用命令式编程,Webflux采用异步响应式编程
-
...
响应式编程
响应式编程(Reactor实现)
-
响应式编程操作中,Reactor是满足Reactive 规范框架
-
Reactor 有两个核心类,Mono和Flux,这两个类实现接口Publisher, 提供丰富操作符。Flux对象实现发布者,返回N个元素; Mono实现发布者,返回0或者1个元素,
-
Flux 和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了。错误信号终止数据流同时把错误信息传递给订阅者。
-
错误信号和完成信号都是终止信号,不能共存的
-
如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
-
如果没有错误信号,没有完成信号,表示是无限数据流
-
调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的。
新建springboot项目并引入依赖
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core --> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.4.22</version> </dependency>
编写代码
public class TestReactor { public static void main(String[] args) { // just 直接声明 Flux.just(1,2,3); Mono.just(1); // 其他方法 Integer[] array = {1,2,3,4}; Flux.fromArray(array); List<Integer> list = Arrays.asList(1,2,3); Flux.fromIterable(list); Stream<Integer> stream = list.stream(); Flux.fromStream(stream); // 运行后没有效果,需要订阅 } }
进行订阅
public class TestReactor { public static void main(String[] args) { // just 直接声明 并订阅 Flux.just(1,2,3).subscribe(System.out::print);// 输出 123 Mono.just(1).subscribe(System.out::print);// 输出 1 } }
-
操作符
-
对数据流进行-道道操作,成为操作符,比如工厂流水线
- map:元素映射为新元素,如:1、2,3 - > 1、4、9(3个旧元素 -->> 3个新元素)
- flatMap:元素映射为流,如:"abc","hhh","hello" -> abchhhhello(3个旧元素 -->> 流)
Webflux
- SpringWebflux执行流程和核心API
- 基于Reactor, 默认使用容器是Netty,Netty,是高性能的IO框架,异步非阻塞的框架。
- 执行过程和SpringMVC相似的
- 核心控制器DispatchHandler, 实现接口WebHandler.
- 与之前mvc的区别,现在单个元素用Mono,多个元素用Flux
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
public interface WebHandler { Mono<Void> handle(ServerWebExchange exchange); }
public Mono<Void> handle(ServerWebExchange exchange) { // http请求响应信息 if (this.handlerMappings == null) { return this.createNotFoundError(); } else { return CorsUtils.isPreFlightRequest(exchange.getRequest()) ? this.handlePreFlight(exchange) : 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 里面DispatcherHandler,负责请求的处理。
- HandlerMapping:请求查询到处理的方法。
- HandlerAdapter:真正负责请求处理。
- HandlerResultHandler:响应结果处理。
-
SpringWebfux实现函数式编程,两个接口: RouterFunction和HandlerFunction
- RouterFunction:路由功能,请求转发到对应handler
- HandlerFunction:处理请求并响应
-
实现方式有两种:
- 注解编程模型:使用注解编程模型方式,和之前SpringMVC使用相似的,只需要把相关依赖配置到项目中SpringBoot自动配置相关运行容器,默认情况下使用Netty服务器
- 函数式编程模型
注解编程模型
创建SpringBoot项目并引入webflux依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
service
public interface UserService { // 根据id查用户 Mono<User> getUserById(); // 查询所有用户 Flux<User> getAllUser(); // 添加用户 Mono<Void> saveUser(Mono<User> user); }
public class User { private String name; private String gender; private Integer age; // ... }
@Repository public class UserServiceImpl implements UserService { private final Map<Integer, User> users = new HashMap<>(); public UserServiceImpl() { this.users.put(1, new User("user1", "男", 18)); this.users.put(2, new User("user2", "女", 18)); this.users.put(3, new User("user3", "男", 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> saveUser(Mono<User> userMono) { return userMono.doOnNext(person -> { int id = users.size() + 1; users.put(id, person); }).thenEmpty(Mono.empty()); } }
@RestController public class UserController { @Resource private UserService userService; // id查询 @GetMapping("/user/{id}") public Mono<User> getUserById(@PathVariable Integer id){ return userService.getUserById(id); } // 查询所有 @GetMapping("/user") public Flux<User> getUsers(){ return userService.getAllUser(); } // 添加 @GetMapping("/save") public Mono<Void> saveUser(@RequestBody User user){ Mono<User> userMono = Mono.just(user); return userService.saveUser(userMono); } }
- SpringMVC方式实现,同步阻塞的方式,基于SpringMVC+Servlet+Tomcat
- SpringWebflux方式实现,异步非阻塞方式,基于SpringWebflux+Reactor+Netty
函数式编程模型
-
SpringWebflux (基于函数式编程模型)
- 在使用函数式编程模型操作时候,需要自己初始化服务器
- 基于函数式编程模型时候,有两个核心接口: RouterFunction (实现路由功能,请求转发给对应的handler)和HandlerFunction (处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
- SpringWebflux 请求和响应不再是ServletRequest 和ServletResponse,而是ServerRequest和ServerResponser
-
初始化服务器,编写Router
UserHandler
public class UserHandler { private final UserService userService; public UserHandler(UserService userService) { this.userService = userService; } // 根据id查询 public Mono<ServerResponse> getUserById(ServerRequest request) { Integer id = Integer.valueOf(request.pathVariable("id")); // 空值处理 Mono<ServerResponse> notFound = ServerResponse.notFound().build(); Mono<User> userMono = this.userService.getUserById(id); // 转换再返回 return userMono.flatMap( (Function<User, Mono<ServerResponse>>) user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono, User.class) .switchIfEmpty(notFound)); } // 查询全部 public Mono<ServerResponse> getAllUser(ServerRequest request) { Flux<User> userFlux = this.userService.getAllUser(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userFlux, User.class); } // 添加用户 public Mono<ServerResponse> saveUser(ServerRequest request) {// 存在异常 MonoOnErrorResume // 得到User Mono<User> userMono = request.bodyToMono(User.class); return ServerResponse.ok().build(this.userService.saveUser(userMono)); } }
Server
public class Server { public static void main(String[] args) throws IOException { Server server = new Server(); server.createReactorServer(); System.out.println("按任意键停止服务..."); System.in.read(); } // 创建路由 public RouterFunction<ServerResponse> routerFunction(){ UserServiceImpl userService = new UserServiceImpl(); UserHandler userHandler = new UserHandler(userService); return RouterFunctions.route( GET("/user/{id}").and(accept(APPLICATION_JSON)),userHandler::getUserById) .andRoute(GET("/user").and(accept(APPLICATION_JSON)), userHandler::getAllUser) .andRoute(GET("/saveUser").and(accept(APPLICATION_JSON)), userHandler::saveUser); } // 创建服务器完成适配 public void createReactorServer(){ // 路由和handler适配 RouterFunction<ServerResponse> router = routerFunction(); HttpHandler httpHandler = toHttpHandler(router); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); // 创建服务器 HttpServer httpServer = HttpServer.create(); httpServer.handle(adapter).bindNow(); } }
-
调用方式
- 网页请求:http://localhost:xxx/user
- WebClient请求:
public class Client { public static void main(String[] args) { // 调用服务器地址 WebClient webClient = WebClient.create("http://localhost:xxx"); // 根据id查询 User userMono = webClient.get() .uri("/user/{id}", 1) .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(User.class) .block(); System.out.println(userMono.getName()); // 查询所有 Flux<User> userFlux = webClient.get() .uri("/user") .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToFlux(User.class); userFlux.map(User::getName) .buffer().doOnNext(System.out::println).blockFirst();// blockFirst相当于订阅 } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术