Detailed explanation of Spring's most complete introductory tutorial

Table of contents

1. Introduction to Spring Framwork

The Spring basic framework can be regarded as the Spring infrastructure, and basically any other Spring project is based on the SpringFramework .

1. Five functional modules of Spring Framework

insert image description here

Module structure diagram of Spring 5:
insert image description here

2. Spring Framework Characteristics

insert image description here

2. IOC container

lOC: Inversion of Control, the translation is inversion of control. Hand over the object creation and calling process between objects to Spring for management

1. IOC idea

insert image description here

2. Implementation of IOC container in Spring

Spring's IOC container is a landing product implementation of the IOC idea. Components managed in an IOC container are also called beans. Before creating a bean, you first need to create an IOC container.

Spring provides two implementations of the IOC container:
insert image description here
(1) BeanFactory: The basic implementation of the IOC container is the internal interface of Spring. It does not provide developers with the ability to
create objects when loading configuration files, and only uses (obtains) objects will only be created when

(2) ApplicationContext: The sub-interface of the BeanFactory interface, which provides more powerful functions and is generally used by developers. When the configuration file is loaded, the objects in the configuration file will be created

insert image description here
insert image description here

The underlying principle of IOC: xml parsing, factory mode, reflection

insert image description here
insert image description here

3. Bean management based on xml

3.1 Introducing dependencies

<dependencies>
  <!--基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.19</version>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
</dependencies>

3.2 Creating classes

package com.fd.spring.pojo;

public interface Person {
    
    
}

package com.fd.spring.pojo;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-06-05
 */

public class Student implements Person{
    
    

    private Integer id;

    private String name;

    private Integer age;

    private String gender;

    public Student() {
    
    
    }

    public Student(Integer id, String name, Integer age, String gender) {
    
    
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

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

    public Integer getAge() {
    
    
        return age;
    }

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

    public String getGender() {
    
    
        return gender;
    }

    public void setGender(String gender) {
    
    
        this.gender = gender;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

3.3 Create a Spring configuration file

<?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:配置一个bean对象,将对象交给IOC容器管理
    属性:
        id: bean的唯一标识,不能重复
        class: 设置bean对象所对应的类型
    -->
    <bean id="studentOne" class="com.fd.spring.pojo.Student"></bean>

    <!--<bean id="studentTwo" class="com.fd.spring.pojo.Student"></bean>-->

</beans>

3.4 Create a test class

@Test
public void studentTest() {
    
    
    /*
    *  获取bean的三种方式:
    * 1、根bean的id获取
    * 2、根bean的类型获取
    *   注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
    *   若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
    *   若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException
    * 3、根据bean的id和类型获取
    *   结论:根据类型来获取bean时,在满足bean唯一性的前提下其实只是看:[对象 instanceof 指定的类型]的返回结果只要返回的是true就可以认定为和类型匹配,能够获取到。
    *   即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
    *
    * */
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    // 根据bean的id获取bean
    Student studentOne = (Student)applicationContext.getBean("studentOne");
    System.out.println(studentOne);
    // 根据bean的类型获取bean, 注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
    Student bean = applicationContext.getBean(Student.class);
    System.out.println(bean);
    // 根据bean的id和类型来获取bean
    Student one = applicationContext.getBean("studentOne", Student.class);
    System.out.println(one);

    // 通过接口获取
    Person person = applicationContext.getBean(Person.class);
    System.out.println(person);
}

3.5 Summary

Three ways to obtain beans:
* 1. Obtain the id of the root bean
* 2. Obtain the type of the root bean
* Note: When obtaining beans according to the type, it is required that there is one and only one bean that matches the type in the IOC container
* If there is no type Matching bean, an exception is thrown at this time: NoSuchBeanDefinitionException
* If there are multiple beans that match the type, an exception is thrown at this time: NoUniqueBeanDefinitionException
* 3. Obtain according to the bean id and type
* Conclusion: When obtaining beans according to the type, when the bean is satisfied On the premise of the uniqueness of the bean, it is actually just to look at: As long as the return result of [the type specified by the object instanceof] returns true, it can be considered as matching the type and can be obtained.
* That is, the bean can be obtained through the type of the bean, the type of the class inherited by the bean, and the type of the interface implemented by the bean
insert image description here

4. DI dependency injection

4.1 setter injection

Spring configuration file

<bean id="studentOne" class="com.fd.spring.pojo.Student">
    <!--
        property:通过成员变量的setXxx()方法进行赋值
        name:设置需要赋值的属性名 (和set方法有关)
        value:设置为属性所赋的值
    -->
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
</bean>

Test Methods:

@Test
public void DiTest() {
    
    
    // 获取IOC容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Student studentOne = (Student)ioc.getBean("studentOne");
    System.out.println(studentOne);
}

4.2 Constructor injection

Spring configuration file

<bean id="studentTwo" class="com.fd.spring.pojo.Student">
    <constructor-arg name="id" value="1002" type="int"></constructor-arg>
    <constructor-arg name="age" value="28"></constructor-arg>
    <constructor-arg name="gender" value=""></constructor-arg>
    <constructor-arg name="name" value="丽丽"></constructor-arg>
</bean>

Test Methods:

@Test
public void DiConstructorTest() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Student studentTwo = applicationContext.getBean("studentTwo", Student.class);
    System.out.println(studentTwo);
}

4.3 Special value assignment

  • literal assignment

What is a literal?
int a = 10:
Declare a variable a and initialize it to 10. At this time, a does not represent the letter a, but serves as the name of a variable. When we reference a, we actually get the value 10.
And if a is quoted: 'a', then it is not a variable now, it just represents the letter a itself, which is the literal quantity. Therefore, the most literal meaning is the data itself that we see.

<constructor-arg name="name" value="丽丽"></constructor-arg>
  • null value
<bean id="studentThree" class="com.fd.spring.pojo.Student">
    <constructor-arg name="age">
        <null/>
    </constructor-arg>
</bean>
  • xml entity

insert image description here

  • CDATA section: its contents are parsed as-is

insert image description here
The CDATA section is a special tag in xml, so it cannot be written in an attribute.

<bean id="studentFour" class="com.fd.spring.pojo.Student">
    <!--
        property:通过成员变量的setXxx()方法进行赋值
        name:设置需要赋值的属性名 (和set方法有关)
        value:设置为属性所赋的值
    -->
    <property name="id" value="1004"></property>
    <property name="name">
        <value><![CDATA[<张二麻子>]]></value>
    </property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
</bean>

Test Results:
insert image description here

  • Attribute assignment of class type

1. Reference the id of the external Bean

<bean id="studentFive" class="com.fd.spring.pojo.Student">
    <property name="id" value="1005"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
    <!--ref: 引用IOC容器中的某个bean的id-->
    <property name="dept" ref="deptOne"></property>
</bean>

<bean id="deptOne" class="com.fd.spring.pojo.Dept">
    <property name="deptId" value="1"></property>
    <property name="deptName" value="1班"></property>
</bean>

insert image description here
2. Assignment by cascading

<bean id="studentFive" class="com.fd.spring.pojo.Student">
    <property name="id" value="1005"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
    <!--ref: 引用IOC容器中的某个bean的id-->
    <property name="dept" ref="deptOne"></property>
    <!--级联的方式,要保证提前为clazz类对象属性赋值或者实例化-->
    <property name="dept.deptId" value="2"></property>
    <property name="dept.deptName" value="2班"></property>
</bean>

<bean id="deptOne" class="com.fd.spring.pojo.Dept">
    <property name="deptId" value="1"></property>
    <property name="deptName" value="1班"></property>
</bean>

insert image description here
3. Internal beans

<bean id="studentFive" class="com.fd.spring.pojo.Student">
    <property name="id" value="1005"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
    <property name="dept">
        <!--内部bean,只能在当前bean的内部使用,不能直接通过IOC容器获取-->
        <bean id="deptTwo" class="com.fd.spring.pojo.Dept">
            <property name="deptId" value="3"></property>
            <property name="deptName" value="3班"></property>
        </bean>
    </property>
</bean>

insert image description here

  • Value type property assignment
<bean id="studentSix" class="com.fd.spring.pojo.Student">
    <property name="id" value="1005"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
    <property name="hobby">
        <array>
            <value>学习</value>
            <value>吃饭</value>
        </array>
    </property>
</bean>

Test Methods:

@Test
public void DiTest1() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Student studentSix = applicationContext.getBean("studentSix", Student.class);
    System.out.println(studentSix);
}

insert image description here

  • List collection type attribute assignment

1. Cascade assignment

<bean id="deptTwo" class="com.fd.spring.pojo.Dept">
    <property name="deptId" value="2"></property>
    <property name="deptName" value="2班"></property>
    <property name="students">
        <list>
            <ref bean="studentOne"></ref>
            <ref bean="studentTwo"></ref>
            <ref bean="studentThree"></ref>
         </list>
    </property>
</bean>

Test Methods:

@Test
public void DiTest2() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Dept deptTwo = applicationContext.getBean("deptTwo", Dept.class);
    System.out.println(deptTwo);
}

insert image description here

2. Reference assignment (need to use the util namespace)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <bean id="deptTwo" class="com.fd.spring.pojo.Dept">
        <property name="deptId" value="2"></property>
        <property name="deptName" value="2班"></property>
        <property name="students" ref="studentList"></property>
    </bean>
    
    <!--配置一个集合类型的bean,需要使用util的约束-->
    <util:list id="studentList">
        <ref bean="studentOne"></ref>
        <ref bean="studentTwo"></ref>
        <ref bean="studentThree"></ref>
    </util:list>
</beans>
  • map collection attribute assignment

1. Cascade assignment

<bean id="studentSeven" class="com.fd.spring.pojo.Student">
    <property name="id" value="1006"></property>
    <property name="name" value="王五"></property>
    <property name="age" value="25"></property>
    <property name="gender" value=""></property>
    <property name="hobby">
        <array>
            <value>学习</value>
            <value>吃饭</value>
        </array>
    </property>
    <property name="teacherMap">
        <map>
            <entry key="10086" value-ref="teacherOne"/>
            <entry key="10087" value-ref="teacherTwo"/>
        </map>
    </property>
</bean>

<bean id="teacherOne" class="com.fd.spring.pojo.Teacher">
    <property name="id" value="10086"></property>
    <property name="name" value="小红"></property>
</bean>
<bean id="teacherTwo" class="com.fd.spring.pojo.Teacher">
    <property name="id" value="10087"></property>
    <property name="name" value="小王"></property>
</bean>

Test Methods:

@Test
public void DiTest3() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Student studentSeven = applicationContext.getBean("studentSeven", Student.class);
    System.out.println(studentSeven);
}

insert image description here

2. Reference assignment

<bean id="studentSeven" class="com.fd.spring.pojo.Student">
        <property name="id" value="1006"></property>
        <property name="name" value="王五"></property>
        <property name="age" value="25"></property>
        <property name="gender" value=""></property>
        <property name="hobby">
            <array>
                <value>学习</value>
                <value>吃饭</value>
            </array>
        </property>
        <property name="teacherMap" ref="map"></property>
    </bean>

    <util:map id="map">
        <entry key="10086" value-ref="teacherOne"/>
        <entry key="10087" value-ref="teacherTwo"/>
    </util:map>

    <bean id="teacherOne" class="com.fd.spring.pojo.Teacher">
        <property name="id" value="10086"></property>
        <property name="name" value="小红"></property>
    </bean>
    <bean id="teacherTwo" class="com.fd.spring.pojo.Teacher">
        <property name="id" value="10087"></property>
        <property name="name" value="小王"></property>
    </bean>
  • p namespace

introduce constraints

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-4.0.xsd">
       
       <bean id="studentEight" class="com.fd.spring.pojo.Student"
          p:id="1007" p:age="35" p:name="老王" p:dept-ref="deptOne">

       </bean>
</beans>

Test Methods:

@Test
public void DiTest4() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
    Student studentEight = applicationContext.getBean("studentEight", Student.class);
    System.out.println(studentEight);
}

insert image description here

  • Manage data sources and import external properties files

Introduce dependencies

<!--mysql驱动-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.21</version>
</dependency>
<!--数据源: 德鲁伊连接池-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.6</version>
</dependency>

Configure the spring configuration file

<?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="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC"/>
        <property name="password" value="mysql123."/>
        <property name="username" value="admin"/>
    </bean>

</beans>

Or: import the properties configuration file, you need to add context constraints

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--引入properties-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="username" value="${jdbc.username}"/>
    </bean>

</beans>

Test Methods:

@Test
public void dataSourceTest() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_datasource.xml");
    DruidDataSource bean = applicationContext.getBean(DruidDataSource.class);
    System.out.println(bean);
}

insert image description here

5. bean scope

5.1 Singleton mode

In the spring configuration file, the scope of the bean can be set through the scope attribute of the bean tag

<?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">

    <!--
        scope:设置bean的作用域
        scope="singleton | prototype"
        singleton (单例):表示获取该bean所对应的对象都是同一个
        prototype (多例): 示获取该bean所对应的对象都不是同一个
    -->
    <bean id="student" class="com.fd.spring.pojo.Student" scope="singleton">
        <property name="id" value="1001"/>
        <property name="name" value="张三"/>
    </bean>
</beans>

insert image description here
Test Methods:

@Test
public void scopeTest() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
    Student bean1 = applicationContext.getBean(Student.class);
    Student bean2 = applicationContext.getBean(Student.class);
    System.out.println(bean1 == bean2);
}

insert image description here

5.2 Multiple instance pattern

<bean id="student" class="com.fd.spring.pojo.Student" scope="prototype">
    <property name="id" value="1001"/>
    <property name="name" value="张三"/>
</bean>

Test Methods:

@Test
public void scopeTest() {
    
    
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
    Student bean1 = applicationContext.getBean(Student.class);
    Student bean2 = applicationContext.getBean(Student.class);
    System.out.println(bean1 == bean2);
}

insert image description here

6. Bean life cycle

6.1 Specific life cycle process

  • Bean object creation (call no-argument constructor)
  • Set properties for the bean object
  • Operation before the bean object is initialized (responsible for the post-processor of the bean)
  • Bean object initialization (you need to specify the initialization method when configuring the bean)
  • Operation after the bean object is initialized (responsible for the post-processor of the bean)
  • The bean object is ready to use
  • Destroy the bean object (need to specify the destruction method when configuring the bean)
  • lOC container closed

6.2 Creating class objects

package com.fd.spring.pojo;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-06-07
 */

public class User {
    
    
    private Integer id;

    private String name;

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
        System.out.println("生命周期2:依赖注入");
    }

    public String getName() {
    
    
        return name;
    }

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

    public User() {
    
    
        System.out.println("生命周期1:实例化");
    }

    public User(Integer id, String name) {
    
    
        this.id = id;
        this.name = name;
    }

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

    public void initMethod() {
    
    
        System.out.println("生命周期3:初始化");
    }

    public void destroyMethod() {
    
    
        System.out.println("生命周期4:销毁");
    }
}

6.3 Configuring beans

<bean id="user" class="com.fd.spring.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1"/>
    <property name="name" value="张三"/>
</bean>

<bean id="beanPostProcessor" class="com.fd.spring.process.MyBeanPostProcessor"></bean>

6.4 Test method

@Test
public void test() {
    
    

    /*
    * 1、实例化
    * 2、依赖注入 
    * 3、bean对象初始化之前操作 
    * 4、初始化,需要通过bean的init-method属性指定初始化的方法
    * 5、bean对象初始化之后操作
    * 6、IOC容器关闭时销毁,需要通过bean的destroy-method属性指定销毁的方法
    *
    * */
    //ConfigurableApplicationContext是ApplicationContext的子接口,其中扩展了刷新和关闭容器的方法
    ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
    User bean = applicationContext.getBean(User.class);
    System.out.println(bean);
    applicationContext.close();
}

Test Results:
insert image description here

6.5 bean post-processor

The post-processor of the bean will add additional operations before and after the initialization of the life cycle. It needs to implement the BeanPostProcessor interface and configure it in the I0C container. It should be noted that,bean后置处理器不是单独针对某一个bean生效,而是针对I0C容器中所有bean都会执行

package com.fd.spring.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-06-09
 */

public class MyBeanPostProcessor implements BeanPostProcessor {
    
    

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
        // 此方法在bean的生命周期初始化之前执行
        System.out.println("MyBeanPostProcessor -> 前置处理器执行postProcessBeforeInitialization");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        // 此方法在bean的生命周期初始化之后执行
        System.out.println("MyBeanPostProcessor -> 后置处理器执行postProcessAfterInitialization");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

7.FactoryBean

7.1 Introduction

FactoryBean is an interface, you need to create a class to implement the interface, which has three methods:

  • getObject(): Pass an object to the IOC container for management
  • getObjectType(): Sets the type of the provided object
  • isSingleton(): Whether the provided object is a singleton

When the implementation class of FactoryBean is configured as a bean, the object returned by getObject() in the current class will be handed over to the IOC container for management, and the object returned by the factory getObject() can be obtained directly through the IOC container getBena

7.2 Create class UserFactoryBean

package com.fd.spring.factory;

import com.fd.spring.pojo.User;
import org.springframework.beans.factory.FactoryBean;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-06-09
 */
public class UserFactoryBean implements FactoryBean<User> {
    
    

    /*
    * FactoryBean是一个接口,需要创建一个类实现该接口其中有三个方法:
    * getObject():通过一个对象交给IOC容器管理
    * getObjectType(): 设置所提供对象的类型
    * isSingleton(): 所提供的对象是否单例
    * 当把FactoryBean的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理
    *
    * */
    @Override
    public User getObject() throws Exception {
    
    
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
    
    
        return User.class;
    }
}

FactoryBean interface

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    
    
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
    
    
        return true;
    }
}

7.3 Configuring beans

<?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 class="com.fd.spring.factory.UserFactoryBean"></bean>

</beans>

7.4 Test method

@Test
public void factoryBeanTest() {
    
    
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
    // 没有配置User类的bean,这里通过UserFactoryBean也获取到了User类的bean对象
    User bean = applicationContext.getBean(User.class);
    System.out.println(bean);
}

Test Results:

insert image description here

8. Automatic assembly

8.1 Concept

Automatic assembly:
According to the specified strategy, match a bean in the IOC container, and automatically assign values ​​​​to the properties of the class type or interface type in the bean

8.2 Manage beans based on xml

Scene simulation: three-tier architecture: controller layer -> service layer -> dao layer (mapper layer)

// 控制层
public class UserController {
    
    

    private UserService userService;

    public UserService getUserService() {
    
    
        return userService;
    }

    public void setUserService(UserService userService) {
    
    
        this.userService = userService;
    }

    public void saveUser() {
    
    
        userService.save();
    }
}

// 业务层
public interface UserService {
    
    

    void save();
}
public class UserServiceImpl implements UserService {
    
    

    private UserDao userDao;

    public UserDao getUserDao() {
    
    
        return userDao;
    }

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

    @Override
    public void save() {
    
    
        userDao.save();
    }
}

// 持久层
public interface UserDao {
    
    
    void save();
}
public class UserDaoImpl implements UserDao {
    
    
    @Override
    public void save() {
    
    
        System.out.println("保存成功");
    }
}

Spring configuration file:
Bean assembly by configuring property requires us to manually configure it in the configuration file

<?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 class="com.fd.spring.controller.UserController" id="userController">
        <property name="userService" ref="userService"/>
    </bean>

    <bean class="com.fd.spring.service.impl.UserServiceImpl" id="userService">
        <property name="userDao" ref="userDao"/>
    </bean>

    <bean class="com.fd.spring.dao.impl.UserDaoImpl" id="userDao"></bean>

</beans>

Test Methods:

@Test
public void autowireByXmlTest() {
    
    
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire-xml.xml");
    UserController userController = applicationContext.getBean(UserController.class);
    userController.saveUser();
}

result:
insert image description here

7.3 XML-based automatic assembly

Autowired strategy autowire:

  • no, default: means no assembly, that is, the attribute in the bean will not automatically match a bean to assign a value to the attribute, and the attribute uses the default value at this time
  • byType: According to the type of the attribute to be assigned, match a bean in the IOC container and assign a value to the attribute
    . The bean with multiple types is found through the type, and an exception will be thrown at this time: NoUniqueBeanDefinitionException Summary: When using byType to implement automatic assembly, there is one and only one type-matching bean in the IOC container that can assign values ​​​​to properties


  • byName: Use the attribute name of the attribute to be assigned as the bean id to match a bean in the IOC container and assign a value to the attribute
    Summary: When there are multiple beans with matching types, you can use byName to implement automatic assembly at this time

insert image description here
Spring configuration file: automatic assembly

<?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 class="com.fd.spring.controller.UserController" id="userController" autowire="byType">
        <!--<property name="userService" ref="userService"/>-->
    </bean>

    <bean class="com.fd.spring.service.impl.UserServiceImpl" id="userService" autowire="byType">
        <!--<property name="userDao" ref="userDao"/>-->
    </bean>

    <bean class="com.fd.spring.dao.impl.UserDaoImpl" id="userDao"></bean>

</beans>

8.4 Annotation-based bean management (annotation + scanning)

1. Notes

Like the XML configuration file, the annotation itself cannot be executed. The annotation itself is just a mark. The specific function is that the framework detects the position of the annotation mark, and then performs specific operations according to the function of the annotation mark for this position.
Essentially: all operations are done by java code, XML and annotations just tell the java code in the framework how to execute
. Paste garlands on the places, and balloons on the yellow places.

insert image description here
The monitor made all the marks, and the students came to complete the specific work. The marks on the wall are equivalent to the annotations we use in the code, and the work done by the students later is equivalent to the specific operation of the framework.

2. Scan

In order to know where programmers have marked what annotations, Spring needs to detect by scanning. Then follow up with the annotations
.

The spring configuration file enables component scanning:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--开启组件扫描,扫描com.fd.spring包下的所有类-->
    <context:component-scan base-package="com.fd.spring"></context:component-scan>

</beans>

3. Common annotations for identifying components

@Component: identify the class as a common component
@Controller: identify the class as a control layer component
@Service: identify the class as a business layer component
@Repository: identify the class as a persistence layer component
through annotation + scanning the configured bean id, The default value is the small camel case of the class, that is, the result of the first letter of the class name being lowercase. You can set the custom id of the bean through the value attribute value of the annotation that identifies the component

insert image description here

The connection and difference between the above four annotations?
By looking at the source code, we know that the three annotations @Controller, @Service, and @Repository are just three new names based on the @Component annotation.
There is no difference for Spring to use the IOC container to manage these components. So the three annotations @Controller, @Service, and @Repository are only for developers to see, so that we can easily distinguish the role of components.
Note: Although they are essentially the same, for the readability of the code and the rigorous structure of the program, we must not randomly mark them.

4. Create class object

@Controller
public class UserController {
    
    
}

public interface UserService {
    
    
}
@Service
public class UserServiceImpl implements UserService {
    
    
}

public interface UserDao {
    
    
}
@Repository
public class UserDaoImpl implements UserDao {
    
    
}

5. Test

@Test
public void iocByAnnotationTest() {
    
    
    /*
    * 通过注解+扫描所配置的bean的id,默认值为类的小驼峰,即类名的首字母为小写的结果,
    * 可以通过标识组件的注解的value属性值设置bean的自定义的id
    */
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
    UserController userController = applicationContext.getBean(UserController.class);
    System.out.println(userController);
    UserService userService = applicationContext.getBean(UserService.class);
    System.out.println(userService);
    UserDao userDao = applicationContext.getBean(UserDao.class);
    System.out.println(userDao);
}

Test Results:
insert image description here

6. Scan component configuration

context:exclude-filter: Exclude scanning

  • type: set the way to exclude scanning, type="annotation | assignable"
  • annotation: exclude according to the type of annotation, expression needs to set the full class name of the excluded annotation to exclude according to the type of class
  • assignable: Exclude according to the type of the class, expression needs to set the full class name of the excluded class

context:include-filter: include scan

Note: You need to set use-default-filters="false" in the context:component-scan tag

  • use-default-filters="true" (default), all classes under the set package need to be scanned, and you can use exclusion scanning at this time
  • use-default-filters="false", all classes under the set package do not need to be scanned, and you can use the include scan at this time

Exclude scanning:

<!--开启组件扫描-->
<context:component-scan base-package="com.fd.spring">
    <!--根据注解进行排除-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!--根据类的类型进行排除-->
    <context:exclude-filter type="assignable" expression="com.fd.spring.controller.UserController"/>
</context:component-scan>

Contains scans:

<context:component-scan base-package="com.fd.spring" use-default-filters="false">
    <!--根据注解只扫描-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!--根据类的类型只扫描-->
    <context:include-filter type="assignable" expression="com.fd.spring.controller.UserController"/>
</context:component-scan>

8.5 Annotation-based autowiring

1. Create components

@Controller("controller")
public class UserController {
    
    

    /*autowire注解放在成员变量上,此时不需要设置成员变量的set方法*/
    @Autowired
    private UserService userService;

    public void saveUser() {
    
    
        userService.saveUser();
    }
}

public interface UserService {
    
    
    void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
    
    
    @Autowired
    private UserDao userDao;
    @Override
    public void saveUser() {
    
    
        userDao.saveUser();
    }
}

public interface UserDao {
    
    
    void saveUser();
}
@Repository
public class UserDaoImpl implements UserDao {
    
    
    @Override
    public void saveUser() {
    
    
        System.out.println("保存成功");
    }
}

Test Methods:

@Test
public void iocByAnnotationTest() {
    
    
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
    UserController userController = applicationContext.getBean("controller", UserController.class);
    userController.saveUser();
}

insert image description here

2. @Autowired: Annotation to realize the automatic assembly function

  1. @Autowired annotation can identify the location

    a. The mark is on the member variable. At this time, there is no need to set the set method of the member variable

    //autowire注解放在成员变量上,此时不需要设置成员变量的set方法
    @Autowired
    private UserService userService;
    

    b. Marked on the set method

    /*autowire注解放在成员变量的set方法上*/
    @Autowired
    public void setUserService(UserService userService) {
          
          
    	this.userService = userService;
    }
    

    c. Identified on the parameterized structure assigned to the current member variable

    /*autowire注解放在当前成员变量的有参构造上*/
    @Autowired
    public UserController(UserService userService) {
          
          
    	this.userService = userService;
    }
    
  2. The principle of @Autowired annotation

a> By default, in the IOC container, assign a value to a bean by matching the type of a bean in the IOC container
b> If there are multiple beans that match the type, it will be automatically converted to the byName method to achieve the effect of automatic assembly, and the value will be assigned soon The attribute name of the attribute is used as the id of the bean to match a bean to assign a value to the attribute
c> byType and byName cannot achieve automatic assembly, that is, there are multiple beans with matching types in the IOC container and the id of these beans and the attribute to be assigned The attribute names are inconsistent, and an exception is thrown at this time: NOUniqueBeanDefinitionException
d> On the basis of c, you can add an annotation Qualifier to the attribute to be assigned at this time, specify the id of a bean through the value attribute value of the annotation, and set this Bean assigns values ​​to properties

@Autowired
@Qualifier("userServiceImpl")
private UserService userService;

Note:
There is no bean with a matching type in the IOC container, and an exception is thrown at this time: NoSuchBeanDefinitionException. There is an attribute required in the @Autowired annotation, and the default value is true. It is required that automatic assembly must be completed and required can be set to false. At this time, if it can be assembled, it will be assembled, and if it cannot be assembled, the default value of the attribute will be used.
insert image description here

3. Proxy mode

1. Concept

One of the twenty-three design patterns, which belongs to the structural pattern. Its role is to provide a proxy class, so that when we call the target method, we no longer directly call the target method, but indirectly call it through the proxy class. Let the code that does not belong to the core logic of the target method be decoupled from the target method. When calling the target method, the method of the proxy object is called first, which reduces the calling and interruption of the target method, and at the same time allows additional functions to be concentrated together, which is also conducive to unified maintenance!

Before using proxy:
insert image description here
After using proxy:

insert image description here

Related terms:

  • Proxy: After stripping out the non-core logic, encapsulate the classes, objects, and methods of these non-core logic
  • Target: Classes, objects, and methods that are "applied" by the proxy to non-core logic codes.

2. Static proxy

2.1 Create an interface object

public interface Calculator {
    
    
    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);
}

2.2 Create the implementation class of the interface object

public class CalculatorImpl implements Calculator{
    
    
    @Override
    public int add(int i, int j) {
    
    
        System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
        int result = i + j;
        System.out.println("打印日志,方法执行后,结果:" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
    
    
        System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
        int result = i - j;
        System.out.println("打印日志,方法执行后,参数:" + i + "," +j);
        return result;
    }

    @Override
    public int mul(int i, int j) {
    
    
        System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
        int result = i * j;
        System.out.println("打印日志,方法执行后,结果:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
    
    
        System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
        int result = i / j;
        System.out.println("打印日志,方法执行后,结果:" + result);
        return result;
    }
}

2.3 Test method

@Test
public void proxyTest() {
    
    
    CalculatorImpl calculator = new CalculatorImpl();
    CalculatorStaticProxy proxy = new CalculatorStaticProxy(calculator);
    int result = proxy.add(10, 5);
}

2.4 Summary

Static proxies do achieve decoupling, but because the code is hard-coded, they do not have any flexibility at all. Take the log function as an example. In the future, additional logs will be required in other places, so more static proxy classes must be declared, which will generate a lot of repeated code. The log function is still scattered without unified management.

A further requirement is put forward: the log function is concentrated into a proxy class, and any log requirements in the future will be realized through this proxy class, which requires the use of dynamic proxy technology.

3. Dynamic proxy

There are two types of dynamic proxy:
1. jdk dynamic proxy, which requires an interface, and the final generated proxy class and target class implement the same interface under the com.sun.proxy package, and the class name is $proxy+number
2. cglib dynamic proxy, The final generated proxy class will inherit the target class and be in the same package as the target class

3.1 Create a proxy object factory

public class ProxyFactory {
    
    

    private final Object target;

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

    public Object getProxy() {
    
    
        /*
            classLoader Loader: 指定加载动态生成的代理类的类加载器
            Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
            InvocationHandler h:设置代理中的抽象方法如何重写
        */
        ClassLoader classLoader = this.getClass().getClassLoader(); // 先获取类的Class实例,再获取类的加载器
        Class<?>[] interfaces = this.target.getClass().getInterfaces(); // 先获取类的Class实例,再获取接口
        // 执行代理方法最终会调用此方法,执行被被代理类的方法
        InvocationHandler h = new InvocationHandler() {
    
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                //proxy表示代理对象,method表示要执行的方法,args表示要执行的方法到的参数列表
                System.out.println("打印日志,方法执行之前, 参数:" + Arrays.toString(args));

                Object result = method.invoke(target, args);

                System.out.println("打印日志,方法执行之后,结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, h);
    }
}

3.2 Test method

@Test
public void proxyTest1() {
    
    
    ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
    Calculator proxy = (Calculator)proxyFactory.getProxy();
    int result = proxy.add(5, 5);
}

4. AOP: aspect-oriented programming

4.1 Overview

AOP (Aspect Oriented Programming) is a design idea and aspect-oriented programming in the field of software design. It is a supplement and perfection of object-oriented programming (OOP). It is realized by pre-compilation and runtime dynamic proxy A technique to dynamically and uniformly add additional functions to a program without modifying the source code.

4.2 Related terms

1. Crosscutting concerns

The same class of non-core business extracted from each method. In the same project, we can use multiple cross-cutting concerns to enhance several different aspects of related methods.
This concept does not exist naturally at the grammatical level, but is based on the logical needs of additional functions: there are ten additional functions, and there are ten cross-cutting concerns.

insert image description here

2. Notice

Every thing to be done on a cross-cutting concern needs to be implemented by writing a method. Such a method is called a notification method.
Pre-advice: Executed before the proxied target method
Return notification: Executed after the proxied target method ends successfully (end of life)
Abnormal notification: Executed after the proxied target method ends abnormally (die)
Post-notification: Executed after the proxied target method ends successfully After the target method of the agent finally ends, execute (conclusive conclusion)
around the notification: use the try...catch...finally structure to surround the entire proxy target method, including all positions corresponding to the above four notifications

The execution order of various notifications:
Before Spring version 5.3.x:
pre-notification
target operation
post-notification
return notification or exception notification.
After Spring version 5.3.x:
pre-notification
target operation
return notification or exception notification
post-notification

3. Section

A class that encapsulates notification methods.

insert image description here

4. Goals

Proxied target object

5. Proxy

The proxy object created after applying the notification to the target object

6. Connection points

This is a purely logical grammatical concept.
Arrange the methods in a row. Each crosscutting position is regarded as the direction of the x-axis, and the order in which the methods are executed from top to bottom is regarded as the y-axis. The intersection of the x-axis and the y-axis is the connection point.

insert image description here

7. Entry point

The way to locate the connection point
Each class method contains multiple connection points, so the connection point is an objective thing (logically speaking) in the class.
If you think of the join point as a record in the database, then the pointcut is the SQL statement that queries the record.
Spring's AOP technology can locate specific connection points through entry points.
Pointcuts are described by the org.springframework.aop.Pointcut interface, which uses classes and methods as query conditions for join points

4.3 Function

Simplify the code: Extract the repeated code in a fixed position in the method, so that the extracted method can focus more on its core functions and improve cohesion.
Code enhancement: Encapsulate specific functions into the aspect class, and apply it wherever there is a need, and the method that is applied with the aspect logic will be enhanced by the aspect.

4.4 Annotation-based AOP

insert image description here

  • Dynamic proxy (lnvocationHandler): JDK's native implementation method, the target class that needs to be proxied must implement the interface. Because this technique requires the proxy object and the target object to implement the same interface (two brother-by-two sub-patterns)
  • cglib: Proxy is implemented by inheriting the proxied target class (recognition mode), so the target class does not need to implement the interface.
  • Aspect: It is essentially a static proxy, which "weaves" the proxy logic into the Ningjie code file compiled by the proxied target class, so the final effect is dynamic. Weaver is the weaver. Spring just borrows annotations from Aspectj.

1. Add dependencies

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.3.1</version>
</dependency>
或者
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.6.7</version>
</dependency>

2. Configure the spring file

<?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-4.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        AOP的注意事项:
        切面类和目标类都需要交给IOC器管理
        切面类必须通过@Aspect注解标识为一个切面
        在Spring的配置文件中设置<aop:aspectj-autoproxy/>开启基于注解的AOP
    -->
    <!--开启扫描-->
    <context:component-scan base-package="com.fd.spring"/>

    <!--开启基于注解的AOP-->
    <aop:aspectj-autoproxy/>
</beans>

3. Create target object

public interface Calculator {
    
    
    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);
}

@Component
public class CalculatorImpl implements Calculator {
    
    
    @Override
    public int add(int i, int j) {
    
    
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
    
    
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
    
    
        return i * j;
    }

    @Override
    public int div(int i, int j) {
    
    
        int result = i / j;
        return result;
    }
}

4. Create an aspect class

1. In the aspect, the method needs to be identified as a notification method through the specified annotation
@Before(): pre-notification, execute before the execution of the target object method
@After(): post-notification, in the finally clause of the target object method Execute
@AfterReturning(): return the notification, execute after the target object obtains the return value
2. Entry point expression: set in the value attribute of the annotation that identifies the notification
execution(* com.fd.spring.annotation.CalculatorImpl. (…) )
The first
represents any access modifier and return value type
The second represents any method in the class
...Represents any parameter list
Class can also be used
, representing all classes under the package
3. Reuse pointcut expression
@ Pointcut declares a public pointcut expression
@Pointcut("execution(* com.fd.spring.annotation.CalculatorImpl.*(...))")
public void pointCut() {}
usage: @After("pointCut() ”) //Using the method name of the reuse pointcut expression
4. Get the join point information
In the parameter position of the notification method, set the parameter of the JoinPoint type, and you can get the information of the method corresponding to the join point
// Get the corresponding method of the join point method name
Signature signature = joinPoint.getSignature();
// Obtain the parameter
Object[] args of the method corresponding to the connection point = joinPoint.getArgs();

package com.fd.spring.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-06-17
 *
 * 切面类必须通过@Aspect注解标识为一个切面
 */
@Component
@Aspect // 将当前组件标记为切面
public class LoggerAspect {
    
    

    /*
        1.在切面中,需要通过指定的注解将方法标识为通知方法
            @Before():前置通知,在目标对象方法执行之前执行
            @After():后置通知,在目标对象方法的finally字句中执行
            @AfterReturning():返回通知,在目标对象获取返回值之后执行

        2.切入点表达式:设置在标识通知的注解的value属性中
            execution(* com.fd.spring.annotation.CalculatorImpl.*(..))
            第一个*表示任意的访问修饰符和返回值类型
            第二个*表示类中任意的方法
            ..表示任意的参数列表
            类的地方也可以使用*,表示包下所有的类

        3.重用切入点表达式
            @Pointcut声明一个公共的切入点表达式
                @Pointcut("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
                public void pointCut() {}
            使用方法:@After("pointCut()") //使用的是重用切入点表达式方法名

        4.获取连接点信息
            在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
            // 获取连接点所对应的方法名
            Signature signature = joinPoint.getSignature();
            // 获取连接点所对应方法的参数
            Object[] args = joinPoint.getArgs();

    */

    // 切入点表达式的重用
    @Pointcut("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
    public void pointCut() {
    
    }

    //@Before("execution(public int com.fd.spring.annotation.CalculatorImpl.add(int, int))") //切入点表达式
    @Before("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
    public void beforeNotice(JoinPoint joinPoint) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        // 获取连接点所对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
    }

    @After("pointCut()")
    public void AfterNotice(JoinPoint joinPoint) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知,方法:" + signature.getName());
    }

    /**
        在返回通知中若要获取目标对象方法的返回值
        只需要通过@AfterReturning注解的returning属性
        就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
     * */
    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturningNotice(JoinPoint joinPoint, Object result) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("返回通知, 方法:" + signature.getName() + ", 返回值:" + result);
    }

    /**
     在返回通知中若要获取目标对象方法的返回值
     只需要通过AfterThrowing注解的throwing属性
     就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
     * */
    @AfterThrowing(value = "pointCut()", throwing = "result")
    public void afterThrowNotice(JoinPoint joinPoint, Exception result) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("异常通知, 方法:" + signature.getName() + ", 异常:" + result);
    }

    /*
        环绕通知的方法的返回值一定要和目标对象方法的返回值一致
    * */
    @Around("pointCut()")
    public Object aroundNotice(ProceedingJoinPoint joinPoint) {
    
    
        Object result;
        try {
    
    
            System.out.println("环绕通知-->前置通知");
            // 目标对象的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->返回通知");
        } catch (Throwable e) {
    
    
            System.out.println("环绕通知-->异常通知");
            throw new RuntimeException(e);
        } finally {
    
    
            System.out.println("环绕通知-->后置通知");
        }
        return result;
    }
}

5. Test class

After the original target object is hidden through AOP proxy in spring, the original target object can no longer be obtained through IOC, and the proxy object of the target can only be obtained through the interface.

@Test
public void aopTest() {
    
    
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop-annotation.xml");
    // 在spring中通过AOP代理后原目标对象就被隐藏了就不能再通过IOC获取原目标对象,只能通过获取接口去获取代理对象
    Calculator bean = applicationContext.getBean(Calculator.class);
    // int add = bean.add(10, 5);
    // int div = bean.div(10, 0);
    int mul = bean.mul(2, 5);
    System.out.println(mul);
}

Test Results:
insert image description here

6. Aspect priority

The priority can be set through the value attribute of the @order annotation. The default value is the maximum value of Integer.
The smaller the value attribute value of the @order annotation, the higher the priority

@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
    
    

   /**
    * The order value.
    * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
    * @see Ordered#getOrder()
    */
   int value() default Ordered.LOWEST_PRECEDENCE;

}
@Component
@Aspect
@Order(1)
public class ValidateAspect {
    
    

    // @Before("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
    @Before("com.fd.spring.annotation.LoggerAspect.pointCut()")
    public void beforeMethod() {
    
    
        System.out.println("前置通知,校验");
    }
}

Test Results:

insert image description here

4.5 XML-based AOP

1. Create facets

@Component
public class LoggerAspect {
    
    

    public void beforeNotice(JoinPoint joinPoint) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        // 获取连接点所对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
    }

    public void afterNotice(JoinPoint joinPoint) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知,方法:" + signature.getName());
    }

    /**
        在返回通知中若要获取目标对象方法的返回值
        只需要通过@AfterReturning注解的returning属性
        就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
     * */
    public void afterReturningNotice(JoinPoint joinPoint, Object result) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("返回通知, 方法:" + signature.getName() + ", 返回值:" + result);
    }

    /**
     在返回通知中若要获取目标对象方法的返回值
     只需要通过AfterThrowing注解的throwing属性
     就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
     * */
    public void afterThrowNotice(JoinPoint joinPoint, Exception result) {
    
    
        // 获取连接点所对应的方法名
        Signature signature = joinPoint.getSignature();
        System.out.println("异常通知, 方法:" + signature.getName() + ", 异常:" + result);
    }

    /*
        环绕通知的方法的返回值一定要和目标对象方法的返回值一致
    * */
    public Object aroundNotice(ProceedingJoinPoint joinPoint) {
    
    
        Object result;
        try {
    
    
            System.out.println("环绕通知-->前置通知");
            // 目标对象的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->返回通知");
        } catch (Throwable e) {
    
    
            System.out.println("环绕通知-->异常通知");
            throw new RuntimeException(e);
        } finally {
    
    
            System.out.println("环绕通知-->后置通知");
        }
        return result;
    }
}

2. Configure the spring configuration file

<?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-4.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.fd.spring.xml"/>

    <aop:config>
        <!--设置一个公共的切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(* com.fd.spring.xml.CalculatorImpl.*(..))"/>
        <!--将IOC容器中的某个bean设置为切面-->
        <aop:aspect ref="loggerAspect">
            <aop:before method="beforeNotice" pointcut-ref="pointCut"/>
            <aop:after method="afterNotice" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturningNotice" pointcut-ref="pointCut" returning="result"/>
            <aop:after-throwing method="afterThrowNotice" pointcut-ref="pointCut" throwing="result"/>
            <aop:around method="aroundNotice" pointcut-ref="pointCut"/>
        </aop:aspect>

        <aop:aspect ref="validateAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

3. Test method:

@Test
public void xmlTest() {
    
    
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop-xml.xml");
    com.fd.spring.xml.Calculator bean = applicationContext.getBean(com.fd.spring.xml.Calculator.class);
    int add = bean.add(5, 5);
}

Test Results:

insert image description here

4. Transaction management

1.jdbcTemplate

The Spring framework encapsulates JDBC and uses JdbcTemplate to facilitate database operations

1.1 Introducing dependencies

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.19</version>
  </dependency>
  <!--Spring 测试相关,整合junit,要求junit在4.12及以上-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.19</version>
  </dependency>
  <!--
    Spring 持久化层支持jar包
    Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc,tx三个jar包
    导入 orm 包就可以通过 Maven 的依传递性把其他两个也导入
  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.19</version>
  </dependency>
  <!--mysql驱动-->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</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.12</version>
    <scope>test</scope>
  </dependency>
</dependencies>

1.2 Create jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
jdbc.username=root
jdbc.password=mysql123.

1.3 spring configuration file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部配置文件jdbc.properties  classpath:指类路径-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--配置jdbc实例-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <!--设置数据源,连接数据库-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

1.4 Create a test class

//Specify that the current test class is executed in the Spring test environment. At this time, the bean in the IOC container can be directly obtained by injection
@RunWith(SpringJUnit4ClassRunner.class)
//Set the configuration file of the Spring test environment
@ContextConfiguration(“classpath: spring-jdbc.xml")

//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
// 设置Spring测试环境的配置文件
@ContextConfiguration("classpath:spring-jdbc.xml")
public class AppTest
{
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void insertTest() {
    
    
        String sql = "insert into t_user values (null, ?, ?, ?, ?, ?)";
        jdbcTemplate.update(sql, "付东", "123456", "28", "nan", "[email protected]");
    }

    @Test
    public void selectTest() {
    
    
        String sql = "select * from t_user where id = ?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), "1");
        System.out.println(user);
    }

    @Test
    public void selectAllTest() {
    
    
        String sql = "select * from t_user";
        List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        users.forEach(System.out::println);
    }
}

2. Transaction concept

(1) Transaction is the most basic unit of database operation. Logically, a group of operations will either succeed, and if one fails, all operations will fail. (2) Typical scenario: Bank
transfer
luy transfers 100 yuan to mary.
Lucy is less than 100, and mary is more than 100.

Four characteristics of transactions (ACID)

  • atomicity
  • consistency
  • isolation
  • Persistence

2.1 Programmatic transactions

The related operations of the transaction function are all realized by writing code yourself

insert image description here

2.2 Declarative transactions

Since the transaction control code has rules to follow and the structure of the code is basically determined, the framework can extract the code with a fixed pattern and perform related packaging. After packaging, we only need to make a simple configuration in the configuration file to complete the operation.

  • Benefit 1: Improve development efficiency
  • Benefit 2: Eliminates a few redundant codes.
  • Benefit 3: The framework will comprehensively consider various problems that may be encountered in the actual development environment in related fields, and optimize robustness, performance and other aspects

Therefore, we can summarize the following two concepts:

  • Programmatic: Write your own code to realize the function
  • Declarative: Let the framework implement functions through configuration

3. Annotation-based declarative transactions

3.1 Preparations

1. Configure the spring configuration file

<!--引入外部配置文件jdbc.properties  classpath:指类路径-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置jdbc实例-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
    <!--设置数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--开启扫描-->
<context:component-scan base-package="com.fd.spring"></context:component-scan>

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

<!--
    基于注解开启事务的驱动
    将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理

    transaction-manager属性设置事务管理器的id
    若事务管理器的bean的id默认为transactionManager,则该属性以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3.2 Specific implementation

1. Create related business classes

POJO layer:

@Component
public class User {
    
    

    private String id;

    private String name;

    private String password;

    private Integer age;

    private String gender;

    private String email;

    private Double balance;
    
    ...
}

@Component
public class Book {
    
    

    private String bookId;

    private String bookName;

    private Double price;

    private Integer stock;
    
    ...
}

Controller layer

@Controller
public class BookController {
    
    

    @Autowired
    private IBookService bookService;

    @Autowired
    private ICheckoutService checkoutService;

    public void BuyBook(String userId, String bookId) {
    
    
        bookService.buyBook(userId, bookId);
    }

    public void checkout(String userId, String[] bookIds) {
    
    
        checkoutService.checkout(userId,bookIds);
    }
}

Service layer

public interface IBookService {
    
    

    void buyBook(String userId, String bookId);
}

@Service
public class BookServiceImpl implements IBookService {
    
    

    @Autowired
    private IBookDao bookDao;

    @Override
    @Transactional()
    public void buyBook(String userId, String bookId) {
    
    
        // 查询图书的价格
        Double price = bookDao.getPriceById(bookId);
        // 更新图书的库存
        bookDao.updateStock(bookId);
        // 更新用户的余额
        bookDao.updateBalance(userId,price);
    }
}

public interface ICheckoutService {
    
    
    void checkout(String userId, String[] bookIds);
}

@Service
public class CheckoutServiceImpl implements ICheckoutService {
    
    

    @Autowired
    private IBookService bookService;

    @Override
    @Transactional
    public void checkout(String userId, String[] bookIds) {
    
    
        for (int i = 0; i < bookIds.length; i++) {
    
    
            bookService.buyBook(userId, bookIds[i]);
        }
    }
}

Dao layer

public interface IBookDao {
    
    

    Double getPriceById(String bookId);

    void updateStock(String bookId);

    void updateBalance(String userId, Double price);
}

@Repository
public class BookDaoImpl implements IBookDao {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Double getPriceById(String bookId) {
    
    
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Double.class, bookId);
    }

    @Override
    public void updateStock(String bookId) {
    
    
        String sql = "update t_book set stock = stock -1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    @Override
    public void updateBalance(String userId, Double price) {
    
    
        String sql = "update t_user set balance = balance - ? where id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}

2. Test transaction

@Test
public void buyBookTest() {
    
    
    /*
        声明式事务的配置步骤:
            1、在Spring的配置文件中配置事务管理器
            2、开启事务的注解驱动
            在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
            @Transactional注解标识的位置:
            1.标识在方法上
            2、标识在类上,则类中所有的方法都全被事务管理

    * */
    bookController.BuyBook("1", "1");

    // bookController.checkout("1", new String[] {"1", "2"});
}

If the user's balance is insufficient and an error is reported, the sql execution of the book will also be rolled back.
insert image description here
SQL [update t_user set balance = balance - ? where id = ?]; Data truncation: Out of range value for column 'balance' at row 1; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column 'balance' at row 1

3.3 Transaction attributes

1. readonly read-only

For a query operation, if we set it to read-only, we can clearly tell the database that this operation does not involve write operations. This allows the database to be optimized for query operations.

Notice: If read-only is set for addition, deletion and modification, the following exception will be thrown:
insert image description here

2. timeout timeout

During the execution of the transaction, the program may be stuck due to certain problems, thus occupying database resources for a long time. The high probability of taking up resources for a long time is because there is a problem with the running of the program (may be] ava program or MySQL database or network connection, etc.). At this time, the program that is likely to have problems should be rolled back, undoing the operations it has done, the transaction ends, and the resources are released so that other normal programs can be executed.

@Override
@Transactional(timeout = 3)
public void buyBook(String userId, String bookId) {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(5);
    }catch (Exception e) {
    
    
        e.printStackTrace();
    }
    // 查询图书的价格
    Double price = bookDao.getPriceById(bookId);
    // 更新图书的库存
    bookDao.updateStock(bookId);
    // 跟新用户的余额
    bookDao.updateBalance(userId,price);
}

An exception is thrown during execution:

insert image description here

3. rollbackFor rollback strategy

By default, declarative transactions are only rolled back for runtime exceptions, and compile-time exceptions are not rolled back.
The rollback strategy can be set through the relevant attributes in @Transactional.

  • rollbackFor property: need to set an object of type Class.
  • rollbackForClassName attribute: need to set a full class name of string type.
  • noRollbackFor attribute: An object of type Class needs to be set.
  • noRollbackForClassName attribute: need to set a full class name of string type
@Transactional(
        rollbackFor = Exception.class,
        rollbackForClassName = "java.lang.Exception"
)

4. isolation transaction isolation level

The database system must have the ability to isolate and run various transactions concurrently, so that they will not affect each other and avoid various concurrency problems. The degree to which a transaction is isolated from other transactions is called an isolation level. The SQL standard specifies multiple transaction isolation levels. Different isolation levels correspond to different interference levels. The higher the isolation level, the better the data consistency, but the weaker the concurrency.

insert image description here
The ability of each isolation level to solve concurrency problems is shown in the following table:
insert image description here
the support level of various database products for transaction isolation level:
insert image description here
the default transaction isolation level is: repeatable read

@Transactional(
    isolation = Isolation.SERIALIZABLE
)

// 枚举对象
public enum Isolation {
    
    
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
    
    
        this.value = value;
    }

    public int value() {
    
    
        return this.value;
    }
}

5. propagation transaction propagation behavior

When a transactional method is called by another transactional method, it must be specified how the transaction should propagate. For example: A method may continue to run in an existing transaction, or it may start a new transaction and run in its own transaction.

Transaction propagation behavior can be set through the propagation attribute in @Transactional.

Modify the propagation attribute of @Transactional on buyBook() in BookServicelmpl

@Transactional(propagation = Propagation.REQUIRED), by default, means that if there is an already opened transaction available on the current thread, it will run in this transaction. After observation, the method buyBook() for purchasing books is called in checkout(), and there are transaction annotations on checkout(), so it is executed in this transaction. The prices of the two books purchased are 80 and 50, and the user's balance is 100, so when the second book is purchased, the balance fails, causing the entire checkout() to roll back, that is, as long as one book cannot be purchased, it will be closed. can't buy

@Transactional(propagation = Propagation.REQUIRES_NEN), means that no matter whether there is an already opened transaction on the current thread, a new transaction must be opened. In the same scene, every purchase of books is executed in the transaction of buyBook(), so the first book is purchased successfully, the transaction ends, and the second book fails to be purchased, only rollback in the second buyBook0, purchase The first book will not be affected, that is, you can buy as many books as you can

insert image description here

4. XML-based declarative transactions

4.1 Introducing dependencies

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.6.7</version>
</dependency>

Note: XML-based declarative transactions must introduce Aspects dependencies

4.2 spring configuration file

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

<!--
    基于xml配置事务通知
    tx:advice标签:配置事务通知
    id属性: 给事务通知标签设置唯一标识,便于引用
    transaction-manager属性: 关联事务管理器
-->
<tx:advice id="tx" transaction-manager="transactionManager">
    <!--
        配置事务属性
        * 可以*表达式来配置方法
    -->
    <tx:attributes>
        <tx:method name="buyBook" timeout="3"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:advisor advice-ref="tx" pointcut="execution(* com.fd.spring.service.impl.*.*(..))"></aop:advisor>
</aop:config>

4.3 Test method

@Test
public void buyBookTest() {
    
    

    /*
        声明式事务的配置步骤:
            1、在Spring的配置文件中配置事务管理器
            2、开启事务的注解驱动
            在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
            @Transactional注解标识的位置:
            1.标识在方法上
            2、标识在类上,则类中所有的方法都全被事务管理

    * */
    bookController.BuyBook("1", "1");

    // bookController.checkout("1", new String[] {"1", "2"});
}

Guess you like

Origin blog.csdn.net/weixin_44863237/article/details/131489611