Spring5笔记

Spring5

视频地址

1,Spring概念

Spring框架概述

  1. Spring是轻量级的开源的JavaEE框架
  2. Spring可以解决企业应用开发的复杂性
  3. Spring有两个核心部分:IOCAOP
    • IOC:控制反转,把创建对象过程交给Spring管理。
    • AOP:面向切面,不修改源代码的情况下进行功能增强。
  4. Spring特点:
    • 方便解耦,简化开发。
    • AOP编程的支持。
    • 方便程序测试。
    • 方便和其他框架进行整合。
    • 降低JavaEE APL开发难度。
    • 方便进行事务操作。
  5. 现在课程中,选取Spring5版本5.X

入门案例

  1. 下载Spring5:https://spring.io/projects/spring-framework#learn

    左侧选择Spring framework,右侧选择learn, 选择GA版本,GA是稳定版本。

    下一步选择GetHub,去GetHub下载,下拉选择Access to Binaries

    下拉选择Downloading a Distribution

    到新网页,选择Artifacts -》libs-release-》org-》springframeword-》spring

    右侧赋值Repository Path 地址,在地址栏上 https://repo.spring.io/追加到后面

    下载地址:https://repo.spring.io/libs-release/org/springframework/spring/

  2. 用idea创建一个普通的module

  3. 导入Spring5的包

    beans core context expression

    还需要一个commons logging 日志包

    日志包网址:http://commons.apache.org/proper/commons-logging/download_logging.cgi 选择二进制文件

  4. 创建普通类,在这个类创建普通方法。

    public class User {
    	public void add() {
    		System.out.println("add...");
    	}
    }
    
  5. spring中创建对象可以通过配置文件或者注解。

    创建spring配置文件,在配置文件使用xml格式,在src下

    <?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">
        <!--配置User对象创建-->
        <bean id="user" class="com.zhiyou100.User"></bean> <!--在bean中可以写两个属性 id:别名 class:类的全路径-->
    </beans>
    
  6. 进行测试代码编写

        @Test
        public void testAdd() {
            //1. 加载spring配置文件
            //创建类路径Xml应用程序上下文
           ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            //2. 获取配置创建的对象
            //根据xml中的id传入,和要传入的类字节码文件
            User user = context.getBean("user", User.class);
            System.out.println(user);
            user.add();
        }
    

2,IOC容器

IOC概念原理

  1. 什么是IOC?

    • 控制反转(Inversion of Control),把对象创建和对象之间的调用过程,交给Spring进行管理。

    • 使用IOC目的:为了耦合度降低

    • 做的入门案例就是IOC实现

  2. IOC底层原理

    xml解析,工厂模式,反射

  3. 图画讲解IOC底层原理

    image

    image

IOC接口

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工程

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

    • BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用。

      特点:加载配置文件的时候不会创建对象,在获取对象(使用)的时候才会创建对象。

    • ApplicationContext:BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员进行使用。

      特点:加载配置文件的时候就会把配置文件对象进行创建。

    • 他们都能进行IOC容器实现,功能都可以做到。

  3. ApplicationContext 接口的实现类

    image

    FileSystemXmlApplicationContext配置文件就要写盘符里面的--绝对路径

    ClassPathXmlApplicationContextsrc下类路径--相对路径

  4. BeanFactory 接口的实现类

    image

Bean管理

  1. 什么是Bean管理?
    Bean管理指的是两个操作
    1.Spring创建对象
    2.Spring注入属性

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

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

IOC操作Bean管理(基于xml)

  1. 基于xml方式创建对象

    <bean id="user" class="com.atguigu.spring5.User"></bean>
    
    1. 在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建。

    2. 在bean标签有很多属性,介绍常用的属性

      • id属性:唯一标识

      • class属性:类全名路径(包类路径)

      • name属性: 和id属性一样

        id和name属性的区别是:id不能加特殊符号,name可以。

    3. 创建对象的时候,默认是执行无参数构造方法完成对象创建。

  2. 基于xml方式注入属性

    1. DI:依赖注入,就是注入属性。di是ioc的一种具体实现,就是依赖输入。必须式在创建对象的前提下。

    2. 第一种方式:使用set方法进行注入

      • 创建类,定义属性和对应的set方法

        public class Book {
            private String bName;
            private String bAuthor;
            public void setbName(String bName) {this.bName = bName;}
            public void setbAuthor(String bAuthor) {this.bAuthor = bAuthor;}
            public void show() {System.out.println(bName+"的作者是:"+bAuthor);}
        }
        
      • 在spring配置文件配置对象创建,配置属性注入

        <!--1. 配置book对象创建-->
        <!--在bean中可以写两个属性 id:别名 class:类的全路径-->
        <bean id="book" class="com.zhiyou100.Book">
            <!--2. set方法注入属性-->
            <!--使用property完成属性注入
                    name:类里面属性名称
                    value:向属性注入的值-->
            <property name="bName" value="《西游记》"></property>
            <property name="bAuthor" value="吴承恩"></property>
        </bean>
        
      • 测试类

        @Test
        public void testBook1() {
            //1. 加载spring配置文件
            BeanFactory context = new ClassPathXmlApplicationContext("bean1.xml");
            //2. 获取配置创建的对象
            Book book = context.getBean("book", Book.class);
            book.show();
        }
        
    3. 第二种方式:使用有参构造方法进行注入

      • 创建类,定义属性,创建属性对应有参数构造方法

        public class Orders {
            private String oName;
            private String address;
            public Orders(String oName, String address) {this.oName = oName;this.address = address;}
            public void show() { System.out.println("名字:" + oName + ",地址:" + address); }
        }
        
      • 在spring配置文件中配置

        <bean id="order" class="com.zhiyou100.Orders">
            <!--使用constructor-arg标签进行构造方法赋值
                name:属性
                value:值  -->
            <constructor-arg name="address" value="电脑"></constructor-arg>
            <constructor-arg name="oName" value="China"></constructor-arg>
            <!--或者使用index=属性 value=值 也可以-->
          	<!--<constructor-arg index="0" value="电脑"></constructor-arg>
          	<constructor-arg index="1" value="China"></constructor-arg> -->
        </bean>
        
      • 测试:

        @Test
        public void testOrder() {
            //创建类路径Xml应用程序上下文,加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            //根据方法传入属性创建对象
            Orders order = context.getBean("order", Orders.class);
            order.show();
        }
        
  3. p名称空间注入(set方法注入)

    • 使用p名称空间注入,可以简化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"
           第一步:添加p名称空间在配置文件中
           xmlns:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        	第二步:进行属性注入,在bean标签里面进行操作
           <bean id="book" class="com.zhiyou100.Book" p:bAuthor="金庸" p:bName="《笑傲江湖》"></bean>
    </beans>
    

    测试:之前那个就可以(o)!

IOC操作Bean管理(xml注入其他类型)

  1. xml注入其他类型属性

    1. 字面量:

      • null值

        <!--根据set方法赋值-->
        <property name="address" ><null/></property>
        
      • 属性值包含特殊符号:

        <!--属性值包含特殊符号
            1. 把<>进行转义  &lt;&gt;
            2. 把带特殊符号内容写道CDATA中(必须大写)
        -->
         <property name="address" >
             <value><![CDATA[
                <<南京>>
             ]]></value>
         </property>
        
  2. 注入属性 --外部bean

    1. 创建两个类service类和dao类

    2. 在service调用dao里面的方法

    3. 在Spring配置文件中进行配置

      public interface UserDao {
          public void update();
      }
      public class UserDaoImpl implements UserDao {
          @Override
          public void update() {System.out.println("dao update ......");}
      }
      public class UserService {
          //属性注入
          private UserDao userDao;
          public void setUserDao(UserDao userDao) {this.userDao = userDao;}
      
          public void add() {
              System.out.println("service add ......");
              //调用方法
              userDao.update();}
      }
      
      <!--创建service和dao对象-->
      <bean id="userService" class="com.zhiyou100.service.UserService">
          <!--注入userDao对象
              ref属性:创建userDao对象bean标签id值
              ref=bean标签id值(传入对象)
          -->
          <property name="userDao" ref="userDaoImp"></property>
      </bean>
      <bean id="userDaoImp" class="com.zhiyou100.dao.UserDaoImpl"></bean>
      
  3. 注入属性 --内部bean

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

      一个部门有对个员工,一个员工属于一个部门

      部门是一,员工是多。

    2. 在实体类之间标识一对多关系

      public class Dept {
          private String dName;
          public void setdName(String dName) {this.dName = dName;}
          public String getdName() {return dName;}
      }
      public class Emp {
          private  String eName;
          private  String gender;
          //员工属于一个部门,使用对象形式表示
          private Dept dept;
          public void setDept(Dept dept) {this.dept = dept;}
          public void seteName(String eName) {this.eName = eName;}
          public void setGender(String gender) {this.gender = gender;}
          public void show() {System.out.println(eName + "," + gender + "," + dept.getdName());}
      }
      
      <!--内部bean-->
      <bean id="emp" class="com.zhiyou100.bean.Emp">
          <!--设置两个不同普通属性-->
          <property name="eName" value="lucy"></property>
          <property name="gender" value="男"></property>
          <!--设置对象属性-->
          <property name="dept">
              <!--在内部创建对象-->
              <bean id="dept" class="com.zhiyou100.bean.Dept">
                  <property name="dName" value="保安部"></property>
              </bean>
          </property>
      </bean>
      
  4. 注入属性 --级联赋值

    • 第一种:(和3一样只是xml配置不一样)

          <!--级联赋值-->
          <bean id="emp" class="com.zhiyou100.bean.Emp">
              <!--设置两个不同普通属性-->
              <property name="eName" value="lucy"></property>
              <property name="gender" value="男"></property>
              <!--级联赋值-->
              <property name="dept" ref="dept"></property>
          </bean>
          <bean id="dept" class="com.zhiyou100.bean.Dept">
              <property name="dName" value="财务部"></property>
          </bean>
      
    • 第二种:

      需要做一件事情,在emp中属性dept生成get方法,如果取不到就不能赋值

      <!--级联赋值-->
      <bean id="emp" class="com.zhiyou100.bean.Emp">
          <!--设置两个不同普通属性-->
          <property name="eName" value="lucy"></property>
          <property name="gender" value="男"></property>
          <!--级联赋值-->
          <property name="dept" ref="dept"></property>
          <property name="dept.dName" value="技术部"></property>
      </bean>
      <bean id="dept" class="com.zhiyou100.bean.Dept">
      </bean>
      

IOC操作Bean管理(xml注入集合属性)

  1. 注入数组,list集合,map集合,set集合,对象集合,类型属性

    public class Stu {
        //1. 数组属性
        private String[] course;
        //2. list集合类型属性
        private List<String> list;
        //3. map集合类型属性
        private Map<String, String> map;
        //4. set集合类型属性
        private Set<String> set;
        //5. 对象集合
        private List<Course> courses;
        //省略set方法toString方法
    }
    
    <bean id="stu" class="com.zhiyou100.collectionType.Stu">
            <!--数组-->
            <property name="course">
                <!--list和array都是可以的-->
                <array>
                    <value>语文</value>
                    <value>数学</value>
                    <value>英语</value>
                </array>
            </property>
            <!--list集合-->
            <property name="list">
                <list>
                    <value>张三</value>
                    <value>李四</value>
                    <value>王五</value>
                </list>
            </property>
            <!--map集合-->
            <property name="map">
                <map>
                    <entry key="java" value="java"></entry>
                    <entry key="php" value="php"></entry>
                </map>
            </property>
            <!--set集合-->
            <property name="set">
                <set>
                    <value>北京</value>
                    <value>南京</value>
                </set>
            </property>
            <!--在集合里面设置对象类型值-->
            <property name="courses">
                <list>
                    <ref bean="course1"></ref>
                    <ref bean="course2"></ref>
                </list>
            </property>
        </bean>
    	<!--创建对象给集合对象赋值-->
        <bean id="course1" class="com.zhiyou100.bean.Course">
            <property name="cName" value="孙子兵法"></property>
        </bean>
        <bean id="course2" class="com.zhiyou100.bean.Course">
            <property name="cName" value="中华小子"></property>
        </bean>
    

    测试省略。

  2. 把集合注入部分提取出来

    public class Book {
        private List<String> list;
        //省略set,toString方法	
    }
    
    <?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">
        <!--1. 在spring配置文件中引入名称空间util
        把 schemaLocation后面的地址赋值一份beans换成util加在后面-->
    
        <!-- 2. 使用util标签完成list集合注入提取。-->
        <!--2.1 提取list集合类型属性注入-->
        <util:list id="bookList">
            <!--如果是对象就用 ref标签bean属性-->
            <value>易筋经</value>
            <value>九阳神功</value>
            <value>八卦神功</value>
        </util:list>
        <!--2.2 提取list集合类型属性注入使用-->
        <bean id="book" class="com.zhiyou100.bean.Book">
            <property name="list" ref="bookList"/>
        </bean>
    </beans>
    

IOC操作Bean管理(FactoryBean)

  1. Spring有两种类型bean,一种普通bean,另外一种工厂bean(factorybean)

  2. 普通bean,在配置文中定义bean类型就是返回类型

    之前写的就是普通bean。

  3. 工厂bean,在配置文件定义bean类型可以返回类型不一样

    • 第一步 创建类,让这个类作为工厂bean,实现FactoryBean。

    • 第二部 实现接口里面的方法,在实现的方法中定义返回的bean类型。

      public class MyBean implements FactoryBean<Course> {
          //定义返回bean对象
          @Override
          public Course getObject() throws Exception {
              Course course = new Course();
              course.setcName("NIKE");
              return course;
          }
          @Override
          public Class<?> getObjectType() {return null;}
          @Override
          public boolean isSingleton() {return false;}
      }
      
      <bean id="myBean" class="com.zhiyou100.factoryBean.MyBean">
      
      @Test
      public void MyBeanTest() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
          Course course = context.getBean("myBean", Course.class);
          System.out.println(course);//Course{cName='NIKE'}
      }
      

IOC操作Bean管理(作用域基于注解)

  1. 在Spring里面,设置创建bean实例是单实例还是多实例。

  2. 在Spring里面,默认情况下,bean是单实例对象。

  3. 如何设置单实例还是多实例

    • 在spring配置文件bean标签里面有属性(scope)用于设置单实例还是多实例

    • scope属性值

      第一个值 默认值,singleton,表示单实例对象

      第二个值,prototype,表示多实例对象

    • singleton和prototype区别

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

    2. 设置scope 值是 singleton的时候,加载spring配置文件的时候就会创建单例对象。

      设置scope 值是 prototype的时候,不是加载spring配置文件时候创建 对象,在调用getBean()方法时候创建多实例对象。

    • request(一次请求):

    • session(一次会话):

IOC操作Bean管理(声明周期基于注解)

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

  2. bean声明周期:

    1. 通过构造器创建bean实例(无参构造方法)
    2. 为bean的属性设置值和其他bean引用(调用set方法)
    3. 调用bean的初始化的方法(需要进行配置初始化的方法)
    4. bean可以使用了(对象获取到了)
    5. 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
  3. 代码:

    public class Orders {
        private String oName;
        public void setoName(String oName) {this.oName = oName;System.out.println("第二分 调用set方法设置值"); }
        public Orders() {System.out.println("第一步 执行无参构造创建bean实例");}
        //创建执行初始化的方法 需要在xml配置
        public void initMethod() {System.out.println("第三步 执行初始化的方法");}
        //创建执行销毁的方法
        public void destroy() {System.out.println("第五步 执行销毁的方法");}
    }
    
    <!--init-method=类中方法名字 就是执行初始化的配置-->
    <!--destroy-method=类之方法名 就是执行销毁的配置-->
    <bean id="orders" class="com.zhiyou100.bean.Orders" init-method="initMethod" destroy-method="destroy">
        <property name="oName" value="摩托"/>
    </bean>
    
    @Test
    public void OrderTest() {
        BeanFactory context = new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("第四步 获取创建bean实例对象");
        System.out.println(orders);
        //销毁需要手动销毁bean实例
        //close()方法是子类的方法,父类没有所有需要使用强转来使用
        ((ClassPathXmlApplicationContext)context).close();
       	   /*
            * 第一步 执行无参构造创建bean实例
            * 第二分 调用set方法设置值
            * 第三步 执行初始化的方法
            * 第四步 获取创建bean实例对象
            * com.zhiyou100.bean.Orders@396e2f39
            * 第五步 执行销毁的方法
            * */
    }
    
  4. bean的后置处理器,bean的声明周期有七步操作

    1. 通过构造器创建bean实例(无参构造方法)
    2. 为bean的属性设置值和其他bean引用(调用set方法)
    3. 把bean实例传递bean后置处理器的方法 postProcessBeforeInitialization
    4. 调用bean的初始化的方法(需要进行配置初始化的方法)
    5. 把bean实例传递bean后置处理器的方法 postProcessAfterInitialization
    6. bean可以使用了(对象获取到了)
    7. 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
  5. 演示添加后置处理器效果

    1. 创建类,实现类BeanPostProcessor,创建后置处理器(和上面的代码不变,除了新类和配置文件)

      public class MyBeanPost implements BeanPostProcessor {
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("在初始化之前执行的方法");return bean;
          }
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              System.out.println("在初始化之后执行的方法");return bean;
          }
      }
      
      <!--init-method=类中方法名字 就是执行初始化的配置-->
      <!--destroy-method=类之方法名 就是执行销毁的配置-->
      <bean id="orders" class="com.zhiyou100.bean.Orders" init-method="initMethod"
      destroy-method="destroy">
      <property name="oName" value="摩托"/>
      </bean>
      
      <!--配置后置处理器-->
      <!--过程:当你加载配置文件的时候,会把配置文件中的对象创建,并且把
        后置处理器创建(当一个类作为BeanPostProcessor的实现类时就把它作为后置处理器执行)
        ,而后置处理器会对你当前配置文件的所有bean都添加后置处理器的处理
      -->
      <bean id="myBeanPost" class="com.zhiyou100.bean.MyBeanPost"></bean>
      
      @Test
      public void OrderTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
          Orders orders = context.getBean("orders", Orders.class);
          System.out.println("第四步 获取创建bean实例对象");
          System.out.println(orders);
          //销毁需要手动销毁bean实例
          //close()方法是子类的方法,父类没有所有需要使用强转来使用
          context.close();
         /* 第一步 执行无参构造创建bean实例
          * 第二分 调用set方法设置值
          * 在初始化之前执行的方法
          * 第三步 执行初始化的方法
          * 在初始化之后执行的方法
          * 第四步 获取创建bean实例对象
          * com.zhiyou100.bean.Orders@365185bd
          * 第五步 执行销毁的方法
          * */
      }
      

IOC操作Bean管理(xml自动装配)

  1. 什么时自动装配

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

  2. 演示自动装配

    public class Dept {
        //省略toStirng
    }
    public class Emp {
        private Dept dept;
        //省略set,get,toString
    }
    
    <!--实现自动装配
        bean标签autowire,配置自动装配
        autowire属性常用两个值:
            byName根据属性名称注入,注入值bean的id值和类属性名称一样
            byType根据属性类型注入,相同类型的bean不能在xml中定义多个
    -->
    <bean id="emp" class="com.zhiyou100.autowire.Emp" autowire="byName">
        <!--<property name="dept" ref="dpt"/>-->
    </bean>
    <bean id="dept" class="com.zhiyou100.autowire.Dept"></bean>
    

IOC操作Bean管理(引入外部属性文件 基于xml)

  1. 直接配置数据库信息

    1. 配置德鲁伊连接池

    2. 引入德鲁伊连接依赖jar

      <!--直接配置连接池-->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
          <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql:///test"/>
          <property name="username" value="root"/>
          <property name="password" value="root"/>
      </bean>
      
  2. 引入外部属性文件配置数据库连接池

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

      url=jdbc:mysql:///test
      username=root
      password=root
      driverClassName=com.mysql.jdbc.Driver
      
    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">
      	<!--1. 引入名称空间↑ context-->
          <!--2. 引入外部属性文件-->
          <context:property-placeholder location="classpath:druid.properties"/>
          <!--3. 配置连接池-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="${driverClassName}"/>
              <!--4. 这里value=${属性文件中的key}-->
              <property name="url" value="${url}"/>
              <property name="username" value="${username}"/>
              <property name="password" value="${password}"/>
          </bean>
      </beans>
      

IOC操作Bean管理(基于注解)

  1. 什么是注解

    • 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值,,)
    • 使用注解,注解作用在类上面,方法上面,属性上面。
    • 使用注解的目的:简化xml配置
  2. Spring针对Bean管理中创建对象(交给spring管理)提供的注解

    • @Component
    • @Service
    • @Controller
    • @Repository >用在持久层的接口上
    • 上面四个注解功能是一样的,都可以用来创建bean实例。
  3. 基于注解方式实现对象创建

    • 第一步 引入依赖:spring-aop-5.3.4.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-->
          <!--开启组件扫描
              1. 入库给扫描多个包,多个包使用逗号隔开
              2. 扫描包上层目录
          -->
          <context:component-scan base-package="com.zhiyou100"/>
      </beans>
      
        //在创建对象注解里面value属性值可以省略不写
        //默认就是类名称,首字母小写
        //UserService --> userService
        @Component(value = "userService") //<bean id="userService" class=".."/ > value和bean里面的id是等价的
        //别的创建对象注解也可以实现效果
        public class UserService {
            public void add() {System.out.println("service add ...");}
        }
        //测试
        @Test
        public void test1() {
            //加载配置文件					类路径应用程序上下文
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            UserService user = context.getBean("userService", UserService.class);
            System.out.println(user);
            user.add();
            /*
                * 第一部分加载配置文件,开启资源扫描,
                * 在xml找组件扫描中的路径,如果类中有相关的注解
                * 根据相关的注解,就把对象创建
                * */
        }
      
  4. 开启组件扫描细节配置

    <!--示例1
        标签context:component-scan,属性:use-default-filters="false" 表示现在不适用默认filter,自己配置filter
        context:include-filter标签,设置扫描包含那些内容
    -->
    <context:component-scan base-package="com.zhiyou100" use-default-filters="false">
        <!--这句话的意思是只在com.zhiyou100下的包中只扫描Controller注解的类,别的注解别的类不扫描-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <!--示例2
        context:exclude-filter标签,设置扫描不包含那些内容
    -->
    <context:component-scan base-package="com.zhiyou100">
        <!--这句话的意思是不扫描注解是Controller-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
  5. 基于注解方式实现属性注入

    • @Autowired:根据类型自动注入进行自动装配

      第一步把 service 和 dao 包的对象创建,在 service 和 dao 中类添加创建对象注解

      第二步把 service 和 dao包中对象,在 service 类添加到dao类型属性,在属性上面使用注解

      public interface UserDao {public void add();}
      @Repository //创建对象 可以根据类型或者名称进行注入
      public class UserDaoImp implements UserDao {
          @Override
          public void add() {System.out.println("dao ad......");}
      }
      @Service //创建对象
      public class UserService {
          //定义dao类型属性
          //不需要添加set方法,因为在里面已经包我们封装了
          //添加注入属性注解
          @Autowired //根据类型注入
          private UserDao userDao;
          public void add() { System.out.println("service add ...");userDao.add();}
      }
      
      <?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">
          <!--1. 引入名称空间context-->
          <!--2. 开启组件扫描
              2.1. 入库给扫描多个包,多个包使用逗号隔开
              2.2. 扫描包上层目录
          -->
          <context:component-scan base-package="com.zhiyou100"/>
      </beans>
      
    • @Qualifier:根据属性名称进行注入

      这个 @Qualifier 注解的使用,和上面 @Autowired一起使用

      @Repository(value = "userDaoImpl1") //可以根据类型或者名称进行注入
      public class UserDaoImpl implements UserDao {
          @Override
          public void add() {System.out.println("dao ad......");}
      }
      @Service//创建对象
      public class UserService {
          //定义dao类型属性
          //不需要添加set方法,因为在里面已经包我们封装了
          //添加注入属性注解
          @Autowired //根据类型注入
          //@Autowired是为了找类型,但是可以有两种或多中相同类型的bean对象,所以需要搭配@Qualifier
          @Qualifier(value = "userDaoImpl1")//根据名称进行注入
          private UserDao userDao;
          public void add() {System.out.println("service add ..."); userDao.add();}
      }
      
    • @Resource:可以根据类型注入,可以根据名称注入

      所在:javax.annotation.Resource ,Java扩展包中的注解,jdk11中取消javax扩展。spring官方推荐使用前两个,因为是spirng的

      @Service
      public class UserService {
          //@Resource//根据类型进行注入
          @Resource(name = "userDaoImpl1")//根据名称进行注入
          private UserDao userDao;
          public void add() {System.out.println("service add ...");userDao.add();}
      }
      
    • @Value:注入普通类型属性

      @Value(value = "李白 ")//这就可以将name设置为abc
      private String name;
      
  6. 完全注解开发

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

      @Configuration//该注解表示当前类作为配置类,代替xml配置文件
      //该注解表示开去组件扫描,属性basePackages是一个数组形式。改注解和xml中开启组件扫描是等价的
      @ComponentScan(basePackages = {"com.zhiyou100"})
      public class SpringConfig {
      }
      
    2. 编写测试类

      @Test
      public void test2() {
          //创建注释配置应用程序上下文, 加载配置类,使用配置类中的内容		
          ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
          //获取配置创建对象
          UserService userService = context.getBean("userService", UserService.class);//此类在上面
          System.out.println(userService);
          userService.add();
      }
      

3,Aop

什么是AOP

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

  • 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。

  • 使用登陆例子

    image

AOP(底层原理)

  1. AOP底层使用动态代理

    • 有两种情况的动态代理

      有接口的情况,使用JDK动态代理

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

      image

      没有接口的情况,使用CGLIB动态代理

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

      image

AOP(jdk动态代理)

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

    java.langObject

    ​ java.lang.reflect.Proxy

    (1)调用newProxyInstance方法

    static Object newProxyInstance​(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回指定接口的代理实例,该代理实例将方法调用分派给指定的调用处理程序。 
    //参数:
    loader - 类加载器来定义代理类 
    interfaces - 增强方法所在的类,这个实现的接口,支持多个接口
    h - 实现这个接口InvocationHandler,创建代理对象,写增强的方法
    
  2. 代码:

    1. 创建接口,定义方法

    2. 创建接口实现类,实现方法

    3. 使用Proxy类创建接口代理对象

      public interface UserDao {
          public int add(int a, int b);
          String update(String id);
      }
      public class UserDaoImpl implements UserDao {
          @Override
          public int add(int a, int b) {return a + b;}
          @Override
          public String update(String id) {return id; }
      }
      public class JDKProxy {
          public static void main(String[] args) {
              //创建接口实现类代理对象
              Class[] interfaces = {UserDao.class};
              /*Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
                  @Override
                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                      return null;
                  }
              }); */
              UserDaoProxy proxy = new UserDaoProxy(new UserDaoImpl());
              UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, proxy);
              System.out.println(dao.add(12, 11));
          }
      }
      
      //创建代理对象代码
      class UserDaoProxy implements InvocationHandler {
          //1. 把创建的是谁的代理对象,把谁传递过来
          private Object o;
          //通过有参构造传递
          public UserDaoProxy(Object o) {this.o = o;}
          //增强的逻辑
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //在方法之前
              System.out.println("方法之前执行" + method.getName() + ",传递的参数" + Arrays.toString(args));
              //被增强的方法执行
              Object invoke = method.invoke(o, args);
              //方法之后
              System.out.println("invoke = " + invoke);
              System.out.println("方法之后执行" + o);
              return invoke;
          }
      }
      

AOP(术语)

  1. 连接点

    类里面那些方法可以被增强,这些方法称为连接点

  2. 切入点

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

  3. 通知(增强)

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

    • 通知有多种类型

      前置通知: @Before

      后置通知(返回值通知): @AfterReturning

      环绕(在被增强类的增强方法之前之后进行通知): @Around

      异常: @AfterThrowing

      最终(类似finally): @After

    • 特点:

      前置,当有异常就不执行

      最终,有没有异常都执行

  4. 切面

    时动作,把通知应用到切入点过程。


AOP操作(准备)

  1. Spring框架一般都是基于AspectJ实现AOP操作

    • 什么是AspectJ?

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

  2. 基于AspectJ实现AOP操作

    • 基于xml配置文件实现
    • 基于注解方式实现(使用)
  3. 在项目工程里面引入相关的依赖:

    在原来的jar包上在引入一个spring-aspects-5.3.4.jar / spring-aspects-5.2.6.RELEASE.jar

    和aop其他架包com.springsource.net.sf.cglib-2.2.0.jar,com.springsource.org.aopalliance-1.0.0.jar,com.springsource.org.aspectj.weaver-1.6.8.release.jar

    image

  4. 切入点表达式

    • 作用:对符合切入点表达式的类,会自动生成代理对象。

    • 语法:该语句在spring框架下载的解压包-》docs-》reference-》core.html-》5.4.3 Declaring a Pointcut-》Examples

                  访问修饰符        返回值类型(必填)     包和类                  方法(必填)			  异常
      execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?namepattern(param-pattern) throws-pattern?)
      execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
      
    • 表达式通配符:

      *:匹配所有字符

      ..:一般用于匹配多个包,多个参数

      +:表示类及其子类

    • 支持逻辑运算符:&&,||,!

    • 注意:

      除了返回类型模式,方法名模式和参数模式外,其他项都是可选的

    • 示例:

      1.最全的写法
      拦截返回void,指定类的指定方法,参数必须有两个:int、String
      execution(public void com.sunny.service.Impl.AccountServiceImpl.save(int,java.lang.String))
      2.省略访问修饰符,返回值任意的指定类的save方法,无参数
      execution(*  com.sunny.service.Impl.AccountServiceImpl.save())
      3.拦截com包下所有的类、以及其子包下所有的类的save()方法
      execution(void  com..*.save())  包名与类名或方法名称都可以使用*
      4.拦截save()方法/拦截所有方法
      execution(* save())  //拦截save()
      execution(* *())  //拦截所有方法
      5.不拦截save()方法
      !execution(* save())
        not execution(* save())  注意not前面要有空格
      6.拦截save()方法或者update()方法
      execution(* save()) || execution(* update()))
      execution(* save()) or execution(* update()))
      7.拦截所有方法,参数任意,但必须有参数
      execution(* *(*))
      8.拦截所有方法,参数任意,参数可有可无
      execution(* *(..))
      9.对IOC容器中以Service结尾的类,生成代理对象
      bean(*Service)
      10.最常用
      execution(* com.sunny..*ServiceImpl.*(..))
      表示com.sunny包及其子包下所有的以ServiceImpl结尾的类生成代理对象
      

AOP操作(AspectJ注解)

  1. 创建类,在类里面定义方法

  2. 创建增强类(编写增强逻辑)

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

  1. 进行通知的配置

    • 在spring配置文件中,开启注解扫描(配置文件或者配置类)
    • 使用注解创建UserUserProxy对象
    • 在增强类上面添加注解@Aspect 生成代理对象
    • 在spring配置文件中开启生成代理对象
    • 配置不同类型的通知
      1. 在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
  2. 代码

    <?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">
        <!--1. 使用名称空间context,aop ↑-->
        <!--2. 开启组件扫描-->
        <context:component-scan base-package="com.zhiyou100.aopanno"></context:component-scan>
        <!--3. 开启Aspect生成代理对象。在类中找@Aspect注解,如果有就把这个对象生成代理对象-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
    
    //被增强的类
    @Component //创建对象
    public class User {
        //int i=10/0;//创建异常
        public void add() {System.out.println("add......");}
    }
    //增强的类
    @Component //创建对象
    @Aspect //生成代理对象
    public class UserProxy {
        //让这个方法作为前置通知
        //@Before 注解表示作为前置通知,value可以省略 里面使用切入点表达式
        @Before(value = "execution(* com.zhiyou100.aopanno.User.add(..))")
        public void before() {System.out.println("before......");}
    }
    //测试
        @Test
        public void test1() {
            //加载配置文件,创建类路径Xml应用程序上下文
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            //根据该类.getBean()方法得到该对象
            User user = context.getBean("user", User.class);//前面创建对象注解value可以不写,默认是类首字母小写
            //调用方法
            System.out.println(user);
            user.add();
        }
    /*before......
    add......*/
    

    在原来的增强的类中添加别的通知类型

    //增强的类
    @Component //创建对象
    @Aspect //生成代理对象
    public class UserProxy {
        //让这个方法作为前置通知
        //@Before 注解表示作为前置通知,value可以省略
        @Before("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void before() {
            System.out.println("before......");
        }
        //最终通知
        @After("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void after() {
            System.out.println("after......");
        }
        //后置通知 或者 在方法返回值之后执行
        @AfterReturning("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void afterReturning() {
            System.out.println("AfterReturning......");
        }
        //异常通知
        @AfterThrowing("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void afterThrowing() {
            System.out.println("AfterThrowing......");
        }
        //环绕通知
        @Around("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void around(ProceedingJoinPoint point) throws Throwable {//通过参数可以让被增强方法在里面执行
            System.out.println("环绕之前。。。");
            //被增强的方法执行
            point.proceed();
            System.out.println("环绕之后");
        }
    }
            //没有异常执行
            环绕之前。。。
            before......
            add......
            AfterReturning......
            after......
            环绕之后
            //有异常执行
            环绕之前。。。
            before......
            AfterThrowing......
            after......
    
  3. 两个细节问题

    • 公共切入点抽取

      在增强的类里面

      //相同切入点抽取
      @Pointcut(value = "execution(* com.zhiyou100.aopanno.User.add(..))") //切入点的注解
      public void pointDemo() {}
      //@Before 注解表示作为前置通知,value可以省略
      @Before("pointDemo()")//通过方法在里面调用,方法上面定义了切入点表达式
      public void before() {
          System.out.println("before......");
      }
      
    • 有多个增强类对同一个方法进行增强,设置增强类优先级

    1. 在增强类上面添加注解 @Order(数字类型值) ,数字类型值越小优先级越高。
    @Component //创建对象
    @Aspect   //生成代理对象
    @Order(-10) //设置优先级
    public class PersonProxy {
        //让这个方法作为前置通知
        //@Before 注解表示作为前置通知,value可以省略
        @Before("execution(* com.zhiyou100.aopanno.User.add(..))")
        public void before() {System.out.println("1 Person before......");}
    }
    
  4. 完全注解开发

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

      @Configuration //表示当前类是配置类
      @ComponentScan(basePackages = {"com.zhiyou100"}) //开启组件扫描
      @EnableAspectJAutoProxy(proxyTargetClass = true)//开启Aspect生成代理对象。在类中找@Aspect注解,如果有就把这个对象生成代理对象
      public class ConfigAop {
      }
      

AOP(AspectJ配置文件)

  1. 创建两个类,增强类和被增强类,创建方法
  2. 在spring配置文件中使用名称空间,创建两个类
  3. 在spring配置文件中配置切入点
//被增强的类
public class Book {
    public void buy(){System.out.println("buy ...");}
}
//增强的类
public class BookProxy {
    //前置通知
    public void before() {System.out.println("before ...");}
}
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--使用名称空间aop ↑-->
    <!--创建对象-->
    <bean id="book" class="com.zhiyou100.aopXml.Book"></bean>
    <bean id="bookProxy" class="com.zhiyou100.aopXml.BookProxy"></bean>
    <!--配置切入点:aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.zhiyou100.aopXml.Book.buy(..))"/>
        <!--配置切面:把增强或通知应用到切入点过程-->
        <aop:aspect ref="bookProxy">
            <!--增强作用在具体的方法上-->
            <!--aop:通知类型 method="增强方法" pointcut-ref="应用的切入点"-->
            <aop:before method="before" pointcut-ref="p"></aop:before><!--这句话的意思是把before方法应用在buy上面-->
        </aop:aspect>
    </aop:config>
</beans>
@Test
public void test2() {
    //加载配置文件 创建类路径Xml应用程序上下文
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean.xml");
    Book book = context.getBean("book", Book.class);
    book.buy();
} 

4,JdbcTemplate

4.1 概念和准备

  1. 什么是JdbcTemplate?

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

  2. 准备工作

    • 引入相关的jar包

      Spring框架里的 spring-jdbc-5.3.4.jar(对jdbc进行了封装),spring-tx-5.3.4.jar(针对事务相关操作),spring-orm-5.3.4.jar(整合其他框架需要的)

      image

    • 在spring配置文件配置数据库连接池

    • 配置JdbcTemplate 对象,注入 DataSource

    • 创建service类注入dao对象,创建dao类,在dao注入JdbcTemplate对象

    <?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:component-scan base-package="com.dalao"/>
    
        <!--使用德鲁伊配置连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///test"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
    
        <!--创建JdbcTemplate对象注入 DataSource-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--注入数据源,父类的set方法注入-->
            <!--property name=属性 ref=数据源信息-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    </beans>
    
    public interface BookDao {}
    
    @Repository //创建对象
    public class BookDaoImp implements BookDao {
        //注入JdbcTemplate
        @Autowired //根据类型自动注入进行自动装配
        private JdbcTemplate jdbcTemplate;
        public JdbcTemplate getJdbcTemplate() {return jdbcTemplate;}
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}
    }
    
    @Service //创建对象
    public class BookService {
        //注入dao
        @Autowired //根据类型自动注入进行自动装配
        private BookDao bookDao;
        public BookDao getBookDao() {return bookDao;}
        public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
    }
    

4.2 JdbcTemplate操作数据库

添加

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

  2. 编写service和dao

    • 编写dao进行数据库添加操作

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

      int update(String sql, @Nullable Object... args)
      参数:
          sql:sql语句
          args:可变参数,设置sql语句值
      
//实体类
public class Book {
    private Integer id;
    private String userName;
    private String ustatus;
    //省略set,get,toString,无参,有参方法
}	
//dao层实现类代码实现
@Repository //创建对象
public class BookDaoImp implements BookDao {
    //注入JdbcTemplate
    @Autowired //根据类型自动注入进行自动装配
    private JdbcTemplate jdbcTemplate;
    @Override//重写接口方法
    public void add(Book book) {
        String sql = "insert into t_book (username,ustatus) values(?,?) ";//id自增
        Object[] args = {book.getUserName(), book.getUstatus()};
        int update = jdbcTemplate.update(sql, args);//返回受影响行数
    }
}
//方法测试
    @org.junit.Test
    public void testAdd() {
        //创建对象
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        BookService book = context.getBean("bookService", BookService.class);
        book.addUser(new Book(100, "赵六", "吃饭"));
    }

修改和删除

 @Override
public void delete(int id) {
    String sql = " delete from t_book where user_id =?";
    int update = jdbcTemplate.update(sql, id);
}

@Override
public void modify(Book book) {
    String sql = "update t_book set username=?,ustatus=? where user_id=?";
    Object[] args = {book.getUserName(), book.getUstatus(), book.getId()};
    int update = jdbcTemplate.update(sql, args);
}

查询返回某个值

  1. 查询表里面有多条记录,返回的某个值

  2. 使用JdbcTemplate实现查询返回某个值

    <T> T queryForObject(String sql, Class<T> requiredType)
    参数:
        sql:sql语句
        requiredType:返回类型Class
    
@Override
public Integer selectCount() {
    String sql = "select count(*) from t_book";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    return count;
}

查询返回一个对象

  1. 场景:查询图书详情

  2. JdbcTemplate实现查询返回对象

    <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
    参数
        sql:sql语句
        rowMapper:RowMapper是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
        args:sql语句值
    
@Override
public Book queryOne(Integer id) {
    String sql = "select user_id as id,username as userName,ustatus from t_book where user_id=? ";
    Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
    return book;
}

查询返回多个对象

  1. 场景:查询图书列表分页。。。

  2. 调用JdbcTemplate方法实现查询返回集合

    <T> List<T> query(String sql, RowMapper<T> rowMapper)
    参数:
        sql:sql语句
        rowMapper:RowMapper是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
        args:sql语句值
    
@Override
public List<Book> queryAll() {
    String sql = "select user_id id,username as userName,ustatus from t_book";
    List<Book> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
    return list;
}

批量操作

  1. 批量插座:操作表里面多条记录

  2. JdbcTemplate实现批量添加操作

    int[] batchUpdate(String sql, List<Object[]> batchArgs)
    参数:
        sql:sql语句
        batchArgs:list集合,添加多条记录数据
    
//dao层
@Override
public void batchAdd(List<Object[]> batchArgs) {
    String sql = "insert into t_book (username,ustatus) values(?,?) ";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);//把list集合遍历,把每个数组值执行sql语句进行添加
    System.out.println(Arrays.toString(ints));
}
//测试
@org.junit.Test
    public void testBatchAdd() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    BookService books = context.getBean("bookService", BookService.class);

    ArrayList<Object[]> batchArgs = new ArrayList<>();
    Object[] o1 = {"小华", "b"};
    Object[] o2 = {"小明", "a"};
    batchArgs.add(o1);
    batchArgs.add(o2);

    books.batchAdd(batchArgs);//[1, 1]
}

批量修改

//dao层
@Override
public void batchUpdate(List<Object[]> batchArgs) {
    String sql = "update t_book set username=?,ustatus=? where user_id=?";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
//测试
@org.junit.Test
    public void testBatchUpdate() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    BookService books = context.getBean("bookService", BookService.class);
    ArrayList<Object[]> list = new ArrayList<>();
    Object[] o1 = {"理想", "吃饭", 2};
    Object[] o2 = {"辉煌", "打豆豆", 3};
    list.add(o1);
    list.add(o2);
    books.batchUpdate(list);
}

批量删除

//dao层
@Override
public void batchDelete(List<Object[]> batchArgs) {
    String sql = " delete from t_book where user_id =?";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
//测试
@org.junit.Test
    public void testBatchDelete() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    BookService books = context.getBean("bookService", BookService.class);
    ArrayList<Object[]> list = new ArrayList<>();
    Object[] o1 = {2};
    Object[] o2 = {4};
    list.add(o1);
    list.add(o2);
    books.batchDelete(list);
}

5,事务管理

事务介绍

  1. 什么是事务?
    • 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。
    • 典型场景:银行转账
  2. 事务四个特性(ACID)
    1. 原子性:
    2. 一致性:
    3. 隔离性:
    4. 持久性:

事务操作(搭建事务操作环境)

  • Service:业务操作

    创建转账的方法

    ​ 调用dao两个的方法

  • Dao:数据库操作不写业务

    创建两个方法

    ​ 少钱的方法

    ​ 多钱的方法

  1. 创建数据库表,添加记录

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

    • service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource
  3. 在dao创建两个方法:多钱和少钱的方法,在service创建方法(转账的方法)

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

    • 上面的问题需要使用--事务解决

    • 事务操作过程

    • //        try {
      //        	第一步 开启事务
      //        	第二部 进行业务操作
      //        	第三步 没有异常提交
      //        } catch (Exception e) {
      //            第四步 出现异常回滚
      //            e.printStackTrace();
      //        } finally {
      //        }
      

事务操作(Spring事务管理介绍)

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

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

    有两种方式:编程事务管理try-case-finally,声明式事务管理(使用)

  3. 声明事务管理

    • 基于注解方式(使用)
    • 基于xml配置文件方式
  4. 在Spring进行声明式事务管理,底层使用AOP原理

  5. Spring事务管理api

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

      image

事务操作(注解 声明式事务管理)

  1. 在Spring配置文件配置事务管理器 DataSourceTransactionManager,注入属性dataSource

  2. 在Spring配置文件,开启事务注解

    • 在spring配置文件引入名称空间tx

    • 开启事务注解

    • 在service类上面(或者 service类里面方法上面)添加事务注解

      1. @Transactional,这个注解添加到类上面,也可以添加方法上面
      2. 如果注解在类上面,这个类里面所有的方法都添加事务
      3. 如果注解在方法上面,为这个方法添加事务
      <?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"
             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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
      
          <!--使用名称空间 context,tx-->
          <!--开启组件扫描-->
          <context:component-scan base-package="com.dalao"/>
      
          <!--使用德鲁伊配置连接池-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql:///test"/>
              <property name="username" value="root"/>
              <property name="password" value="root"/>
          </bean>
      
          <!--创建JdbcTemplate对象注入 DataSource-->
          <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
              <!--注入数据源,父类的set方法注入-->
              <!--property name=属性 ref=数据源信息-->
              <property name="dataSource" ref="dataSource"></property>
          </bean>
      
          <!--创建事务管理-->
          <bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <!--注入数据源dataSource  使用set方法-->
              <property name="dataSource" ref="dataSource"/>
          </bean>
           <!--开启事务注解 transaction-manager=指定的事务管理 -->
          <tx:annotation-driven transaction-manager="transaction"></tx:annotation-driven>
      </beans>
      
      public interface AccountDao {
          void modifyAccount(Account account);
      }
      @Repository//创建对象
      public class AccountDaoImpl implements AccountDao {
          @Autowired //根据类型自动装配
          private JdbcTemplate jdbcTemplate;
          @Override
          public void modifyAccount(Account account) {
              String sql = "update t_account set username=?,money=? where id=?";
              int update = jdbcTemplate.update(sql, account.getUsername(), account.getMoney(), account.getId());
              System.out.println(update);
          }
      }
      @Service //创建对象
      @Transactional //添加事务注解
      public class AccountService {
          //注入dao
          @Autowired //根据类型自动装配
          private AccountDao accountDao;
          public void modifyAccount() {
      //        try {
      //        第一步 开启事务
      //        第二部 进行业务操作
              //转账
              accountDao.modifyAccount(new Account(1, "lucy", 800));
              //模拟异常
              int a = 1 / 0;
              accountDao.modifyAccount(new Account(2, "mary", 1200));
      //            第三步 没有异常提交
      //        } catch (Exception e) {
      //            第四步 出现异常回滚
      //            e.printStackTrace();
      //        } finally {
      //        }
          }
      }
      //测试
          @org.junit.Test
          public void test1() {
              //加载spring配置文件
              ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
              //获取配置创建对象 默认v是类名小写
              AccountService service = context.getBean("accountService", AccountService.class);
              //service.modifyAccount();
              service.queryAll().forEach(System.out::println);
          }
      

事务操作(声明式事务管理参数配置)

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

    image

  2. propagation:事务传播行为

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

      image

      image

      //添加事务注解 可以在类上,或者类中方法外
      @Transactional(propagation = Propagation.REQUIRED) //默认不写就是 REQUIRED
      
  3. isolation:事务隔离级别

    1. 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题

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

    3. 脏读:一个未提交事务读取到另一个未提交事务的数据

      image

    4. 不可重复读:一个未提交事务读取到了另一个提交事务修改的数据

      image

    5. 幻读:一个未提交事务读取到了另一个提交事务添加的数据

    6. 通过设置事务隔离级别,解决读问题

      image

      @Service //创建对象
      //添加事务注解 REPEATABLE_READ -->mysql默认隔离级别
      @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
      
  4. timeout:超时时间

    • 事务需要在一定时间内进行提交,如果不提交就进行回滚
    • 默认值是-1,设置使时间以秒单位进行计算
  5. readOnly:是否只读

    1. 读:查询操作。写:添加修改删除操作
    2. readOnly默认值false,表示可以查询,可以添加修改删除操作
    3. 设置readOnly值为true,表示只能查询
  6. rollbackFor:回滚

    • 设置出现那些异常进行事务回滚
  7. noRollbackFor:不回滚

    • 设置出现那些异常不进行事务回滚

事务操作(xml 声明式事务管理)

  1. 在spring配置文件中配置

    第一步配置事务管理器

    第二部配置通知

    第三步配置切面和切入点

    <?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 http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--使用名称空间 context,tx,aop-->
        <!--开启组件扫描-->
        <context:component-scan base-package="com.dalao"/>
    
        <!--使用德鲁伊配置连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///test"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
    
        <!--创建JdbcTemplate对象注入DataSource(连接池配置信息)-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--注入数据源,父类的set方法注入-->
            <!--property name=属性 ref=连接池数据源信息-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!--1. 创建事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源dataSource  使用set方法-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <!--2. 配置(事务)通知-->
        <tx:advice id="txadvice">
            <!--配置事务相关的参数-->
            <tx:attributes>
                <!--name属性:指定哪种规则的方法上面添加事务。后面可跟其他事务参数
                第一种写法:方法名字
                第二部写法:方法名字* (只要是方法名字开头添加事务操作)
                -->
                <tx:method name="modifyAccount" propagation="REQUIRED"/>
            </tx:attributes>
        </tx:advice>
        <!--3. 配置切入点和切面-->
        <aop:config>
            <!--配置切入点 使用表达式-->
            <aop:pointcut id="pt" expression="execution(* com.dalao.service.AccountService.*(..))"/>
            <!--配置切面-->
            <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
        </aop:config>
    </beans>
    
    @org.junit.Test
    public void test2() {
        //加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        //获取配置创建对象 默认v是类名小写
        AccountService service = context.getBean("accountService", AccountService.class);
        //service.modifyAccount();
        service.queryAll().forEach(System.out::println);
    }
    

事务操作(完全注解声明式事务管理)

  1. 创建配置类,使用配置类代替xml配置文件
@Configuration  //表示当前类式配置类
@ComponentScan(basePackages = "com.dalao")//开启组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource source = new DruidDataSource();
        source.setDriverClassName("com.mysql.jdbc.Driver");
        source.setUrl("jdbc:mysql:///test");
        source.setUsername("root");
        source.setPassword("root");
        return source;
    }
    //创建JdbcTemplate对下个你
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource source) {//ioc容器中已经存在了数据源,根据它的类型找到它的对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(source);
        return jdbcTemplate;
    }
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource source) {//从ioc容器中找到,然后传入
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        //注入datasource
        transactionManager.setDataSource(source);
        return transactionManager;
    }
}
    @org.junit.Test
    public void test3() {
        //创建注释配置应用程序上下文
        ApplicationContext context =
                new AnnotationConfigApplicationContext(TxConfig.class);
        //获取配置创建对象 默认v是类名小写
        AccountService service = context.getBean("accountService", AccountService.class);
//        service.modifyAccount();
        service.queryAll().forEach(System.out::println);
    }

6,Spring5新特性

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

  2. Spring 5.0框架自带了通用的日志封装。

    • Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2, 如果还想使用Log4j需要spring降到4的版本

    • Spring5框架整合Log4j2(http://logging.apache.org/log4j/2.x/download.html)

      第一步引入jar包

      image

      第二部创建Log4j2.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
      <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
      <configuration status="INFO">
          <!--先定义所有的appender-->
          <appenders>
              <!--输出日志信息到控制台-->
              <console name="Console" target="SYSTEM_OUT">
                  <!--控制日志输出的格式-->
                  <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
              </console>
          </appenders>
          <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
          <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
          <loggers>
              <root level="info">
                  <appender-ref ref="Console"/> 
              </root>
          </loggers>
      </configuration>
      

      手动输出日志

      class UserLog {
          //都是 org.slf4j 包下的
          private static final Logger LOGGER= LoggerFactory.getLogger(UserLog.class);
          public static void main(String[] args) {
              LOGGER.info("hello 1");
              LOGGER.warn("hello 2"); }
      }
      
  3. spring5支持@Nullable注解

    • @Nullable:可以使用在方法,属性,参数上面,表示方法返回值可以为空,属性值可以为空,参数值可以为空。
  4. spring5支持函数式风格GenericApplicationContext / AnnotationConfigApplicationContext

    @org.junit.Test
    public void test4() {
        //创建GenericApplicationContext 对象
        GenericApplicationContext context = new GenericApplicationContext();
        //调用context的方法对象注册
        context.refresh();//把里面内容清空,然后注册
        //context.registerBean(User.class, () -> new User());
        context.registerBean("user1",User.class, User::new);//简化上面
        //3. 获取在spring注册的对象
        //User user = (User) context.getBean("com.dalao.bean.User");
        User user = (User) context.getBean("user1");
        System.out.println(user);
    }
    
  5. Spring支持JUnit 5's Juptier编程和拓展模块在Spring TestContext框架

    • 整合JUnit4

      第一步 引入Spring相关针对测试依赖:spring-test-5.3.4.jar,JUnit4的jar包

      第二步 创建测试类,使用注解完成

      @RunWith(SpringJUnit4ClassRunner.class)//里面设置相关的单元测试版本
      @ContextConfiguration("classpath:bean1.xml")//加载配置文件
      public class TestJ {
          @Autowired //自动注入属性
          private AccountService accountService;
          @Test
          public void test1() {accountService.queryAll();}
      }
      
    • 整合JUnit5 导入junit的jar包

    第一步 导入junit5的jar包

    第二步 创建测试类,使用注解完成

    @ExtendWith(SpringExtension.class) //注解引用
    @ContextConfiguration("classpath:bean1.xml")//加载配置文件
    public class TestJ5 {
        @Autowired
        private AccountService accountService;
        @Test
        public void test1() {accountService.queryAll();}
    }
    
    • 使用 @SpringJunitConfig:一个复合注解,来代替上面两个注解完成整合

      @SpringJUnitConfig(locations = "classpath:bean1.xml")//
      public class TestJ5 {
          @Autowired
          private AccountService accountService;
          @Test
          public void test1() {accountService.queryAll();}
      }
      

Spring5新模块--WebFlux

  1. spring webflux介绍

    • (web --》 WebSocket,webMVC,Web,WebFlux)

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

    • 使用传统web框架,比如SpringMVC,这些基于 Servvlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是居于 Reactor(响应式编程) 的相关api实现的。

    • 什么是异步非阻塞:针对对象不一样。

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

      阻塞,非阻塞针:针对被调用者。被调用者收到请求之后, 做完请求任务之后才给出反馈就是阻塞;收到请求之后马上给出反馈然后再去做其他事情就是非阻塞。(简单来说阻塞需要等待,非阻塞不需要等待)

    • Webflux特点

      第一非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以JReactor为基础实现响应式编程。

      第二函数式编程:Spring5框架基于java8,Webflux使用Java8函数式编程方式实现路由请求。

    • 比较SpringMVC

      image

      第一,两个框架都可以使用注解方式,都运行在Tomcat等容器中。

      第二,SpringMVC采用命令式编程,Webflux采用异步相应式编程。

      一般项目使用mvc就可以,如果使用远程服务调用不妨使用webflux。

  2. 响应式编程

    • 什么是响应式编程:

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

    • Java8及其之前的版本。提供了观察者模式两个类 ObserverObservable,在java9及之后的版本取代了这两个类(Flow )。

      //创建spring initializer项目
      public class ObservableDemo extends Observable {
          public static void main(String[] args) {
              //创建对象
              ObservableDemo demo = new ObservableDemo();
              //添加观察者模式
              demo.addObserver((o, arg) -> {
                  System.out.println("发生了变化");
              });
              demo.addObserver((o, arg) -> {
                  System.out.println("手动被观察者通知,准备改变");
              });
              demo.setChanged();//监控数据是否发生变化
              demo.notifyObservers();//通知
          }
      }
      
  3. 响应式编程( Reactor实现)(有订阅才会有输出)

    • 响应式编程操作中,Reactor 是满足 Reactive规范框架。

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

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

    • 代码演示Flux 和 Mono

      第一步引入依赖

      <dependency>
          <groupId>io.projectreactor</groupId>
          <artifactId>reactor-bom</artifactId>
          <version>2020.0.5</version>
      </dependency>
      

      第二部编写代码(有订阅才会有输出)

      public static void main(String[] args) {
          //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);
          //错误信号
          //Flux.error(Throwable error);
      }
      
    • 调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的。

      Flux.just(1, 2, 3, 4).subscribe(System.out::println) ;
      Mono.just(1).subscribe(System.out::println);
      
    • 操作符:对数据流进行一道道操作,成为操作符,比如工厂流水线。

      • 第一 map 元素映射为新元素

        image

      • 第二 flatMap 元素映射为流

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

        image

  4. WebFlux执行流程和核心API

    SpringWebflux基于 Reactor,默认使用容器时Netty,Netty是高性能的NIO框架,异步非阻塞的框架。

    • Netty

      • BIO(阻塞状态)

        image

      • NIO(非阻塞)

        image

    • SpringjWebflux执行过程和SpringMVC相似的

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

      接口WebHandler有一个方法

      修改导入

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-webflux</artifactId>
      </dependency>
      
      public interface WebHandler {
          Mono<Void> handle(ServerWebExchange var1);
      }
      public class DispatcherHandler implements WebHandler, PreFlightRequestHandler, ApplicationContextAware {
          public Mono<Void> handle(ServerWebExchange exchange) {//放http请求响应信息
              if (this.handlerMappings == null) {
                  return this.createNotFoundError();//如果为空创建一个notfound一个错误
              } 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:响应结果进行处理

    • SpringWebflux实现函数式编程,两个接口:RouterFunction(路由处理),HandlerFunction(函数处理)

    • WebHandler的体系

      image

SpringWebFlux(基于注解编程模型)

SpringWebflux实现方式有两种:注解编程模型,函数式编程模型。

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

  • 第一步 创建SpringBoot工程,引入webfux 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    
  • 第二步 配置启动端口号

    image

  • 第三步 创建包和相关类

    image

    //创建实体类
    public class User {
        private String name;
        private String gender;
        private Integer age;
        //省略set,get,toString,无参构造,有参构造方法
    }
    
    //用户操作接口
    public interface UserService {
        //根据id查询用户
        Mono<User> getUserById(int id);
        //查询所有用户
        Flux<User> getAllUser();
        //添加用户
        Mono<Void> saveUserInfo(Mono<User> user);
    }
    
    //操作接口实现类
    @Repository //创建对象并交给spring管理
    public class UserServiceImpl implements UserService {
        //创建map集合存储数据,充当数据库
        private final Map<Integer, User> map = new HashMap<>();
        public UserServiceImpl() {
            this.map.put(1, new User("张三", "男", 18));
        }
        @Override
        public Mono<User> getUserById(int id) {
            return Mono.justOrEmpty(this.map.get(id));
        }
        @Override
        public Flux<User> getAllUser() {
            return Flux.fromIterable(this.map.values());
        }
        @Override
        public Mono<Void> saveUserInfo(Mono<User> userMono) {
            //doOnNext取值(遍历) ; person是起名字
            return userMono.doOnNext(person -> {
                //向map集合中放值
                int id = map.size() + 1;
                map.put(id, person);
            }).thenEmpty(Mono.empty());//thenEmpty,控制处理。Mono.empty操作之后把里面内容清空。
        }
    }
    

    创建controller

    @RestController //交给Spring管理,并返回相关数据
    public class UserController {
        //注入属性 UserService
        @Autowired
        private UserService userService;
        //id查询
        @GetMapping("/user/{id}")//查询一般是get请求
        public Mono<User> getUserId(@PathVariable int id) {//@PathVariable:获取路径中的id
            return userService.getUserById(id);
        }
        //查询所有
        @GetMapping("/user")
        public Flux<User> getUsers() {
            return userService.getAllUser();
        }
        //添加
        @PostMapping("/saveuser")
        public Mono<Void> saveUser(@RequestBody User user) {//@RequestBody:使用json形式传递对象
            //变成mono形式
            Mono<User> userMono = Mono.just(user);
            return userService.saveUserInfo(userMono);
        }
    }
    

    启动在main方法中启动,浏览器中打开:localhost:8081/user/1

  • 说明: SpringMVC方式实现,同步阻塞的方式,基于SpringMVC+Servlet+Tomcat

    SpringWebflux方式实现,异步非阻塞方式,基于SpringWebflux+Reactor+Netty

SpringWebFlux(基于函数式编程模型)

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

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

  3. SpringWebflux请求和响应不再是Servlet Request 和 Servlet Response,而是ServiceRequest 和 ServerResponse。

  4. 第一步把注解编程模型工程复制一份

  5. 第二部创建Handler(具体方法)

    public class UserHandler {
        private final UserService userService;
        public UserHandler(UserService userService) {
            this.userService = userService;
        }
        //根据id查询
        public Mono<ServerResponse> getUserId(ServerRequest request) {
            //获取id值
            int userId = new Integer(request.pathVariable("id"));//得到路径中的值
            //空值处理
            Mono<ServerResponse> notFound = ServerResponse.notFound().build();
            //调用service方法的得到数据
            Mono<User> userMono = this.userService.getUserById(userId);
            //把userMono进行转换成流返回
            //使用Reactor操作符FlatMap
            return userMono.flatMap(person ->
                    ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                            .body(fromObject(person))) //这种方法和下面那个差不多
                    .switchIfEmpty(notFound); //判断为空返回
        }
        //查询所有
        public Mono<ServerResponse> getUserAll(ServerRequest request) {
            //调用service得到结果
            Flux<User> allUser = this.userService.getAllUser();
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(allUser, User.class);
        }
        //添加
        public Mono<ServerResponse> saveUser(ServerRequest request) {
            //得到user对象
            Mono<User> userMono = request.bodyToMono(User.class);//得到请求中的值转成Mono的形式
            return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));//build执行
        }
    }
    
  6. 第三步初始化服务器,编写Router

    public class Server {//创建服务器
        //1. 创建Router路由
        public RouterFunction<ServerResponse> routerFunction() {
            //创建hanler对象
            UserService userService = new UserServiceImpl();
            UserHandler handler = new UserHandler(userService);
            //设置路由
            return RouterFunctions.route(
                    //请求的路径                      接收类型的数据              掉handler的方法
                    GET("/users/{id}").and(accept(APPLICATION_JSON)), handler::getUserId)
                    .andRoute(GET("/users").and(accept(APPLICATION_JSON)), handler::getUserAll);
        }
        //2. 创建服务器完成适配
        public void createReactorServer() {
            //2.1 路由和handler适配
            //调用路由方法,获取路由对象
            RouterFunction<ServerResponse> route = routerFunction();
            //创建 HttpHandler ->http请求和存储请求相关的信息
            HttpHandler httpHandler = toHttpHandler(route);
            //适配
            ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
            //2.2 创建服务器
            //创建http服务
            HttpServer httpServer = HttpServer.create();
            httpServer.handle(adapter).bindNow();//bindNow:现在进行构建
        }
        //测试调用
        public static void main(String[] args) throws IOException {
            Server server = new Server();
            server.createReactorServer();//服务启动
            System.out.println("enter to exit");
            System.in.read();
        }
    }
    
  7. 使用WebClient调用

    public class Client {
        public static void main(String[] args) {
            //WebClient.create("指定需要调的那个服务器的地址")
            WebClient webClient = WebClient.create("http:127.0.0.1:6436");
            //根据id查询
            String id = "1";
            //uri(路径,参数)                        accept(指定接收的类型)                 retrieve初始化操作
            User user = webClient.get().uri("/users/id{}", id).accept(MediaType.APPLICATION_JSON).retrieve().
                    bodyToMono(User.class).block();//block()执行
            //bodyToMono(returnBean.class):得到数据
            System.out.println(user.getName());
    
            //查询所有
            Flux<User> userFlux = webClient.get().uri("/users")
                    .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
            //输出
            //userFlux.map(stu -> stu.getName()).buffer().doOnNext(System.out::println);
            userFlux.map(User::getName).buffer().doOnNext(System.out::println).blockFirst();//blockFirst订阅
        }
    }
    
原文地址:https://www.cnblogs.com/zk2020/p/15351703.html