Spring 5 笔记

阅读: 评论:0

Spring 5 笔记

Spring 5 笔记

课程内容介绍

尚硅谷 Spring 5

视频地址:

  1. Spring 概念

  2. IoC 容器

    1. IoC 底层原理

    2. IoC 接口(BeanFactory)

    3. IoC 操作 Bean 管理(基于xml)

    4. IoC 操作 Bean 管理(基于注解)

  3. AOP

  4. JdbcTemplate

  5. 事务管理

  6. Spring 5 新特性

Spring 框架概述

  1. Spring 是轻量级的开源的 JavaEE 框架

  2. Spring 可以解决企业应用开发的复杂性

  3. 两个核心部分:IoC、AOP

    • IoC:Inversion of Control,控制反转。把创建对象过程交给 Spring 进行管理
    • AOP:Aspect Oriented Programming,面向切面编程。不修改源代码进行功能增强
  4. Spring 特点

    1. 方便解耦,简化开发
    2. AOP 编程支持
    3. 方便程序测试
    4. 方便和其他框架进行整合
    5. 方便进行事务操作
    6. 降低 API 开发难度
  5. 本课程选取 Spring 5.x

IoC

概念和原理

1、什么是 IoC ?

控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理

使用 IoC 目的:降低耦合度

入门案例就是 IoC 实现

2、IoC 底层原理

xml 解析、工厂模式、反射

接口

1、IoC 思想基于 IoC 容器完成,IoC 容器底层就是对象工厂

2、Spring 提供 IoC 容器实现两种方式(两个接口):

(1) BeanFactory :IoC 容器基本实现,是 Spring 内部使用接口,一般不提供开发人员使用

加载配置文件时不会创建对象,在获取(使用)对象时才创建

(2) ApplicationContextBeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员使用

加载配置文件时就会将配置的对象创建

3、 ApplicationContext 接口有实现类

IoC 操作 Bean 管理

1、什么是 Bean 管理

Bean 管理指的是两个操作:Spring 创建对象,Spring 注入属性

2、Bean 管理操作有两种方式

  • 基于 xml 配置文件方式实现

  • 基于注解方式实现

基于 xml 方式

基于 xml 方式创建对象
<bean id="alice" class="com.yin.spring5.bean.User"></bean>
  1. 在 Spring 配置文件中,使用 bean 标签,标签中添加对应属性,就可以实现对象创建

  2. bean 标签有很多属性,常用属性:

    • id:唯一标识
    • class:全类名
  3. 创建对象的时候,默认执行无参构造器

基于 xml 方式注入属性

DI:Dependency Injection,依赖注入,是 IoC 的一种实现方式

  1. 第一种注入方式:set 方法注入

    <!--set 方法注入-->
    <bean id="alice" class="com.yin.spring5.bean.User"><property name="name" value="Alice"/><property name="age" value="20"/>
    </bean>
    
  2. 第二种注入方式:有参构造器注入

    <!--有参构造器注入-->
    <bean id="bob" class="com.yin.spring5.bean.User"><constructor-arg name="name" value="Bob"/><constructor-arg name="age" value="20"/>
    </bean>
    
  3. p 名称空间注入(了解即可)

    添加约束:

    xmlns:p=""
    
    <!--p命名空间-->
    <bean id="cindy" class="com.yin.spring5.bean.User" p:name="Cindy" p:age="18">
    </bean>
    
  4. c 名称空间注入(了解即可)

    添加约束:

    xmlns:c=""
    
    <!--c命名空间-->
    <bean id="david" class="com.yin.spring5.bean.User" c:name="David" c:age="18">
    </bean>
    
xml 注入其他类型
  1. 字面量

    • 注入 null

      <!--注入null-->
      <bean id="eva" class="com.yin.spring5.bean.User"><property name="name" value="Eva"/><property name="age"><null/></property>
      </bean>
      
    • 属性值包含特殊字符

      • 将特殊字符进行转义

        <!--属性值包含特殊字符-->
        <bean id="special" class="com.yin.spring5.bean.User"><!--将特殊字符进行转义--><property name="name" value="&lt;&lt;TRUMP&gt;&gt;"/><property name="age" value="18"/>
        </bean>
        

        输出:User{name='<<TRUMP>>', age=18}

      • 使用 CDATA

        <!--属性值包含特殊字符-->
        <bean id="special" class="com.yin.spring5.bean.User"><!--使用 CDATA--><property name="name"><value><![CDATA[<<BIDEN>>]]></value></property><property name="age" value="18"/>
        </bean>
        

        输出:User{name='<<BIDEN>>', age=18}

  2. 外部 bean

    public class UserDao {public void delete() {System.out.println("UserDao#delete()...");}
    }
    
    public class UserService {UserDao dao;public void setDao(UserDao dao) {this.dao = dao;}public void deleteUser() {System.out.println("UserService#deleteUser()...");dao.delete();}
    }
    
    <bean id="userDao" class="com.yin.spring5.dao.UserDao">
    </bean>
    <bean id="userService" class="com.yin.spring5.service.UserService"><!--注入外部 bean--><property name="dao" ref="userDao"/>
    </bean>
    
  3. 内部 bean

    public class Department {private String departName;// getter setter toString 等方法在此省略
    }
    
    public class Employee {private String empName;private String email;private Department dept;// getter setter toString 等方法在此省略
    }
    
    <bean id="employee" class="com.yin.spring5.bean.Employee"><property name="empName" value="杰克"/><property name="email" value="jack@jack"/><!--内部 bean--><property name="dept"><bean class="com.yin.spring5.bean.Department"><property name="departName" value="开发部"/></bean></property>
    </bean>
    

    输出:Employee{empName='杰克', email='jack@jack', dept=Department{departName='开发部'}}

  4. 级联赋值

    写法一:

    <bean id="employee2" class="com.yin.spring5.bean.Employee"><property name="empName" value="Pony"/><property name="email" value="pony@pony"/><property name="dept" ref="department"/>
    </bean>
    <bean id="department" class="com.yin.spring5.bean.Department"><property name="departName" value="运维部"/>
    </bean>
    

    输出:Employee{empName='Pony', email='pony@pony', dept=Department{departName='运维部'}}

    写法二:

    <bean id="employee2" class="com.yin.spring5.bean.Employee"><property name="empName" value="Pony"/><property name="email" value="pony@pony"/><property name="dept" ref="department"/><!--注意这里--><property name="dept.departName" value="开发部"/>
    </bean>
    <bean id="department" class="com.yin.spring5.bean.Department"><property name="departName" value="运维部"/>
    </bean>
    

    输出:Employee{empName='Pony', email='pony@pony', dept=Department{departName='开发部'}}

xml 注入集合属性
  1. 注入数组类型属性

    <!--数组类型注入-->
    <property name="array"><array><value>array1</value><value>array2</value><value>array3</value></array>
    </property>
    
  2. 注入 List 集合类型属性

    <!--List 类型注入-->
    <property name="list"><list><value>list1</value><value>list2</value><value>list3</value></list>
    </property>
    
  3. 注入 Map 集合类型属性

    <!--Map 类型注入-->
    <property name="map"><map><entry key="key1" value="value1"/><entry key="key2" value="value2"/><entry key="key3" value="value3"/></map>
    </property>
    
  4. 注入 Set 集合类型属性

    <!--Set 类型注入-->
    <property name="set"><set><value>set1</value><value>set2</value><value>set3</value></set>
    </property>
    

    输出:

    Student{stuName='Pony', array=[array1, array2, array3], list=[list1, list2, list3], map={key1=value1, key2=value2, key3=value3}, set=[set1, set2, set3]}
    
  5. 在集合里面设置对象类型

    <bean id="student" class="com.yin.spring5.bean.Student"><!--省略部分代码,只看有关的--><!--在集合里面设置对象类型--><property name="courses"><list><ref bean="course1"/><ref bean="course2"/></list></property>
    </bean><bean id="course1" class="com.yin.spring5.bean.Course"><property name="courseName" value="数据结构"/><property name="credit" value="5"/>
    </bean>
    <bean id="course2" class="com.yin.spring5.bean.Course"><property name="courseName" value="操作系统"/><property name="credit" value="4"/>
    </bean>
    

    输出:

    [Course{courseName='数据结构', credit=5}, Course{courseName='操作系统', credit=4}]
    
  6. 把集合注入部分提取出来

    a. 在配置文件中引入 util 名称空间

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns=""xmlns:xsi=""xmlns:util=""xsi:schemaLocation=" .xsd .xsd"></beans>
    

    b. 使用 util 标签完成 List 集合注入提取

    public class Book {private List<String> list;// getter setter toString 等方法在此省略
    }
    
    <bean id="book" class="com.yin.spring5.bean.Book"><property name="list" ref="bookList"/>
    </bean><!--List 集合注入提取-->
    <util:list id="bookList"><value>Java编程思想</value><value>算法导论</value><value>Effective Java</value>
    </util:list>
    

    输出:

    [Java编程思想, 算法导论, Effective Java]
    

FactoryBean

普通 bean:配置文件中定义的 bean 类型就是返回类型

工厂 bean:配置文件中定义的 bean 类型可以和返回类型不一样

  1. 创建类,实现 FactoryBean 接口
  2. 实现接口的方法
/*** MyFactoryBean 工厂Bean*/
public class MyFactoryBean implements FactoryBean<Course> {@Overridepublic Course getObject() throws Exception {return new Course();}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return false;}
}
<bean id="myFactoryBean" class="com.yin.spring5.bean.MyFactoryBean">
</bean>
@Test
public void test3() {ApplicationContext ioc =new ClassPathXmlApplicationContext(&#l");Course course = Bean("myFactoryBean", Course.class);course.setCourseName("数据结构");course.setCredit(5);System.out.println(course);
}

测试:

Course{courseName='数据结构', credit=5}

Bean 作用域

在 Spring 中,设置创建 bean 实例是单实例还是多实例(默认单实例

@Test
public void test4() {ApplicationContext ioc =new ClassPathXmlApplicationContext(&#l");Book book1 = Bean("book", Book.class);Book book2 = Bean("book", Book.class);// 这里打印对象地址,需要把其 toString() 方法注释掉System.out.println(book1);System.out.println(book2);System.out.println(book1==book2);
}

输出:

com.yin.spring5.bean.Book@433defed
com.yin.spring5.bean.Book@433defed
true

如何设置单实例/多实例?bean 标签中 scope 属性设置作用域

scope 属性值:

  • singleton :单实例
  • prototype :多实例

将 scope 属性值设置为 `` 后,再进行上面测试:

<bean id="book" class="com.yin.spring5.bean.Book" scope="prototype">

输出:

com.yin.spring5.bean.Book@548a24a
com.yin.spring5.bean.Book@433defed
false

singletonprototype 的区别:

  1. singleton 是单实例,prototype 是多实例

  2. singleton 在 Spring 加载配置文件时就会创建单实例对象

    prototype 在获取对象时才会进行创建

Bean 生命周期

生命周期:从对象创建到销毁的过程

bean 生命周期:

  1. 通过构造器创建 bean 实例(无参构造)
  2. 为 bean 注入属性和对其他 bean 的引用(set 方法)
  3. 调用 bean 的初始化方法(需要进行配置初始化方法)
  4. 获取并使用 bean
  5. 当容器关闭时,调用 bean 的销毁方法(需要进行配置销毁方法)

代码验证:

public class User {private String name;private Integer age;private Character gender;public User() {System.out.println("User 无参构造器执行。。。");}public String getName() {return name;}public void setName(String name) {System.out.println("User.setName 执行。。。");this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {System.out.println("User.setAge 执行。。。");this.age = age;}public Character getGender() {return gender;}public void setGender(Character gender) {System.out.println("User.setGender 执行。。。");der = gender;}public void init() {System.out.println("自定义 User.init 执行。。。");}public void destroy() {System.out.println("自定义 User.destroy 执行。。。");}
}
<bean id="user" class="com.yin.spring5.bean.User"init-method="init" destroy-method="destroy"><property name="name" value="Alice"/><property name="gender" value="F"/><property name="age" value="20"/>
</bean>
@Test
public void test5() {ClassPathXmlApplicationContext ioc =new ClassPathXmlApplicationContext(&#l");User user = Bean("user", User.class);System.out.println(user);// 关闭容器ioc.close();
}

输出:

User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。
自定义 User.init 执行。。。
com.yin.spring5.bean.User@16eb3ea3
自定义 User.destroy 执行。。。

其实完整来讲,bean 的声明周期有 7 步。在初始化方法执行前后,分别有 postProcessBeforeInitializationpostProcessAfterInitialization 方法执行。

在上述代码的基础上,再添加以下内容:

public class MyBeanPost implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {System.out.println("    postProcessBeforeInitialization 执行。。。");return null;}@Overridepublic Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {System.out.println("    postProcessAfterInitialization 执行。。。");return null;}
}
<bean id="myBeanPost" class="com.yin.spring5.bean.MyBeanPost"></bean>

再次进行测试,输出如下:

User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。postProcessBeforeInitialization 执行。。。
自定义 User.init 执行。。。postProcessAfterInitialization 执行。。。
com.yin.spring5.bean.User@33bc72d1
自定义 User.destroy 执行。。。

综上,bean 生命周期完整表述如下:

  1. 通过构造器创建 bean 实例(无参构造)
  2. 为 bean 注入属性和对其他 bean 的引用(set 方法)
  3. 把 bean 实例传递给初始化前的后置处理 postProcessBeforeInitialization
  4. 调用 bean 的初始化方法(需要进行配置初始化方法)
  5. 把 bean 实例传递给初始化后的后置处理 postProcessAfterInitialization
  6. 获取并使用 bean
  7. 当容器关闭时,调用 bean 的销毁方法(需要进行配置销毁方法)

xml 自动装配

什么是自动装配?

根据指定装配规则(属性名称或属性类型),Spring 自动将匹配的属性值进行注入

bean 标签中的 autowire 属性可以配置自动装配,autowire 常用属性值:

  • byName :根据名称装配(注入 bean 的 id 要与被注入 bean 的 set 方法一致
  • byType :根据类型装配(有多个同类型的 bean 时,此属性值无法使用)

代码演示:

public class Department {// 此处略去 get set toString 等方法private String deptName;
}
public class Employee {// 此处略去 get set toString 等方法private String empName;private Department dept;
}
<bean id="dept" class="com.yin.spring5.autowire.Department"><property name="deptName" value="开发部"/>
</bean>
<bean id="dept2" class="com.yin.spring5.autowire.Department"><property name="deptName" value="测试部"/>
</bean><bean id="emp" class="com.yin.spring5.autowire.Employee" autowire="byName"><property name="empName" value="Pony"/>
</bean>

输出:

Employee{empName='Pony', dept=Department{deptName='开发部'}}

若将 Employee 类中默认生成的 setDept 方法名修改为 setDept2,则输出变为:

Employee{empName='Pony', dept=Department{deptName='测试部'}}

由此证实:byName 的自动注入是根据 set 方法判断的,而不是类中的属性名

外部属性文件

以数据库连接池为例:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value=&#sql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai"/><property name="username" value="root"/><property name="password" value="123456"/>
</bean>

将数据库配置抽取到 properties 文件

jdbc.driver&#sql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456

则 Spring 配置文件改写如下,注意需要引入 context 名称空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xmlns:context=""xsi:schemaLocation=" .xsd .xsd"><!--引入外部 properties 文件--><context:property-placeholder location="classpath:db-config.properties"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><!--取出 properties 文件中的值--><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>

基于注解方式

创建 bean

Spring 创建 bean 的四个注解:@Component@Controller@Service@Repository

<!--开启组件扫描,将会扫描 base-package 及其子包下所有组件-->
<context:component-scan base-package="com.yin.spring5"/>

排除某些组件,以排除 @Controller 组件扫描为例:

<!--开启组件扫描-->
<context:component-scan base-package="com.yin.spring5"><!--排除 Controller 组件扫描--><context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

context:component-scan 标签部分属性:

  • base-package :要扫描组件的基础包,默认将会扫描 base-package 及其子包下所有组件
  • use-default-filters :是否使用默认的扫描规则

context:component-scan 标签的子标签:

  • context:exclude-filter :指定排除规则
  • context:include-filter :指定包含规则
属性注入
  • @Autowired :根据属性类型自动装配
  • @Qualifier :根据属性名称注入
  • @Resource :可以根据类型注入,可以根据名称注入(这是 javax 包下的注解,已移除)
  • @Value :注入普通类型属性

AOP

AOP,Aspect Oriented Programming,面向切面编程

利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

底层原理

AOP 底层使用动态代理。有两种情况:

  • 有接口,使用 JDK 动态代理

    创建接口实现类代理对象,增强类的方法

  • 没有接口,使用 CGLIB 动态代理

    创建子类的代理对象,增强类的方法

JDK 动态代理

使用 JDK 动态代理,使用 flect.Proxy 类里面的方法创建代理对象

代码演示:

  1. 创建接口,定义方法

    public interface UserDao {int add(int a, int b);String update(String id);
    }
    
  2. 创建接口实现类,实现方法

    public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add 方法执行");return a + b;}@Overridepublic String update(String id) {System.out.println("update 方法执行");return id;}
    }
    
  3. 使用 flect.Proxy 类创建接口代理对象

    public class JdkProxy {public static void main(String[] args) {// 创建接口实现类代理对象Class[] interfaces = {UserDao.class};UserDao userDao = new UserDaoImpl(); // 被代理对象UserDao proxyInstance =(UserDao) wProxyInstance(ClassLoader(),interfaces, new UserDaoProxy(userDao));System.out.println(proxyInstance.add(2, 3));System.out.println(proxyInstance.update("abc"));}
    }// 创建代理对象代码
    class UserDaoProxy implements InvocationHandler {private Object obj;// 把被代理对象传递过来public UserDaoProxy(Object obj) {this.obj = obj;}// 增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法之前System.out.Name() + " 方法之前执行,参数:" + String(args));// 被增强的方法执行Object returnValue = method.invoke(obj, args);// 方法之后System.out.Name() + " 方法之后执行");return returnValue;}
    }
    

输出:

add 方法之前执行,参数:[2, 3]
add 方法执行
add 方法之后执行
5
update 方法之前执行,参数:[abc]
update 方法执行
update 方法之后执行
abc

AOP 术语

  1. 连接点:类中可以被增强的方法称为连接点
  2. 切入点:实际被真正增强的方法称为切入点
  3. 通知(增强):实际增强的逻辑部分称为通知(增强)
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知
  4. 切面:把通知应用到切入点的过程

AOP 操作

Spring 框架一般基于 AspectJ 实现 AOP 操作

AspectJ 不是 Spring 组成部分,而是独立的 AOP 框架,但是一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作

Spring 基于 AspectJ 实现 AOP 操作有两种方式:xml 配置文件方式、注解方式。

引入依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${spring.version}</version></dependency>
</dependencies>

切入点表达式

切入点表达式作用:指明对哪个类的哪个方法进行增强

语法结构:

execution([权限修饰符][返回类型][全类名][方法名]([参数列表]))

例 1:对 com.yin.spring5.UserDao#add 方法进行增强

execution(* com.yin.spring5.UserDao.add(..))    // 权限修饰符省略

例 2:对 com.yin.spring5.UserDao 类中所有方法增强

execution(* com.yin.spring5.UserDao.*(..))    // 权限修饰符省略

例 2:对 com.yin.spring5 包下所有类中所有方法增强

execution(* com.yin.spring5.*.*(..))    // 权限修饰符省略

基于注解

  1. 创建类,在类中定义方法

    public class User {public void add() {System.out.println("User.add 方法执行");}
    }
    
  2. 创建增强类,编写增强逻辑

    在增强类中,创建方法,让不同的方法代表不同的通知

    public class UserProxy {// 前置通知public void before() {System.out.println("UserProxy.before 方法执行");}......
    }
    
  3. 进行通知的配置

    1. 配置文件开启注解扫描

      <context:component-scan base-package="com.yin.spring5.anno"/>
      
    2. 使用注解创建 User 和 UserProxy 对象

      @Component
      public class User {...}
      
      @Component
      public class UserProxy {...}
      
    3. 在增强类上面添加注解 @Aspect

      @Component
      @Aspect
      public class UserProxy {...}
      
    4. 配置文件开启生成代理对象

      <!--开启 Aspect 生成代理对象,要引入aop名称空间-->
      <aop:aspectj-autoproxy/>
      
  4. 配置不同类型的通知

    在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

    @Component
    @Aspect
    public class UserProxy {// 前置通知@Before("execution(* com.yin.spring5.anno.User.add(..))")public void before() {System.out.println("UserProxy.before 方法执行");}// 最终通知@After("execution(* com.yin.spring5.anno.User.add(..))")public void after() {System.out.println("UserProxy.after 方法执行");}// 后置通知(返回通知)@AfterReturning("execution(* com.yin.spring5.anno.User.add(..))")public void afterReturning() {System.out.println("UserProxy.afterReturning 方法执行");}// 异常通知@AfterThrowing("execution(* com.yin.spring5.anno.User.add(..))")public void afterThrowing() {System.out.println("UserProxy.afterThrowing 方法执行");}// 环绕通知@Around("execution(* com.yin.spring5.anno.User.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕之前。。。");// 被增强的方法执行proceedingJoinPoint.proceed();System.out.println("环绕之后。。。");}
    }
    

测试:

@Test
public void test1() {ApplicationContext context = new ClassPathXmlApplicationContext(&#l");User user = Bean("user", User.class);user.add();
}

输出:

环绕之前。。。
UserProxy.before 方法执行
User.add 方法执行
UserProxy.afterReturning 方法执行
UserProxy.after 方法执行
环绕之后。。。

抽取相同的切入点

@Component
@Aspect
public class UserProxy {// 抽取相同的切入点@Pointcut("execution(* com.yin.spring5.anno.User.add(..))")public void pointDemo() {}// 前置通知@Before("pointDemo()")public void before() {System.out.println("UserProxy.before 方法执行");}...
}

有多个增强类对同一个方法进行增强,设置增强类优先级

在增强类上添加注解 @Order(value = ) ,数字越小,优先级越高

完全使用注解

创建配置类,不需要创建 xml 配置文件

@Configuration
@ComponentScan(basePackages = "com.yin.spring5.anno")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}

基于 xml 配置文件


JdbcTemplate

什么是 JdbcTemplate ?

Spring 对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

环境配置

引入依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-jdbc --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!--  --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency><!-- .alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency>
</dependencies>

配置数据库连接池

<!--引入外部数据库配置文件-->
<context:property-placeholder location="classpath:db-config.properties"/>
<!--Druid 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>
</bean>

配置 JdbcTemplate

<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.JdbcTemplate"><!--注入数据源--><property name="dataSource" ref="dataSource"/>
</bean>

编写类文件

编写 Book 实体类,创建对应数据表

public class Book {private Integer id;private String title;private String author;private BigDecimal price;// 此处省略 getter setter toString 等方法
}

环境一览

<?xml version="1.0" encoding="UTF-8"?>
<beans><!--注意:此处文件头等信息略去--><!--组件扫描--><context:component-scan base-package="com.yin.spring5"/><!--引入外部数据库配置文件--><context:property-placeholder location="classpath:db-config.properties"/><!--Druid 数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--JdbcTemplate--><bean id="jdbcTemplate" class="org.JdbcTemplate"><!--注入数据源--><property name="dataSource" ref="dataSource"/></bean>
</beans>
public interface BookDao {
}
@Repository
public class BookDaoImpl implements BookDao {JdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}
}
@Service
public class BookService {BookDao bookDao;@Autowiredpublic void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
}

操作数据库

@Override
public int insert(Book book) {String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";return jdbcTemplate.update(sql, Title(), Author(), Price());
}

@Override
public int update(Book book) {String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";return jdbcTemplate.update(sql, Title(), Author(),Price(), Id());
}

@Override
public int delete(Integer id) {String sql = "DELETE FROM book WHERE id=?";return jdbcTemplate.update(sql, id);
}

某个值
@Override
public int recordCount() {String sql = "SELECT COUNT(*) FROM book";return jdbcTemplate.queryForObject(sql, Integer.TYPE);
}
对象
@Override
public Book getOneById(Integer id) {String sql = "SELECT id, title, author, price FROM book WHERE id=?";return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
集合
@Override
public List<Book> getAll() {String sql = "SELECT * FROM book";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
}

批量操作

批量添加
@Override
public int[] batchInsert(List<Object[]> bookList) {String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchAdd() {ApplicationContext context = new ClassPathXmlApplicationContext(&#l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> bookList = new ArrayList<>();// 按占位符顺序写参数Object[] book1 = {"城南旧事", "林海音", 29.00};Object[] book2 = {"追风筝的人", "忘了", 29.00};Object[] book3 = {"小王子", "安东尼", 19.00};bookList.add(book1);bookList.add(book2);bookList.add(book3);bookService.batchAddBook(bookList);
}
批量修改
@Override
public int[] batchUpdate(List<Object[]> bookList) {String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchUpdate() {ApplicationContext context = new ClassPathXmlApplicationContext(&#l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> bookList = new ArrayList<>();// 按占位符顺序写参数Object[] book1 = {"城南旧事", "林海音(台湾)", 26.00, 24};Object[] book2 = {"追风筝的人", "还是没想起来", 29.00, 25};Object[] book3 = {"小王子", "安东尼奥", 20.00, 26};bookList.add(book1);bookList.add(book2);bookList.add(book3);bookService.batchUpdateBook(bookList);
}
批量删除
@Override
public int[] batchDelete(List<Object[]> idList) {String sql = "DELETE FROM book WHERE id=?";return jdbcTemplate.batchUpdate(sql, idList);
}
@Test
public void testBatchDelete() {ApplicationContext context = new ClassPathXmlApplicationContext(&#l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> idList = new ArrayList<>();// 按占位符顺序写参数Object[] id1 = {19};Object[] id2 = {20};Object[] id3 = {21};Object[] id4 = {25};idList.add(id1);idList.add(id2);idList.add(id3);idList.add(id4);bookService.batchDeleteBook(idList);
}

事务

概念

  1. 什么是事务?

    事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个操作失败,所有操作都失败

    典型场景:银行转账

  2. 事务四个特性 ACID

    • 原子性 Atomicity

      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功

    • 一致性 Consistency

      事务必须使数据库从一个一致性状态变换到另一个一致性状态

    • 隔离性 Isolation

      事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰

    • 持久性 Durability

      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

环境搭建

引入依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-jdbc --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-orm --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>${spring.version}</version></dependency><!--  --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency><!-- .alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency>
</dependencies>

创建数据库表

use `jdbc_template`;create table `account` (`id`      int primary key auto_increment comment 'id',`name`    varchar(50) comment '姓名',`balance` int comment '余额'
);insert into `account`(`name`, `balance`)
VALUES ('亚索', 1000),('永恩', 1000);

编写实体类、DAO、Service 等

public class Account {private Integer id;private String name;private Integer balance;// 此处省略 getter setter toString 等方法
}
public interface AccountDao {/*** 增加金额** @param id    用户id* @param money 金额数目*/void addMoney(int id, int money);/*** 扣除金额** @param id    用户id* @param money 金额数目*/void deductMoney(int id, int money);
}
@Repository
public class AccountDaoImpl implements AccountDao {final JdbcTemplate jdbcTemplate;@Autowiredpublic AccountDaoImpl(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic void addMoney(int id, int money) {String sql = "UPDATE account SET balance=balance+? WHERE id=?";jdbcTemplate.update(sql, money, id);}@Overridepublic void deductMoney(int id, int money) {String sql = "UPDATE account SET balance=balance-? WHERE id=?";jdbcTemplate.update(sql, money, id);}
}
public class AccountService {final AccountDao accountDao;@Autowiredpublic AccountService(AccountDao accountDao) {this.accountDao = accountDao;}/*** 转账业务** @param deductId 扣除金额方id* @param addId    增加金额方id* @param money    转账金额数目*/public void transferAccount(int deductId, int addId, int money) {accountDao.deductMoney(deductId, money);accountDao.addMoney(addId, money);}
}

上面的代码,如果正常执行是没有问题的,但是如果代码执行过程中出现异常,会有问题

比如:

public void transferAccount(int deductId, int addId, int money) {accountDao.deductMoney(deductId, money);// 模拟异常int i = 10 / 0;accountDao.addMoney(addId, money);
}

为避免诸如上述问题,就需要事务。

事务操作流程:

  1. 开启事务
  2. 业务操作
  3. 若无异常,提交事务
  4. 出现异常,回滚事务

Spring 事务管理

  1. 事务一般添加到 Service 层

  2. Spring 进行事务管理有两种方式:编程式事务管理 和 声明式事务管理。一般都使用声明式事务管理。

  3. 声明式事务管理有两种方式:基于注解,基于 xml 配置文件。

  4. Spring 进行声明式事务管理,底层使用 AOP 原理

  5. Spring 事务管理 API

    提供 PlatformTransactionManager 接口,代表事务管理器,这个接口针对不同框架提供不同的实现类

基于注解的声明式事务管理

配置事务管理器

<!--配置事务管理器-->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource"/>
</bean>

开启事务注解

<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>

上面讲到,事务一般添加到 Service 层

在 Service 类上(或者类中的方法上)添加事务注解 @Transactional

事务注解 @Transactional 既可以添加到类上,也可以添加到方法上

  • 如果添加到类上,则该类中的所有方法都会添加事务
  • 如果添加到方法上,则只会给该方法添加事务
@Service
@Transactional
public class AccountService {...}

事务注解 @Transactional 部分参数:

  • propagation :事务传播类型(默认 REQUIRED

    多事务方法直接进行调用,这个过程中事务是如何进行管理的

  • isolation :事务隔离级别

    事务特性之一。不考虑隔离性会产生很多问题,三个读问题:脏读、不可重复读、幻读(虚读)

    • 脏读:一个未提交事务读取到另一个未提交事务的数据
    • 不可重复读:一个未提交事务读取到另一个提交事务修改数据
    • 幻读:一个未提交事务读取到另一个提交事务添加数据
  • timeout :事务超时时间(秒,默认 -1 即永不超时)

    事务需要在指定时间内进行提交,如果不提交就会回滚

  • readOnly :事务是否只读(默认 false

  • rollbackFor :指明哪些异常类型进行事务回滚

  • noRollbackFor :指明哪些异常类型不进行事务回滚

事务方法:对数据库表数据进行变化的操作

事务的传播行为可以由传播类型指定,Spring 定义了 7 种传播类型

传播类型描述
REQUIRED如果有事务在运行,当前的方法就在这个事务内运行;否则,就启动一个新的事务,并在自己的事务内运行
REQUIRES_NEW当前的方法必须启动新事务,并在它自己的事务内运行;如果有事务正在运行,应该将它挂起
SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行;否则,就启动一个新的事务,并在它自己的事务内运行

事务隔离级别

隔离级别脏读不可重复读幻读
READ_UNCOMMITTED(读未提交)
READ_COMMITTED(读已提交)
REPEATABLE_READ(可重复读,MySQL默认)
SERIALIZABLE(串行化)

基于 XML 的声明式事务管理

第一步,配置事务管理器;第二步,配置通知;第三步,配置切入点和切面

<!--基于 XML 的事务-->
<!--1.配置事务管理器-->
<bean id="transactionManager2"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource2"/>
</bean><!--2.配置通知-->
<tx:advice id="txAdvice"><!--配置事务参数--><tx:attributes><!--指定哪种规则的方法上添加事务--><tx:method name="transferAccount" propagation="REQUIRED"isolation="REPEATABLE_READ"/></tx:attributes>
</tx:advice><!--3.配置切入点和切面-->
<aop:config><!--配置切入点--><aop:pointcut id="myPointCut"expression="execution(* com.yin.spring5.service.AccountService.*(..))"/><!--配置切面--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointCut"/>
</aop:config>

完全注解

创建配置类,代替 xml 配置文件

@Configuration    // 配置类
@ComponentScan(basePackages = "com.yin.spring5")    // 组件扫描
@EnableTransactionManagement    // 开启事务
public class TxConfig {@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(&#sql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc_template" +"?serverTimezone=Asia/Shanghai");dataSource.setUsername("root");dataSource.setPassword("123456");return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager =new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}

Spring 5 新功能

  1. Spring 5 基于 Java 8,运行时兼容 Java 9,移除了不建议使用的类和方法。

  2. Spring 5 自带了通用的日志封装

    Spring 5 移除了 Log4jConfigListener,官方建议使用 Log4j2

  3. Spring 5 核心注解支持 @Nullable 注解

    @Nullable 注解可以用在方法上、属性上、参数上,表示方法返回值、属性、参数可以为空

  4. 支持函数式风格 GenericApplicationContext / AnnotationConfigApplicationContext

    @Test
    public void test4() {GenericApplicationContext context = new GenericApplicationContext();fresh();// 注册 isterBean("acct", Account.class, Account::new);// 获取注册的对象Account acct = Bean("acct", Account.class);System.out.println(acct);
    }
    
  5. Spring 5 支持整合 JUnit 5

Spring 5 整合 Log4j2

  1. 引入依赖
  2. 创建 l 配置文件(文件名固定)

Spring WebFlux

Spring WebFlux 介绍

Spring WebFlux 是 Spring 5 新增的模块,用于 Web 开发,功能与 Spring MVC 类似,使用当前比较流行的响应式编程

使用传统 Web 框架,比如 Spring MVC,这些基于 Servlet 容器。WebFlux 是一种异步非阻塞式的框架,异步非阻塞的框架在 Servlet 3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。

什么是异步非阻塞?

  • 同步与异步

    异步和同步针对调用者。调用者发送请求,如果等着对方回应之后才去左其他事就是同步;如果发送请求后不等着对方回应就去做其他事就是异步。

  • 阻塞与非阻塞

    阻塞和非阻塞针对被调用者。被调用者收到请求之后,做完请求任务之后才给出反馈就是阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。

WebFlux 特点

  1. 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
  2. 函数式编程:使用 Java 8 函数式编程方式实现路由请求

对比 Spring MVC

  1. 两种方式都可以使用注解方式,都运行在 Tomcat 等容器中
  2. SpringMVC 采用命令式编程,WebFlux 采用异步响应式编程

响应式编程

什么是响应式编程?

响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

例如,在命令式编程环境中,a=b+c 表示将表达式的结果赋给 a,而之后改变 b 或 c 的值不会影响 a。但在响应式编程中,a 的值会随着 b 或 c 的更新而更新。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似 “=B1+C1” 的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

Java 8 及之前版本

提供的观察者模式两个类:Observer 接口,Observable 类(两者从 Java 9 开始被遗弃)

响应式编程(Reactor 实现)

  • 响应式编程操作中,Reactor 是满足 Reactive 规范框架
  • Reactor 有两个核心类,Mono 和 Flux,这两个类实现 Publisher 接口,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或 1 个元素
  • Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值、错误信号、完成信号错误信号和完成信号都表示终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
  • 三种信号特点:
    • 错误信号和完成信号都是终止信号,二者不能共存
    • 如果没有发送任何元素值,而是直接发送终止信号,表示是空数据流
    • 如果没有终止信号,表示是无限数据流

代码演示 Flux 和 Mono

引入依赖

<!-- .projectreactor/reactor-core -->
<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.4.0</version>
</dependency>

调用 just 方法或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生。

// just 方法直接声明
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(5).subscribe(System.out::println);// 其他的方法
Integer[] array = {1,2,3,4};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);

操作符

对数据流进行一道道操作,称为操作符,比如工厂流水线

  • map:将元素映射为新元素

  • flatMap:将元素映射为流。把每个元素转换流,把转换之后多个流合并为大的流

WebFlux执行流程和核心 API

Spring WebFlux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能 NIO(Non-blocking IO,非阻塞IO) 框架。(与 NIO 相对的是 BIO,即 Blocking IO)

BIO 的通信方式:

NIO 的通信方式:

Spring WebFlux 执行过程与 Spring MVC 相似

Spring WebFlux 核心控制器 DispatchHandler,实现接口 WebHandler

DispatcherHandler.handle 方法:

在 Spring WebFlux 中,DispatcherHandler 负责请求的处理

以下三个都是接口:

  • HandlerMapping:请求查询到处理的方法。

    Interface to be implemented by objects that define a mapping between requests and handler objects.

    由定义请求和处理程序对象之间的映射关系的对象实现的接口。

  • HandlerAdapter:真正负责请求处理。

    Contract that decouples the {@link DispatcherHandler} from the details of invoking a handler and makes it possible to support any handler type.

    使 DispatcherHandler 与调用处理程序的详细信息分离的契约,并且可以支持任何处理程序类型。

  • HandlerResultHandler:响应结果处理。

    Process the {@link HandlerResult}, usually returned by an {@link HandlerAdapter}.

    处理 HandlerResult,通常由 HandlerAdapter 返回。

Spring WebFlux 实现函数式编程,两个接口:

  • RouterFunction :Represents a function that routes to a {@linkplain HandlerFunction handler function}.
  • HandlerFunction :Represents a function that handles a {@linkplain ServerRequest request}.

基于注解编程模型

使用注解方式,与之前 Spring MVC 使用相似,只需要引入相关依赖,Spring Boot 自动配置相关运行容器,默认情况下使用 Netty 服务器。

引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

编写代码

public interface UserService {/*** 根据 id 查询 User** @param id 要查询的 id* @return 查询的结果*/Mono<User> getUserById(int id);/*** 查询所有 User** @return 查询的结果*/Flux<User> getAllUsers();/*** 添加 User** @param user 要添加的 User* @return 。*/Mono<Void> saveUser(Mono<User> user);
}
@Service
public class UserServiceImpl implements UserService {// 为了方便,这里使用 Map 代替数据库存储数据private final Map<Integer, User> map = new HashMap<>();public UserServiceImpl() {this.map.put(1, new User(1, "Alice", 'F', 18));this.map.put(2, new User(2, "Bella", 'F', 20));this.map.put(3, new User(3, "Cindy", 'F', 18));this.map.put(4, new User(4, "Diana", 'F', 20));this.map.put(5, new User(5, "Emily", 'F', 18));}@Overridepublic Mono<User> getUserById(int id) {return Mono.justOrEmpty((id));}@Overridepublic Flux<User> getAllUsers() {return Flux.fromIterable(this.map.values());}@Overridepublic Mono<Void> saveUser(Mono<User> userMono) {return userMono.doOnNext(user -> map.Id(), user)).pty());}
}
@RestController
public class UserController {UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@GetMapping("/user/{id}")public Mono<User> getUserById(@PathVariable("id") int id) {UserById(id);}@GetMapping("/users")public Flux<User> getAllUsers() {AllUsers();}@PostMapping("/user")public Mono<Void> saveUser(User user) {Mono<User> userMono = Mono.just(user);return userService.saveUser(userMono);}
}
  • Spring MVC 方式,同步阻塞的方式,基于 Spring MVC + Servlet + Tomcat;
  • Spring WebFlux 方式,异步非阻塞的方式,基于 Spring WebFlux + Reactor + Netty。

基于函数式编程模型

  • 使用函数式编程模型,需要自己初始化服务器
  • 两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
  • Spring WebFlux 的请求和响应不再是 ServletRequest 和 ServletResponse,而是 ServerRequest 和 ServerResponse。
  1. 把基于注解的复制一份,删去 controller 部分

  2. 创建 Handler

    public class UserHandler {private final UserService userService;public UserHandler(UserService userService) {this.userService = userService;}public Mono<ServerResponse> getUserById(ServerRequest request) {// 获取 id 值int id = Integer.parseInt(request.pathVariable("id"));// 空值处理Mono<ServerResponse> notFound = Found().build();// 调用 service 得到数据Mono<User> userMono = UserById(id);// 把 userMono 进行转换返回,使用 Reactor 操作符 flatMapreturn userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(user))).switchIfEmpty(notFound);}public Mono<ServerResponse> getAllUsers() {Flux<User> allUsers = AllUsers();return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(allUsers, User.class);}public Mono<ServerResponse> saveUser(ServerRequest request) {Mono<User> userMono = request.bodyToMono(User.class);return ServerResponse.ok().build(userService.saveUser(userMono));}
    }
    
  3. 初始化服务器,编写 Router

    • 创建路由的方法
    • 创建服务器完成适配
    • 最终调用
    public class Server {public static void main(String[] args) throws IOException {Server server = new Server();ateReactorServer();System.out.println("enter to exit");ad();}// 1. 创建 Router 路由public RouterFunction<ServerResponse> routingFunction() {// 创建 handler 对象UserService userService = new UserServiceImpl();UserHandler userHandler = new UserHandler(userService);// 设置路由ute(GET("/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::getUserById).andRoute(GET("/users").and(accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers);}// 2. 创建服务器完成适配public void createReactorServer() {// 路由和 handler 适配RouterFunction<ServerResponse> route = routingFunction();HttpHandler httpHandler = toHttpHandler(route);ReactorHttpHandlerAdapter handlerAdapter =new ReactorHttpHandlerAdapter(httpHandler);// 创建服务器HttpServer httpServer = ate();httpServer.handle(handlerAdapter).bindNow();}
    }
    
  4. 使用 WebClient 调用

    public class Client {public static void main(String[] args) {WebClient webClient = ate("localhost:8235");User user = ().uri("/user/{id}", 1).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();System.out.println(user);Flux<User> users = ().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);users.map(User::toString).buffer().doOnNext(System.out::println).blockFirst();}
    }
    

课程总结

  1. Spring 框架概述

    轻量级开源 JavaEE 框架,核心 IoC 和 AOP

  2. IoC 容器

    1. IoC 底层原理(工厂、反射等)
    2. IoC 接口(BeanFactory)
    3. IoC 操作 Bean 管理(基于 xml)
    4. IoC 操作 Bean 管理(基于注解)
  3. AOP

    1. AOP 底层原理:动态代理,有接口(JDK 动态代理),没有接口(CGLIB 动态代理)
    2. 术语:切入点、增强(通知)、切面
    3. 基于 AspectJ 实现 AOP 操作
  4. JdbcTemplate

    1. 使用 JdbcTemplate 实现 CRUD 操作
    2. 使用 JdbcTemplate 实现批量操作
  5. 事务管理

    1. 事务概念
    2. 重要概念(传播行为、隔离级别)
    3. 基于注解实现声明式事务管理
    4. 完全注解方式
  6. Spring 5 新特性

    1. 整合日志框架
    2. @Nullable 注解
    3. 函数式注册对象
    4. 整合 JUnit 5 单元测试框架
    5. Spring WebFlux

本文发布于:2024-02-04 17:28:31,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170712664057789.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:笔记   Spring
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23