一万字长文带你熟悉Spring5核心内容

1 篇文章 0 订阅
1 篇文章 0 订阅

Spring框架的基本概念

Spring框架概述

  1. Spring是轻量级开源JavaEE框架
  2. 解决企业应用开发的复杂性
  3. Spring有两个核心部分:IOC和AOP
    1. IOC:控制反转, 把创建对象的过程交给spring进行管理
    2. AOP:面向切面,不修改源代码的情况下进行功能增强
  4. Spring特点
    1. 方便解耦,简化开发
    2. AOP编程支持
    3. 方便程序的测试
    4. 方便集成各种优秀框架
    5. 方便进行事务操作
    6. 降低API开发难度
  5. Spring5.x

IOC容器

IOC底层原理

IOC是什么?

​ IOC是控制反转,是面向对象编程中的一种设计原则,可以用来降低计算机代码中的耦合度

把对象的创建和对象之间的调用过程,交给Spring管理

底层原理

(1)xml解析

(2)工厂模式

(3)反射:得到类的字节码文件

过程

  1. 配置xml文件,配置创建的对象
  2. 创建工厂类
    1. xml解析
    2. 通过反射创建对象返回
IOC接口(BeanFactory)
  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

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

    1. BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员使用
      • 加载配置文件的时候,不会创建对象,在使用对象的时候才去创建对象
    2. ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
      • 加载配置文件的时候就会把配置文件对象进行创建
  3. ApplicationContext实现类:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLiAdfBS-1617946922003)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210330213935809.png)]

IOC操作Bean管理(基于xml)
  1. 什么是Bean管理

    1. Spring创建对象

      <!--配置user对象创建对象-->
      <bean id="User" class="com.hxq.spring5.User"></bean>
      

      id:给对象取个别名

      class:创建对象所属类的全路径

      name:与id类似,可以用特殊符合

    2. Spring注入属性

      1. 基于set方法进行注入

        1. 创建属性和set方法
        private String bName;
        private String bAuthor;
        
        public void setbName(String bName) {
            this.bName = bName;
        }
        
        public void setbAuthor(String bAuthor) {
            this.bAuthor = bAuthor;
        }
        
        1. 在xml文件中配置
        <!--基于set方法注入属性-->
        <bean id="book" class="com.hxq.spring5.Book">
            <property name="bName" value="葵花宝典" />
            <property name="bAuthor" value="教主" />
        </bean>
        
        1. 测试
        @Test
        public void testAdd() {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Book book = context.getBean("book", Book.class);
            System.out.println(book);
        }
        
      2. 基于构造函数进行注入

        1. 创建属性和对应的构造方法

          private String bName;
          private String bAuthor;
          
          public Book(String bName, String bAuthor) {
              this.bName = bName;
              this.bAuthor = bAuthor;
          }
          
        2. 在xml文件中配置

          <!--基于构造函数注入属性-->
          <bean id="book" class="com.hxq.spring5.Book">
              <constructor-arg name="bName" value="葵花宝典" />
              <constructor-arg name="bAuthor" value="教主" />
          </bean>
          
        3. 测试

      3. p命名空间注入

        1. 是set方法的简化,本质上是用set方法注入

        2. 配置

          <!--基于p命名空间注入,相当于property-->
          <bean id="book" class="com.hxq.spring5.Book" p:bAuthor="教主" p:bName="葵花宝典" />
          
      4. 注入空值和特殊符号

        1. null值

          <!--  设置空值  -->
          <bean id="book" class="com.hxq.spring5.Book">
              <property name="bAuthor">
                  <null/>
              </property>
              <property name="bName">
                  <null/>
              </property>
          </bean>
          
        2. 特殊符号

          1. 把特殊符号进行转义, 例如:"<", “>”

          2. <![CDATA[ ... ]]>
            <!-- 属性值中包含特殊符号   -->
            <bean id="book" class="com.hxq.spring5.Book">
                <property name="bName">
                    <value><![CDATA[<<书名>>]]></value>
                </property>
            </bean>
            
      5. 注入外部Bean和级联赋值

        <!--1 service和dao对象创建-->
        <bean id="userDao" class="com.hxq.spring5.dao.UserDaoImpl" />
        <bean id="userService" class="com.hxq.spring5.service.UserService">
            <property name="userDao" ref="userDao" />
        </bean>
        
      6. 注入内部Bean

        1. 一对多关系:部门和员工

        2. 在实体类之间表示一对多关系

          //员工类
          public class Emp {
              //员工属于某一个部门,使用对象形式表示
              private Dept dept;
          }
          
        3. 在spring配置文件中进行配置

          <!-- 内部bean-->
          <bean id="emp" class="com.hxq.spring5.bean.Emp">
              <!--设置普通属性-->
              <property name="eName" value="Lucy"/>
              <property name="gender" value=""/>
              <!--设置对象类型属性-->
              <property name="dept">
                  <bean id="dept" class="com.hxq.spring5.bean.Dept">
                      <property name="dName" value="保安部"/>
                  </bean>
              </property>
          </bean>
          
        4. 测试

      7. 注入集合类型

        1. 数组类型

        2. List集合

        3. Map集合

        4. set集合

          <!--集合类型属性注入-->
          <bean id="stu" class="com.hxq.spring5.collectiontype.Stu">
              <!--数组类型属性注入-->
              <property name="courses">
                  <array>
                      <value>java课程</value>
                      <value>数据库课程</value>
                  </array>
              </property>
              <!--list类型属性注入-->
              <property name="list">
                  <list>
                      <value>大三</value>
                      <value>小三</value>
                  </list>
              </property>
              <!--map类型属性注入-->
              <property name="map">
                  <map>
                      <entry key="Java" value="java"/>
                      <entry key="C" value="c"/>
                  </map>
              </property>
              <!--set类型属性注入-->
              <property name="set">
                  <set>
                      <value>MySQL</value>
                      <value>Redis</value>
                  </set>
              </property>
          </bean>
          
        5. 在集合里面设置对象类型

          <!--注入list集合类型, 值是对象-->
          <property name="courseList">
              <list>
                  <ref bean="course1"/>
                  <ref bean="course2"/>
              </list>
          </property>
          
        6. 把集合注入部分提取出来

          1. 引入util命名空间

      <?xml version="1.0" encoding="UTF-8"?>
           <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:util="http://www.springframework.org/schema/util"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                http://www.springframework.org/schema/util
                http://www.springframework.org/schema/util/spring-util-4.1.xsd">
           ```
        
        2. 使用util标签完成list集合的提取(map, set等集合类似)
        
           ```xml
           <!--提取list集合类型属性注入-->
           <util:list id="bookList">
               <value>易筋经</value>
               <value>九阴真经</value>
               <value>九阳神功</value>
           </util:list>
           
           <!--提取list集合类型注入使用-->
           <bean id="book" class="com.hxq.spring5.collectiontype.Book">
               <property name="list" ref="bookList"/>
           </bean>
           ```
      
    3. 工厂Bean (FactoryBean)

      1. 普通bean:定义什么类型返回什么类型

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

        1. 创建类,让这个类作为工厂bean,实现接口FactoryBean

        2. 实现接口里面得方法,在实现的方法中定义返回的bean类型

          public class MyBean implements FactoryBean<Course> {
          
              //定义返回bean对象
              public Course getObject() throws Exception {
                  Course course = new Course();
                  course.setCnmame("abc");
                  return course;
              }
              public Class<?> getObjectType() {
                  return null;
              }
              public boolean isSingleton() {
                  return false;
              }
          }
          
        3. 测试

          @Test
          public void myBeanTest() {
              ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");
              Course myBean = context.getBean("myBean", Course.class);
              System.out.println(myBean);
          }
          
    4. bean的作用域

      在spring里面,可以设置bean实例是单实例还是多实例默认情况下是单实例对象

      scope属性值:第一个值默认值,singleton,表示单实例对象,在加载配置文件时创建实例对象

      ​ 第二个值prototype, 表示是多实例对象,在运行时才创建实例对象

      requestsession基本不用,要用的话,就是在web项目中,把对象放到request或session中

    5. bean的生命周期(!!!)

      对象从创建到销毁的过程

      (1)通过构造器创建bean实例(无参构造)

      (2)为bean的属性设置值和对其他bean的引用(调用set方法)

      (3)把bean实例传递bean后置处理器的方法(当加入了后置处理器后才有)postProcessBeforeInitialization方法

      (4)调用bean的初始化的方法(需要进行配置) init-method

      (5)把bean实例传递bean后置处理器的方法(当加入了后置处理器后才有)postProcessAfterInitialization方法

      (6)bean可以使用了(对象获取到了)

      (7)当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)

      <bean id="orders" class="com.hxq.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
          <property name="oname" value="手机" />
      </bean>
      
      public class Orders {
          public Orders() {
              System.out.println("第一步无参构造函数执行...........");
          }
      
          private String oname;
      
      
          public void setOname(String oname) {
              this.oname = oname;
              System.out.println("第二步,调用set方法。。。。。。。。。");
          }
      
          //创建执行的初始化方法
          public void initMethod() {
              System.out.println("第三步, 执行初始化的方法。。。。。。。");
          }
      
          //创建销毁的方法
          public void destroyMethod() {
              System.out.println("第五步, 执行销毁的方法。。。。。");
          }
      }
      
      @Test
      public void OrdersTest() {
          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
          Orders orders = context.getBean("orders", Orders.class);
          System.out.println("第四步 获取实例对象。。。。。");
          System.out.println(orders);
          context.close();
      }
      
      • ApplicationContext没有close方法,需要强转为子接口CPX,或者直接返回CPX

      加入后置处理器后:

      (1)创建类,实现接口BeanPostProcessor,创建后置处理器

      public class MyBeanPost implements BeanPostProcessor {
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 	  {
              System.out.println("在初始化之前执行的方法" + bean);
              return bean;
          }
      
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 	 {
              System.out.println("在初始化之后执行的方法" + bean);
              return bean;
          }
      }
      

      (2)配置后置处理器

      <!--配置后置处理器-->
      <bean id="myBeanPost" class="com.hxq.spring5.bean.MyBeanPost" />
      
    6. 自动装配

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

      bean标签中属性autowire,配置自动装配

      常用两个值:

      (1)byName根据属性名称注入, 属性名称需要与bean的id相同

      (2)byType根据属性类型注入,相同类型的bean不能有多个,否则找不到要使用哪个bean

      <!--自动装配-->
      <!--<bean id="emp" class="com.hxq.spring5.autowire.Emp" autowire="byName" />-->
      <bean id="emp" class="com.hxq.spring5.autowire.Emp" autowire="byType" />
      <bean id="dept" class="com.hxq.spring5.autowire.Dept" />
      
    7. 引入外部属性文件

      (1)直接配置数据库信息

      1. 引入相关依赖

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.5</version>
        </dependency>
        
      2. 配置德鲁伊连接池

        <!--直接配置连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/userDb" />
            <property name="username" value="root" />
            <property name="password" value="hexiqi123" />
        </bean>
        

      (2)引入外部属性文件配置数据库连接池

      1. 创建外部属性文件,properties格式的文件,写数据库信息

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0YX76xr-1617946922004)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210402133856926.png)]

        prop.driverClass=com.mysql.jdbc.Driver
        prop.url=jdbc:mysql://localhost:3306/userDb
        prop.username=root
        prop.password=hexiqi123
        
      2. 把外部properties属性文件引入到spring配置文件中

        • 引入context命名空间

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
          
        • 在spring配置文件使用标签引入外部属性文件

          <context:property-placeholder location="jdbc.properties" />
          
        • 配置连接池

          <!--配置连接池-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="${prop.driverClass}" />
              <property name="url" value="${prop.url}" />
              <property name="username" value="${prop.username}" />
              <property name="password" value="${prop.password}" />
          </bean>
          
IOC操作Bean管理(基于注解)
Spring针对Bean管理中创建对象提供的注解
  1. @Component
  2. @Service:业务层
  3. @Controller:web层
  4. @Repository:持久层dao层
    • 四个注解功能一样,都可用来创建bean实例
基于注解方式实现对象的创建
  1. 引入依赖

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.5</version>
    </dependency>
    
  2. 开启组件扫描

    (1)引入context命名空间

    (2)开启组件扫描

    • 如果扫描多个包,多个包使用逗号隔开
    • 扫描包的上层目录
    <!--开启组件扫描-->
    <context:component-scan base-package="com.hxq.spring5" />
    

    (3)创建类,添加注解

    @Service(value = "userService") //相当于<bean id="userService" class="..."/>
    public class UserService {
    
        public void add() {
            System.out.println("service add.........");
        }
    }
    
    • 在注解里面value属性值可以省略不写,默认是类名称,首字母小写
组件扫描中的细节配置
<!--示例1-->
<context:component-scan base-package="com.hxq" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • use-default-filters=“false” :表示现在不使用默认的filter,而使用自己配置的filter

  • context:include-filter:扫描哪些内容

<!--示例2-->
<context:component-scan base-package="com.hxq">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
  • context:exclude-filter:不扫描哪些内容
基于注解的属性注入
  1. @AutoWired:根据属性类型进行自动装配

  2. @Qualifier:根据属性名称进行注入,需要和**@AutoWired**一起使用

    @Service(value = "userService") 
    public class UserService {
        //不需要添加set方法
        //根据类型进行注入,如果接口有多个实现类,需要配合@Qualifier使用
        @Autowired
        //根据名称进行注入
        @Qualifier(value = "userDaoImpl1")
        private UserDao userDao;
    
        public void add() {
            System.out.println("service add.........");
            userDao.add();
        }
    }
    
  3. @Resource:可以根据类型注入,也可以根据名称注入

    //  @Resource//根据类型注入
        @Resource(name = "userDaoImpl1") //根据名称进行注入
        private UserDao userDao;
    
    • 注意:@Resource是javax拓展包中的注解,不是Spring中的注解
  4. @Value:注入普通类型属性

    @Value("abc")
    private String name;
    
完全注解开发
  1. 创建配置类,替代xml配置文件

    @Configuration
    @ComponentScan(basePackages = {"com.hxq.spring5"})
    public class SpringConfig {
    }
    
  2. 编写测试类

    @Test
    public void testService2() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
    
    • AnnotationConfigApplicationContext

AOP

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

  • 不通过修改源代码方式,在主干功能上添加新的功能

  • 底层使用动态代理

    • 有两种情况的动态代理
      1. 有接口:使用JDK动态代理:创建实现类代理对象,增强类的方法
      2. 没有接口:使用CGLIB动态代理:创建子类代理对象,增强类的方法

JDK动态代理

(1)Proxy类

  1. newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)方法返回接口代理类对象

    (1)第一个参数,类加载器

    (2)第二个参数,增强方法所在类,这个类实现的接口,支持多个接口

    (3)实现这个接口InvocationHandler,创建代理对象,写增强的方法

(2)代码实现

  1. 创建接口,定义方法

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

    public class UserDaoImpl implements UserDao {
    
        @Override
        public int add(int a, int b) {
            System.out.println("add方法执行了,,,");
            return a + b;
        }
    
    }
    
  3. 创建处理生成代理对象的处理类,实现InvocationHandler接口

    class UserDaoProxy implements InvocationHandler {
        private Object obj;
        //1 把创建的谁的代理对象,把谁传递过来
        //有参构造传递
        public UserDaoProxy(Object o) {
            this.obj = o;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在方法之前
            System.out.println("方法之前执行。。。。" + method.getName() + ":传递的参数..." + Arrays.toString(args));
            //被增强的方法
            Object res = method.invoke(obj, args);
            //方法之后
            System.out.println("方法之后执行, "+ obj);
            return res;
        }
    }
    
  4. 使用Proxy类创建接口代理对象

    Class[] interfaces = {UserDao.class};
    UserDaoImpl userDao = new UserDaoImpl();
    UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
    

AOP主要相关术语

(1)连接点:类里面的哪些方法可以被增强,这些方法就称为连接点

(2)切入点:实际被真正增强的方法,称为切入点

(3)通知(增强):实际增强的逻辑部分称为通知(增强)

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

(4)切面:是一个过程,把通知应用到切入点的过程

AOP操作

  • Spring框架一般都是基于AspectJ实现AOP操作
  • AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJSpring框架一起使用,进行AOP操作

准备工作:

  1. 在项目工程中引入AOP相关依赖

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.5</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.5</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
    
  2. 切入点表达式

    (1)作用:知道对哪个类里面的那个方法进行增强

    (2)语法结构:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))

基于AspectJ实现AOP操作
基于xml配置文件实现(了解即可)
  1. 创建类,在类中定义方法

    public class BookProxy {
        public void before() {
            System.out.println("before..............");
        }
    }
    
    public class Book {
        public void buy() {
            System.out.println("buy..............");
        }
    }
    
  2. 在spring配置文件中创建两个类对象

    <!--创建对象-->
    <bean id="book" class="com.hxq.spring5.aopxml.Book" />
    <bean id="bookProxy" class="com.hxq.spring5.aopxml.BookProxy" />
    
  3. 在spring配置文件中配置切入点

    <!--配置aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.hxq.spring5.aopxml.Book.buy())"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <!--增强作用在具体的方法上-->
            <aop:before method="before" pointcut-ref="p" />
        </aop:aspect>
    </aop:config>
    
基于注解方式实现
  1. 创建类,在类里面定义方法

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

    (1)在增强类里面创建方法,让不同方法代表不同通知类型

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

    (1)在spring配置文件中,开启注解扫描

    <!--开启注解扫描-->
    <context:component-scan base-package="com.hxq.spring5.aopanno" />
    

    (2)使用注解创建User和UserProxy对象

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8McgB4Ds-1617946922005)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403161847528.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oTIl0GgC-1617946922006)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403161902370.png)]

    (3)在增强类上面添加注解**@Aspect**

    (4)在spring配置文件中开启生成代理对象

    <!--开启AspectJ生成代理对象-->
    <aop:aspectj-autoproxy />
    
  4. 配置不同类型的通知

    //前置通知
    @Before(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    //最终通知
    @After(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    //后置通知(返回通知)
    @AfterReturning(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    //异常通知
    @AfterThrowing(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    //环绕通知
    @Around(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前.............");
        //被增强的方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后。。。。。。");
    }
    
    • 其中环绕通知带参数ProceedingJoinPoint类,方法proceed()表示方法执行

    • 最终通知不管有没有异常,都会执行,相当于finally

    • 执行结果:

      ​ 环绕之前…
      ​ before…
      ​ add…
      ​ AfterReturning…
      ​ after…
      ​ 环绕之后。。。。。。

  5. 相同的切入点抽取

    //相同切入点抽取
    @Pointcut(value = "execution(* com.hxq.spring5.aopanno.User.add())")
    public void pointdemo() {}
    
    • 之后只需在注解中调用方法即可
  6. 有多个增强类对同一个方法进行增强,设置增强类优先级

    • 在增强类上添加注解@Order(数字类型值),数字类型值越小优先级越高
  7. 完全使用注解开发

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

    • @Configuration
      @ComponentScan(basePackages = {"com.hxq.spring5"})
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      public class ConfigAop {
      }
      

JdbcTemplate

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

  • 引入相关依赖

    • <!-- 德鲁伊连接池依赖 -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.5</version>
      </dependency>
      
      <!-- 数据库连接的依赖 -->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.23</version>
      </dependency>
      
      <!-- spring封装了jdbc -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.3.5</version>
      </dependency>
      
      <!-- 针对事务的相关操作的依赖 -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>5.3.5</version>
      </dependency>
      
      <!--整合其他框架需要用到的依赖-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-orm</artifactId>
          <version>5.3.5</version>
      </dependency>
      
  • 在spring配置文件中配置数据库连接池

    • <!--数据库连接池-->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
          <property name="url" value="jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true" />
          <property name="username" value="root" />
          <property name="password" value="hexiqi123" />
          <property name="driverClassName" value="com.mysql.jdbc.Driver" />
      </bean>
      
  • 配置JdbcTemplate对象,注入DataSource

    • <!--JdbcTemplate对象-->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <!--注入dataSource对象-->
          <property name="dataSource" ref="dataSource" />
      </bean>
      
  • 创建service类,创建dao类,在dao注入jdbcTemplate对象

    • 配置文件

      • <!--组件扫描-->
        <context:component-scan base-package="com.hxq.spring5" />
        
    • service

      • @Service
        public class BookService {
            //注入dao
            @Autowired
            private BookDao bookDao;
        }
        
    • dao

      • @Repository
        public class BookDaoImpl implements BookDao {
            //注入JdbcTemplate
            @Autowired
            private JdbcTemplate jdbcTemplate;
        }
        

JdbcTemplate操作数据库

(1)添加

  1. 对应数据库创建实体类

  2. 编写servicedao

    • 在dao中进行数据库操作

    • 调用JdbcTemplate对象里面update方法实现添加操作

      • 第一个参数:sql语句
      • 第二个参数,可变参数,设置sql语句值
    • @Repository
      public class BookDaoImpl implements BookDao {
          //注入JdbcTemplate
          @Autowired
          private JdbcTemplate jdbcTemplate;
      
          //添加的方法
          @Override
          public void add(Book book) {
              //1 创建sql语句
              String sql = "insert into t_book values(?,?,?)";
              //2 调用方法实现
              Object[] args = {book.getUserId(), book.getUserName(), book.getUstatus()};
              int update = jdbcTemplate.update(sql, args);
              System.out.println(update);
          }
      }
      

(2)修改和删除

//修改的方法
@Override
public void updateBook(Book book) {
    String sql = "update t_book set username=?, ustatus=?, where user_id=?";
    Object[] args = {book.getUserName(), book.getUstatus(), book.getUserId()};
    int update = jdbcTemplate.update(sql, args);
    System.out.println(update);
}

//删除的方法
@Override
public void deleteBook(String id) {
    String sql = "delete from t_book where user_id=?";
    int update = jdbcTemplate.update(sql, id);
    System.out.println(update);
}

(3)查询

  1. 返回值

    (1)查询表里面有多少条记录,返回是某个值

    (2)使用JdbcTemplate的queryForObject方法实现查询返回某个值代码

    //查询表中的记录数
    @Override
    public int selectCount() {
        String sql = "select count(*) from t_book";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        return count;
    }
    
    • 参数1:sql语句
    • 参数2:返回类型的class
  2. 返回对象

    (1)场景:查询图书的详情页面,返回查询图书的信息

    (2)JdbcTemplate中的queryForObject方法实现查询返回对象

    @Override
    public Book findBookInfo(String id) {
        String sql = "select * from t_book where user_id=?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }
    
    • 第一个参数:sql语句
    • 第二个参数:RowMapper,是一个接口,返回不同类型数据,使用这个接口里面的实现类完成数据封装
    • 第三个参数:sql语句值
  3. 返回集合

    (1)场景:查询图书列表分页…

    (2)调用JdbcTemplate中的query方法实现查询返回集合

    //查询返回集合
    @Override
    public List<Book> findAllBook() {
        String sql = "select * from t_book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }
    

(4)批量添加功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zygOWbjR-1617946922007)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210403220232359.png)]

  • 第一个参数:sql语句
  • 第二个参数:List集合,添加多条记录数据
@Override
public void batchAddBook(List<Object[]> batchArgs) {
    String sql = "insert into t_book values(?,?,?)";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
  • 修改删除类似

事务管理

  • 事务是数据库操作的基本单元,逻辑上的一组操作,要么都成功,如果有一个操作失败,则所有操作都失败
  • 典型场景:银行转账
  • 四大特性(ACID):
    • 原子性:所有操作,要么全成功,要么全部失败
    • 一致性:一个事务执行之前和执行之后都必须处于一致性状态
    • 隔离性:多个用户同时访问数据库,每个用户之间的操作互相隔离,互不影响
    • 持久性:一个事务一旦被提交了,就会修改数据库,而且这个修改是永久性的

搭建事务操作环境

  • web层:显示到web端的数据
  • service层:主要编写业务逻辑代码
  • dao层:主要编写数据操作的代码
  1. 创建数据库表

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4Asdj6g-1617946922008)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404145310331.png)]

  2. 创建service,搭建dao,完成对象的创建和注入关系

    (1)service注入dao,在dao里面注入JdbcTemplate,在JdbcTemplate中注入dataSource

  3. 在dao里面定义方法,在service里面定义方法

  • 中间出现异常,会导致数据不一致,需要使用事务进行处理
  1. 事务的操作过程

    (1)开启事务

    (2)进行业务操作

    (3)没有发生异常,则提交事务

    (4)出现异常,则捕获异常,进行事务回滚

事务管理

  1. 事务添加到JavaEE三层结构中的Service层中(业务逻辑层)

  2. 在Spring中进行事务管理操作

    (1)编程式事务管理

    (2)声明式事务管理

  3. 声明式事务管理

    (1)基于注解方式

    (2)基于xml配置文件方式

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

  5. Spring事务管理API

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNwNprBU-1617946922009)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404153041893.png)]

    (2)PlatformTransactionManager是一个事务管理器接口,如果使用jdbc模板,则使用DataSourceTransactionManager实现类

spring声明式事务管理(注解方式)

  1. 在spring配置文件中配置事务管理器

    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
  2. 在Spring配置文件中开启事务注解

    (1)在Spring配置文件中引入名称空间tx

    (2)开启事务注解,transaction-manager指定事务管理器

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />
    
  3. 在service类上面或方法上添加事务注解**@Transactional**

    (1)注解可以添加到类上面,也可以添加到方法上

    (2)如果把这个注解添加到类上面,这个类中的所有方法都添加事务

    (3)如果把这个注解添加到某个方法上面,那么只为这个方法添加事务

    @Service
    @Transactional
    public class UserService {
    
事务参数

在service类上添加注解@Transactional,在这个注解里可以配置事务相关参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bul3KXzB-1617946922010)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404224703399.png)]

(1)propagation: 事务的传播行为

(2)isolation: 事务的隔离级别

(3)timeout: 超时时间

(4)readOnly: 是否只读

(5)rollbackFor: 回滚

(6)noRollbackFor: 不会滚

  1. 传播行为(propagation)

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

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

    (3)Spring框架事务传播行为有7种

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVngoAg4-1617946922011)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210404230245564.png)]

    • REQUIRED: 如果add方法本身有事务,则调用update之后,update使用当前的add方法里的事务,如果add方法本身没有事务,则调用update之后,创建新的事务
    • REQUIRES_NEW: 使用add方法调用update方法,add无论是否有事务,都创建新的事务
    • SUPPORTS
    • NOT_SUPPORTS
    • MANDATORY
    • NEVER
    • NESTED
    @Service
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public class UserService {
    
  2. 隔离级别(isolation)

    (1)事务里面有一个特性称为隔离性,在多事务操作之间不会产生影响。不考虑隔离性会产生很多问题。

    (2)有三个读问题:脏读,不可重复读,虚读(幻读)。

    • 脏读:一个未提交事务读取到另一个未提交事务的数据
    • 不可重复读:一个未提交的事务读取到另一个提交事务修改数据
    • 虚读(幻读):一个未提交事务读取到另一个已提交事务添加的数据

    (3)通过设置事务的隔离性解决读问题

    脏读不可重复读幻读
    READ UNCOMMITTED(读未提交)
    READ COMMITTED(读已提交)
    REPEATABLE READ(可重复读)(mysql默认)
    SERIALIZABLE(串行化)
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
public class UserService {
  1. 超时时间(timeout)

    (1)事务需要在一定时间内进行提交,如果不提交则进行回滚

    (2)默认值:-1,设置时间以为单位进行计算

  2. 是否只读(readOnly)

    (1)读:查询操作,写:添加修改删除操作

    (2)readOnly默认值:false, 可以查询也可以添加修改删除

    (3)设置readOnly值为true,只能进行查询操作

  3. 回滚(rollbackFor)

    (1)设置出现哪些异常进行事务回滚

  4. 不回滚(noRollbackFor)

    (1)设置出现哪些异常不进行事务回滚

spring声明事务管理(xml方式)

  1. 在spring配置文件中,进行配置

    • 配置事务管理器

    • 配置通知

      <!--配置通知-->
      <tx:advice id="txadvice">
          <!--配置事务的相关参数-->
          <tx:attributes>
              <!--指定哪种规则的方法上添加事务-->
              <tx:method name="account*" propagation="REQUIRED"/>
          </tx:attributes>
      </tx:advice>
      
    • 配置切入点切面

      <!-- 配置切入点和切面-->
      <aop:config>
          <!--配置切入点-->
          <aop:pointcut id="pt" expression="execution(* com.hxq.spring5.service.UserService.*(..))"/>
          <!--配置切面-->
          <aop:advisor advice-ref="txadvice" pointcut-ref="pt" />
      </aop:config>
      

声明式事务管理(完全注解开发)

  1. 创建配置类,使用配置类代替xml配置文件

    @Configuration //配置类
    @ComponentScan(basePackages = "com.hxq.spring5") //组件扫描
    @EnableTransactionManagement //开启事务
    public class TxConfig {
    
        //创建数据库的连接池
        @Bean
        public DruidDataSource getDruidDataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/spring_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true");
            dataSource.setUsername("root");
            dataSource.setPassword("hexiqi123");
            return dataSource;
        }
    
        //创建JdbcTemplate对象
        @Bean
        public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
            //到ico容器中根据类型找到dataSource
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            jdbcTemplate.setDataSource(dataSource);
            return jdbcTemplate;
        }
    
        //创建事务管理器对象
        @Bean
        public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
    
    }
    

Spring5新特性

  1. 整个Spring5框架的代码基于jdk8,运行时兼容jdk9,许多不建议使用的类和方法在代码库中删除

spring5框架自带通用的日志封装

(1)spring5已经移除了Log4jConfigListener,官方建议使用Log4j2

(2)Spring5整合Log4j2

  1. 引入相关依赖

    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.2</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.2</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.11.2</version>
        <scope>test</scope>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    
  2. 创建log4j2.xml配置文件

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration status="DEBUG">
        <appenders>
            <console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:ss:SSS} [%t] %-51level %logger{36} - %msg%n" />
            </console>
        </appenders>
        <loggers>
            <root level="info">
                <appender-ref ref="Console" />
            </root>
        </loggers>
    </configuration>
    

Spring5框架核心容器支持@Nullable注解

(1)@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。

(2)注解用在方法上面,表示方法返回值可以为空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4Fif1th-1617946922012)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184030155.png)]

(3)注解使用在参数上,表示参数可以为空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1LLcs6sT-1617946922013)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184147207.png)]

(4)注解使用在属性上,表示属性值可以为空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncgdUhS0-1617946922015)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210405184343892.png)]

spring5核心容器支持函数式风格GenericApplicationContext

//函数式风格创建对象,交给spring管理
@Test
public void testGenericApplicationContext() {
    //1 创建GenericApplicationContext对象
    GenericApplicationContext context = new GenericApplicationContext();
    //2 调用context的方法对象注册
    context.refresh();
    context.registerBean("user1", User.class, () -> new User());
    //3 获取在spring注册的对象
    User user = (User) context.getBean("user1");
    System.out.println(user);

}
  • 如果其中未指定bean的名字,则获取bean时,可以使用类的全路径来获取

spring5支持整合JUnit5框架

  1. 整合JUnit4

    (1)引入Spring相关针对测试的依赖

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.5</version>
        <scope>test</scope>
    </dependency>
    

    (2)创建测试类,使用注解完成

    @RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
    @ContextConfiguration("classpath:bean2.xml") //加载配置文件
    public class JTest4 {
        @Autowired
        private UserService userService;
        @Test
        public void test1() {
            userService.accountMoney();
        }
    }
    
    • @RunWith(SpringJUnit4ClassRunner.class)
  2. spring5整合JUnit5

    (1)引入JUnit5的相关依赖

    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.0-M1</version>
        <scope>test</scope>
    </dependency>
    

    (2)创建测试类,使用注解完成

    @ExtendWith(SpringExtension.class)
    @ContextConfiguration("classpath:bean2.xml")
    public class JTest5 {
        @Autowired
        private UserService userService;
        @Test
        public void test1() {
            userService.accountMoney();
        }
    }
    
    • @ExtendWith(SpringExtension.class)
    • 或者直接使用复合注解**@SpringJUnitConfig(locations = “classpath:bean2.xml”)**

SpringWebFlux

介绍

SpringWebFlux是Spring5添加的新的模块,用于web开发,功能SpringMVC类似的,Webflux使用当前一种比较流行响应式编程出现的框架。

使用传统web框架,比如SpringMVC,这些基于Servlet容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。

  • 异步非阻塞
    • 异步和同步针对调用者,调用者发送请求后,异步不会等着请求返回后执行其他操作,同步需等待请求返回后再去执行其他操作
    • 非阻塞和阻塞针对被调用者,被调用者收到请求后,做完请求任务之后才给出反馈,这个叫阻塞,收到请求直接给出反馈,再去执行请求的任务,这是非阻塞

Webflux特点

  • 异步非阻塞:在有限的资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
  • 函数式编程:Spring5框架基于java8,Webflux使用java8函数式编程方式实现路由请求

比较springMVC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veMwCRcd-1617946922016)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406125654954.png)]

(1)两个框架都可以使用注解方式,都运行在Tomcat等容器中

(2)SpringMVC采用命令式编程,WebFlux采用异步响应式编程

响应式编程

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

例如:Excel中的表格如果填的一个函数,那个格子的值会根据函数中的参数改变而改变

  1. Java8及其之前版本

    • 提供的观察者模式的两个类ObserverObservable

      public class ObserverDemo extends Observable {
      
          public static void main(String[] args) {
              ObserverDemo observer = new ObserverDemo();
              //添加观察者
              observer.addObserver((o, arg) -> {
                  System.out.println("发生了变化");
              });
              observer.addObserver((o, arg) -> {
                  System.out.println("收到被观察者通知,准备改变");
              });
              
              observer.setChanged(); //数据变化
              observer.notifyObservers(); //通知
          }
      }
      
  2. Reactor实现

    (1)响应式编程操作中,Reactor满足Reactive规范框架

    (2)Reactor有两个核心类,MonoFlux,这两个类实现接口Publisher,提供丰富的操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或者1个元素。

    (3)Flux 和 Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。

    (4)代码演示:

    1. 引入依赖

      <!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
      <dependency>
          <groupId>io.projectreactor</groupId>
          <artifactId>reactor-core</artifactId>
          <version>3.4.4</version>
      </dependency>
      
    2. 编写代码

      //just方法直接声明
      Flux.just(1,2,3,4);
      Mono.just(1);
      
      //其他方法
      //数组
      Integer[] array = {1,2,3,4};
      Flux.fromArray(array);
      
      //集合
      List<Integer> list = Arrays.asList(array);
      Flux.fromIterable(list);
      
      //Stream流
      Stream<Integer> stream = list.stream();
      Flux.fromStream(stream);
      
      • 订阅需要用到subscribe()方法

        Flux.just(1,2,3,4).subscribe(System.out::print);
        Mono.just(1).subscribe(System.out::print);
        

    (5)三种信号特点

    • 错误信号和完成信号都是终止信号,不能同时共存
    • 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
    • 如果没有错误信号,没有完成信号,表示无限数据流

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kGgKdNY-1617946922017)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406134730630.png)]

    (7)操作符

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

    • map 元素映射为新元素

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXHRy0ON-1617946922018)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406135119337.png)]

    • flatmap 元素映射为流

      • 把每个元素转换流,把转换之后多个流合并大的流

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ajm0JmT-1617946922019)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406135212444.png)]

执行流程和核心API

SpringWebFlux基于Reator,默认使用的容器是Netty,Netty是高性能的NIO框架,异步非阻塞框架

  1. Netty

    • BIO

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zyvkxapr-1617946922020)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406150807475.png)]

    • NIO

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lg7yJpDe-1617946922022)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406150914068.png)]

  2. SpringWebFlux执行过程和SpringMVC相似的

    • SpringWebFlux核心控制器DispatchHandler,实现接口WebHandler

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WlDh4WJs-1617946922023)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406151412639.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBujsvPu-1617946922023)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210406151730898.png)]

  3. SpringWebFlux里面DispatcherHandler,负责请求的处理

    • HandlerMapping:请求查询到处理的方法
    • HandlerAdapter:真正负责处理请求
    • HandlerResultHandler:相应结果处理
  4. SpringWebFlux实现函数式编程,两个接口:RouterFunction(路由处理) 和 HandleFunction(处理函数)

  5. SpringWebFlux(基于注解编程模型)

    • SpringWebFlux实现方式有两种:注解编程模型和函数式编程模型

    • 使用注解编程模型方式,和之前SpringMVC使用相似的,只需把相关的依赖配置到项目中,SpringBoot自动配置相关运行容器,默认使用Netty服务器

    (1)创建SpringBoot工程,引入WebFlux依赖

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMnTY3Ik-1617946922024)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210408195916482.png)]

    (2)配置启动端口号

    server.port=8081
    

    (3)创建包和相关类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfYLrUyu-1617946922025)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210408200248193.png)]

    (4)创建实体类

    @Data
    public class User {
        private String name;
        private String gender;
        private Integer age;
    }
    

    (5)创建了接口,定义操作的方法

    public interface UserService {
        //根据id查询用户
        Mono<User> getUserById(String id);
        //查询所有用户
        Flux<User> getAllUser();
        //添加用户
        Mono<Void> saveUserInfo(Mono<User> user);
    }
    

    (6)接口的实现类

    public class UserServiceImpl implements UserService {
    
        //创建map集合存储数据
        private final Map<Integer, User> users = new HashMap<>();
    
        public UserServiceImpl() {
            this.users.put(1, new User("lucy","nan",20));
            this.users.put(2, new User("mary","nv",20));
            this.users.put(3, new User("jack","nv",20));
        }
    
        @Override
        public Mono<User> getUserById(Integer id) {
            return Mono.justOrEmpty(this.users.get(id));
        }
    
        @Override
        public Flux<User> getAllUser() {
            return Flux.fromIterable(this.users.values());
        }
    
        @Override
        public Mono<Void> saveUserInfo(Mono<User> userMono) {
            return userMono.doOnNext(person -> {
                users.put(users.size() + 1, person);
            }).thenEmpty(Mono.empty());
        }
    }
    

    (6)创建controller

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        //id查询
        @GetMapping("/user/{id}")
        public Mono<User> getUserById(@PathVariable int id) {
            return userService.getUserById(id);
        }
    
        //查询所有
        @GetMapping("/user")
        public Flux<User> getUsers() {
            return userService.getAllUser();
        }
    
        //添加
        @PostMapping("/saveuser")
        public Mono<Void> saveUser(@RequestBody User user) {
            Mono<User> userMono = Mono.just(user);
            return userService.saveUserInfo(userMono);
        }
    
    }
    
    • 说明
      • SpringMVC方式实现,同步阻塞的方式,基于SpringMVC+Servlet+Tomcat
      • SpringWebFlux方式实现,异步非阻塞方式,基于SpringWebFlux+Reactor+Netty
  6. SpringWebFlux(基于函数式编程模型)

    (1)在使用函数式编程模型操作时候,需要自己初始化服务器

    (2)基于函数式编程模型实现,有两个核心接口:RouteFunction(实现路由功能,请求转发给对应的handler)和HandlerFunction(处理请求生成响应函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。

    (3)SpringWebFlux请求和响应不再是ServletRequestServletResponse,而是ServerRequestServerResponse

    1. 把注解编程模型工程复制一份

    2. 创建Handler(具体实现方法)

      public class UserHandler {
      
          private final UserService userService;
      
          public UserHandler(UserService userService) {
              this.userService = userService;
          }
      
          //根据id查询方法
          public Mono<ServerResponse> getUserById(ServerRequest request) {
              //获取id值
              int userId = Integer.valueOf(request.pathVariable("id"));
              //空值处理
              Mono<ServerResponse> notFount = ServerResponse.notFound().build();
              Mono<User> userMono = this.userService.getUserById(userId);
              //把userMono进行转换返回
              //使用Reactor操作符flatMap
              return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person))).switchIfEmpty(notFount);
          }
      
          //查询素有
          public Mono<ServerResponse> getAllUsers() {
              Flux<User> users = this.userService.getAllUser();
              return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class);
          }
      
          //添加
          public Mono<ServerResponse> saveUser(ServerRequest request) {
              Mono<User> userMono = request.bodyToMono(User.class);
              return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
          }
      
      }
      
    3. 初始化服务器,编写Router

      public class Server {
          //1 创建Router路由
          public RouterFunction<ServerResponse> routingFunction() {
              UserService userService = new UserServiceImpl();
              UserHandler handler = new UserHandler(userService);
              return RouterFunctions.route(
                      GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                  .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
          }
      }
      
    4. 创建服务器完成适配

      //2 创建服务器完成适配
      public void createReactorServer() {
          //路由和handler适配
          RouterFunction<ServerResponse> route = routingFunction();
          HttpHandler httpHandler = toHttpHandler(route);
          ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
          //创建服务器
          HttpServer httpServer = HttpServer.create();
          httpServer.handle(adapter).bindNow();
      }
      
    5. 最终调用

      public static void main(String[] args) throws IOException {
          Server server = new Server();
          server.createReactorServer();
          System.out.println("enter to exit");
          System.in.read();
      }
      
    6. 使用WebClient调用

      public class Client {
          public static void main(String[] args) {
              //调用服务器地址
              WebClient webClient = WebClient.create("http://127.0.0.1:51095");
      
              //根据id查询
              String id = "1";
              User userresult = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
              System.out.println(userresult.getName());
      
              //查询所有
              Flux<User> results = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
              results.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
      
          }
      }
      

Response> routingFunction() {
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler(userService);
return RouterFunctions.route(
GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
.andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}
}
```

  1. 创建服务器完成适配

    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和handler适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
    
  2. 最终调用

    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
    
  3. 使用WebClient调用

    public class Client {
        public static void main(String[] args) {
            //调用服务器地址
            WebClient webClient = WebClient.create("http://127.0.0.1:51095");
    
            //根据id查询
            String id = "1";
            User userresult = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
            System.out.println(userresult.getName());
    
            //查询所有
            Flux<User> results = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
            results.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
    
        }
    }
    

spring学习笔记–尚硅谷
此文是在学习尚硅谷视频时手打的笔记。
另附上尚硅谷spring5视频链接:尚硅谷Spring5

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值