主頁 > 後端開發 > Spring5 學習筆記

Spring5 學習筆記

2021-12-28 06:12:04 後端開發

學習地址: B站-動力節點

個人代碼: GitHub

1. Spring 概述

1.1 Spring 簡介

??Spring Framework 是一個使用Java開發的、輕量級的、開源框架,它的主要作用是為了解耦合,Spring 的核心技術是 IOC(控制反轉)AOP(面向切面編程)

  • 官方網站: https://spring.io

??Spring 框架提高了很多功能,包括IOC容器、AOP、資料訪問、事務、測驗功能、定時任務、快取等等,

001_Spring簡介_功能模塊

1.2 優點

輕量、解耦、面向切面編程、方便與其他框架集成、方便測驗、減低開發難度,

2. IOC 控制反轉

2.1 IOC 是什么

??IOC (Inversion of Control, 控制反轉) 是一種理論,指導開發人員如何使用物件、管理物件,將物件的生命周期交給容器來管理,通過容器管理物件,開發人員只需要拿到物件,執行物件的方法即可,

  • 控制:管理物件的創建、屬性賦值、生命周期的管理,
  • 正轉:讓開發人員掌控物件的創建、屬性賦值,即整個生命周期的管理,
  • 反轉:把開發人員管理物件的權限轉移給容器來實作,讓容器完成管理,

2.2 IOC 的技術實作

??DI (Dependency Injection, 依賴注入) 是 IOC 的一種技術實作,開發人員通過物件的名稱獲取已初始化的物件,而物件的創建、屬性賦值、物件間的呼叫等都由容器內部實作,

2.3 IOC-創建物件 牛刀小試

Source Code

2.3.1 測驗步驟

  1. 創建 maven-quickstart 專案,并調整專案結構(字符編碼、JDK版本等)
  2. 添加依賴
    • spring-context
    • junit
  3. 定義介面和實作類
    • 介面: SomeService
      • 方法: doSome(): void
    • 實作類: SomeServiceImpl
  4. 創建 Spring 組態檔(.xml),宣告需要創建的物件
    • 通過<bean>標簽宣告物件,一個標簽對應一個物件,
  5. 使用容器中的物件
    • 創建 ApplicationContext 物件
    • 通過 getBean() 獲取容器中的物件

2.3.2 依賴檔案

<?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>com.bpf</groupId>
    <artifactId>M01-ioc-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.3.3 介面與實作類

  • HIDE
  • SomeService.java
  • SomeServiceImpl.java
package com.bpf.service;

public interface SomeService {

    void doSome();
}
package com.bpf.service.impl;

import com.bpf.service.SomeService;

public class SomeServiceImpl implements SomeService {

    public SomeServiceImpl() {
        System.out.println("[SomeServiceImpl] 無參構造方法");
    }

    @Override
    public void doSome() {
        System.out.println("[SomeServiceImpl] someService()...");
    }
}

2.3.4 組態檔

<?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      自定義物件的名稱,保持唯一,
        class   自定義物件的全限定類名,不能是介面,

        >>> Spring 根據 id 和 class 創建物件,并將物件放入一個 map 物件中,
    -->
    <bean id="someService"  />
    <bean id="someService1"  />

    <bean id="mydate"  />
</beans>

2.3.5 測驗創建物件

測驗創建物件: CreateBeanTest.java
package com.bpf.service;

import com.bpf.service.impl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Arrays;
import java.util.Date;

public class CreateBeanTest {

    /**
     * 傳統方式: new 獲取物件
     */
    @Test
    public void testCreateBeanClassical() {
        SomeService someService = new SomeServiceImpl();
        someService.doSome();
    }

    /**
     * 使用 Spring 容器方式獲取物件
     */
    @Test
    public void testCreateBean() {
        // 創建容器物件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // 通過 getBean() 獲取 bean 物件
        SomeService someService = (SomeService) ctx.getBean("someService");
        // 呼叫物件方法
        someService.doSome();
    }

    /**
     * Spring 創建物件,呼叫的是類的哪個構造器呢?
     *  默認呼叫的是類的無參構造器!
     */
    @Test
    public void testCreateStyle() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        SomeService someService = (SomeService) ctx.getBean("someService");
        someService.doSome();
        // 在無參構造器上添加輸出陳述句,如果把無參構造器改成有參構造器,執行測驗方法時會報錯:無法找到默認的構造方法,

        /** 執行結果
         * [SomeServiceImpl] 無參構造方法
         * [SomeServiceImpl] someService()...
         */
    }

    /**
     * Spring 創建物件,是什么時候創建的呢?
     *   Spring在創建容器物件 ApplicationContext時,會讀取組態檔,并創建檔案中宣告的所有java物件,
     *
     * 優點:獲取物件速度快,
     * 缺點:占用記憶體,
     */
    @Test
    public void testCreateTime() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        /** 執行結果
         * [SomeServiceImpl] 無參構造方法
         * [SomeServiceImpl] 無參構造方法
         */
    }

    /**
     * 獲取Spring容器中的物件資訊
     */
    @Test
    public void testGetCtxInfo() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // 容器中物件的數量
        int count = ctx.getBeanDefinitionCount();
        // 容器中物件的名稱
        String[] names = ctx.getBeanDefinitionNames();

        System.out.println("容器中物件的數量:" + count);
        System.out.println("容器中物件的名稱:" + Arrays.toString(names));

        /** 執行結果
         * [SomeServiceImpl] 無參構造方法
         * [SomeServiceImpl] 無參構造方法
         * 容器中物件的數量:2
         * 容器中物件的名稱:[someService, someService1]
         */
    }

    /**
     * 創建非自定義物件
     */
    @Test
    public void testOtherBean() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Date date = (Date) ctx.getBean("mydate");
        System.out.println("date = " + date);

        /** 執行結果
         * [SomeServiceImpl] 無參構造方法
         * [SomeServiceImpl] 無參構造方法
         * date = Wed Dec 22 19:35:37 CST 2021
         */
    }
}

2.4 Spring 的組態檔

??Spring 組態檔通常命名為ApplicationContext.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">

<!-- 
    1) 根標簽是 beans
    2) xxx.xsd 是當前XML檔案的約束檔案
    3) 在 beans 標簽內宣告 bean 物件,
       一個 bean 就是一個java物件,
 -->
</beans>

002_Spring組態檔

??Spring 支持多組態檔方式,Spring 管理多組態檔常用的是包含關系,即在主組態檔中使用import標簽包含其他組態檔,在其他組態檔中定義宣告各自的資訊,

<!-- 主組態檔 -->

<!-- 路徑中可以使用通配符 * 同時引入多個檔案 -->
<import resource="classpath:其他組態檔路徑" />

2.5 Spring IOC ? 創建物件

2.5.1 Spring 容器創建物件的特點

Spring 框架使用 DI 實作 IOC 思想,底層通過反射機制創建物件、初始化物件,

  1. 容器物件是ApplicationContext,它是一個介面,常用的實作類是ClassPathXmlApplicationContext,并且通過getBean()方法獲取已初始化的物件,
  2. Spring 創建物件默認呼叫類的無參構造器
  3. Spring 在創建容器物件后,會讀取組態檔,并創建檔案中宣告的所有java物件,然后都放在map物件(ConcurrentMap)中,

2.5.2 XML方式

??Spring 通過在組態檔中使用bean標簽宣告物件,使用id屬性指定創建的物件名稱,使用class屬性指定創建的物件型別,

<!-- 組態檔中宣告一個 bean 標簽代表一個 java物件 -->
<bean id="物件名稱"  />

2.5.3 注解方式

??使用注解代替組態檔中的bean標簽,在Java類上使用注解,通過value屬性指定創建的物件名稱(相對于標簽的id屬性),同時還需要在組態檔中開啟注解掃描并指定掃描的包路徑,

?Spring 提供了四個注解

注解 說明
@Component 表示普通的java物件
@Repository 常用于創建DAO層的物件,持久層物件,表示可以訪問資料庫
@Service 常用于創建Service層的物件,業務層物件,表示擁有事務功能
@Controller 常用于創建Controller層的物件,控制器物件,表示可以接收和處理請求,

?組態檔開啟注解掃描:

<!-- base-package 指定要掃描的包路徑,Spring 會自動掃描包及其子包內表有上述注解之一的類,并創建和管理, -->
<context:componet-scan base-package="包路徑" />

<!-- 如何掃描多個包? -->
<!-- 1. 使用多個標簽 -->
<context:componet-scan base-package="xx.yy.pack01" />
<context:componet-scan base-package="xx.yy.pack02" />

<!-- 2. 使用分隔符:分號(;)或逗號(,) -->
<context:componet-scan base-package="xx.yy.pack01;xx.yy.pack02" />

<!-- 3. 使用共同的父包 -->
<context:componet-scan base-package="xx.yy" />

2.6 Spring IOC ? 屬性注入

Source Code

2.6.1 XML方式

(1)set注入(設值注入)

set注入:通過物件的 setXxx() 方法給屬性賦值,

特點

  • 注入的屬性必須存在對應的 setter 方法
  • 如果屬性在物件中不存在,但存在 setter 方法,依然不會報錯,
  • Spring 容器只負責呼叫 setter 方法,與方法的具體實作無關,
<!-- 簡單型別注入: 基本資料型別、String型別 -->
<bean id="xxx" >
    <property name="屬性名" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
    ...
</bean>

<!-- 參考Java物件 -->
<bean id="xxx" >
    <property name="屬性名" ref="其他bean標簽的id值" />
    ...
</bean>
<!-- 或 -->
<bean id="xxx" >
    <property name="屬性名">
        <bean ></bean>
    </property>
    ...
</bean>

<!-- 注入null值 -->
<bean id="xxx" >
    <property name="屬性名">
        <null/>
    </property>
    ...
</bean>

<!-- 集合型別 -->
<bean id="xxx" >
    <property name="屬性名">
        <!-- 陣列 -->
        <array>
            <value>xxx</value>
        </array>
    </property>

    <property name="屬性名">
        <!-- List -->
        <list>
            <value>xxx</value>
            <ref bean="其他bean標簽的id值" />
        </list>
    </property>

    <property name="屬性名">
        <!-- Set -->
        <set>
            <value>xxx</value>
        </set>
    </property>

    <property name="屬性名">
        <!-- Map -->
        <map>
            <entry key="xxx" value="https://www.cnblogs.com/bpf-1024/p/yyy" />
        </map>
    </property>

    <property name="屬性名">
        <!-- 陣列 -->
        <array>
            <value>xxx</value>
        </array>
    </property>
</bean>

(2)構造注入

構造注入:通過物件的 含參構造器 方法給屬性賦值,

特點

  • 不需要屬性的 setter 方法
  • 需要有相對應的含參構造器
<!-- 
    index   對應構造器的形參索引,從0開始,可以省略
    name    對應構造器的形參名
    value   對應構造器的形參值
    ref     對應其他的Java Bean
 -->
<bean id="xxx" >
    <constructor-arg name="構造器形參名" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
    <constructor-arg index="構造器形參索引" value="https://www.cnblogs.com/bpf-1024/p/xxx" />
    ...
</bean>

(3)參考型別自動注入

參考型別自動注入:只針對物件中的參考型別有效,可以指定根據名稱或型別自動注入屬性的值,

  • byName: 根據名稱注入,當組態檔中bean標簽的id值與物件的屬性名匹配且屬于同個型別時,可以進行注入,
  • byType: 根據型別注入,當組態檔中bean標簽的class值與物件的屬性型別同源時,可以進行注入,
    • bean標簽的class值與物件的屬性型別相同時,
    • bean標簽的class值與物件的屬性型別存在父子關系時,
    • bean標簽的class值與物件的屬性型別存在介面-實作類關系時,

特點

  • byName 方式通過 bean 標簽的id屬性,需要保證id唯一
  • byType 方式提供 bean 標簽的class屬性,需要保證只能存在一個同源的bean,否則會報錯,
  • 參考型別自動注入本質上使用的是setter方法進行屬性賦值的,
<!-- 參考型別自動注入 -->
<bean id="xxx"  autowired="byName | byType">
    ...
</bean>

(4)小作業

主要功能:模擬用戶注冊操作,

  • 物體類 User,保存用戶資料,
  • 定義一個 UserDao 介面,提供方法 insertUser(User),同時定義介面的實作類 MySqlUserDao,方法實作輸出 "通過MySQL插入用戶:用戶資料",
  • 定義一個 UserService 介面,提供方法 addUser(User),同時定義介面的實作類 UserServiceImpl,并實作方法,

要求:使用 Spring 創建和管理介面的實作類物件,并通過 Spring 獲取物件完成用戶注冊操作,

Source Code

2.6.2 注解方式

(1)@Value

  • @Value注解只能為屬性賦普通型別的值,
  • @Value注解的位置:
    • 屬性宣告上:無需setter方法
    • setter方法上:需要setter方法,并且會呼叫setter方法
  • 賦的值可以通過外部組態檔(.properties)指定,
<!-- 組態檔中引入外部組態檔 -->
<context:property-placeholder location="classpath:properties檔案的路徑" />
  • HIDE
  • anno-value-applicationContext.xml
  • bean-value.properties
  • Student.java
  • TestAnnoValue.java
<?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">

    <context:component-scan base-package="com.bpf.anno.value" />
    <context:property-placeholder location="classpath:bean-value.properties" />
</beans>
stu.name=凱特斯
stu.age=13
package com.bpf.anno.value;

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

@Component
public class Student {

    /**
     * @Value 注解:為屬性賦值
     * 使用位置:
     *      1. 屬性宣告上:無需setter方法
     *      2. setter方法上:需要setter方法且會呼叫setter方法
     */
    @Value("${stu.name}")
    private String name;
    private Integer age;

    public void setName(String name) {
        System.out.println("name = " + name);
        this.name = name;
    }

    @Value("${stu.age}")
    public void setAge(Integer age) {
        System.out.println("age = " + age);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.bpf.xml;

import com.bpf.anno.value.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAnnoValue {

    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("anno-value-applicationContext.xml");
        Student student = (Student) ctx.getBean("student");
        System.out.println("student = " + student);

        /** 執行結果
         * age = 13
         * student = Student{name='凱特斯', age=13}
         */
    }
}

(2)@Autowired

  • @Autowired注解可以為屬性賦參考型別的值,默認方式是byType
  • @Autowired注解的位置:
    • 屬性宣告上:無需setter方法
    • setter方法上:需要setter方法,并且會呼叫setter方法
/**
 * Autowired 注解原始碼
 *   包含了 required 屬性,默認值為true,表示當賦值的屬性必須有值且賦值成功,當賦值的物件為null時,會拋出例外,
 */
public @interface Autowired {
    boolean required() default true;
}
  • HIDE
  • anno-autowired-applicationContext.xml
  • UserService.java
  • StudentServiceImpl.java
  • Student.java
  • TestAnnoAutowired.java
<?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">

    <context:component-scan base-package="com.bpf.anno.autowired" />
</beans>
package com.bpf.anno.service;

public interface UserService {

    void sayHello();
}
package com.bpf.anno.autowired;

import com.bpf.anno.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class StudentServiceImpl implements UserService {

    @Override
    public void sayHello() {
        System.out.println("<com.bpf.anno.autowired> [StudentServiceImpl] sayHello()...");
    }
}
package com.bpf.anno.autowired;

import com.bpf.anno.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Student {

    /**
     * @Autowired 注解:為屬性賦值
     * 使用位置:
     *      1. 屬性宣告上:無需setter方法
     *      2. setter方法上:需要setter方法且會呼叫setter方法
     * 屬性:
     *      boolean required: 表示此屬性是否必須,默認值為true,表示當對應的java物件為null時會拋出例外,
     *          org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    // @Autowired(required = false)
    @Autowired
    private UserService userService;

    public void sayHello() {
        userService.sayHello();
    }
}
package com.bpf.xml;

import com.bpf.anno.autowired.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAnnoAutowired {

    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("anno-autowired-applicationContext.xml");
        Student student = (Student) ctx.getBean("student");
        student.sayHello();

        /** 執行結果
         * <com.bpf.anno.autowired> [StudentServiceImpl] sayHello()...
         *
         * 當 StudentServiceImpl 類去掉 @Service 注解,Student 類中參考型別 userService 注解改成 @Autowired(required=false) 時:
         * 會拋出空指標例外,因為在 Student 的 sayHello() 方法中,userService未成功賦值,所以在真正使用上并不會修改 required
         */
    }
}

(3)@Qualifer

??當使用@Autowired注解進行參考型別注入時,由于默認方式為byType,當存在多個同源的bean時,會拋出例外:org.springframework.beans.factory.NoUniqueBeanDefinitionException,這時候就需要使用byName方式了,

  • @Qualifer注解結合@Autowired注解使用可以實作byName方式的參考型別自動注入,
  • 注解位置同上,
/**
 * Qualifer 注解中只有一個屬性 value, 用來指定 bean 的名稱即 id,
 */
public @interface Qualifier {
    String value() default "";
}

(4)@Resource

  • @Resource注解是JDK自帶的注解,但 Spring 支持這樣的注解使用,
  • @Resource注解只能為屬性賦參考型別的值,默認方式是byName
    • 當使用byName無法匹配到任何bean時,會使用byType方式,
    • 通過指定name屬性讓注解只通過byName方式注入bean,
  • 在 JDK8 及之前是自帶此注解的,更高的版本需要手動匯入依賴,
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

2.7 Spring IOC 總結

??IOC 就是用來管理物件、管理依賴關系的,通過 IOC 可以實作解決處理業務邏輯物件之間的耦合關系,即 Service 和 DAO 之間的解耦合,

  • 不適合交給Spring管理的物件:
    • 物體類
    • servlet、listener、filter 等 WEB 中的物件,因為它們是由 Tomcat 創建和管理的物件,

補充

> 完全注解開發

> Spring Bean 的生命周期

3. AOP 面向切面編程

3.1 AOP 是什么

??AOP (Aspect Orient Programming, 面向切面編程) 是一種編程思想,它可以在不改變源代碼的基礎上,給業務方法新增功能,

??AOP 是一種動態的思想,它是在程式運行期間,為特定的業務創建代理,通過代理來增加切面功能,而這個代理是存在于記憶體中的,

什么是切面

  • 給業務功能新增的功能就是切面,
  • 切面一般是非業務功能,而且一般都是可復用的,
  • 比如:日志功能、事務功能、權限檢查、引數檢查、資訊統計等等,

AOP的作用

  • 給業務功能新增方法不需改變源代碼,
  • 讓開發人員專注業務邏輯,提高開發效率,
  • 實作業務功能與非業務功能解耦合,
  • 切面復用,

3.2 AOP 中的重要術語

術語 翻譯 解釋
Aspect 切面 給業務方法新增的功能
JoinPoint 連接點 即業務方法
Pointcut 切入點 切面的執行位置,一個或多個連接點的集合,即增加切面的所有業務方法,
Target 目標物件 業務方法的執行者
Advice 通知 切面的執行時間

??AOP 中重要的三個要素:AspectPointcutAdvice,表示在 Advice時間、在 Pointcut位置 執行 Aspect切面

3.3 AOP 的使用時機

  • 當某些方法需要增加相同功能,而源代碼又不方便修改時
  • 當給業務方法增加非業務功能時

3.4 AOP 的技術實作

??常用的 AOP 實作技術是 SpringAspectJ

  • Spring:Spring 框架實作了 AOP 思想中的部分功能,但其操作比較繁瑣和笨重,
  • AspectJ:獨立的框架,專門負責 AOP,屬于 Eclipse 基金會,
    • 官網: https://www.eclipse.org/aspectj/

3.5 AspectJ 框架

??AspectJ 框架中可以使用 注解XML組態檔 的方式實作 AOP,

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.12</version>
</dependency>

3.5.1 注解方式

Source Code

(1)Advice 通知注解

?AspectJ 框架中表示切面執行的時間是五種通知注解,分別代表不同的執行時間,

注解 通知型別 執行時間
@Before 前置通知 業務方法前執行
@AfterReturning 后置通知 業務方法后執行
@Around 環繞通知 業務方法前和后都執行
@AfterThrowing 例外通知 業務方法程序中出現例外時執行
@After 最終通知 業務方法后執行

(2)Pointcut 切入點運算式

?AspectJ 框架中表示切面執行的位置是切入點運算式,本質上可以看作是業務方法的定位標志,

execution(訪問權限? 回傳值型別 全限定類名?方法名(引數串列) 例外型別?)
  • ? 代表可選,
    • 最簡形式:execution(回傳值型別 方法名(引數串列))
  • 四個部分之間通過空格分開,并且都可以使用通配符??,
通配符 含義
* 代表任意字符
.. 用在方法引數中,表示任意引數串列
用在包名中,表示當前包及其子包路徑
+ 用在類名后,表示當前類及其子類
用在介面后,表示當前介面及其實作類

(3)@Before 前置通知

  • 注解
    • 前置通知在目標方法執行之前起作用,
  • 屬性
    • value: 切入點運算式
  • 方法定義
    • public void 方法名(引數)
    • 第一個引數只能是JoinPoint
    • JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
package com.bpf.before.handler;

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

import java.util.Arrays;
import java.util.Date;

@Component
@Aspect
public class MyBeforeAspect {

    @Before("execution(public void com.bpf.before.service.impl.SomeServiceImpl.doSome(String) )")
    public void addExecTime() {
        System.out.println("[MyBeforeAspect] (前置通知) 當前執行時間:" + new Date());
    }

    @Before("execution(void do*(..))")
    public void noteExecMethod(JoinPoint point) {
        System.out.println("[MyBeforeAspect] (前置通知) 當前正在運行的方法是:");
        System.out.println("\tSign: " + point.getSignature());
        System.out.println("\tTarget: " + point.getTarget());
        System.out.println("\tKind: " + point.getKind());
        System.out.println("\tArgs: " + Arrays.toString(point.getArgs()));
    }
}

(4)@AfterReturning 后置通知

  • 注解
    • 前置通知在目標方法執行之后起作用,
  • 屬性
    • value: 切入點運算式
    • returning: 宣告自定義變數名,必須與形參中的變數名一致,代表目標方法的執行結果,
  • 方法定義
    • public void 方法名(引數)
    • 第一個引數只能是JoinPoint
    • JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
    • Object: 表示目標方法的執行結果,推薦使用Object
  • 特點
    • 當業務方法的回傳值型別是 基本資料型別及其包裝類 或 String 時,切面方法無法改變回傳值內容,
    • 當業務方法的回傳值型別是 其他參考型別的Java物件時,切面方法可以改變回傳值內容,
package com.bpf.afterreturning.handler;

import com.bpf.afterreturning.bean.Person;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAfterReturningAspect {

    @AfterReturning(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )",
            returning = "res")
    public void process(JoinPoint point, Object res) {
        System.out.println("[MyAfterReturningAspect] (后置通知) 目標方法的執行結果是:" + res);

        // 當 回傳值型別為 String 時,嘗試修改,但修改失敗,
        if (res instanceof String) {
            res += " < AfterReturning";
            System.out.println("[MyAfterReturningAspect] (后置通知) 修改方法回傳值:string = " + res);
        }

        // 當 回傳值型別為 其他參考型別的java物件時,可以修改成功,
        if (res instanceof Person) {
            Person person = (Person) res;
            person.setName("ZH-" + person.getName());
            System.out.println("[MyAfterReturningAspect] (后置通知) 修改方法回傳值:person = " + person);
        }
    }
}

(5)@Around 環繞通知

  • 注解
    • 前置通知在目標方法執行之前或之后起作用,
  • 屬性
    • value: 切入點運算式
  • 方法定義
    • public Object 方法名(引數)
    • 回傳值型別必須有,推薦是Object,表示目標方法的執行結果回傳值,
    • 第一個引數只能是ProceedingJoinPoint
    • ProceedingJoinPoint: 是JoinPoint的子類,代表執行的業務方法,可以執行目標方法proceed()、獲取方法的相關資訊,如引數、方法名等,
  • 特點
    • 可以選擇是否執行目標方法,
    • 可以修改目標方法的回傳結果,
package com.bpf.around.handler;

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

import java.util.Date;

@Component
@Aspect
public class MyAroundAspect {

    @Around(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )")
    public Object process(ProceedingJoinPoint point) throws Throwable {
        System.out.println("[MyAroundAspect] (環繞通知) 目標方法之前:記錄執行時間 " + new Date());

        // 執行目標方法并拿到執行結果
        Object result = point.proceed();

        if (result instanceof String) {
            String res = (String) result;
            if (res.contains("doSome")) {
                result = res.replace("doSome", "something here");
            }
        }

        System.out.println("[MyAroundAspect] (環繞通知) 目標方法之后:執行事務功能");

        return result;
    }
}

(6)@AfterThrowing 例外通知

  • 注解
    • 前置通知在目標方法執行拋出例外后起作用,
  • 屬性
    • value: 切入點運算式
    • throwing: 宣告自定義變數名,必須與形參中的變數名一致,代表目標方法拋出的例外物件,
  • 方法定義
    • public void 方法名(引數)
    • 第一個引數只能是JoinPoint
    • JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
    • Exception: 例外型別的引數表示目標方法執行時拋出的例外,
  • 特點
    • 只有在目標方法執行拋出例外時才執行,否則不執行,
    • 此切面方法只適合當作目標方法的監控程式,不適合作為例外處理程式!
package com.bpf.afterthrowing.handler;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAfterThrowingAspect {

    @AfterThrowing(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )",
            throwing = "ex")
    public void process(JoinPoint point, Exception ex) {
        System.out.println("[MyAfterThrowingAspect] (例外通知) 目標方法拋出例外時執行:");
        System.out.println("\t記錄執行時間: " + new Date());
        System.out.println("\t記錄例外資訊:" + ex.getMessage());
        System.out.println("\t記錄例外型別:" + ex.getClass());
    }
}

(7)@After 最終通知

  • 注解
    • 前置通知在目標方法的最后起作用,
  • 屬性
    • value: 切入點運算式
  • 方法定義
    • public void 方法名(引數)
    • 第一個引數只能是JoinPoint
    • JoinPoint: 表示連接點,即執行的業務方法,可以獲取方法的相關資訊,如引數、方法名等,
  • 特點
    • 在目標方法的最后執行,無論有沒有拋出例外,
package com.bpf.after.handler;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAfterAspect {

    @After(value = "https://www.cnblogs.com/bpf-1024/p/execution(* *..SomeServiceImpl.do*(..) )")
    public void process(JoinPoint point) {
        System.out.println("[MyAfterAspect] (最終通知) 目標方法的最后執行:記錄完成時間 " + new Date());
    }
}

(8)@Pointcut 切入點運算式注解

  • 注解
    • 用于定義可復用的切入點運算式,
  • 屬性
    • value: 切入點運算式
  • 方法定義
    • * void 方法名()
package com.bpf.pointcut.handler;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Aspect
public class MyAfterAspect {

    @Pointcut("execution(* *..SomeServiceImpl.do*(..) )")
    private void doMethods() {}

    @After(value = "https://www.cnblogs.com/bpf-1024/p/doMethods()")
    public void process() {
        System.out.println("[MyAfterAspect] (最終通知) 目標方法的最后執行:記錄完成時間 " + new Date());
    }

    @AfterThrowing(value = "https://www.cnblogs.com/bpf-1024/p/doMethods()", throwing = "ex")
    public void process(Exception ex) {
        System.out.println("[MyAfterThrowingAspect] (例外通知) 目標方法拋出例外時執行:");
        System.out.println("\t記錄執行時間: " + new Date());
        System.out.println("\t記錄例外資訊:" + ex.getMessage());
        System.out.println("\t記錄例外型別:" + ex.getClass());
    }
}

3.5.2 XML方式

【詳見 5.4 AspectJ 事務控制 】

3.6 AOP 總結

?AOP 是一種動態的技術思想,目的是實作業務功能和非業務功能的解耦合,

?當目標方法需要增加功能,而不想修改或不能修改源代碼時,使用 AOP 技術就最適合不過了,

4. Spring 集成 MyBatis

Source Code

4.1 集成步驟

  1. 使用 MySQL 資料庫,創建學生表
  2. 創建 maven 專案
  3. 匯入依賴
  4. 創建物體類 Student
  5. 創建 DAO 介面 和 Mapper檔案
  6. MyBatis 組態檔
  7. 創建 Service 介面和實作類
  8. Spring 組態檔
    1. 宣告資料源 DataSource, 用于連接資料庫
    2. 宣告 SqlSessionFactoryBean, 用于創建 SqlSessionFactory 物件
    3. 宣告 MapperScannerConfigurer, 用于創建 DAO 的代理物件
    4. 宣告 Service 物件,并注入 DAO
  9. 測驗方法測驗

:當 MyBatis 整合 Spring 時,所有事務都默認是自動提交的,

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

4.2 編碼

  • HIDE
  • pom.xml
  • StudentDao.java
  • StudentDao.xml
  • StudentService.java
  • StudentServiceImpl.java
  • mybatis.xml
  • applicationContext.xml
  • StudentServiceTest.java
<?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>com.bpf</groupId>
    <artifactId>M05-spring-mabtis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.12</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <!-- 包含Mapper 檔案 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>
package com.bpf.dao;

import com.bpf.bean.Student;

import java.util.List;

public interface StudentDao {

    int insertStudent(Student student);

    List<Student> selectStudents();
}
<?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.bpf.dao.StudentDao">
    <!--int insertStudent(Student student);-->
    <insert id="insertStudent">
        insert into student(name, age) values(#{name}, #{age})
    </insert>
    <!--List<Student> selectStudents();-->
    <select id="selectStudents" resultType="com.bpf.bean.Student">
        select id,name,age from student
    </select>
</mapper>
package com.bpf.service;

import com.bpf.bean.Student;

import java.util.List;

public interface StudentService {

    int addStudent(Student student);

    List<Student> queryStudent();
}
package com.bpf.service.impl;

import com.bpf.bean.Student;
import com.bpf.dao.StudentDao;
import com.bpf.service.StudentService;

import java.util.List;

public class StudentServiceImpl implements StudentService {

    private StudentDao studentDao;

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public int addStudent(Student student) {
        return studentDao.insertStudent(student);
    }

    @Override
    public List<Student> queryStudent() {
        return studentDao.selectStudents();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 設定日志 -->
        <setting name="logImpl" value="https://www.cnblogs.com/bpf-1024/p/STDOUT_LOGGING"/>
    </settings>

    <!-- 別名 -->
    <typeAliases>
        <package name="com.bpf.bean"/>
    </typeAliases>

    <!-- 指定Mapper檔案位置
        package: 需要保證 介面 與 mapper映射檔案 在同一包下,且名稱相同,
     -->
    <mappers>
        <package name="com.bpf.dao"/>
    </mappers>
</configuration>
<?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">

    <!-- 引入外部組態檔 -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置資料源 -->
    <bean id="dataSource"  init-method="init" destroy-method="close">
        <property name="url" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.url}" />
        <property name="username" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.username}" />
        <property name="password" value="https://www.cnblogs.com/bpf-1024/p/${jdbc.password}" />
    </bean>

    <!-- 宣告 SqlSessionFactoryBean 用來創建 SqlSessionFactory 物件 -->
    <bean id="sqlSessionFactory"  >
        <!-- 指定資料源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 指定MyBatis組態檔 -->
        <property name="configLocation" value="https://www.cnblogs.com/bpf-1024/p/classpath:mybatis.xml" />
    </bean>

    <!-- 宣告 MapperScannerConfigurer 用來創建 DAO介面的動態代理物件
        回圈遍歷 basePackage 中所有的介面,然后使用 SqlSession.getMapper() 為每個介面創建對應的物件,并添加到容器中,

        ApplicationContext ctx = ...

        SqlSessionFactory factory = ctx.getBean("", SqlSessionFactory.class);
        SqlSession session = factory.openSession();

        for(介面: com.bpf.dao ) {
            介面 物件 = session.getMapper(介面.class);
            springMap.put(物件名,物件);

        }

        物件名:介面名首字母小寫
     -->
    <bean  >
        <!-- 指定 SqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="https://www.cnblogs.com/bpf-1024/p/sqlSessionFactory" />
        <!-- 指定 DAO 介面所在的包 -->
        <property name="basePackage" value="https://www.cnblogs.com/bpf-1024/p/com.bpf.dao" />
    </bean>

    <!-- 宣告 StudentServiceImpl -->
    <bean id="studentService" >
        <property name="studentDao" ref="studentDao" />
    </bean>
</beans>
package com.bpf;

import com.bpf.bean.Student;
import com.bpf.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StudentServiceTest {

    // 獲取 Spring 容器中的物件
    @Test
    public void testSpringInfo() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        System.out.println("定義的物件個數:" + ctx.getBeanDefinitionCount());
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println("\t" + name + " ==> " + ctx.getBean(name));
        }

        /** 執行結果
         * 定義的物件個數:11
         * 	org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0 ==> org.springframework.context.support.PropertySourcesPlaceholderConfigurer@6221a451
         * 	dataSource ==> {
         * 	    CreateTime:"2021-12-25 19:17:26",
         * 	    ActiveCount:0,
         * 	    PoolingCount:0,
         * 	    CreateCount:0,
         * 	    DestroyCount:0,
         * 	    CloseCount:0,
         * 	    ConnectCount:0,
         * 	    Connections:[]
         *  }
         * 	sqlSessionFactory ==> org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@3012646b
         * 	org.mybatis.spring.mapper.MapperScannerConfigurer#0 ==> org.mybatis.spring.mapper.MapperScannerConfigurer@4a883b15
         * 	studentService ==> com.bpf.service.impl.StudentServiceImpl@25641d39
         * 	studentDao ==> org.apache.ibatis.binding.MapperProxy@7b36aa0c
         * 	org.springframework.context.annotation.internalConfigurationAnnotationProcessor ==> org.springframework.context.annotation.ConfigurationClassPostProcessor@5824a83d
         * 	org.springframework.context.annotation.internalAutowiredAnnotationProcessor ==> org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@537f60bf
         * 	org.springframework.context.annotation.internalCommonAnnotationProcessor ==> org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@5677323c
         * 	org.springframework.context.event.internalEventListenerProcessor ==> org.springframework.context.event.EventListenerMethodProcessor@18df8434
         * 	org.springframework.context.event.internalEventListenerFactory ==> org.springframework.context.event.DefaultEventListenerFactory@65c7a252
         */
    }

    // 插入資料
    @Test
    public void testInsert() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentService service = (StudentService) ctx.getBean("studentService");

        service.addStudent(new Student("Tom", 14));
        service.addStudent(new Student("Marry", 15));
    }

    // 查詢資料
    @Test
    public void testQuery() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentService service = (StudentService) ctx.getBean("studentService");

        service.queryStudent().forEach(System.out::println);

        /** 執行結果
         * Student{id=1, name='Tom', age=14}
         * Student{id=2, name='Marry', age=15}
         */
    }
}

5. Spring 事務

事務:可以理解為多個 sql 陳述句的組合,要么都執行成功,要么都執行失敗,

開發中,一般將事務放在 public 的業務方法上,

5.1 事務管理器

5.1.1 不同的資料庫訪問技術

(1)JDBC 的事務處理

public void updateAccount() {
    Connection conn = ...
    conn.setAutoCommit(false);
    stat.insert(..);
    stat.update(..);
    conn.commit();
    conn.setAutoCommit(true);
}

(2)MyBatis 的事務處理

public void updateAccount() {
    SqlSession session = SqlSession.openSession(false);

    try {
        session.insert(..);
        session.update();
        session.commit();
    } catch(Exception e) {
        session.rollback();
    }
}

5.1.2 Spring 統一事務管理

??由于不同的資料庫技術使用的事務管理方式也不同,當專案中使用不同資料庫且來回切換時,會導致代碼需要頻繁修改,
??Spring 提供了統一的事務管理器,用來管理不同資料庫訪問技術的事務處理,開發人員就只需要面對 Spring 的事務處理一種介面進行編程,省去了不同資料庫之間的差別,

5.1.3 Spring 事務管理器

?Spring 提供的統一事務管理器介面是 PlatformTransactionManager

?這個介面提供了很多實作類,如對于 JDBC或MyBatis的DataSourceTransactionManager,Hibernate的HibernateTransactionManager等等,

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

5.1.4 Spring 事務的作業原理

?Spring 事務使用AOP 的環繞通知來實作目標業務方法的事務增強功能,這樣就無需修改源代碼了,

@Around("execution(* *..*.*(..))")
public Object myAround(ProceedingJoinPoint point) {
    try {
        // 事務開始
        PlatformTransactionManager.beginTransaction();
        // 執行目標業務方法
        point.proceed();
        PlatformTransactionManager.commit();
    } catch (Exception e) {
        PlatformTransactionManager.rollback();
    }
}

5.2 事務定義介面 TransactionDefinition

?TransactionDefinition事務定義介面定義了事務隔離級別事務傳播行為事務超時時間三類事務屬性的常量值,

5.2.1 事務隔離級別

?隔離級別:控制事務之間影響的程度,

隔離級別 說明
DEFAULT 根據資料庫型別選擇默認的隔離級別,
?MySQL:REPEATABLE_READ
?Oracle:READ_COMMITTED
READ_UNCOMMITTED 讀未提交,為解決任何并發問題,
READ_COMMITTED 讀已提交,解決臟讀,存在不可重復讀與幻讀,
REPEATABLE_READ 可重復讀,解決臟讀、不可重復讀,存在幻讀,
SERIALIZABLE 串行化,不存在并發問題,

5.2.2 事務超時時間

?超時時間:表示一個業務方法最長的執行時間,以秒為單位,整數值,默認值為-1,表示無限長,

5.2.3 事務傳播行為

?傳播行為:當業務方法被呼叫時,事務在方法之間的傳播和使用的變化,

傳播行為 說明
PROPAGATION_REQUIRED 默認的傳播行為,如果已存在事務就使用當前的事務,否則創建新事務,
PROPAGATION_REQUIRES_NEW 必須創建新事務,如果已存在事務就將其掛起,
PROPAGATION_SUPPORTS 有無事務都能正常執行,
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
PROPAGATION_NESTED
PROPAGATION_MANDATORY

5.3 Spring 事務控制

Source Code

5.3.1 Spring 事務控制的方式

??Spring 框架提供了@Transactional注解用于控制事務,使用這個注解可以定義事務的屬性,包括隔離級別、傳播行為、超時時間等等,

注解屬性

屬性 型別 默認值 說明
propagation enum Propagation Propagation.REQUIRED 事務的傳播行為
isolation enum Isolation Isolation.DEFAULT 事務的隔離級別
readOnly boolean false 是否只讀
timeout int -1 事務的超時時間,單位:秒
rollbackFor Class<? extends Throwable>[] - 事務回滾的例外類串列,取值為例外型別別
rollbackForClassName String[] - 事務回滾的例外類串列,取值為例外類名稱
noRollbackFor Class<? extends Throwable>[] - 事務不回滾的例外類串列
noRollbackForClassName String[] - 事務不回滾的例外類串列
  • rollbackFor: 當業務方法拋出的例外存在于引數串列中時,事務一定回滾;否則繼續判斷是否為RuntimeException或其子類,若是則事務一定回滾,

使用方法

  1. 在 Spring 組態檔中宣告事務管理器
  2. 在 Spring 組態檔中宣告開啟事務注解驅動
  3. 在 public 的 Service 方法上使用@Transactional注解,

特點

  • 優點:使用方便,效率高;適合中小型專案,
  • 缺點:需要改動源代碼,

5.3.2 牛刀小試

Spring 組態檔

<!-- 宣告事務管理器 -->
<bean id="transactionManager" >
    <!-- 指定資料源 -->
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 開啟事務注解驅動 -->
<tx:annotation-driven transaction-manager="transactionManager" />

Service方法

@Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        timeout = 20, readOnly = false,
        rollbackFor = {NullPointerException.class, GoodNotEnoughException.class})
/**
 * rollbackFor: 表示當拋出的例外屬于 NullPointerException 或 GoodNotEnoughException,事務一定回滾,
 *          否則如果是 RuntimeException,事務也一定回滾,
 */
public class BuyGoodServiceImpl implements BuyGoodService {...}

5.4 AspectJ 事務控制

Source Code

5.4.1 AspectJ 事務控制的方式

??AspectJ 框架通過全組態檔的方式進行事務控制,無需改動代碼,

使用方法

  1. 匯入依賴:spring-aspects
  2. 在 Spring 組態檔中宣告事務管理器
  3. 在 Spring 組態檔中宣告業務方法的事務屬性和切入點運算式

特點

  • 缺點:理解難,配置較復雜,
  • 優點:實作代碼于事務配置解耦,實作事務功能無需修改源代碼;哪個快速的了解和掌控專案的全部事務;適合大型專案,

5.4.2 牛刀小試

匯入依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.12</version>
</dependency>

Spring 組態檔

<!-- 宣告事務管理器 -->
<bean id="transactionManager" >
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 宣告業務方法的事務屬性
    在此,只宣告具有某種規則方法名的方法具有哪些事務屬性,而沒指定具體哪些方法具有事務功能
    -->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--
            tx:method  指定具有哪些規則方法名的方法的事務屬性
                name        可以使用通配符,若只有*,表示除上述的方法之外
                propagation 傳播行為
                isolation   隔離級別
                read-only   是否只讀
                timeout     超時時間
                rollback-for 事務回滾時的例外型別串列,使用逗號(,)隔開
        -->
        <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                    read-only="false" timeout="20"
                    rollback-for="java.lang.NullPointerException, com.bpf.except.GoodNotEnoughException"/>

        <tx:method name="add*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception" />
        <tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
        <tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception" />

        <!-- "*" 表示除了上述方法之外使用下面的事務屬性: query*, search*, find*, get* ... -->
        <tx:method name="*" propagation="SUPPORTS" read-only="true" />
    </tx:attributes>
</tx:advice>

<aop:config>
    <!-- 宣告切入點運算式:表示任何包下的service下的任何方法 -->
    <aop:pointcut id="servicePoint" expression="execution(* *..service..*.*(..))"/>

    <!-- 關聯切入點運算式 與 事務通知 -->
    <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePoint" />
</aop:config>

6. Spring Web

Source Code

6.1 存在的問題

在每個 Servlet 程式中,如果每次的容器物件都是新建出來的,那一個請求就會創建一次容器,這不僅耗時,而且浪費空間,

public class AddStudentServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String age = req.getParameter("age");
        Student student = new Student(name, Integer.valueOf(age));

        // 直接創建容器物件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("ctx = " + ctx);

        // 從容器中獲取 Service 物件
        StudentService studentService = (StudentService) ctx.getBean("studentService");
        studentService.addStudent(student);

        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");
        resp.getWriter().write("<h1>注冊成功</h1>");
    }
}
26-Dec-2021 15:48:06.790 資訊 [http-nio-8080-exec-4] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-2} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@340804ae, started on Sun Dec 26 15:48:06 CST 2021

26-Dec-2021 15:48:12.408 資訊 [http-nio-8080-exec-5] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-3} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@4db4beb9, started on Sun Dec 26 15:48:12 CST 2021

26-Dec-2021 15:48:15.947 資訊 [http-nio-8080-exec-6] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-4} inited
ctx = org.springframework.context.support.ClassPathXmlApplicationContext@2f50dd69, started on Sun Dec 26 15:48:15 CST 2021
...

6.2 解決方法

目前要處理的問題是:只讓容器物件創建一次,并且能在多個Servlet程式之間共享,

?Spring 提供了監聽器ServletContextListener,它可以創建容器物件,并且能夠放入ServletContext全域共享作用域中,

?在這個介面中,定義了兩個方法:分別對應初始化時的操作和銷毀時的操作,

public interface ServletContextListener extends EventListener {
    public void contextInitialized(ServletContextEvent sce);

    public void contextDestroyed(ServletContextEvent sce);
}

?介面的常用實作類是ContextLoaderListener,類中的contextInitialized()方法呼叫的是父類ContextLoaderinitWebApplicationContext方法,

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    try {
        // 如果 容器物件為空,就創建
        if (this.context == null) {
            this.context = this.createWebApplicationContext(servletContext);
        }

        // 將創建的 容器物件 放入 ServletContext中
        // WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE: 用于保存的key
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        return this.context;
    } catch (Error | RuntimeException e) {
        ...
    }
}

6.3 如何使用監聽器

匯入依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.12</version>
</dependency>

在web.xml中宣告監聽器

<!-- 配置監聽器,用于創建容器物件 ApplicationContext 并放入全域共享域 ServletContext 中
    ContextLoaderListener 默認讀取組態檔的地址是: /WEB-INF/applicationContext.xml
    可以通過 <context-param> 重新指定組態檔路徑
    -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 指定 Spring 組態檔路徑 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

Servlet程式中獲取容器物件

?從上述原始碼可見,監聽器將容器物件保存到ServletContext的key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,于是:

public class AddStudentServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String age = req.getParameter("age");
        Student student = new Student(name, Integer.valueOf(age));

        // 方法一:直接創建容器物件
        // ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 方法二:使用監聽器,然后從 ServletContext 中獲取容器物件
        WebApplicationContext ctx = null;
        // ContextLoaderListener 監聽器將容器物件保存到 ServletContext 中的key
        String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attribute = getServletContext().getAttribute(key);
        if (attribute != null) {
            ctx = (WebApplicationContext) attribute;
        }

        System.out.println("ctx = " + ctx);

        // 從容器中獲取 Service 物件
        StudentService studentService = (StudentService) ctx.getBean("studentService");
        studentService.addStudent(student);

        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html");
        resp.getWriter().write("<h1>注冊成功</h1>");
    }
}

?如果覺得麻煩,可以使用 Spring 提供的工具類,用來獲取容器物件,其實就是上述代碼的封裝,

// 不同在于:第一個方法找不到時會拋例外 java.lang.IllegalStateException
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/394918.html

標籤:Java

上一篇:git安裝與使用,未完待續... ...

下一篇:nginx 轉發 rtmp 直播流

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more