Spring

Spring

ref:

https://www.bilibili.com/video/BV1Vf4y127N5

初始化项目

  • 创建空项目

  • 创建对应文件夹

  • 导入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含特殊字符

一:使用&lt代替

<bean id="user" class="com.learn.bean.User">
<property name="name" value="&lt;&lt;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 resulttrue
  • 指定方法做处理
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)

基于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();
}
}
  • 调用方式

    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相当于订阅
    }
    }
posted @   夏末秋初~  阅读(155)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示