Spring6

环境准备

  • 开发工具: IDEA2022.3

  • JDK17

    JDK17

起步

  • 问题

    如下代码出现的问题
    • OCP

      以上代码违背了 OCP 开闭原则

      • 什么是OCP

        OCP 是软件七大开发原则中最基本的一个原则: 开闭原则

      • OCP 核心

        只要你在扩展系统功能的时侯,没有修改以前写好的代码,那么就是符合OCP 原则

        反之,如果在扩展系统功能的时候,如果动了之前修改的程序,那么这个设计就是失败的,违背了OCP 原则

    • 依赖倒置原则(DIP 原则)

      • 什么是依赖倒置原则

        面向接口编程、面向抽象编程、不要面向具体编程

        1
        2
        // 面向具体(x)
        private UserService userService = new UserServiceImpl();
      • 依赖倒置原则的目的

        降低程序的耦合度,提高扩展力

      • 什么叫做符合依赖倒置

        (Controller: UserService = new xx) 不依赖 下(Service: UserMapper = new xxx),就是符合

      • 什么叫做违背依赖倒置

        上依赖下,就是违背

        只要一改动,就受到牵连

  • 控制反转

    1
    2
    3
     private UserService userService = new UserServiceImpl();
    =>
    private UserService userService;
    • 反转是什么

      反转是两件事:

      1. 第一件事: 我不在程序中采用硬编码的方式来new 对象了。(new 对象的权力交出去了)
      2. 第二件事: 我不在程序中采用硬编码的方式来维护对象的关系了
    • 控制反转

      控制反转: 是一种编程思想。或者叫做一种新型的设计模式。由于出现的比较新,没有被纳入GoF23 种设计模式范围内。

Spring

  • Spring 框架实现了控制反转IoC 这种思想
    • Spring 框架可以帮你new 对象
    • Spring 框架可以帮你维护对象和对象之间的关系
  • Spring 是一个实现可IoC 思想的容器
  • 控制反转的实现方式有多种,其中比较重要的叫做: 依赖注入,简称 DI
  • 控制反转是思想。依赖注入是这种思想的具体实现
  • 依赖注入(DI),包含常见的两种方式
    1. set 注入
    2. 构造方法注入
  • 依赖注入中依赖是什么意思?注入是什么意思?
    • 依赖: A 对象和B 对象的关系
    • 注入: 是一种手段,通过这种手段,可以让A 对象和B 对象产生关系。
  • 中文官网地址: http://spring.p2hp.com

Hello-World

  • 实现步骤

    1. 创建maven 项目

    2. 添加依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <!-- 
      https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts
      属于里程碑还暂未发布,所以需要添加 仓库地址
      -->
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>

      <groupId>org.example</groupId>
      <artifactId>spring6-002</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>

      <!-- 配置多个仓库 -->
      <repositories>
      <repository>
      <id>repository.spring.milestone</id>
      <name>Spring Milestone Repository</name>
      <url>https://repo.spring.io/milestone</url>
      </repository>
      </repositories>

      <dependencies>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
      <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.9.1</version>
      <scope>test</scope>
      </dependency>
      </dependencies>

      </project>
    3. 创建spring 的配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      <!--
      bean 标签的两个重要属性:
      id: 是这个 bean 的唯一标识
      class: 必须填写类的全路径,全限定类名(带包名的类名)
      -->
      <bean id="userBean" class="org.example.bean.User"/>
      </beans>
    4. 测试获取bean

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      package com.example;

      import org.example.bean.User;
      import org.junit.jupiter.api.Test;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.support.ClassPathXmlApplicationContext;

      public class FirstSpringTest {
      /**
      * ApplicationContext: 翻译为: 应用上下文,其实就是 spring 容器
      * ApplicationContext 是一个接口
      * ApplicationContext 接口下有很多实现类.其中有一个实现类叫做: ClassPathXmlApplicationContext
      * ClassPathXmlApplicationContext: 专门从类路径中加载 spring 配置文件的一个 Spring 上下文对象
      *
      * ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      * 上面这行代码只要执行,就相当于开启了 Spring 容器,解析 spring.xml 文件,并且实例化所有的 bean 对象,放到 spring 容器中
      */
      @Test
      public void testSayHello() {
      // 第一步: 获取 Spring 容器对象
      ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      // 第二步: 根据 bean 的 id 从 spring 容器中获取这个对象
      User user = ac.getBean("userBean", User.class);
      System.out.println(user);
      }
      }

    5. Spring 是怎么实例化对象的 ?

      默认情况下Spring 会通过反射机制,调用类的无参构造方法来实例化对象

      1
      2
      3
      // 实现原理
      Class class = Clazz.forName("org.example.bean.User");
      Object obj = class.newInstance();
      1
      2
      3
      4
      5
      public class User {
      public User() {
      System.out.println("无参构造被调用.................");
      }
      }
      无参构造被自动调用

Spring6 启动 Log4j2 日志框架

Spring5 之后,Spring 框架支持继承的日志框架是Log4j2

  • 启用步骤

    1. 引入Log4j2 的依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <!--  Spring6 的日志依赖 -->
      <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.19.0</version>
      </dependency>
      <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.19.0</version>
      </dependency>
    2. 在类路径下提供log4j2.xml 配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <!-- 文件名固定为: log4j2.xml 文件必须放到类的根路径下 -->
      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
      <loggers>
      <!--
      level指定日志级别,从低到高的优先级:
      ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
      -->
      <root level="DEBUG">
      <appender-ref ref="spring6log"/>
      </root>
      </loggers>
      <appenders>
      <!--输出日志信息到控制台-->
      <console name="spring6log" target="SYSTEM_OUT">
      <!--控制日志输出的格式-->
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
      </console>
      </appenders>
      </configuration>
    3. 使用

      1
      2
      Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
      logger.info("我是一条日志消息");

Spring 对 IoC 的实现

  • IoC 控制反转

    • 控制反转是一种思想

    • 控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP 原则,达到DIP 原则

    • 控制反转,反转的是什么?

      • 将对象的创建权力交出去,交给第三方容器负责

        1
        <bean id="userBean" class="org.example.bean.User"/>
      • 将对象和对象之间关系的维护权交出去,交给第三方容器实现

    • 控制反转这种思想是如何实现呢?

      • DI: 依赖注入
  • 依赖注入

    依赖注入实现了控制反转的思想

    Spring 通过依赖注入的方式来完成Bean 管理的

    Bean 管理说的是: Bean 对象的创建,以及Bean 对象中属性的赋值(或者叫做Bean 对象之间关系的维护)

    • 依赖注入
      • 依赖指的是对象和对象之间的关联关系
      • 注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系
    • 依赖注入常见的实现方式包括两种
      • 第一种: set 注入
      • 第二种: 构造注入

Set 注入

  • dao

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package org.example.dao;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    public void insert() {
    logger.info("mysql 正在保存用户信息..............");
    }
    }

  • serviceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package org.example.service.impl;

    import org.example.dao.UserDao;
    import org.example.service.UserService;

    public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // TODO: set 注入,必须提供 set 方法,spring 会自动调用构造方法
    @Override
    public void saveUser() {
    userDao.insert();
    }
    }

    注意
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.example.service.impl;

    import org.example.dao.UserDao;
    import org.example.service.UserService;

    public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 提供 set 方法
    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }

    // set 注入,必须提供 set 方法,spring 会自动调用构造方法
    @Override
    public void saveUser() {
    userDao.insert();
    }
    }

  • spring 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- application.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- dao -->
    <bean id="userDaoBean" class="org.example.dao.UserDao"/>

    <!-- service -->
    <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">

    <!--
    property 标签属性:
    name: set 方法的方法名,去掉 set,然后把剩下的单次首字母变小写,写到这里
    ref: ref 是 references。ref 后面指定的是要注入的 bean 的 id
    -->
    <property name="userDao" ref="userDaoBean"/>
    </bean>
    </beans>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    public class UserTest {
    @Test
    public void userTest() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserServiceImpl userServiceImpl = ac.getBean("userServiceImplBean", UserServiceImpl.class);
    userServiceImpl.saveUser();
    }
    }

构造注入

  • serviceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 构造方法
    public UserServiceImpl(UserDao userDao) {
    this.userDao = userDao;
    }

    @Override
    public void saveUser() {
    userDao.insert();
    }
    }

  • spring 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- dao -->
    <bean id="userDaoBean" class="org.example.dao.UserDao"/>

    <!-- service -->
    <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
    <!--
    构造注入通过标签: constructor-arg
    constructor-arg 属性:
    index: 指定构造方法的第一个参数,下标是 0,以此类推
    ref: 用来指定注入的 bean 的 id
    -->
    <constructor-arg index="0" ref="userDaoBean"></constructor-arg>
    </bean>
  • 为什么注入配置在serviceImpl

    配置位置说明

Set 注入专题

  • 注入外部Bean

    1
    2
    3
    4
    5
    6
    7

    <!-- dao -->
    <bean id="userDaoBean" class="org.example.dao.UserDao"/>
    <!-- service -->
    <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDaoBean"/>
    </bean>
  • 注入内部Bean

    1
    2
    3
    4
    5
    6
    7
    <!-- service -->
    <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl">
    <property name="userDao">
    <!-- dao -->
    <bean id="userDaoBean" class="org.example.dao.UserDao"/>
    </property>
    </bean>
  • 注入简单类型

    • User.class

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package org.example.bean;

      public class User {
      private String username;
      private Integer age;

      public void setUsername(String username) {
      this.username = username;
      }

      public void setAge(Integer age) {
      this.age = age;
      }

      @Override
      public String toString() {
      return "User{" +
      "username='" + username + '\'' +
      ", age=" + age +
      '}';
      }
      }

    • Spring 配置

      1
      2
      3
      4
      5

      <bean id="userBean" class="org.example.bean.User">
      <property name="username" value="coder-itl"/>
      <property name="age" value="18"/>
      </bean>
    • 通过源码分析得知,简单类型包括:

      • 基本数据类型
      • 基本数据类型对应的包装类
      • String 或其他的CharSequence 子类
      • Number 子类
      • Date 子类,虽然它是简单类型,但在使用时不推荐直接以简单类型使用,因为日期书写格式有要求 font>
      • Enum 子类
      • URI
      • URL
      • Temporal 子类
      • Locale
      • Class
      • 另外还包括以上简单值类型对应的数组类型。
  • 级联属性赋值

    • Student

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      package org.example.bean;

      public class Student {
      // 学生姓名
      private String stuName;
      // 所属班级
      private Clazz clazz;

      public void setStuName(String stuName) {
      this.stuName = stuName;
      }

      public void setClazz(Clazz clazz) {
      this.clazz = clazz;
      }

      public Clazz getClazz() {
      return clazz;
      }

      @Override
      public String toString() {
      return "Student{" +
      "stuName='" + stuName + '\'' +
      ", clazz=" + clazz +
      '}';
      }
      }

    • Clazz

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      package org.example.bean;

      public class Clazz {
      // 班级名称
      private String clazzName;

      public void setClazzName(String clazzName) {
      this.clazzName = clazzName;
      }

      @Override
      public String toString() {
      return "Clazz{" +
      "clazzName='" + clazzName + '\'' +
      '}';
      }
      }

    • Spring 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!-- 
      级联属性赋值注意点:
      1. 配置的顺序不能颠倒,必须如下顺序
      2. clazz 属性必须提供 getter 方法
      -->
      <bean id="studentBean" class="org.example.bean.Student">
      <property name="stuName" value="coder-itl"/>
      <property name="clazz" ref="clazzBean"/>
      <!-- 级联属性赋值 clazz.clazzName 是通过提供的 get 方法 -->
      <property name="clazz.clazzName" value="高三一班"/>
      </bean>
      <bean id="clazzBean" class="org.example.bean.Clazz"/>
    • 测试

      1
      2
      3
      4
      5
      6
      7
      @Test
      public void stuClazzTest() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      Student bean = ac.getBean("studentBean", Student.class);
      // Student{stuName='coder-itl', clazz=Clazz{clazzName='高三一班'}}
      System.out.println(bean);
      }
  • 注入数组

    • 数组元素是简单类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      package org.example.bean;

      import java.util.Arrays;

      public class Hobby {
      private String[] hobby;

      public void setHobby(String[] hobby) {
      this.hobby = hobby;
      }

      @Override
      public String toString() {
      return "Hobby{" +
      "hobby=" + Arrays.toString(hobby) +
      '}';
      }
      }

      • Spring 配置

        1
        2
        3
        4
        5
        6
        7
        8
        <bean id="hobbyBean" class="org.example.bean.Hobby">
        <property name="hobby">
        <array>
        <value>喝酒</value>
        <value>打麻将</value>
        </array>
        </property>
        </bean>
    • 非简单类型

      • Hobby

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        package org.example.bean;

        import java.util.Arrays;

        public class Hobby {
        private String[] hobby;
        private Women[] women;

        public void setHobby(String[] hobby) {
        this.hobby = hobby;
        }

        public void setWomen(Women[] women) {
        this.women = women;
        }

        @Override
        public String toString() {
        return "Hobby{" +
        "hobby=" + Arrays.toString(hobby) +
        ", women=" + Arrays.toString(women) +
        '}';
        }
        }

      • Women

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        package org.example.bean;

        public class Women {
        private String girlName;

        public void setGirlName(String girlName) {
        this.girlName = girlName;
        }

        @Override
        public String toString() {
        return "Women{" +
        "girlName='" + girlName + '\'' +
        '}';
        }
        }

      • Spring 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29

        <bean id="w1" class="org.example.bean.Women">
        <property name="girlName" value="aa"/>
        </bean>
        <bean id="w2" class="org.example.bean.Women">
        <property name="girlName" value="bb"/>
        </bean>
        <bean id="w3" class="org.example.bean.Women">
        <property name="girlName" value="cc"/>
        </bean>

        <bean id="hobbyBean" class="org.example.bean.Hobby">
        <!-- 简单类型 -->
        <property name="hobby">
        <array>
        <value>喝酒</value>
        <value>打麻将</value>
        </array>
        </property>
        <!-- 非简单类型赋值 -->
        <property name="women">
        <array>
        <ref bean="w1"/>
        <ref bean="w2"/>
        <ref bean="w3"/>
        </array>
        </property>
        </bean>

  • List Set 集合注入

    • Person

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      package org.example.bean;

      import java.util.List;
      import java.util.Set;

      public class Person {
      private List<String> personName;
      private Set<String> address;

      public void setPersonName(List<String> personName) {
      this.personName = personName;
      }

      public void setAddress(Set<String> address) {
      this.address = address;
      }

      @Override
      public String toString() {
      return "Person{" +
      "personName=" + personName +
      ", address=" + address +
      '}';
      }
      }

    • Spring 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <bean id="personBean" class="org.example.bean.Person">
      <property name="personName">
      <list>
      <value>aa</value>
      <value>bb</value>
      <value>cc</value>
      </list>
      </property>
      <property name="address">
      <set>
      <value>aa</value>
      <value>bb</value>
      <value>cc</value>
      </set>
      </property>
      </bean>
  • Map Properties 注入

    • Map

      • Person

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        package org.example.bean;

        import java.util.List;
        import java.util.Map;
        import java.util.Set;

        public class Person {
        private Map<Integer, String> phones;

        public void setPhones(Map<Integer, String> phones) {
        this.phones = phones;
        }

        @Override
        public String toString() {
        return "Person{" +
        "phones=" + phones +
        '}';
        }
        }

      • Spring 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <bean id="mapAndPropertiesBean" class="org.example.bean.Person">
        <property name="phones">


        <map>
        <entry key="1" value="110"/>
        <entry key="2" value="120"/>
        <entry key="3" value="119"/>
        </map>
        </property>
        </bean>
    • Properties

      • Person

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        package org.example.bean;

        import java.util.List;
        import java.util.Map;
        import java.util.Properties;
        import java.util.Set;

        public class Person {
        private Properties properties;

        public void setProperties(Properties properties) {
        this.properties = properties;
        }

        @Override
        public String toString() {
        return "Person{" +
        "properties=" + properties +
        '}';
        }
        }

      • Spring 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <bean id="mapAndPropertiesBean" class="org.example.bean.Person">
        <property name="properties">
        <props>
        <prop key="Driver">mysql://localhost:3306</prop>
        <prop key="username">root</prop>
        <prop key="password">root</prop>
        </props>
        </property>
        </bean>
  • 注入null 和空字符串

    • Cat

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package org.example.bean;

      public class Cat {
      private String catName;
      private Integer catAge;

      public void setCatName(String catName) {
      this.catName = catName;
      }

      public void setCatAge(Integer catAge) {
      this.catAge = catAge;
      }

      @Override
      public String toString() {
      return "Cat{" +
      "catName='" + catName + '\'' +
      ", catAge=" + catAge +
      '}';
      }
      }

    • Spring 配置

      1
      2
      3
      4
      <!-- 如果只对一个属性赋值,默认为 null => Cat{catName='null', catAge=1} -->
      <bean id="catBean" class="org.example.bean.Cat">
      <property name="catAge" value="1"/>
      </bean>
      1
      2
      3
      4
      5

      <bean id="catBean" class="org.example.bean.Cat">
      <property name="catName" value="null"/>
      <property name="catAge" value="1"/>
      </bean>
      1
      2
      3
      4
      5
      6
      7
      <!-- 手动注入: null -->
      <bean id="catBean" class="org.example.bean.Cat">
      <property name="catName">
      <null/>
      </property>
      <property name="catAge" value="1"/>
      </bean>

基于 XML 的自动装配

  • 自动装配是基于set 注入的

  • 根据名称自动装配

    • UserDao

      1
      2
      3
      4
      5
      6
      7
      public class UserDao {
      private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

      public void insert() {
      logger.info("mysql 正在保存用户信息..............");
      }
      }
    • ServiceImpl

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      public class UserServiceImpl implements UserService {
      private UserDao userDao;

      public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
      }

      @Override
      public void saveUser() {
      userDao.insert();
      }
      }
    • 配置

      1
      2
      3

      <bean id="userDao" class="org.example.dao.UserDao"/>
      <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl" autowire="byName"/>
  • 根据类型自动装配

    1
    2
    3

    <bean class="org.example.dao.UserDao"/>
    <bean id="userServiceImplBean" class="org.example.service.impl.UserServiceImpl" autowire="byType"/>

引入外部的属性配置文件

  • 外部属性文件

    1
    2
    3
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.username=root
    jdbc.password=root
    • 配置使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      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
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd">
      <!--
      引入外部的 properties 文件
      第一步: 引入 context 命名空间
      第二步: 使用标签 context:property-placeholder 的 location 属性来指定属性配置文件的路径
      -->
      <context:property-placeholder location="classpath*:jdbc.properties"/>
      <!-- 配置数据源 -->
      <bean id="ds" class="org.example.bean.MyDataSource">
      <!-- 属性取值: ${key} -->
      <property name="driver" value="${jdbc.driver}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
      </bean>
      </beans>
    • 测试

      1
      2
      3
      4
      5
      6
      7
      @Test
      void dataSourceTest() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("spring-properties.xml");
      MyDataSource bean = ac.getBean("ds", MyDataSource.class);
      // MyDataSource{driver='com.mysql.cj.jdbc.Driver', username='root', password='root'}
      System.out.println(bean);
      }

Bean 的作用域

  • 单例

    • SpringBean

      1
      2
      3
      4
      5
      public class SpringBean {
      public SpringBean() {
      System.out.println("SpringBean 的构造方法................");
      }
      }
    • 配置

      1
      <bean id="springBean" class="org.example.bean.SpringBean"/>
    • 测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      public class SpringScopeTest {
      /**
      * Spring 默认情况下是如何管理这个 Bean 的
      * 默认情况下 Bean 是单例的(单例: singleton)
      * 在 Spring 上下文初始化的时候实例化
      * 在每一次调用 getBean() 方法的时候,都返回那个单例的对象
      */
      @Test
      void springScopeTest() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("spring-scope.xml");
      SpringBean bean1 = ac.getBean("springBean", SpringBean.class);
      System.out.println("bean1 = " + bean1);

      SpringBean bean2 = ac.getBean("springBean", SpringBean.class);
      System.out.println("bean2 = " + bean2);
      }

      }
  • 多例

    1
    <bean id="springBean" class="org.example.bean.SpringBean" scope="prototype"/>
    1
    2
    3
    4
    5
    6
    /**
    * 当 bean 的 scope 属性设置为 prototype
    * bean 是多例的
    * Spring 上下文初始化的时候,并不会初始化这些 prototype 的 bean
    * 每一次调用 getBean() 方法的时候,实例化该 bean 对象
    */

循环依赖

  • singleton+setter 模式

    • 输出

      1
      2
      Husband{name='张三', wife=菊花}
      Wife{name='菊花', husband=张三}
    • Husband

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      package org.example.bean;

      public class Husband {
      private String name;
      private Wife wife;

      public void setName(String name) {
      this.name = name;
      }

      public void setWife(Wife wife) {
      this.wife = wife;
      }

      public String getName() {
      return name;
      }

      @Override
      public String toString() {
      return "Husband{" +
      "name='" + name + '\'' +
      ", wife=" + wife.getName() +
      '}';
      }
      }

    • Wife

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      package org.example.bean;

      public class Wife {
      private String name;
      private Husband husband;

      public void setName(String name) {
      this.name = name;
      }

      public void setHusband(Husband husband) {
      this.husband = husband;
      }

      public String getName() {
      return name;
      }

      @Override
      public String toString() {
      return "Wife{" +
      "name='" + name + '\'' +
      ", husband=" + husband.getName() +
      '}';
      }
      }

    • 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      <!--
      singleton 表示在整个 Spring 容器中是单例的,独一无二的对象
      在 singleton + setter 模式下,为什么循环依赖不会出现问题,Spring 是如何应对的?
      主要的原因是,在这种模式下 Spring 对 Bean 的管理主要分为清晰的两个阶段
      第一个阶段: 在 Spring 容器加载的时候,实例化 Bean,只要其中任意一个 Bean 实例化之后,马上进行“曝光[不等属性赋值就曝光]”
      第二个阶段: Bean 在 “曝光”之后,再进行属性的赋值(调用 set 方法)

      核心解决方案是: 实例化对象和对象属性赋值分为两个阶段来完成的
      -->
      <bean id="husbandBean" class="org.example.bean.Husband" scope="singleton">
      <property name="name" value="张三"/>
      <property name="wife" ref="wifeBean"/>
      </bean>
      <bean id="wifeBean" class="org.example.bean.Wife" scope="singleton">
      <property name="name" value="菊花"/>
      <property name="husband" ref="husbandBean"/>
      </bean>
  • prototype+setter 模式

    1
    2
    3
    4
    5
    6
    <!--
    在 prototype + setter 模式下的循环依赖,存在问题,会出现异常
    BeanCurrentlyInCreationException: 当前 Bean 正处于创建中
    注意: 当只有两个 bean 的 scope 都是 prototype 的时候,才会出现异常
    当两个中任意一个是 singleton 就不会出现异常
    -->

Spring IoC 注解声明 Bean

  • 使用步骤

    • 第一步:加入aop 的依赖

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
    • 第二步:在配置文件中添加context 命名空间

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      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
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd">


      </beans>
    • 第三步:在配置文件中指定扫描的包

      1
      2
      <!-- 开启包扫描 -->
      <context:component-scan base-package="org.example.bean"/>
    • 第四步:在Bean 类上使用注解

      1
      2
      3
      4
      // 普通类 value: bean 名称,如果没有声明,则默认为类名首字母小写
      @Component(value = "userBean")
      public class User {
      }
      1
      2
      3
      4
      // 持久层 => dao/mapper
      @Repository
      public interface UserMapper {
      }
      1
      2
      3
      4
      5
      // 服务层
      @Service
      public class UserServiceImpl implements UserService {
      }

      1
      2
      3
      4
      5
      // 控制器
      @Controller
      public class UserController {
      }

    • 测试

      1
      2
      3
      4
      5
      6
      @Test
      public void springAnnTest() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("spring-anno.xml");
      User userBean = ac.getBean("userBean", User.class);
      System.out.println(userBean);
      }
    • 如果有多个包,可以通过如下解决

      • 第一种: 在配置文件中指定多个包,用逗号隔开

        1
        <context:component-scan base-package="org.example.bean,org.example.service.impl"/>
      • 第二种: 指定多个包的父包

        1
        <context:component-scan base-package="org.example"/>
        当前项目包结构

选择性实例化 Bean

  • 创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package org.example.bean;

    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Controller;
    import org.springframework.stereotype.Repository;
    import org.springframework.stereotype.Service;

    @Component
    class A {
    public A() {
    System.out.println("A的无参构造..........");
    }
    }

    @Repository
    class B {
    public B() {
    System.out.println("B的无参构造..........");
    }
    }

    @Service
    class C {
    public C() {
    System.out.println("C的无参构造..........");
    }
    }

    @Controller
    class D {
    public D() {
    System.out.println("D的无参构造..........");
    }
    }

    @Controller
    class E {
    public E() {
    System.out.println("E的无参构造..........");
    }
    }

  • 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启包扫描 -->
    <!--
    第一种解决方案
    use-default-filters="false" 表示所有带有声明的 Bean 注解全部失效
    -->
    <context:component-scan base-package="org.example" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    ...
    </context:component-scan>

    <!-- 只有这个失效 -->
    <context:component-scan base-package="org.example">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    ...
    </context:component-scan>
    </beans>

负责注入的注解

  • @Value

    1
    // 简单类型注入值
  • @Autowired

    1
    // 可以对非简单类型注入,单独使用默认是根据类型装配的 【byType】
    1
    2
    3
    4
    5
    6
    7
    // 可以出现的位置(源码)
    @Target({ElementType.CONSTRUCTOR【构造】, ElementType.METHOD【方法】, ElementType.PARAMETER【参数】, ElementType.FIELD【字段】, ElementType.ANNOTATION_TYPE【注解 @Qualifier】})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Autowired {
    boolean required() default true;
    }
  • @Qualifier

    1
    2
    3
    4
    5
    6
    7
    8
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Qualifier {
    String value() default "";
    }

  • @Resource

    • @Resource @Autowired 的区别

      1. @Resource 注解是JDK 扩展包中的,也就是属于JDK 的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250 标准中指定的注解类型。JSR Java 规范提案)

      2. @Autowired 注解是Spring 框架自己的

      3. @Resource 注解默认根据名称装配,未指定name 时,使用属性名作为name。通过name 找不到的话会自动启动通过byType 装配

      4. @Resource 注解用在属性上,setter 方法上

      5. @Autowired 注解用在属性上、setter 方法上、构造方法上、构造方法参数上

        1
        2
        3
        4
        5
        6

        <dependency>
        <groupId>jakarta.annotation</groupId>
        <artifactId>jakarta.annotation-api</artifactId>
        <version>2.1.1</version>
        </dependency>

全注解开发

  • 创建配置类

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan("org.example")
    public class SpringConfig {

    }

JDBCTemplate

  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring6-003</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
    <repository>
    <id>repository.spring.milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
    </repository>
    </repositories>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0-M2</version>
    </dependency>
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.1</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    </dependency>
    <!-- Spring6 的日志依赖 -->
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
    </dependency>
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.19.0</version>
    </dependency>
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.1</version>
    <scope>compile</scope>
    </dependency>

    <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
    </dependency>
    <!-- spring jdbc 依赖: 简单封装了 jdbc -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.0-M2</version>
    </dependency>
    </dependencies>
    </project>
  • 数据库表准备

    • 创建数据库

      1
      CREATE DATABASE `spring6`;
    • 创建表

      1
      2
      3
      4
      5
      CREATE TABLE `tb_user` ( 
      `id` INT NOT NULL AUTO_INCREMENT,
      `real_name` VARCHAR ( 20 ) COLLATE utf8mb4_unicode_ci NOT NULL,
      PRIMARY KEY ( `id` )
      )ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
  • 实体类

    1
    2
    3
    4
    5
    6
    @Data
    public class User {
    private Integer id;
    private String realName;
    }

  • 数据源

    • druid

    • c3p0

    • 自定义数据源实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      package org.example.bean;

      import lombok.Data;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.stereotype.Component;

      import javax.sql.DataSource;
      import java.io.PrintWriter;
      import java.sql.*;
      import java.util.logging.Logger;

      @Data
      @Component
      public class MyDataSource implements DataSource {
      @Value("com.mysql.cj.jdbc.Driver")
      private String driver;
      @Value("jdbc:mysql://localhost:3306/spring6")
      private String url;
      @Value("root")
      private String username;
      @Value("root")
      private String password;


      @Override
      public Connection getConnection() throws SQLException {
      try {
      // 注册驱动
      Class.forName(driver);
      // 获取数据库连接对象
      Connection connection = DriverManager.getConnection(url, username, password);
      return connection;
      } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
      }
      }

      @Override
      public Connection getConnection(String username, String password) throws SQLException {
      return null;
      }

      @Override
      public PrintWriter getLogWriter() throws SQLException {
      return null;
      }

      @Override
      public void setLogWriter(PrintWriter out) throws SQLException {

      }

      @Override
      public void setLoginTimeout(int seconds) throws SQLException {

      }

      @Override
      public int getLoginTimeout() throws SQLException {
      return 0;
      }

      @Override
      public <T> T unwrap(Class<T> iface) throws SQLException {
      return null;
      }

      @Override
      public boolean isWrapperFor(Class<?> iface) throws SQLException {
      return false;
      }

      @Override
      public Logger getParentLogger() throws SQLFeatureNotSupportedException {
      return null;
      }
      }

  • 配置类注入数据源

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @ComponentScan("org.example")
    public class SpringConfig {
    @Bean("jdbcTemplate")
    public JdbcTemplate jdbcTemplate(MyDataSource myDataSource) {
    return new JdbcTemplate(myDataSource);
    }
    }
  • 测试

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void springAnnTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate",JdbcTemplate.class);
    // org.springframework.jdbc.core.JdbcTemplate@299266e2
    System.out.println(jdbcTemplate);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void springAnnTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
    // insert 语句
    String sql = "insert into tb_user(real_name) values(?)";
    // 注意在 JdbcTemplate 中,只要是 insert,update,delete 都调用 update 方法
    int count = jdbcTemplate.update(sql, "aa");
    System.out.println(count);
    }
  • 查询一个对象

    1
    2
    3
    4
    String sql = "select * from tb_user where id=?";
    // 查询 id 为 2 的用户
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
    System.out.println(user);
  • 查询多个对象

    1
    2
    3
    String sql = "select * from tb_user";
    List<User> user = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    System.out.println(user);
  • 查一个值

    1
    2
    3
    String sql = "select count(*) from tb_user";
    int count = jdbcTemplate.queryForObject(sql, int.class);
    System.out.println(count);
  • 批量添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void springAnnTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);

    // 批量添加
    String sql = "insert into tb_user(id,real_name) values(?,?)";

    Object[] obj1 = {null, "Java"};
    Object[] obj2 = {null, "JavaWeb"};
    Object[] obj3 = {null, "Spring6"};
    List<Object[]> list = new ArrayList<>();
    Collections.addAll(list, obj1, obj2, obj3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
    }
    批量插入
  • 批量更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Test
    public void springAnnTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);

    String sql = "update tb_user set real_name=? where id=?";

    Object[] obj1 = {"spring-java", 4};
    Object[] obj2 = {"lisi", 2};
    Object[] obj3 = {"javaweb", 5};
    List<Object[]> list = new ArrayList<>();
    Collections.addAll(list, obj1, obj2, obj3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
    }
  • 批量删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Test
    public void springAnnTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);

    String sql = "delete from tb_user where id=?";

    Object[] obj1 = {4};
    Object[] obj2 = {5};
    Object[] obj3 = {6};
    List<Object[]> list = new ArrayList<>();
    Collections.addAll(list, obj1, obj2, obj3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
    }
  • 回调函数:可以书写JDBC 代码

Druid 数据源

  • 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.11</version>
    </dependency>
  • 配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package org.example.config;

    import com.alibaba.druid.pool.DruidDataSource;
    import jakarta.annotation.Resource;
    import org.example.bean.MyDataSource;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;

    import javax.sql.DataSource;
    import java.time.Duration;

    @Configuration
    @ComponentScan("org.example")
    public class SpringConfig {
    @Bean
    public DataSource dataSource() {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    return dataSource;
    }

    // mybatis 的化需要配置 sqlSessionFactoryBean

    @Bean("jdbcTemplate")
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Bean
    public DataSource dataSource(
    @Value("com.mysql.cj.jdbc.Driver") String driver
    ) {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName(driver);
    dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    return dataSource;
    }

GoF-代理模式

  • 代理模式属于结构型设计模式

  • 代理模式的作用

    为其他对象提供一致代理以控制这个对象的访问。在某些情况下,一个客户端不想或者不能直接引用一个对象,此时可以通过一个称之为代理的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户端不应该看到的内容和服务或者添加客户需要的额外服务。通过引入一个新的对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式机制。

  • 代理模式中的角色

    • 代理类
    • 目标类
    • 代理类和目标类的公共接口: 客户端在使用代理对象就是在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口
  • 代理模式的形式

    1. 静态代理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      package org.example.service.impl;

      import org.example.service.OrderService;

      public class OrderServiceImpl implements OrderService {
      @Override
      public void generate() {
      try {
      Thread.sleep(100);
      System.out.println("订单已生成.............");
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      }

      @Override
      public void modify() {
      try {
      Thread.sleep(200);
      System.out.println("订单已修改.............");
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      }

      @Override
      public void detail() {
      try {
      Thread.sleep(300);
      System.out.println("订单详情.............");
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      // 在上述业务的基础上添加需求: 统计耗时
      package org.example.client;

      import org.example.service.impl.OrderServiceImpl;

      public class Sub extends OrderServiceImpl {
      @Override
      public void generate() {
      Long start = System.currentTimeMillis();
      super.generate();
      Long end = System.currentTimeMillis();
      System.out.println("订单生成耗时: " + (end - start));
      }

      @Override
      public void modify() {
      Long start = System.currentTimeMillis();
      super.modify();
      Long end = System.currentTimeMillis();
      System.out.println("订单修改耗时: " + (end - start));
      }

      @Override
      public void detail() {
      Long start = System.currentTimeMillis();
      super.detail();
      Long end = System.currentTimeMillis();
      System.out.println("查看订单详情耗时: " + (end - start));
      }
      }

      // 虽然实现功能,但是冗余代码过多

      需求实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      // 代理实现
      package org.example.client;

      import org.example.service.OrderService;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

      /**
      * 代理对象: 代理对象和目标对象要具有相同的行为,那么就要实现同一个或者同一些接口
      */
      public class StaticProxy implements OrderService {
      private static final Logger logger = LoggerFactory.getLogger(StaticProxy.class);

      /**
      * 将目标对象作为代理对象的一个属性。这种关系叫做关联关系。比继承关系的耦合度低。
      * 代理对象中还有目标对象的引用。关联关系 has a
      * 注意: 这里一定要是一个公共接口的类型。因为公共接口的耦合度低。
      * private OrderService target = null;
      */
      private OrderService target;

      // 创建代理对象的时候,传一个目标给代理对象
      public StaticProxy(OrderService target) {
      this.target = target;
      }

      @Override
      public void generate() {
      logger.error("StaticProxy generate.........................");
      target.generate();
      }

      @Override
      public void modify() {
      logger.error("StaticProxy modify.........................");
      target.modify();
      }

      @Override
      public void detail() {
      logger.error("StaticProxy detail.........................");
      target.detail();
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      // 客户端
      package org.example.client;

      import org.example.service.OrderService;
      import org.example.service.impl.OrderServiceImpl;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

      public class Client {
      private static final Logger logger = LoggerFactory.getLogger(Client.class);

      public static void main(String[] args) {
      OrderService orderService = new OrderServiceImpl();
      // 1. 项目创建后的运行状态测试
      orderService.generate();
      orderService.modify();
      orderService.detail();
      logger.info("....................................................\n\n");
      // 2. 添加需求: 统计任务的耗时
      OrderService sub = new Sub();
      sub.generate();
      sub.modify();
      sub.detail();
      logger.info("....................................................\n\n");
      // 3. 静态代理实现
      // 创建目标对象
      OrderService target = new OrderServiceImpl();
      // 创建代理对象
      StaticProxy staticProxy = new StaticProxy(target);
      // 调用代理对象的代理方法
      staticProxy.generate();
      staticProxy.detail();
      staticProxy.modify();
      }
      }

    2. 动态代理

      在程序运行阶段,在内存中动态代理生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题

      • 基于JDK 的动态代理实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        package org.example.service.impl;

        import org.example.service.OrderService;

        public class OrderServiceImpl implements OrderService {
        @Override
        public void generate() {
        try {
        Thread.sleep(100);
        System.out.println("订单已生成.............");
        } catch (InterruptedException e) {
        throw new RuntimeException(e);
        }
        }

        @Override
        public void modify() {
        try {
        Thread.sleep(200);
        System.out.println("订单已修改.............");
        } catch (InterruptedException e) {
        throw new RuntimeException(e);
        }
        }

        @Override
        public void detail() {
        try {
        Thread.sleep(300);
        System.out.println("订单详情.............");
        } catch (InterruptedException e) {
        throw new RuntimeException(e);
        }
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        package org.example.client;

        import org.springframework.validation.ObjectError;

        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;

        /**
        * 1. 为什么强行要求你必须实现 InvocationHandler 接口?
        * 因为一个类实现接口就必须实现接口中的方法
        * 而 InvocationHandler 的方法 invoke 就是必须实现的方法。
        * 因为 JDK 在底层调用 invoke() 方法的程序已经提前写好了
        * 注意: invoke 方法不是我们程序负责调用的,而是 JDK 负责调用的
        * 2. invoke 方法被调用的时机
        * 当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法将会被调用
        */
        public class TimerInvocationHandler implements InvocationHandler {
        private Object target;

        public TimerInvocationHandler(Object target) {
        this.target = target;
        }

        /**
        * invoke 方法的三个参数:
        * invoke 方式是 JDK 负责调用的,所以 JDK 调用方法的时候会自动给我们传过来这三个参数
        * 第一个参数: Object proxy 代理对象的引用。这个参数使用比较少
        * 第二个参数: Method method 目标对象上的目标方法(要执行的目标方法就是它)
        * 第三个参数: Object[] args 目标方法上的实参
        */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("\n\ninvoke........................before");
        Object returnValue = method.invoke(target, args);
        System.out.println("invoke........................after\n\n");
        // 注意这个 invoke 方法的返回值,如果代理对象调用代理方法之后,需要返回结果的化,invoke 方法必须将目标对象的目标方法执行结果继续返回
        return returnValue;
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        package org.example.client;

        import org.example.service.OrderService;
        import org.example.service.impl.OrderServiceImpl;
        import org.junit.jupiter.api.Order;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;

        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        import java.lang.reflect.Proxy;

        public class Client {
        private static final Logger logger = LoggerFactory.getLogger(Client.class);

        public static void main(String[] args) {
        // 创建目标
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        /**
        * 1. newProxyInstance: 翻译为 新建代理对象,也就是说,通过调用这个方法可以创建代理对象
        * 本质上,这个 Proxy.newProxyInstance() 方法的执行,做了两件事:
        * 1. 在内存中动态的生成了一个代理类的字节码 class
        * 2. new 对象了。通过内存中生成的代理类这个代码。实例化了代理对象
        *
        * 2. 关于 newProxyInstance() 方法的三个重要参数
        * 第一个参数: ClassLoader loader:
        * 类加载器,作用是: 在内存中商城字节码也就是 class 文件,要执行也得先加载到内存中。加载类就需要类加载器。所以这里就需要指定类加载器
        * 并且 JDK 要求,目标类的类加载器必须和代理类的加载器使用同一个
        * 第二个参数: Class<?>[] insterfaces
        * 代理类和目标类要实现用一个接口或者同一些接口
        * 在内存中生成代理类的时候,这个代理类是需要你告诉他实现那些接口的
        * 第三个参数: InvocationHandler h
        * InvocationHandler 翻译为: 调用处理器。是一个接口
        * 在调用处理器接口中编写的就是: 增强代码
        * 因为具体要增强什么代码,JDK 动态代理计数他是才不到的。
        * 他是一个接口,就需要有实现类。
        */
        OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(
        // 类加载器
        target.getClass().getClassLoader(),
        // 公共接口
        target.getClass().getInterfaces(),
        // 增强需求
        new TimerInvocationHandler(target)
        );

        // 调用代理对象的代理方法
        proxyInstance.detail();
        proxyInstance.generate();
        proxyInstance.modify();
        }
        }

      • 基于CGLIB 的动态代理实现

        CGLIB 既可以做代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final 修饰

        • 实现步骤

          1. 添加依赖

            1
            2
            3
            4
            5
            <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
            </dependency>

面向切面编程 AOP

  • 说明

    IoC 使软件组件松耦合。AOP 让你能够捕捉系统中建厂使用的功能,把他转化成组件。

    AOP(Aspect Oroented Programming): 面向切面编程,面向方面编程(AOP 是一种编程技术)

    AOP 是对OOP 的补充延申

    AOP 底层是使用动态代理来完成的

    Spring AOP 使用的动态代理是: JDK+CGLIB动态代理技术Spring 在这两种动态代理中灵活切换,如果代理是接口,会默认使用JDK 动态代理。如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB

  • AOP

    总结: 将于核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP

    优点:

    1. 代码复用性强
    2. 代码易维护
    3. 使开发者更关注业务逻辑
  • 七大术语

    • 连接点:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置
    • 切点: 在程序执行流程中,真正织入切面的方法
    • 通知
      • 通知又叫增强,就是具体你要织入的代码(增强功能的代码)
      • 通知包括
        • 前置通知: @Before 目标方法执行之前的通知
        • 后置通知: @AfterReturning 目标方法执行之后的通知
        • 环绕通知: @Around 目标方法之前添加的通知,同时目标方法执行之后添加的通知
        • 异常通知: @AfterThrowing 发生在异常之后执行的通知
        • 最终通知: @After 放在finally 语句块中的通知
    • 切面: 切点 + 通知就是切面
  • 切入点表达式

    切点表达式用来定义通知(Advice)往那些方法上切入

    • 语法

      1
      2
      3
      4
      execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

      // []: 可选项(可以写也可以不写)
      // (): 必填项(必须出现)
  • Spring+AspectJ+Annotation(注解) 的使用实现步骤

    1. 添加依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.0.0-M2</version>
      </dependency>

      <!-- apo=>(spring-context 中) -->

      <!-- spring-aspects -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
    2. spring 的配置文件中添加命名空间

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <?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">

      </beans>
      修改原理
    3. 编写目标类并纳入spring 容器管理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package org.example.service;

      import org.springframework.stereotype.Service;

      // 目标类
      @Service
      public class UserService {
      // 目标方法
      public void login() {
      System.out.println("正在验证身份信息............");
      }
      }

    4. 创建切面

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package org.example.aspect;

      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.springframework.stereotype.Component;

      // 切面 = 通知 + 切点
      @Component
      @Aspect
      public class LogAspect {
      /**
      * execution(* org.example.service.UserService.*(..))
      * 权限修饰符: *
      *
      */
      @Before("execution(* org.example.service.UserService.*(..))")
      public void buildUp() {
      System.out.println("我是一个通知,我是一段增强代码...............");
      }
      }

    5. spring 配置

      1
      2
      3
      4
      5
      <!-- 开启包扫描 -->
      <context:component-scan base-package="org.example.service,org.example.aspect"/>
      <!-- 开启 aspectj 的自动代理 -->

      <aop:aspectj-autoproxy/>
    6. 测试

      1
      2
      3
      4
      5
      6
      @Test
      public void testAspect() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
      UserService userService = ac.getBean("userService", UserService.class);
      userService.login();
      }
      输出
  • 先后顺序

    先后顺序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package org.example.aspect;

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;

    // 切面 = 通知 + 切点
    @Component
    @Aspect
    public class LogAspect {

    @Before("execution(* org.example.service.UserService.*(..))")
    public void buildUp() {
    System.out.println("\t前置通知: 我是一个通知,我是一段增强代码...............");
    }

    @AfterReturning("execution(* org.example.service.UserService.*(..))")
    public void afterTest() {
    System.out.println("\t后置通知..............");
    }

    // * org.example..*(..))
    @Around("execution(* org.example.service.UserService.*(..))")
    public void aroundTest(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("前环绕.........");
    // 执行目标
    joinPoint.proceed();
    System.out.println("后环绕.........");
    }
    }

    1
    2
    // 多个切面的执行顺序通过 Order 控制,数字越小,优先级越高。
    @Order(number)
  • 通用切点表达式

    1
    2
    3
    4
    5
    // 本类和其他类(需要添加全限定)都可以使用

    // 定义通用切点
    @Pointcut("execution(* org.example.service.OrderService.*(..))")
    public void pointcut(){}
    1
    2
    // 使用通用切点 通用切点方法的调用
    @Before("pointcut()")

AOP-基于全注解开发

  • 创建配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.example.config;

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;

    /**
    * <context:component-scan base-package="org.example"/>
    * <aop:aspectj-autoproxy/>
    */
    @Configuration // => application.xml
    @ComponentScan({"org.example"}) // <context:component-scan base-package="org.example"/>
    @EnableAspectJAutoProxy // <aop:aspectj-autoproxy/>
    public class SpringConfig {

    }

  • 测试

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void testAspect() {
    // 传入 配置类的字节码
    ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = ac.getBean("userService", UserService.class);
    userService.login();
    }

AOP-编程事务的解决方案

  • 业务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.example;

    import org.springframework.stereotype.Service;

    @Service
    public class AccountService {
    // 转账
    public void transfer() {
    System.out.println("\t\t转账执行中...................................");
    }

    // 取款
    public void withDraw() {
    System.out.println("\t\t正在取款,请稍后............................");
    }

    }
  • 切面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package org.example.aspect;

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class TransAspect {
    // 环绕通知
    @Around("execution(* org.example..*(..))")
    public void aroundAspect(ProceedingJoinPoint joinPoint) {
    try {
    System.out.println("开启事务....................");
    // 执行目标方法
    joinPoint.proceed();
    System.out.println("事务提交...................");

    } catch (Throwable e) {
    System.out.println("事务回滚.................");
    throw new RuntimeException(e);
    }
    }
    }

  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import org.example.AccountService;
    import org.example.aspect.TransAspect;
    import org.example.config.SpringConfig;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    public class TestTransAspect {
    @Test
    public void testTransAspect() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    AccountService accountService =ac.getBean("accountService", AccountService.class);
    // 目标方法
    accountService.transfer();
    accountService.withDraw();
    }
    }

AOP-安全日志

  • 需求

    项目开发结束了,已经上线了。运行正常。客户提出了新需求: 凡是在系统中进行修改操作的、新增操作的,都要把这个人记录下来。因为这属于危险行为。

    记录操作事件
  • 业务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package org.example;

    import org.springframework.stereotype.Service;

    @Service
    public class UserService {
    public void saveUser() {
    System.out.println("新增用户.....................");
    }

    public void deleteUser() {
    System.out.println("删除用户.....................");
    }

    public void updateUser() {
    System.out.println("修改用户.....................");
    }

    public void showUser() {
    System.out.println("查询用户.....................");
    }
    }

  • 配置

  • 切面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package org.example.aspect;

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;

    import java.text.SimpleDateFormat;
    import java.util.Date;

    @Aspect
    @Component
    public class SecAspect {
    @Pointcut("execution(* org.example..save*(..))")
    public void savePointcut() {
    }

    @Pointcut("execution(* org.example..update*(..))")
    public void updatePointcut() {
    }

    @Pointcut("execution(* org.example..delete*(..))")
    public void deletePointcut() {
    }

    @Before("savePointcut() || updatePointcut() || deletePointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
    String nowTime = sdf.format(new Date());
    System.out.println(nowTime + " coder-itl: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    }
    }

  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 测试安全日志
    @Test
    public void testSecurityAspect() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = ac.getBean("userService", UserService.class);
    userService.deleteUser();
    userService.saveUser();
    userService.updateUser();
    }

事务

使用步骤
  • 事务的四个处理过程
    1. 开启事务
    2. 执行核心业务代码
    3. 提交事务(如果核心业务处理过程中没有出现异常)
    4. 回滚事务(如果核心业务处理过程中出现异常)
事务案例演示
  • 数据库表准备

    • 数据库spring6

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      CREATE TABLE `tb_act`
      (
      `actno` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号',
      `balance` double DEFAULT NULL COMMENT '余额',
      PRIMARY KEY (`actno`)
      ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8mb4
      COLLATE = utf8mb4_unicode_ci

      -- 测试数据
      insert into tb_act values('act-001',50000);
      insert into tb_act values('act-002',0);

  • 三层

    • domain

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      package org.example.domain;

      import lombok.Data;

      @Data
      public class Account {
      private String actno;
      private Double balance;
      }

    • dao

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      package org.example.dao.impl;

      import jakarta.annotation.Resource;
      import jakarta.annotation.Resources;
      import org.example.dao.AccountDao;
      import org.example.domain.Account;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.jdbc.core.BeanPropertyRowMapper;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.stereotype.Repository;

      @Repository("accountDao")
      public class AccountDaoImpl implements AccountDao {
      @Resource(name = "jdbcTemplate")
      private JdbcTemplate jdbcTemplate;

      @Override
      public Account selectByActno(String acton) {
      String sql = "select * from tb_act where actno=?";
      return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), acton);
      }

      @Override
      public int update(Account act) {
      String sql = "update tb_act set balance=? where actno=?";
      return jdbcTemplate.update(sql, act.getBalance(), act.getActno());
      }
      }

    • service

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      package org.example.service.impl;

      import jakarta.annotation.Resource;
      import org.example.dao.AccountDao;
      import org.example.domain.Account;
      import org.example.service.AccountService;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;


      /**
      * 转账业务
      * 事务就在此处
      */
      @Service("accountService")
      public class AccountServiceImpl implements AccountService {
      private static final Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
      @Autowired
      private AccountDao accountDao;

      @Override
      @Transactional
      public void transfer(String fromActno, String toActno, double money) {
      // 查询转出账户的余额是否充足
      Account fromAct = accountDao.selectByActno(fromActno);
      if (fromAct.getBalance() < money) {
      throw new RuntimeException("余额不足............");
      }
      // 余额充足
      Account toAct = accountDao.selectByActno(toActno);

      // 将内存中两个对象的余额先修改
      fromAct.setBalance(fromAct.getBalance() - money);
      toAct.setBalance(toAct.getBalance() + money);

      // 数据库更新
      int count = accountDao.update(fromAct);

      // TODO: 模拟异常
      logger.error("error: ...............................");
      int num = 10 / 0;
      logger.error("error: ................................");

      // count = count + accountDao.update(toAct) => 成功一条为 1,两条成功记录就是 2
      count += accountDao.update(toAct);

      if (count != 2) {
      throw new RuntimeException("转账失败................");
      }
      }
      }

    • 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      // 全注解 Java 类配置
      package org.example.config;

      import com.alibaba.druid.pool.DruidDataSource;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      import org.springframework.jdbc.core.JdbcTemplate;

      import javax.sql.DataSource;

      @Configuration
      @ComponentScan("org.example")
      @EnableAspectJAutoProxy
      public class SpringConfig {
      @Bean
      public DataSource dataSource() {
      DruidDataSource dataSource = new DruidDataSource();
      dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
      dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
      dataSource.setUsername("root");
      dataSource.setPassword("root");
      return dataSource;
      }

      @Bean("jdbcTemplate")
      public JdbcTemplate jdbcTemplate(DataSource dataSource) {
      return new JdbcTemplate(dataSource);
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      <!-- xml 配置 -->
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
      https://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

      <context:component-scan base-package="org.example"/>
      <!-- 配置数据源 -->
      <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
      <property name="username" value="root"/>
      <property name="password" value="root"/>
      </bean>
      <!-- jdbcTemplate -->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      <!-- 注入数据源 -->
      <property name="dataSource" ref="ds"/>
      </bean>
      <!-- 配置事务管理器 -->
      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="ds"/>
      </bean>
      <!-- 开启事务注解驱动器 -->
      <tx:annotation-driven transaction-manager="txManager"/>
      </beans>
    • 测试转账

      1
      2
      3
      4
      5
      6
      7
      8
      // 配置采用了 XML
      @Test
      public void testTransfer() {
      ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
      AccountServiceImpl accountServiceImpl = ac.getBean("accountServiceImpl", AccountServiceImpl.class);
      accountServiceImpl.transfer("act-001", "act-002", 800);
      }

  • 模拟异常

    添加异常代码 运行测试

    出现右边的结果是非常不理想的

  • Spring 对事物的支持

    • Spring 实现事务的两种方式

      • 编程式事务: 通过编写代码的方式来实现事务的管理
      • 声明式事务
        • 基于注解方式
        • 基于XML 配置方式
    • Spring 事务管理 API

      Spring 对事务的管理底层实现方式是基于AOP 实现的。采用AOP 的方式进行了封装。所以Spring 专门针对事务开发了一套API

      API:如果在spring6 中使用jdbcTemplate,就要使用DataSourceTransactionManager 来管理事务
声明式事务之注解实现方式
  • 第一步:在spring 配置文件中配置事务管理器

    1
    2
    3
    4
    <!--  配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds"/>
    </bean>
  • 第二步:在spring 配置文件中引入tx 命名空间

    1
    2
    3
    xmlns:tx="http://www.springframework.org/schema/tx"

    http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
  • 第三步:在spring 配置文件中配置事务注解驱动器”,开始注解的方式控制事务

    1
    2
    <!-- 开启事务注解驱动器 -->
    <tx:annotation-driven transaction-manager="txManager"/>
  • 第四步:在service 类上或方法上添加@Transactional 注解

事务的传播行为
  • 事务中的的重点属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public @interface Transactional {
    // 传播行为
    Propagation propagation() default Propagation.REQUIRED;
    // 隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    // 事务超时
    int timeout() default -1;
    // 只读事务
    boolean readOnly() default false;
    // 设置出现那些异常回滚事务
    Class<? extends Throwable>[] rollbackFor() default {};
    // 设置出现那些异常不回滚事务
    Class<? extends Throwable>[] noRollbackFor() default {};
    }
    • 传播行为是什么

      service 类中有a 方法和b 方法,两个方法各自都有事务,当a 方法在执行过程中调用了b 方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务? 这就是事务的传播行为。

  • 一共七种传播行为(*是重要标记)

    • REQUIRED*: 支持当前事务,如果不存在就新建一个(`默认`)【**没有就新建,有就加入**】
    • SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行【**有就加入,没有就不管了**】

    • MANDATORY: 必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【**有就加入,没有就抛出异常**】

    • REQUIRES_NEW*: 开启一个新事物,如果一个事务已经存在,则将这个存在的事务挂起【**不管有没有,直接开启一个新事务,开启的新事物和之前的事务不存在嵌套关系,之前事务被挂起**】

    • NOT_SUPPORTED: 以非事务方式运行,如果有事务存在

    • NEVER: 以非事务方式运行,如果有事务存在
    • NESTED: 如果当前正有一个事务正在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像 REQUIRED一样 【**有事务的话,就在这个事务里在嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样**】
  • 事务的隔离级别

    • 事务的隔离级别类似于教室A 和教室B 之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好

    • 数据库中数据存在的三大问题

      1. 脏读: 读取到没有提交到数据库的数据,叫做脏读
      2. 不可重复读: 在同一个事务当中,第一次和第二次读取的数据不一样
      3. 幻读: 读取到的数据是假的
    • 事务的隔离级别包括四个隔离级别

      • 读未提交: READ_UNCOMMITED

        这种隔离界别,存在脏读问题,所谓的脏读表示能够读取到其他事务未提交的数据

      • 读提交:read_committed

        解决了脏读问题,其他事务提交之后才能读到,但存在不可重复度问题

      • 可重复读:repeatable_read

        解决了不可重复读,可以到可重读效果,只要当前事务不结束,读取到的数据一致都是一样的。但存在幻读问题

      • 序列化: Serializable

        解决了幻读问题,事务排队执行。不支持并发。

        1
        2
        3
        4
        5
        6
        7
        8

        public enum Isolation {
        DEFAULT(-1), // 默认的隔离级别
        READ_UNCOMMITTED(1),
        READ_COMMITTED(2), // ORACLE 的隔离级别
        REPEATABLE_READ(4), // MYSQL 的隔离级别
        SERIALIZABLE(8);
        }
  • 事务的超时

    1
    @Transactional(timeout = 10)

    timeout = 10 代表设置事务的超时时间为10s

    表示超过10s 如果该事务中所有的DML 语句还没有执行完毕的话,最终结果会选择回滚

    默认值-1,表示没有时间限制

    • 细节注意

      事务的超时时间指的是那段时间?

      在当前事务当中,最后一条DML 语句执行之前的语句。如果最后一条DML 语句后面还有很多业务逻辑,这些业务代码执行的时间不计入超时时间。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //  以下代码不会计入超时时间内
      @Transactional(timeout = 10) // 设置事务超时时间为10秒。
      public void save(Account act) {
      accountDao.insert(act);
      // 睡眠一会
      try {
      Thread.sleep(1000 * 15);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      // 是因为后续无相关 DML 语句执行
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 从上到下的执行,到 DML 语句执行结束都计入超时时间
      @Transactional(timeout = 10) // 设置事务超时时间为10秒。
      public void save(Account act) {
      // 睡眠一会
      try {
      Thread.sleep(1000 * 15);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      accountDao.insert(act);
      }

      当然,如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。

  • 只读事务

    1
    @Transactional(readOnly = true)

    将当前事务设置为只读事务,在该事务执行过程中允许select 语句执行,delete、insert、update 均不可执行

    该特性的作用是: 启动 spring 的优化策略。提高select 语句的执行效率

    如果该事务中确实没有增删改操作,建议设置为只读事务

  • 设置遇到那些异常回滚

    1
    2
    @Transactional(rollbackFor = RuntimeException.class)
    // 表示只有发生 RuntimeException 异常或该异常的子类异常才回滚。
  • 设置遇到那些异常不回滚

    1
    2
    @Transactional(noRollbackFor = NullPointerException.class)
    // 表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
事务的全注解开发
  • 根据xml 配置文件修改为配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="org.example"/>
    <!-- 配置数据源 -->
    <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </bean>
    <!-- jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 注入数据源 -->
    <property name="dataSource" ref="ds"/>
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds"/>
    </bean>
    <!-- 开启事务注解驱动器 -->
    <tx:annotation-driven transaction-manager="txManager"/>
    </beans>
  • 对应的配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // xml 配置到对应的 java 类的过程中,我们只需要明白返回值与参数的作用,就可以轻松实现转换
    package org.example.config;

    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;

    @Configuration // 1. application.xml
    @ComponentScan("org.example") // 2. 开启包扫描
    @EnableTransactionManagement // 6. 开启事务注解驱动器
    public class SpringConfig {
    // 3. 配置数据源
    @Bean
    public DruidDataSource dataSource() {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    return dataSource;
    }

    // 4. jdbcTemplate 配置数据源
    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource dataSource) {
    return new JdbcTemplate(dataSource);
    }

    // 5. 配置事务管理器
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DruidDataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
    }
    }

    xmlconfig类转换理解
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void testTransfer() {
    // ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
    // 传入配置类的字节码等同于传入配置文件
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    AccountService accountService = ac.getBean("accountService", AccountService.class);
    try {
    accountService.transfer("act-001", "act-002", 800);
    System.out.println("转账成功");
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
声明式事务之 XML 实现方式
  • 配置步骤

    • 第一步: 配置事务管理器
    • 第二步: 配置通知
    • 第三步: 配置切面
  • 总体配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="org.example"/>
    <!-- 配置数据源 -->
    <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </bean>
    <!-- jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 注入数据源 -->
    <property name="dataSource" ref="ds"/>
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds"/>
    </bean>


    <tx:advice id="txAdvice" transaction-manager="txManager">
    <!-- 配置通知相关的属性 -->
    <tx:attributes>

    <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
    <!-- 可以采用通配符方式配置 -->
    <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
    <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
    </tx:attributes>
    </tx:advice>
    <!-- 配置切面 -->
    <aop:config>
    <!-- 切点 -->
    <aop:pointcut id="txPointcut" expression="execution(* org.example..*(..))"/>

    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    </beans>

Spring6 整合 JUnit5

SpringJUnit4 的支持
  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    <repositories>
    <repository>
    <id>repository.spring.milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
    </repository>
    </repositories>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0-M2</version>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.0-M2</version>
    </dependency>
    <!-- junit4 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
    </dependency>

    </dependencies>
  • 创建Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package org.example.bean;

    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    @Data
    @Component
    public class User {
    @Value("张三")
    private String name;
    }

  • 创建配置

    1
    <context:component-scan base-package="org.example"/>
  • 创建测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 由于 spring 对 junit 的支持,可以简化如下代码
    package org.example.test;

    import org.example.bean.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;


    public final class UserTest {
    @Test
    public void testUser() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
    User user = ac.getBean("user", User.class);
    System.out.println(user);
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 简化后的测试
    package org.example;

    import org.example.bean.User;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:application.xml")
    public class UserTest {
    @Autowired
    private User user;

    @Test
    public void testUser() {
    System.out.println(user.getName());
    }

    }

SpringJUnit5 的支持
  • 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
    <repository>
    <id>repository.spring.milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
    </repository>
    </repositories>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0-M2</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.0-M2</version>
    </dependency>
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.0-M1</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
  • JUnit 的基础上,变动测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package org.example;

    import org.example.bean.User;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit.jupiter.SpringExtension;


    @ExtendWith(SpringExtension.class)
    @ContextConfiguration("classpath:application.xml")
    public class UserTest {
    @Autowired
    private User user;

    // JUnit5 的包路径: import org.junit.jupiter.api.Test;
    @Test
    public void testUser() {
    System.out.println(user.getName());
    }
    }

  • 配置类的加载

    • 配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      package org.example.config;

      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;

      @Configuration
      @ComponentScan("org.example")
      public class SpringConfig {

      }

    • 简化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @ExtendWith(SpringExtension.class)
      @ContextConfiguration(classes = SpringConfig.class)
      public class UserTest {
      @Autowired
      private User user;

      // JUnit5 的包路径: import org.junit.jupiter.api.Test;
      @Test
      public void testUser() {
      System.out.println(user.getName());
      }
      }

集成 Mybatis

  • 实现步骤

    • 第一步: 准备数据库表: tb_act(账户表)

      1
      2
      3
      4
      5
      6
      7
      create database spring6;

      create table tb_act
      (
      actno varchar(255) not null comment '账号' primary key,
      balance double null comment '余额'
      );
    • 第二步: IDEA 中创建一个模块,并引入依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://maven.apache.org/POM/4.0.0"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>

      <groupId>org.example</groupId>
      <artifactId>springmybatis</artifactId>
      <version>1.0-SNAPSHOT</version>

      <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
      <java.version>17</java.version>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      </properties>
      <!-- 配置多个仓库 -->
      <repositories>
      <repository>
      <id>repository.spring.milestone</id>
      <name>Spring Milestone Repository</name>
      <url>https://repo.spring.io/milestone</url>
      </repository>
      </repositories>

      <dependencies>

      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
      <!-- start: 做 spring 事务 -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.23</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
      <!-- end: 做 spring 事务 -->
      <!-- mysql 连接驱动 -->
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
      </dependency>
      <!-- mybatis 依赖 -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
      </dependency>
      <!-- spring 和 mybatis 整合 -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.7</version>
      </dependency>
      <!-- 数据库连接池 -->
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.11</version>
      </dependency>
      <!-- 单元测试 -->
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>6.0.0-M2</version>
      </dependency>
      <!-- 简化实体类 -->
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      </dependency>
      <!-- 日志 -->
      <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.11</version>
      </dependency>

      </dependencies>
      <build>
      <!-- 打包后的名称 -->
      <finalName>coder-itl</finalName>
      <!-- 资源文件扫描的配置 -->
      <resources>
      <resource>
      <directory>src/main/java</directory>
      <includes>
      <include>**/*.xml</include>
      <include>**/*.properties</include>
      </includes>
      </resource>
      <resource>
      <directory>src/main/resources</directory>
      <includes>
      <include>**/*.xml</include>
      <include>**/*.properties</include>
      </includes>
      </resource>
      </resources>
      </build>
      </project>
    • 第三步: 基于三层架构实现,创建相关包结构

      • mapper
      • service,impl
      • entity

    • 第四步: 编写entity,实现JavaBean

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package com.example.entity;


      import lombok.Data;

      import java.io.Serializable;

      /**
      * @TableName tb_act
      */
      @Data
      public class Act implements Serializable {
      private String actno;
      private Double balance;

      }
    • 第五步: 编写mapper 接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package com.example.mapper;

      import com.example.entity.Act;

      import java.util.List;


      public interface ActMapper {
      List<Act> findAll();

      Act findByActNo(String actno);

      // 添加账户信息
      int createAct(Act act);

      // 删除账户信息
      int removeByActNo(String actno);

      int updateByAct(Act act);
      }

    • 第六步: 编写mapper 配置文件,在配置文件中配置命名空间,以及每个方法对应的sql

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.example.mapper.ActMapper">

      <!-- List<Act> findAll(); -->
      <select id="findAll">
      select *
      from tb_act;
      </select>
      <!-- Act findByActNo(String actno); -->
      <select id="findByActNo" parameterType="string" resultType="com.example.entity.Act">
      select *
      from tb_act
      where actno = #{actno}
      </select>
      <!-- int createAct(Act act); -->
      <insert id="createAct" parameterType="com.example.entity.Act">
      insert into tb_act
      values (#{actno}, #{balacne});
      </insert>
      <!-- int removeByActNo(String actno); -->
      <delete id="removeByActNo" parameterType="string">
      delete
      from tb_act
      where actno = #{actno}
      </delete>
      <!-- int updateByAct(Act act); -->
      <update id="updateByAct" parameterType="com.example.entity.Act">
      update tb_act
      set balance=#{balance}
      where actno = #{actno}
      </update>
      </mapper>

    • 第七步: 编写service service 接口实现类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 接口
      package com.example.service;

      import com.example.entity.Act;

      import java.util.List;

      public interface ActService {
      int save(Act act);
      int deletebyActNo(String actno);
      int modify(Act act);
      Act getByActNo(String actno);
      List<Act> getAll();
      void transfer(String fromACtno, String toActno, double monry);
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      package com.example.service.impl;

      import com.example.entity.Act;
      import com.example.mapper.ActMapper;
      import com.example.service.ActService;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;

      import java.util.List;

      @Transactional
      @Service("actService")
      @Slf4j
      public class ActServiceImpl implements ActService {
      @Autowired
      private ActMapper actMapper;

      @Override
      public int save(Act act) {
      return actMapper.createAct(act);
      }

      @Override
      public int deletebyActNo(String actno) {
      return actMapper.removeByActNo(actno);
      }

      @Override
      public int modify(Act act) {
      return actMapper.updateByAct(act);
      }

      @Override
      public Act getByActNo(String actno) {
      return actMapper.findByActNo(actno);
      }

      @Override
      public List<Act> getAll() {
      return actMapper.findAll();
      }

      @Override
      public void transfer(String fromACtNo, String toActNo, double money) {
      Act fromAct = actMapper.findByActNo(fromACtNo);
      if (fromAct.getBalance() < money) {
      throw new RuntimeException("余额不足");
      }
      Act toAct = actMapper.findByActNo(toActNo);
      fromAct.setBalance(fromAct.getBalance() - money);
      toAct.setBalance(toAct.getBalance() + money);
      int count = actMapper.updateByAct(fromAct);
      count += actMapper.updateByAct(toAct);
      if (count != 2) {
      throw new RuntimeException("转账失败");
      }
      }
      }

    • 第八步: 编写jdbc.properties 配置文件,配置数据库连接池相关的信息

      1
      2
      3
      4
      5
      # 8.0 resource/jdbc.properties
      jdbc.driverClassName=com.mysql.cj.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true
      jdbc.username=root
      jdbc.password=root
    • 第九步: 编写mybatis-config.xml 配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "https://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
      <settings>
      <setting name="logImpl" value="SLF4J"/>
      </settings>
      </configuration>
    • 第十步: 编写spring.xml 配置文件

      • 组件扫描

      • 引入外部属性文件

      • 数据源

      • SqlSessionFactroyBean 配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        <?xml version="1.0" encoding="UTF-8"?>
        <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="http://www.springframework.org/schema/beans"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!-- 1. 组件扫描 -->
        <context:component-scan base-package="com.example"/>
        <!-- 2. 引入外部属性文件 -->
        <context:property-placeholder location="classpath*:jdbc.properties"/>
        <!-- 3. 配置数据源 -->
        <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        </bean>

        <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 配置数据源 -->
        <property name="dataSource" ref="myDataSource"/>
        <!--
        注入 mybatis 核心配置文件路径
        configLocation属性是 Resource类型,作用是: 读取配置文件
        它的赋值,使用 value,指定文件的路径,使用 classpath: 表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        </bean>
        <!--
        创建 dao 对象,使用 SqlSession 的 getMapper(XXXDao/XXXMapper.class)
        MapperScannerConfigurer: 在内部调用 getMapper()生成每个 dao 接口的代理对象
        -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定 SqlSessionFactory 对象的 id -->
        <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
        <!--
        指定包名,包名是 dao/mapper 接口所在的包名
        MapperScannerConfigurer 会扫描这个包中所有的接口,把每个接口都执行一次 getMapper() 方法,得到每个接口的 dao 对象
        创建好的 dao 对象放入到 spring 的容器中
        -->
        <property name="basePackage" value="com.example.mapper"/>
        </bean>
        <!-- 6. 配置事务管理器 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource"/>
        </bean>
        <!-- 启用事务管理器 -->
        <tx:annotation-driven transaction-manager="txManager"/>
        </beans>
      • 转账测试

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        public class TestSM {
        @Test
        public void testSM() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        String[] definitionNames = ac.getBeanDefinitionNames();
        for (String definitionName : definitionNames) {
        System.out.println(definitionName);
        }
        ActService actService = ac.getBean("actService", ActService.class);
        actService.transfer("act-001", "act-002", 800);
        }
        }
        事务控制测试

Spring 中的八大设计模式

  • 简单工厂模式

    • BeanFactory getBean() 方法,通过唯一标识来获取Bean 对象。是典型的简单工厂模式(静态工厂模式)
  • 工厂方法模式

    • FactoryBean 是典型的工厂方法模式。在配置文件中通过factory-method 属性来指定工厂方法,该方法是一个实例方法
  • 单例模式

    • Spring 用的是双重判断加锁的单例模式
  • 代理模式

    • SpringAOP 就是使用了动态代理实现的
  • 装饰器模式

    • JavaSE 中的IO 流是非常典型的装饰器模式

      Spring 中配置DataSource 的时候,这些dataSource 可能是各种不同类型的,比如不同的数据库,也可能是不同的数据源。这时,能否在尽可能少修改原有类代码的情况下,做到动态切换不同的数据源?此时就可以用到装饰器模式。

      Spring 根据每次请求的不同,将dataSource 属性设置成不同的数据源,以达到切换数据源的目的。

      Spring 中类名中带有:Decorator Wrapper 单次的类,都是装饰器模式。

  • 观察者模式

    定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并自动更新。Spring 中观察者模式一般用在listener 的实现

    Spring 中的事件编程模型就是观察者模式的实现。在Spring 中定义了一个ApplicationListener 接口。用来监听Application 的事件,Application 其实就是ApplicationContext,ApplicationContext 内置了几个事件,其中比较容易理解的是: ContextRefreshedEvent,ContextStartedEvent,ContextStoppedEvent,ContextCloseEvent

  • 策略模式

    策略模式就是行为性模式,调用不同的方法,适应行为的变化,强调父类的调用子类的特性。

    getHandler HandlerMapping 接口中的唯一方法,用于根据请求封装找到匹配的处理器

    比如我们自己写的AccountDao 接口,然后这个接口下有不同的实现类:AccountDaoForMySQL,AccountDaoOracle.对于Service 来说不需要关心底层具体的实现,只需要面向AccountDao 接口调用,底层可以灵活切换实现,这就是策略模式

  • 模板方法模式

    Spring 中的jdbcTemplate 类九四一个模板类。他就是一个模板方法设计模式的体现。在模板类的模板方法execute 中编写核心算法,具体的实现步骤在子类中完成。