Spring5框架概述

  • Spring是轻量级的开源的JavaEE框架

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

  • Spring有两个核心部分:IOCAOP

    1. IOC:控制反转,把创建对象过程交给Spring进行管理
    2. AOP:面向切面,不修改源代码进行功能增强
  • Spring特点

    1. 方便解耦,简化开发(IOC可以降低耦合性)
    2. AOP编程支持
    3. 方便程序测试(Spring对Junit4支持,可以通过注解方便的测试Spring程序)
    4. 方便和其他框架进行整合
    5. 方便进行事务操作
    6. 降低API开发难度
  • Spring5系统结构

    img

IOC

概念和原理

什么是IOC

  1. 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
  2. 使用IOC目的:为了降低耦合度

IOC底层原理

xml解析工厂模式反射

  • 原始方式与工厂模式的对比

    img

  • 画图讲解IOC底层原理

    img

IOC之BeanFactory接口

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

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

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

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

    • ApplicationContextBeanFactory接口的子接口,提供更多更强大的功能,一般由开发人人员进行调用

      加载配置文件时会把在配置文件对象进行创建

  3. ApplicationContext接口有实现类

    img

    1. FileSystemXmlApplicationContext

      configLocation:要写上配置文件在系统盘(某个盘)里的路径

    2. ClassPathXmlApplicationContext

      configLocation:要写上类路径

IOC操作Bean管理

概念

  1. 什么是Bean管理

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

  2. Bean管理操作的方式:

    1. 基于xml配置文件方式实现
    2. 基于注释方式实现

基于xml方式

  1. 创建对象

    <!--配置User对象创建-->
    <bean id="user" class="com.atguigu.spring.User"></bean>
    
    1. 在Spring配置文件中,使用bean标签标签里面添加对应属性,就可以实现对象创建
    2. bean标签常用的属性
      • id属性:唯一标识
      • class属性:类全路径(包类路径)
    3. 创建对象时候,默认执行无参构造方法
  2. 注入属性

    • DI:依赖注入(Dependency Injection),就是注入属性

      DI是一种设计模式,用于管理对象之间的依赖关系,它将创建和管理对象的责任转移给了第三方的容器或框架,从而降低了系统的耦合度。

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

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

         /**
           * 演示set方法注入属性
           */
          public class Book {
              //创建属性
              private String bname;
              private String bauthor;
          
              //创建属性对应set方法
              public void setBauthor(String bauthor) {
                  this.bauthor = bauthor;
              }
              public void setBname(String bname) {
                  this.bname = bname;
              }
          }
        
      2. Spring配置文件配置对象创建,配置属性注入

        <!--set方法注入属性-->
        <bean id="book" class="com.atguigu.spring5.Book">
            <!--使用property完成属性注入
                name:类里面属性名称
                value:向属性注入的值
        	-->
            <property name="bname" value="易筋经"></property>
            <property name="bauthor" value="达摩老祖"></property>
        </bean>
        
    2. 第二种注入方式:使用有参构造进行注入

      1. 创建类,定义属性,创建属性对应的有参构造方法

        /**
         *使用有参数构造注入
         */
        public class Orders {
            //属性
            private String oname;
            private String address;
            //有参构造
            public Orders(String oname, String address) {
                this.oname = oname;
                this.address = address;
            }
        }
        
      2. 在spring配置文件中进行配置

        <!--用有参构造注入属性-->
        <bean id="orders" class="com.atguigu.spring5.Orders">
                <constructor-arg name="oname" value="电脑"></constructor-arg>
                <constructor-arg name="address"value="China"></constructor-arg>
        </bean>
        
    3. p名称空间注入(了解)

      使用p名称空间注入,可以简化基于xml配置方式

      1. 添加p名称空间在配置文件中

        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               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"></beans>
        
        
      2. 进行属性注入,在bean标签里面进行操作

         <!--set方法注入属性-->
        <bean id="book" class="com.atguigu.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏">
        </bean>
        

xml注入其他类型属性

  • 字面量

    1. null值

      <!--null值-->
      <property name="address">
          <null/>
      </property>
      
      
    2. 属性值包含特殊符号

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

    1. 创建两个类service类dao类

      package com.atguigu.spring5.dao;
      public interface UserDao{
      	public void update();
      }
      
      package com.atguigu.spring5.dao;
      public class UserDaoImpl implements UserDao{
          @Override
          public void update(){
              System.out.println("dao update...........");
          }
      }
      
    2. service调用dao里面的方法

      package com.atguigu.spring5.service;
      public class UserService {
          //创建UserDao类型属性,生成set方法
          private UserDao userDao;
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          public void add(){
              System.out.println("service add.................");
              userDao.update();
      
      
          }
      }
      
    3. spring配置文件中进行配置

      <!--1 service和dao对象的创建-->
      <bean id="userService" class="com.atguigu.spring5.service.UserService">
      	<!--注入userDao对象
              name属性值:类里面属性名称
              ref属性:创建userDao对象的bean标签id值
      	-->
              <property name="userDao" ref="userDaoImpl"></property>
      </bean>
      <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"/></bean>
      
  • 注入属性——内部bean

    1. 一对多关系

    比如:部门和员工,一个部门有多个员工,一个员工属于一个部门。

    1. 在实体类之间表示一对多关系:员工使用对象类型属性进行表示所属部门

      //部门类
      public static Dept{
          private String dname;
          public void setDname(String dname){
              this.dname=dnamel
          }
      }
      
      //员工类
      public static Emp{
          private String name;
          private String gender;
          private Dept dept;  //用对象表示员工所属的部门
          
          public void setName(String name){
              this.name = name;
          }
          public void setGender(String gender){
              this.gender = gender;
          }
      }
      
    2. Spring配置文件

      <!--内部bean-->
      <bean id="Emp" class="com.gtguigu.spring5.bean.Emp">
          <!--设置两个普通属性-->
          <property name="ename" value="lucy"></property>
          <property name="gender" value="女"></property>
          <!--设置对象类型属性-->
          <property name="dept">
              <bean id="dept" class="com.atguigu.spring5.bean.Dept">
                  <property name="dname" value="安保部"></property>
              </bean>
          </property>
      </bean>
      
  • 注入属性——级联赋值

    1. 第一种写法

      <!--级联赋值-->
      <bean id="emp" class="com.atguigu.spring5.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.atguigu.spring5.bean.Dept">
          <property name="dname" value="财务部"></property>
      </bean>
      
    2. 第二种写法

      //使用对象形式表示员工属于某一个部门
      private Dept dept;
      //生成dept的get方法
      public Dept getDept(){
          return dept;
      }
      public void setDept(Dept dept){
          this.dept = dept;
      }
      
      <!--级联赋值-->
      <bean id="emp" class="com.atguigu.spring5.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.atguigu.spring5.bean.Dept">
          <property name="dname" value="财务部"></property>
      </bean>
      

xml注入集合属性

  1. 注入数组类型属性
  2. 注入List集合类型属性
  3. 注入Map集合类型属性
  4. 注入Set集合类型属性

第一步,创建类,定义数组ListMapSet类型属性,生成对应set方法

public class Stu {
    //1 数组类型的属性
    private String[] courses;
    //2 List集合类型
    private List<String> list;
	//3 Map集合类型
    private Map<String,String> maps;
	//4 set集合类型
    private Set<String> sets;
    
    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }
}

第二步,在Spring配置文件进行配置

<!--1 集合类型属性的注入-->
<bean id="stu" class="com.atguigu.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="maps">
        <map>
            <entry key="JAVA" value="java" ></entry>
            <entry key="PHP" value="php"></entry>
        </map>
    </property>
    <!--set集合注入-->
    <property name="sets">
        <set>
            <value>MySQL</value>
            <value>redis</value>
        </set>
    </property>
</bean>
  1. 在集合里面设置对象类型值

    <!--创建多个course对象-->
    <bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
        <property name="cname" value="Spring5框架"></property>
    </bean>
    <bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
        <property name="cname" value="Mybatis框架"></property>
    </bean>
    <!--注入list集合类型,值是对象-->
    <property name="courseList">
        <list>
            <ref bean="course1"></ref>
            <ref bean="course2"></ref>
        </list>
    </property>
    
  2. 把集合注入部分提取出来

    1. Spring配置文件中引入名称空间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/util/spring-beans.xsd
                                  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
              <!--把集合注入部分提取出来-->
      </beans>
      
    2. 使用util标签完成list集合注入提取

      <!--1 把提取list集合类型属性注入-->
      <util:list id="bookList">
          <value>易筋经</value>
          <value>九阳神功</value>
          <value>九阴真经</value>
      </util:list>
      
      <!--2 提取list集合类型属性注入使用-->
      <bean id="book" class="com.atguigu.spring5.collectiontype.Book">
          <property name="list" ref="bookList"></property>
      </bean>
      

FactoryBean

  1. Spring有两种类型bean,一种普通bean,另一种工厂bean(FactoryBean)
  2. 普通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("abc");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

第三步,配置spring配置文件

<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>

测试类:

public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Course course = context.getBean("mybean", Course.class);
    System.out.println(course);
}

bean作用域

  1. 在Spring里面,设置创建bean实例可以是单实例,也可以是多实例

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

    @Test
    public void testCollection2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
        Book book =context.getBean("book", Book.class);
        Book book1 =context.getBean("book", Book.class);
        System.out.println(book);  //com.atguigu.sqpring5.collectiontype.Book@5d11346a
        System.out.println(book1);  //com.atguigu.sqpring5.collectiontype.Book@5d11346a
    }
    

    以上例子的两个输出是相同的,说明bean默认是单实例对象。

  3. 如何设置单实例or多实例

    1. 在Spring配置文件bean标签里面有属性scope,用于设置单实例还是多实例
    2. scope属性值
      • singleton:表示单实例对象(默认值)
      • prototype:表示多实例对象
    <!--2 提取list集合类型属性注入使用-->
    <bean id="myBean" class="com.frx01.spring5.factorybean.MyBean" scope="prototype">
        <property name="list" ref="bookList"></property>
    </bean>
    

    再次测试:

    @Test
    public void testCollection2(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
        Book book =context.getBean("book", Book.class);
        Book book1 =context.getBean("book", Book.class);
        System.out.println(book);  //com.atguigu.sqpring5.collectiontype.Book@5d11346a
        System.out.println(book1);  //com.atguigu.sqpring5.collectiontype.Book@7a36aefa
    }
    

    以上例子的两个输出不同,说明此时bean是多实例对象。

  • singletonprototype区别
    1. singleton表示单实例,prototype表示多实例
    2. 设置scope值singleton时候,加载spring配置文件就会创建一个单实例对象
    3. 设置scope值是prototype时候,不是在加载spring配置文件时候创建对象,在调用getBean方法时候创建多实例对象

bean生命周期

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

  • bean生命周期

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

    public class Orders {
    	//无参数构造
        public Orders() {
            System.out.println("第一步 执行无参构造创建bean实例");
        }
    
        //创建set方法设置属性的值和对其他bean的引用
        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("第五步 执行销毁方法");
        }
    
    }
    

    在Spring配置文件中使用init-method指定初始化方法,用destroy-method指定销毁方法

    <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="手机"></property>
    </bean>
    

    测试:

    @Test
    public void testCollection4() {
    //        ApplicationContext context =
    //                new ClassPathXmlApplicationContext("bean4.xml");
        ClassPathXmlApplicationContext context =
            new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("第四步 获取创建bean实例对象");
        System.out.println(orders);
    
        //手动让bean实例销毁
        context.close();
    }
    

    输出:

    第一步 执行无参构造创建bean实例

    第二步 调用set方法设置属性的值

    第三步 执行初始化方法

    第四步 获取创建bean实例对象

    com.atguigu.spring5.bean.Order@192d3247

    第五步 执行销毁方法

  • bean的后置处理器,bean生命周期有七步

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

    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;
          }
      }
      
      <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
          <property name="oname" value="手机"></property>
      </bean>
      <!--    配置后置处理器-->
      <bean id="MyBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
      </beans>
      

      测试:

      @Test
      public void testCollection4() {
      //        ApplicationContext context =
      //                new ClassPathXmlApplicationContext("bean4.xml");
          ClassPathXmlApplicationContext context =
              new ClassPathXmlApplicationContext("bean4.xml");
          Orders orders = context.getBean("orders", Orders.class);
          System.out.println("第四步 获取创建bean实例对象");
          System.out.println(orders);
      
          //手动让bean实例销毁
          context.close();
      }
      

      输出:

      第一步 执行无参构造创建bean实例

      第二步 调用set方法设置属性的值

      在初始化之前执行的方法

      第三步 执行初始化方法

      在初始化之后执行的方法

      第四步 获取创建bean实例对象

      com.atguigu.spring5.bean.Order@192d3247

      第五步 执行销毁方法

xml自动装配

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

  • 实现自动装配:bean标签属性autowire,配置自动装配
    autowire属性常用两个值:
    byName根据属性名称注入,注入值bean的id值和类属性名称一样
    byType根据属性类型注入

  • 演示自动装配过程

    1. 根据属性名称自动注入

      <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName">
      	<!--<property name="dept" ref="dept"></property>-->
      </bean>
      <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
      
    2. 根据属性类型自动注入

      <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType">
      	<!--<property name="dept" ref="dept"></property>-->
      </bean>
      <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
      

    外部属性文件

    1. 直接配置数据库信息

      1. 配置德鲁伊连接池

      2. 引入德鲁伊连接池依赖jar包(druid)

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

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

      2. 把外部properties属性文件引入到spring配置文件

        1. 引入context名称空间

          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:p="http://www.springframework.org/schema/p"
                 xmlns:util="http://www.springframework.org/schema/util"
                 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/util http://www.springframework.org/schema/util/spring-util.xsd
                                      http://www.springframework.org/schema/context  http://www.springframework.org/schema/util/spring-util.context.xsd">
          
        2. 在Spring配置文件使用标签引入外部属性文件

          <!--引入外部属性文件-->
          <context:property-placeholder location="classpath:jdbc.properties"/>
          <!--配置连接池-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="${prop.driverClass}"></property>
              <property name="url" value="${prop.url}"></property>
              <property name="username" value="${prop.userName}"></property>
              <property name="password" value="${prop.passwd}"></property>
          </bean>
          

基于注解方式

  • 什么是注解

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

    1. @Component
    2. @Service
    3. @Controller
    4. @Repository

    以上四个注解功能是一样的,都可以用来创建bean实例

  • 基于注解方式实现对象创建

    1. 引入依赖

      img

    2. 开启组件扫描

      <!--开启组件扫描
              1 如果扫描多个包 使用逗号隔开
              2 扫描包上层目录
      -->
      <context:component-scan base-package="com.atguigu"></context:component-scan>
      
      
    3. 创建类,在类上面添加创建对象注解

      //在注解里面value属性值可以省略不写
      //默认值是类名称,首字母小写
      //UserService --> userService
      @Component(value = "userService")  //<bean id="userService" class=".."/>
      public class UserService {
          public void add(){
              System.out.println("service add......");
          }
      }
      
    4. 开启组件扫描细节配置

      <!--示例1
              use-default-filters="false 表示现在不使用默认filter,不扫描全部,自己配置filter
              context:include-filler,设置扫描哪些内容
      -->
      <context:component-scan base-package="com.atguigu" use-default-filters="false">
          <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
      <!--示例2
              下面配置扫描包所有内容
              context:exclude-filter:设置哪些内容不进行扫描
      -->
      <context:component-scan base-package="com.atguigu">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
      
    5. 基于注解方式实现属性注入

      1. @AutoWired:根据属性类型自动装配

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

        第二步,在service注入dao对象在service类添加dao类型属性在属性上面使用注解

        @Service
        public class UserService {
            //定义dao类型的属性
            //不需要添加set方法
            //添加注入属性注解
            @Autowired
            private UserDao userDao;
        
            public void add(){
                System.out.println("service add......");
                userDao.add();
            }
        }
        
      2. @Qualifier根据属性名称注入

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

        //定义dao类型的属性
        //不需要添加set方法
        //添加注入属性注解
        @Autowired  //根据类型进行注入
        @Qualifier(value = "userDaoImpl1")  //根据名称注入
        private UserDao userDao;
        
      3. @Resource:可以根据类型注入,可以根据名称注入

        //	  @Resource  //根据类型进行注入
        	@Resource(name="userDaoImpl1") //根据名称进行注入
            private UserDao userDao;
        
      4. @Value:注入普通类型属性

        @Value(value = "abc")
        private String name;
        
    6. 完全注释开发

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

        @Configuration //作为配置类,替代xml配置文件
        @ComponentScan(basePackages = "com.atguigu")
        public class SpringConfig {
        }
        
      2. 编写测试类

        @Test
        public void testService2(){
            //加载配置类
            ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            UserService userService = context.getBean("userService", UserService.class);
            System.out.println(userService);
            userService.add();
        }
        

AOP

概念

什么是AOP?

  1. 面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发的效率。
  2. 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  3. 使用登录例子说明AOPimg

底层原理

AOP底层使用动态代理

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

JDK动态代理

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

    Proxy类是属于java.lang包中的

    • 调用newProxyInstance方法

      static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)

      返回指定接口的代理类的实例,该接口将调用分派给指定的调用处理程序。

      参数:

      ​ 第一个参数:类加载器

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

      ​ 第三个参数:实现这个接口InvocationHandler,创建代理对象,写增强的部分

  2. 编写JDK动态代理代码

    1. 创建接口,定义方法

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

    public class UserDaoImpl implements UserDao{
        @Override
        public int add(int a, int b) {
            return a+b;
        }
    
        @Override
        public String update(String id) {
            return id;
        }
    }
    
    1. 使用Proxy类创建接口代理对象

      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;
      //            }
      //        });
              UserDaoImpl userDao=new UserDaoImpl();
              UserDao dao=(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
              int result = dao.add(1, 2);
              System.out.println("result"+result);
          }
      }
      
      //创建代理对象代码
      class UserDaoProxy implements InvocationHandler{
          //1 把创建的是谁的代理对象 把谁传递过来
          //有参数构造器
          private Object obj;
          public UserDaoProxy(Object obj){
              this.obj=obj;
          }
      
          //增强的逻辑
          @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;
          }
      }
      

术语

  • 连接点(Jointpoint):类里面哪些方法可以被增强,这些方法被称为连接点。(类中可以被增强的方法

  • 切入点(Pointcut)实际被真正增强的方法

  • 增强(Advice):指的是在目标对象的方法调用前、后或抛出异常时,通过动态代理技术在目标对象的方法周围插入拦截器的逻辑代码,从而实现对目标对象方法的增强和定制。

  • 通知(增强)实际增强的逻辑部分。通知定义了增强在何时被调用,并指定了增强的类型。

    通知有多种类型:

    • 前置通知
    • 后置通知
    • 返回增强
    • 环绕通知
    • 异常通知
    • 最终通知
  • 切面/方面(Aspect)把通知应用到切入点的过程(动作)

AOP操作——准备工作

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

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

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

  4. 切入点表达式

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

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

      举例1:

      对com.atguigu.dao.BookDao类里面的add进行增强:execution(* com.atguigu.dao.BookDao.add(..))

      举例2:

      对com.atguigu.dao.BookDao类里面的所有方法进行增强:execution(* com.atguigu.dao.BookDao.*(..))

      举例3:

      对com.atguigu.dao包里面所有类,类里面的所有方法进行增强:execution(* com.atguigu.dao*.*(..))

    AOP操作——AspectJ注解

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

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

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

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

      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: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/beans/spring-context.xsd
                                   http://www.springframework.org/schema/aop
                                   http://www.springframework.org/schema/beans/spring-aop.xsd">
            <!--    开启全盘扫描-->
            <context:component-scan base-package="com.frx01.spring5.aopanno"></context:component-scan>
        </beans>
        
      2. 使用注解创建UserUserProxy对象

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

        //增强的类
        @Component
        @Aspect //生成代理对象
        public class UserProxy {
        }
        
      4. spring配置文件开启生成代理对象

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

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

      //增强的类
      @Component
      @Aspect //生成代理对象
      public class UserProxy {
          //前置通知
          //@Before注解表示作为前置通知
          @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void before(){
              System.out.println("before.......");
          }
          //后置通知(返回通知)
          @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void afterReturning(){
              System.out.println("afterReturning.....");
          }
          //最终通知
          @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void after(){
              System.out.println("after.....");
          }
          //异常通知
          @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
          public void afterThrowing(){
              System.out.println("afterThrowing.....");
          }
      
          //环绕通知
          @Around(value = "execution(* com.frx01.spring5.aopanno.User.add(..))")
          public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
              System.out.println("环绕之前.......");
              //被增强的方法执行
              proceedingJoinPoint.proceed();
              System.out.println("环绕之后.......");
          }
      }
      
    5. 相同切入点抽取

      //相同切入点抽取
      @Pointcut(value ="execution(* com.atguigu.spring5.aopanno.User.add(..))")
      public void pointdemo(){
      }
      
      //前置通知
      //@Before注解表示作为前置通知
      @Before(value = "pointdemo()")  //可直接使用pointdemo作为value
      public void before(){
          System.out.println("before.......");
      }
      
    6. 有多个增强类多同一个方法进行增强,设置增强类优先级

      在增强类上面添加注解@Order(数字类型值)数字类型值越小优先级越高

      @Component
      @Aspect
      @Order(1)
      public class PersonProxy {
      }
      
    7. 完全使用注解开发

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

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

AOP操作——AspectJ配置文件

  1. 创建两个类,增强类被增强类,创建方法

  2. Spring配置文件创建两个类对象

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

    <!--配置aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.frx01.spring5.aopxml.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <!--配置增强作用在哪个方法上-->
            <aop:before method="before" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
    

JdbcTemplate

概念和准备

  • 什么是JdbcTemplate

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

  • 准备工作

    1. 创建数据库和表

      CREATE DATABASE user_db
      CREATE TABLE t_book(
      	userId BIGINT PRIMARY KEY,
      	username VARCHAR(100) NOT NULL,
      	ustatus VARCHAR(50) NOT NULL)
      
    2. 引入相关的jar包img

    3. Spring配置文件配置数据库的连接池

      <!-- 数据库连接池 -->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
            destroy-method="close">
          <property name="url" value="jdbc:mysql:///user_db" />
          <property name="username" value="root" />
          <property name="password" value="root" />
          <property name="driverClassName" value="com.mysql.jdbc.Driver" />
      </bean>
      
    4. 配置JdbcTemplate对象,注入DataSource

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

      配置文件:

      <!--开启组件扫描-->
      <context:component-scan base-package="com.atguigu"></context:component-scan>
      

      Service类:

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

      Dao类:

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

JdbcTemplate操作数据库

添加

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

    public class User {
        private String userId;
        private String username;
        private String ustatus;
    
        public String getUserId() {
            return userId;
        }
    
        public String getUsername() {
            return username;
        }
    
        public String getUstatus() {
            return ustatus;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public void setUstatus(String ustatus) {
            this.ustatus = ustatus;
        }
    }
    
  2. 编写ServiceDao

    1. dao进行数据库添加操作

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

      update(String sql, Object... args)

      参数:

      String sql:sql语句

      Object... args:可变参数,设置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);
        }
    }
    
  3. 测试

    @Test
    public void testJdbcTemplate(){
        ApplicationContext context =
            new ClassPathXmlApplicationContext("bean1.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        Book book = new Book();
        book.setUserId("1");
        book.setUsername("java");
        book.setUstatus("A");
        bookService.addBook(book);
    }
    

    结果:img

修改和删除

  1. 修改

    @Override
    public void update(Book book) {
        String sql="update t_book set username=?,ustate=? where userId=?";
        Object[] args={ book.getUsername(), book.getUstatus(), book.getUserId()};
        int update = jdbcTemplate.update(sql,args);
        System.out.println(update>0?"修改成功":"修改失败");
    }
    
  2. 删除

    @Override
    public void delete(String id) {
        String sql="delete from t_book where userId=?)";
        int update = jdbcTemplate.update(sql, id);
        System.out.println(update>0?"删除成功":"删除失败");
    }
    

查询返回某个值

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

  2. 使用JdbcTemplate对象里面的queryForOcject方法实现查询操作

    queryForObject(String sql, Class<T> requiredType)

    参数:

    String sql:sql语句

    Class<T> requiredType:返回类型Class

//查询表记录数
@Override
public int selectCount(){
    String sql="select count(*) from t_book";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    return count;
}

查询返回对象

  1. 场景:查询图书详情

  2. JdbcTemplate实现查询返回对象的方法:queryForObject

    queryForObject(String sql, RowMapper<T> rowMapper, Object... args)

    参数:

    String sql:sql语句

    RowMapper<T> rowMapperRowMapper是接口,返回不同类型的数据,使用这个接口里面实现类完成数据封装

    Object... args:sql语句值

 //查询返回对象
@Override
public Book findBookInfo(String id) {
    String sql="select * from t_book where userId=?";
    //调用方法
    Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class),id);
    return book;
}

查询返回集合

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

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

    query(String sql, RowMapper<T> rowMapper, Object... args)

    参数:

    String sql:sql语句

    RowMapper<T> rowMapperRowMapper是接口,返回不同类型的数据,使用这个接口里面实现类完成数据封装

    Object... args:sql语句值

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

批量操作

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

  2. JdbcTemplate实现批量添加操作

    batchUpdate(String sql, List<Object[]> batchArgs)

    参数:

    String sql:sql语句

    List<Object[]> batchArgs: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));
    }
    

    测试:

    //批量添加_测试
    List<Object[]> batchArgs=new ArrayList<>();
    Object[] o1={"3","java","a"};
    Object[] o2={"4","c++","b"};
    Object[] o3={"5","MySQL","e"};
    batchArgs.add(o1);
    batchArgs.add(o2);
    batchArgs.add(o3);
    //调用批量添加
    bookService.batchAdd(batchArgs);
    
  3. JdbcTemplate实现批量修改操作

    //批量修改
    @Override
    public void batchUpdateBook(List<Object[]> batchArgs) {
        String sql="update t_book set username=?,ustatus=? where userId=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(Arrays.toString(ints));
    
    }
    

    测试:

    //批量修改_测试
    List<Object[]> batchArgs=new ArrayList<>();
    Object[] obj1={"java111","a3","3"};
    Object[] obj2={"c++1010","b4","4"};
    Object[] obj3={"MySQL11","c5","5"};
    batchArgs.add(obj1);
    batchArgs.add(obj2);
    batchArgs.add(obj3);
    //调用方法
    bookService.batchUpdate(batchArgs);
    
  4. JdbcTemplate实现批量删除操作

    //批量删除
    @Override
    public void batchDeleteBook(List<Object[]> batchArgs) {
        String sql="delete from t_book where userId=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(Arrays.toString(ints));
    }
    

    测试:

    //批量修改
    List<Object[]> batchArgs=new ArrayList<>();
    Object[] obj1={"3"};
    Object[] obj2={"4"};
    batchArgs.add(obj1);
    batchArgs.add(obj2);
    //调用方法实现批量删除
    bookService.batchDelete(batchArgs);
    

事务管理

概念

  • 什么是事务?

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

    1. 典型场景:银行转账
    • lucy转账100元给mary
    • lucy少100,mary多100
  • 事务的四个特性(ACID

    • 原子性
    • 一致性
    • 隔离性
    • 持久性

搭建事务操作环境

img

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

    CREATE TABLE t_account(
    	id VARCHAR(20),
    	username VARCHAR(50),
    	money VARCHAR(50))
    INSERT INTO t_account VALUES('1','lucy',1000)
    INSERT INTO t_account VALUES('2','mary',1000)
    
  2. 创建service,搭建dao,完成对象创建注入关系

    service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource

    @Service
    public class UserService {
        //注入dao
        @Autowired
        private UserDao userDao;
    }
    
    @Repository
    public class UserDaoImpl implements UserDao{
        @Autowired
        private JdbcTemplate jdbcTemplate;
    }
    
  3. dao创建两个方法,多钱和少钱的方法,在service创建方法(转账的方法)

    @Repository
    public class UserDaoImpl implements UserDao{
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        //少钱
        @Override
        public void reduceMoney() {
            String sql="update t_account set money=money-? where username=?";
            jdbcTemplate.update(sql,100,"lucy");
        }
    
        //多钱
        @Override
        public void addMoney() {
            String sql="update t_account set money=money+? where username=?";
            jdbcTemplate.update(sql,100,"mary");
        }
    }
    
    @Service
    public class UserService {
        //注入dao
        @Autowired
        private UserDao userDao;
    
        //转账的方法
        public  void  accountMoney(){
            //lucy少100
            userDao.reduceMoney();
            
            //mary多100
            userDao.addMoney();
        }
    }
    
  4. 上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题

    //转账的方法
    public  void  accountMoney(){
        //lucy少100
        userDao.reduceMoney();
    
        //模拟异常
        int i =10/0;
    
        //mary多100
        userDao.addMoney();
    }
    //结果lucy少了100,而mary并没有增加100
    
    • 以上的问题如何解决呢?

      • 使用事务进行解决
    • 事务操作过程

      //转账的方法
      public  void  accountMoney(){
          try {
              //第一步 开启事务
      
              //第二步 进行业务操作
              //lucy少100
              userDao.reduceMoney();
      
              //模拟异常
              int i = 10 / 0;
      
              //mary多100
              userDao.addMoney();
              
              //第三步 没有发生异常,提交事务
          }catch (Exception e){
              //第四步 出现异常,事务回滚
          }
      }
      

Spring事务管理介绍

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

  2. 在Spring进行事务管理操作有两种方式:编程式事务管理声明式事务管理(使用)

  3. 声明式事务管理

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

  5. Spring事务管理API:PlatformTransactionManager

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

注解声明式事务管理

  1. Spring配置文件配置事务管理器

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

    1. Spring配置文件引入名称空间tx

      <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"
             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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                                  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
      
    2. 开启事务注解

          <!--开启事务注解-->
          <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
      
  3. service类上面(获取service类里面方法上面)添加事务注解

    1. @Transactional,这个注解添加到类上面,也可以添加到方法上面
    2. 如果把这个注解添加到上面,这个类里面的所有的方法都添加事务
    3. 如果把这个注解添加到方法上面,为这个方法添加事务
    @Service
    @Transactional
    public class UserService {
    }
    

声明式事务管理参数配置

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

img

  1. propagation事务传播行为

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

    事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

    传播属性 描述
    REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事物,并在自己的事务内运行
    REQUIRED_NEW 当前的方法必须启动新事物,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
    SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
    NOT_SUPPROTS 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
    MANDATORY 当前的方法必须运行在事物内部,如果没有正在运行的事务,就抛出异常
    NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
    NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行
    @Service
    @Transactional(propagation = Propagation.REQUIRED)
    public class UserService {
    }
    
  2. isolation事务隔离级别

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

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

      1. 脏读:一个未提交事务读取到另一个未提交事务的数据img
      2. 不可重复读:一个未提交事务读取到另一提交事务修改的数据img
      3. 虚读:一个未提交事务读取到另一个提交事务添加数据
    3. 解决:通过设置事务隔离性,解决读问题

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

    1. 事务需要在一定时间内提交,如果不提交进行回滚
    2. 默认值是-1,设置时间以秒为单位进行计算
    @Service
    @Transactional(timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public class UserService {
    }
    
  4. readOnly是否只读

    1. 读:查询操作,写:添加修改删除操作
    2. readOnly默认值false,表示可以查询,可以添加修改删除操作
    3. 设置readOnly值成true之后,只能查询
    @Service
    @Transactional(readOnly = true, timeout = -1, propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public class UserService {
    }
    
  5. rollbackFor回滚

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

  6. noRollbackFor不回滚

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

XML声明式事务管理(了解)

  1. Spring配置文件中进行配置

    第一步,配置事务管理器

    第二步,配置通知

    第三步,配置切入点和切面

    <!--1 创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--2 配置通知-->
    <tx:advice id="txadvice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>
    <!--3 配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.frx01.spring5.service.UserService.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
    

完全注解声明式事务管理

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

@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //到ioc容器中根据类型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return  jdbcTemplate;
    }
    //创建事务事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

Spring5框架新功能

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

Spring5.0框架自带了通用的日志封装

  1. Spring5已经移除Log4jConfigListener,官方建议使用Log4j2
  2. Spring5框架整合Log4j2

第一步,引入jar包

第二步,创建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>

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

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

  1. 注解用在方法上面,方法返回值可以为空

    @Nullable
    String getId();
    
  2. 注解使用在方法参数里面,方法参数可以为空

    public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
        this.reader.registerBean(beanClass, beanName, supplier, customizers);
    }
    
  3. 注解使用在属性上面,属性可以为空

    @Nullable
    private String bookName;
    

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("com.atguigu.spring5.test.User");
    User user = (User)context.getBean("user1");
    System.out.println(user);
}

Spring5 支持整合JUnit5

  1. 整合JUnit4

    第一步,引入Spring相关针对测试依赖

    imgimg

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

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

    Junit5的用途:

    1. 单元测试:JUnit 5 可以用于编写和运行单元测试,而 Spring 框架提供了对依赖注入、AOP、事务管理等特性的支持,可以帮助开发人员编写更加全面和真实的单元测试。
    2. 集成测试:在使用 Spring 框架开发应用程序时,通常需要进行集成测试以确保不同组件之间的协作正常运行。JUnit 5 可以与 Spring 的测试支持模块结合,提供对 Spring 上下文加载、自动装配、事务管理等功能的支持,从而实现更加全面的集成测试。
    3. 测试驱动开发(TDD):通过使用 JUnit 5 和 Spring 框架,开发人员可以实践测试驱动开发方法,即先编写测试用例,然后根据测试用例编写实际的业务逻辑代码。这有助于提高代码的质量和稳定性。
    4. 参数化测试:JUnit 5 提供了对参数化测试的支持,而 Spring 框架中的一些组件和特性可能需要进行各种参数化测试以验证其行为。结合 JUnit 5 的参数化测试功能,可以更方便地进行这些测试。

    第一步,引入JUnit5的jar包img

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

    @ExtendWith(SpringExtension.class)
    @ContextConfiguration("classpath:bean1.xml")
    public class JTest5 {
        @Autowired
        private UserService userService;
        @Test
        public void test1(){
            userService.accountMoney();
        }
    }
    
    • 使用一个复合注解替代上面两个注解完成整合

      @SpringJUnitConfig(locations = "classpath:bean1.xml")
      public class JTest5 {
          @Autowired
          private UserService userService;
          @Test
          public void test1(){
              userService.accountMoney();
          }
      }
      

Spring5框架新功能——Webflux

SpringWebflux介绍

  1. SpringWebflux是Spring5添加新的模块,用于web开发的,功能和SpringMVC类似的,Webflux是使用了当前一种比较流行的响应式编程而出现的框架。img
  2. 传统 web 框架,比如 SpringMVC,是基于 Servlet 容器的。而Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于Reactor的相关 API 实现的
  3. 异步非阻塞
    • 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。
    • 阻塞和非阻塞针对被调用者,被调用者收到请求之后,做完了请求任务才给出反馈,收到请求之后马上给出犯规然后再去做事情就是非阻塞。
  4. Webflux 特点:
    1. 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
    2. 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
  5. 与SpringMVC作比较img
    1. 两个框架都可以使用注解方式,都运行在Tomet等容器中
    2. SpringMVC 采用命令式编程Webflux 采用异步响应式编程

响应式编程(Java实现)

响应式编程的介绍

响应式编程是一种面向数据流变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

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();//通知
    }
}

响应式编程(Reachor实现)

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

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

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

    img

  • 代码演示FluxMono

    第一步,引入依赖

    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-core</artifactId>
        <version>3.1.5.RELEASE</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<Integer> stream = list.stream();
        Flux.fromStream(stream);
    }
    
  • 三种信号特点

    • 错误信号和完成信号都是终止信号,不能共存
    • 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
    • 如果没有错误信号,没有完成信号,表示是无限数据流
  • 调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的

    //just方法直接声明
    Flux.just(1, 2, 3, 4).subscribe(System.out::print);
    Mono.just(1).subscribe(System.out::print);
    
  • 操作符:对数据流进行一道道操作,称为操作符,比如工厂流水线

    1. map 元素映射为新元素img
    2. flatmap 元素映射为流(把每个元素转换成流,把转换之后的多个流合并成大的流)img

SpringWebflux执行流程和核心API

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

  • Netty

    • BIOimg
    • NIOimg
  • SpringWebflux执行过程和SpringMVC相似

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

    • 接口WebHandler有一个方法:

      public interface WebHandler{
      	Mono<Void> handle(ServerWebExchange var1);
      }
      
    • SpringWebflux 里面 DispatcherHandler,负责请求的处理

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

    SpringWebflux(基于注解编程模型)

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

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

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

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

    第二步,配置启动端口号

    ​ 在application.properties

    server.port=8081
    

    第三步,创建包和相关类

    ​ 实体类

    //实体类
    public class User{
        private String name;
        private String gender;
        private Integer age;
        
        public User(String name, String gender, Integer age){
            this.name = name;
            this.gender = gender;
            this.age = age;
        }
        public void setName(String name){
        ......
    }
    

    ​ 创建接口定义操作的方法

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

    ​ 接口实现类

    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",30));
            this.users.put(3,new User("jack","nv",50));
        }
        //根据id查询
        @Override
        public Mono<User> getUserById(int 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 -> {
                //向map集合里面放值
                int id = users.size()+1;
                users.put(id,person);
            }).thenEmpty(Mono.empty());
        }
    }
    

    ​ 创建controller

    @RestController
    public class UserController {
        //注入 service
        @Autowired
        private UserService userService;
        //id 查询
        @GetMapping("/user/{id}")
        public Mono<User> geetUserId(@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

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

  1. 在使用函数式编程模型操作时候,需要自己初始化服务器
  2. 基于函数式编程模型时候,有两个核心接口:RouterFunction实现路由功能,请求转发给对应的 handler)和 HandlerFunction处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
  3. SpringWebflux 请求和响应不再是 ServletRequestServletResponse ,而是ServerRequestServerResponse

第一步 把注解编程模型工程复制一份 ,保留 entityservice 内容

第二步 创建 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> 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> getAllUsers() {
        //调用 service 得到结果
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }
    //添加
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到 user 对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }
}

第三步 初始化服务器,编写 Router

  • 创建路由的方法

    //1 创建 Router 路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建 hanler 对象
        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);
    }
    
  • 创建服务器完成适配

    //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();
    }
    
  • 最终调用

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

第四步 使用 WebClient 调用

public class Client {
    public static void main(String[] args) {
        //调用服务器地址
        WebClient webClient = WebClient.create("http://127.0.0.1:5794");
        //根据 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();
    }
}

参考资料:https://www.bilibili.com/video/BV1Vf4y127N5?p=1&vd_source=cf21268954e139179e71f046bac01e56