天天看點

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

目錄

文章目錄

    • 目錄
    • 前言
    • 下載下傳Spring 依賴
    • 導入Spring相關jar包
    • IOC 容器
    • IOC 容器Bean的操作
      • Bean管理操作有兩種方式
      • xml配置檔案中``标簽介紹
      • Spring DI 的實作方式:屬性注入和構造注入Book類: (依賴注入)
      • Spring 執行個體化:bean(service與dao為案例)
      • Spring 執行個體化:嵌套bean與級聯指派建立對象(一對多為案例)
      • Spring 執行個體化:集合資料注入
      • Spring 執行個體化:提取List集合、Map對象的資料注入
      • Spring 執行個體化:工廠Bean(FactoryBean)
      • Spring Bean的作用域
      • Spring Bean的生命周期
      • Spring Bean自動注入
        • 什麼是自動注入?
        • Bean自動注入使用方法
      • Spring Bean自動注入druid連接配接池
    • Spring Annotation (注解)
      • 注解注入Bean
        • 配置注解掃描和注解掃描規則幾種方式
          • 方式一:指定包路徑掃描
          • 方式二:規則掃描定向注解
          • 方式三:規則掃描反向注解(過濾注解)
        • 注解Bean測試
      • 注解屬性注入
        • @Autowired根據類型來注入屬性值 (service和dao為案例)
        • @Qualifier根據Bean名稱注入屬性,與@Autowired 注解配合使用(service和dao為案例)
        • @Resource根據Bean名稱注入屬性,預設按照 Bean 執行個體名稱進行裝配
      • Spring 使用配置類代替配置檔案
    • AOP 面向切面程式設計
      • AOP JDK動态代理
      • AspectJ開發AOP
      • AspectJ 注解操作
        • AspectJ 注解介紹
        • AspectJ 注解切入點表達式介紹
        • AspectJ 注解的使用
      • AspectJ 配置檔案操作
    • Spring JDBCTemplate操作資料庫
      • 什麼是JDBCTemplate?
      • 導入相關jar包
      • JDBCTemplate使用執行個體
      • JDBCTemplate 操作資料庫(增、删、改)
      • JDBCTemplate 操作資料庫(查詢、批量操作)
    • Spring 事務操作
      • 事務的特性(ACID)
      • Spring事務管理API
        • PlatformTransactionManager 接口介紹
        • TransactionDefinition 接口介紹
        • @Transactional 注解
        • 案例:
    • Spring使用配置類代替xml配置檔案
    • Spring5 架構新功能
      • Spring5 架構日志
      • Spring5 架構核心容器支援@Nullable 注解
      • Spring5 核心容器支援函數式風格 GenericApplicationContext
      • Spring5 支援整合 JUnit5

前言

Spring 是輕量級開源的JavaEE架構,它可以解決企業應用開發的複雜性。Spring 有兩個核心部分分别是 控制反轉(Inversion of Control 簡稱:IOC)、面向切面程式設計(Aspect Oriented Programming 簡稱:Aop),所謂的IOC則是把建立對象的過程交給 Spring 進行管理。而AOP則是對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

下載下傳Spring 依賴

  1. 首先進入Spring官網找到Projects下的Spring Framework。
Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  1. 在Spring Framework中可以檢視Spring GA 穩定版本;進入Github。
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  2. 在Github中找到Access to Binaries裡的 Spring Framework Artifacts點選。
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  3. 找到Downloading a Distribution點選 https://repo.spring.io位址。
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  4. 找到release/org/springframework/spring,複制的路徑拼接到repo.spring.io後面:https://repo.spring.io/release/org/springframework/
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  5. 下載下傳所需的spring版本
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

導入Spring相關jar包

commons-logging.jar 可以在mvnrepository上下載下傳

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

IOC 容器

所謂的IOC則是把建立對象的過程交給 Spring 進行管理,實作低耦合。
  • IOC底層原理:xml解析、工廠模式、反射。
  • Spring提供IOC容器實作兩接口方式:
    • BeanFactory:是 Spring 内部的使用接口,不推薦開發人員使用 。加載配置檔案時候不會建立對象,在擷取對象(使用)才去建立對象。
    • ApplicationContext:BeanFactory 接口的子接口,提供更多更強大的功能,推薦開發人 員使用,加載配置檔案時就會把配置檔案中的所有對象進行建立。
      • FileSystemXmlApplicationContext(String str) 實作類:某磁盤内的檔案位址。
      • ClassPathXmlApplicationContext(String str)實作類:src下某個檔案名。

IOC 容器Bean的操作

Bean管理操作有兩種方式

Bean管理有兩個操作:Spring 建立對象、Spring 注入屬性。

xml配置檔案中

<bean>

标簽介紹

屬性 描述
id 是一個 Bean 的唯一辨別符,Spring 容器對 Bean 的配置和管理都通過該屬性完成
name Spring 容器同樣可以通過此屬性對容器中的 Bean 進行配置和管理,name 屬性中可以為 Bean 指定多個名稱,每個名稱之間用逗号或分号隔開
class 該屬性指定了 Bean 的具體實作類,它必須是一個完整的類名,使用類的全限定名
scope 用于設定 Bean 執行個體的作用域,其屬性值有 singleton(單例)、prototype(原型)、request、session 和 global Session。其預設值是 singleton
constructor-arg

<bean>

元素的子元素,可以使用此元素傳入構造參數進行執行個體化。該元素的 index 屬性指定構造參數的序号(從 0 開始),type 屬性指定構造參數的類型
property

<bean>

元素的子元素,用于調用 Bean 執行個體中的 Set 方法完成屬性指派,進而完成依賴注入。該元素的 name 屬性指定 Bean 執行個體中的相應屬性名
ref

<property>

<constructor-arg>

等元素的子元索,該元素中的 bean 屬性用于指定對 Bean 工廠中某個 Bean 執行個體的引用
value

<property>

<constractor-arg>

等元素的子元素,用于直接指定一個常量值
list 用于封裝 List 或數組類型的依賴注入
set 用于封裝 Set 類型屬性的依賴注入
map 用于封裝 Map 類型屬性的依賴注入
entry

<map>

元素的子元素,用于設定一個鍵值對。其 key 屬性指定字元串類型的鍵值,ref 或 value 子元素指定其值
  • <property>

    标簽屬性介紹:(bean子标簽)
    屬性 詳情
    name Java類中所對應的屬性名稱。
    value 要指派的屬性。
    ref xml中bean标簽的id,也就是xml檔案中的一個對象。
  • <constructor-arg>

    标簽有參構造器介紹:(bean子标簽)
    屬性 詳情
    name 有參構造器中對應的名稱。
    value 要指派的屬性。
    index 有參構造器中第幾個參數。

Spring DI 的實作方式:屬性注入和構造注入Book類: (依賴注入)

public class Book {
    private String name;
    public Book(String name) {this.name = name;}
    public void setName(String name) {this.name = name;}
    public String getName() {return name;}
}
           

xml檔案:

  • 使用setter方法注入屬性。
    <bean id="book" class="com.tmsklst.spring5.Book">
        <property name="name" value="托馬斯·克裡斯特"></property>
    </bean>
               
  • 使用有參構造器注入屬性。
    <bean id="book" class="com.tmsklst.spring5.Book">
        <constructor-arg name="name" value="托馬斯·克裡斯特"></constructor-arg>
    </bean>
               
  • 字面量設定null值
    <bean id="book" class="com.tmsklst.spring5.Book">
        <property name="name">
        	<null/>
        </property>
    </bean>
               
  • 屬性值包含特殊符号
    <bean id="book" class="com.tmsklst.spring5.Book">
        <property name="name">
            <value><![CDATA[<<夏洛克·福爾摩斯>>]]></value>
        </property>
    </bean>
               

Spring 執行個體化:bean(service與dao為案例)

  1. 建立UserDaoImpl類:
    public class UserDaoImpl implements UserDao {
        @Override
        public void update() {
            System.out.println("userDaoImpl update.............");
        }
    }
               
  2. 建立UserServiceImpl類:(注意:需要個屬性添加set方法)
    public class UserServiceImpl implements UserService {
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        @Override
        public void add() {
            System.out.println("UserServiceImpl add..............");
            userDao.update();
        }
    }
               
  3. 建立bean.xml配置檔案:
    <!-- service對象的建立 -->
    <bean id="UserService" class="com.tmsklst.service.impl.UserServiceImpl">
        <!-- 注入service對象 -->
        <property name="userDao" ref="UserDao"></property>
    </bean>
    <!-- dao對象的建立 -->
    <bean id="UserDao" class="com.tmsklst.dao.impl.UserDaoImpl"></bean>
               
  4. 測試:
    public void newInstanceTest() {
        //加載配置檔案
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //擷取配置檔案對象
        UserServiceImpl userService = context.getBean("UserService", UserServiceImpl.class);
        userService.add();
    }
               

Spring 執行個體化:嵌套bean與級聯指派建立對象(一對多為案例)

  1. 建立部門類Dept:
    public class Dept {
        private String dname;
        public void setDname(String dname) {
            this.dname = dname;
        }
        @Override
        public String toString() {
            return "Dept{" +"dname='" + dname + '\'' + '}';
        }
    }
               
  2. 建立員工類Emp:
    public class Emp {
        private String ename;
        private String gender;
        private Dept dept;//員工屬于一個部門,使用對象形式表示
        public Dept getDept() { //級聯指派所需的get方法
            return dept;
        }
        public void setEname(String ename) {
            this.ename = ename;
        }
        public void setGender(String gender) {
            this.gender = gender;
        }
        public void setDept(Dept dept) {
            this.dept = dept;
        }
        public void add() {
            System.out.println(ename + "::" + gender + "::" + dept);
        }
    }
               
  3. 建立bean.xml配置檔案:(嵌套)
    <bean id="emp" class="com.tmsklst.pojo.Emp">
        <!-- 普通屬性 -->
        <property name="ename" value="托馬斯·克裡斯特"></property>
        <property name="gender" value="男"></property>
        <!-- 對象屬性 -->
        <property name="dept">
            <bean id="dept" class="com.tmsklst.pojo.Dept">
                <property name="dname" value="技術部"></property>
            </bean>
        </property>
    </bean>
               
    或 (級聯指派 注意:在emp類中一定要生成dept屬性的get方法)
    <bean id="emp" class="com.tmsklst.pojo.Emp">
        <!-- 普通屬性 -->
        <property name="ename" value="托馬斯·克裡斯特"></property>
        <property name="gender" value="男"></property>
        <!-- 級聯指派 -->
        <property name="dept" ref="dept"></property>
        <property name="dept.dname" value="财務部"></property>
    </bean>
    <bean id="dept" class="com.tmsklst.pojo.Dept"></bean>
               
  4. 測試:
    public void newInstanceTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Emp emp = context.getBean("emp", Emp.class);
        emp.add();
    }
               

Spring 執行個體化:集合資料注入

  1. 建立Student類:
    public class Student {
        //數組屬性
        private String[] array;
        //List屬性
        private List<String> list;
        //set屬性
        private Set<String> set;
        //Map屬性
        private Map<String,String> map;
        //List對象屬性
        private List<User> userList;
        
        public void setArray(String[] courses) {
            this.array = courses;
        }
        public void setList(List<String> list) {
            this.list = list;
        }
        public void setSet(Set<String> set) {
            this.set = set;
        }
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
        public void setUserList(List<User> userList) {
            this.userList = userList;
        }
        public void collectionToString() {
            System.out.println("array:" + Arrays.asList(array));
            System.out.println("list:" + list);
            System.out.println("set:" + set);
            System.out.println("map:" + map);
            System.out.println("userList:" + userList);
        }
    }
               
  2. 建立xml配置檔案:
    <!-- 集合屬性的注入 -->
    <bean id="student" class="com.tmsklst.collection.Student">
        <!-- 數組注入 -->
        <property name="array">
            <array>
                <value>array01</value>
                <value>array02</value>
            </array>
        </property>
        <!-- List注入 -->
        <property name="list">
            <list>
                <value>list01</value>
                <value>list02</value>
            </list>
        </property>
        <!-- set注入 -->
        <property name="set">
            <set>
                <value>set01</value>
                <value>set02</value>
            </set>
        </property>
        <!-- map注入 -->
        <property name="map">
            <map>
                <entry key="key01" value="value01"></entry>
                <entry key="key02" value="value02"></entry>
            </map>
        </property>
        <!-- 對象list注入 -->
        <property name="userList">
            <list>
                <ref bean="user1"></ref>
                <ref bean="user2"></ref>
            </list>
        </property>
    </bean>
    <bean id="user1" class="com.tmsklst.pojo.User">
        <property name="name" value="托馬斯·克裡斯特"></property>
    </bean>
    <bean id="user2" class="com.tmsklst.pojo.User">
        <property name="name" value="湯姆"></property>
    </bean>
               

Spring 執行個體化:提取List集合、Map對象的資料注入

  1. 建立Book類:
    public class Book {
        private List<String> list;
        private Map<Integer,User> map;
    
        public void setList(List<String> list) {
            this.list = list;
        }
        public void setMap(Map<Integer, User> map) {
            this.map = map;
        }
        public void add() {
            System.out.println("list:" + list);
            System.out.println("map:" + map);
        }
    }
               
  2. 在配置檔案beans頭部标簽中引入名稱空間 util:
    <beans xmlns:util="http://www.springframework.org/schema/util"
            xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd>
               
  3. 使用 util 标簽完成 list 集合與map集合對象的注入提取:
    <!-- 建立user對象 -->
    <bean id="user1" class="com.tmsklst.pojo.User">
        <property name="name" value="托馬斯·克裡斯特"></property>
    </bean>
    <bean id="user2" class="com.tmsklst.pojo.User">
        <property name="name" value="湯姆"></property>
    </bean>
    
    <!-- 1.提取list集合類型屬性注入 -->
    <util:list id="bookList">
        <value>九陽神功</value>
        <value>九陰正經</value>
    </util:list>
    <util:map id="bookMap">
        <entry key="1">
            <ref bean="user1"></ref>
        </entry>
        <entry key="2">
            <ref bean="user2"></ref>
        </entry>
    </util:map>
    
    <!-- 2.提取list集合類型屬性注入的使用 -->
    <bean id="book" class="com.tmsklst.pojo.Book">
        <property name="list" ref="bookList"></property>
        <property name="map" ref="bookMap"></property>
    </bean>
               
    完整配置檔案:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
        <!-- 建立user對象 -->
        <bean id="user1" class="com.tmsklst.pojo.User">
            <property name="name" value="托馬斯·克裡斯特"></property>
        </bean>
        <bean id="user2" class="com.tmsklst.pojo.User">
            <property name="name" value="湯姆"></property>
        </bean>
        
        <!-- 1.提取list集合類型屬性注入 -->
        <util:list id="bookList">
            <value>九陽神功</value>
            <value>九陰正經</value>
        </util:list>
        <util:map id="bookMap">
            <entry key="1">
                <ref bean="user1"></ref>
            </entry>
            <entry key="2">
                <ref bean="user2"></ref>
            </entry>
        </util:map>
        
        <!-- 2.提取list集合類型屬性注入的使用 -->
        <bean id="book" class="com.tmsklst.pojo.Book">
            <property name="list" ref="bookList"></property>
            <property name="map" ref="bookMap"></property>
        </bean>
    </beans>
               
  4. 測試
    public void collectionTest2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Book book = context.getBean("book", Book.class);
        book.add();
    }
               

Spring 執行個體化:工廠Bean(FactoryBean)

在配置檔案定義bean類型可以和傳回類型不一樣。
  1. 建立MyBean類,讓這個類作為工廠 bean,實作接口 FactoryBean
    public class MyBean implements FactoryBean<User> {
        //傳回 Bean 的類型取決于 FactoryBean<T> 泛型值
        @Override
        public User getObject() throws Exception {
            User user = new User();
            return user;
        }
    }
               
  2. 第二步 xml配置
  3. 第三步 測試
    public void myBeanTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User myBean = context.getBean("myBean", User.class);
        System.out.println(myBean);
    }
               

Spring Bean的作用域

  • 在 Spring 裡面,設定建立 bean 執行個體是單執行個體還是多執行個體。
  • 在 Spring 裡面,預設情況下,bean 是單執行個體對象。
  • 如何設定單執行個體還是多執行個體。
    • 在 spring 配置檔案 bean 标簽裡面有屬性 scope 用于設定單執行個體還是多執行個體。
    • scope 屬性:
      • 預設值 singleton 表示是單執行個體對象。
      • prototype 表示是多執行個體對象。
        <bean scope="singleton"></bean>
        <bean scope="prototype"></bean>
                   
      • request 單執行個體對象。而對不同的 HTTP 請求,會傳回不同的執行個體,該作用域僅在目前 HTTP Request 内有效。(不常用)
      • session 單執行個體對象。而對不同的 Session會話,會傳回不同的執行個體,該作用域僅在目前 HTTP Session 内有效。(不常用)
  • singleton 和 prototype 差別
    • 設定 scope 值是 singleton 時候,加載 spring 配置檔案時候就會建立單執行個體對象。
      <bean id="user1" class="com.tmsklst.pojo.User" scope="singleton"></bean>
      <bean id="user2" class="com.tmsklst.pojo.User" scope="propertype"></bean>
                 
      public void Test() {
          ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
          User singUser01 = context.getBean("user1", User.class);
          User singUser02 = context.getBean("user1", User.class);
          System.out.println(singUser01);//[email protected]
          System.out.println(singUser02);//[email protected]
          User proUser01 = context.getBean("user2", User.class);
          User proUser02 = context.getBean("user2", User.class);
          System.out.println(proUser01);//[email protected]
          System.out.println(proUser02);//[email protected]
      }
                 
    • 設定 scope 值是 prototype 時候,不是在加載 spring 配置檔案時候建立對象,在調用 getBean 方法時候建立多執行個體對象。

Spring Bean的生命周期

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)
  1. 建立Order類 ,并在類中自定以兩個方法

    initMethod()

    destroyMethod()

    用于初始化和銷毀。
    public class Order {
        private String name;
        public Order() {
            System.out.println("第一步 建立Bean的執行個體");
        }
        public void setName(String name) {
            this.name = name;
            System.out.println("第二步 設定屬性值");
        }
        //初始化的方法
        public void initMethod() {
            System.out.println("第三步 執行初始化方法");
        }
        //銷毀方法
        public void destroyMethod() {
            System.out.println("第五步 執行銷毀方法");
        }
    }
               
  2. 建立MyBeanPost類 作為後置處理器并實作BeanPostProcessor接口。
    public class MyBeanPost implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("在初始化之前執行的方法");
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("在初始化之後執行的方法");
            return bean;
        }
    }
               
  3. 建立xml配置檔案,在bean标簽中使用

    init-method

    destroy-method

    屬性分别指定Order類中的初始化方法與銷毀方法。
    <bean id="order" class="com.tmsklst.pojo.Order" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="iPhont"></property>
    </bean>
    <!-- 配置後置處理器 -->
    <bean id="myBeanPost" class="com.tmsklst.pojo.MyBeanPost"></bean>
               
  4. 測試
    public void test1() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Object order = context.getBean("order", Order.class);
        System.out.println("第四步 擷取建立bean執行個體對象");
        System.out.println(order);
        //銷毀執行個體
        context.close();
    }
               
    Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

Spring Bean自動注入

什麼是自動注入?

根據指定裝配規則屬性名稱或者屬性類型,Spring自動将比對的屬性值進行注入。

Bean自動注入使用方法

給bean标簽添加屬性autowire:

  • byType:根據 xml 中 bean的類型自動注入。(對多種同源類型無效)
  • byName:根據 xml 中 bean的名稱自動注入。
    <bean id="emp" class="com.tmsklst.autowire.Emp" autowire="byName">
        <!-- 或 -->
    <bean id="emp" class="com.tmsklst.autowire.Emp" autowire="byType">
               

案例:

Emp類:

public class Emp {
    private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "dept=" + dept +
                '}';
    }
    public void test() {
        System.out.println(dept);
    }
}
           

Dep類:

xml配置檔案:

<bean id="emp" class="com.tmsklst.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.tmsklst.autowire.Dept"></bean>
           

測試:

public void test1() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Emp emp = context.getBean("emp", Emp.class);
    System.out.println(emp);
}
           

Spring Bean自動注入druid連接配接池

  1. 在src下建立jdbc.properties配置檔案
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc.mysql://localhost:3306/test
    username=root
    passowrd=root
               
  2. 在xml中新增名稱空間context
    <beans xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
               
  3. 名稱空間引入配置檔案
  4. bean配置
    <bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
    </bean>
               

Spring Annotation (注解)

在 Spring 中,盡管使用 XML 配置檔案可以實作 Bean 的裝配工作,但如果應用中 Bean 的數量較多,會導緻 XML 配置檔案過于臃腫,進而給維護和更新帶來一定的困難。使用注解大大簡化了xml配置檔案。

注解格式:

@注解名稱(屬性名稱=屬性值,屬性名稱=屬性值。。。)

可包含多個屬性。

注解的使用範圍:類、方法、屬性。

所需依賴:spring-aop.jar

注解名稱 描述
@Component 可以使用此注解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個元件(Bean),并且可以作用在任何層次。使用時隻需将該注解标注在相應類上即可。
@Service 通常作用在業務層(Service 層),用于将業務層的類辨別為 Spring 中的 Bean,其功能與 @Component 相同。
@Controller 通常作用在控制層(如 Struts2 的 Action),用于将控制層的類辨別為 Spring 中的 Bean,其功能與 @Component 相同。
@Repository 用于将資料通路層(DAO層)的類辨別為 Spring 中的 Bean,其功能與 @Component 相同。
@Autowired 用于對 Bean 的屬性變量、屬性的 Set 方法及構造函數進行标注,配合對應的注解處理器完成 Bean 的自動配置工作。預設按照 Bean 的類型進行裝配。
@Qualifier 與 @Autowired 注解配合使用,會将預設的按 Bean 類型裝配修改為按 Bean 的執行個體名稱裝配,Bean 的執行個體名稱由 @Qualifier 注解的參數指定。
@Resource

其作用與 Autowired 一樣。其差別在于 @Autowired 預設按照 Bean 類型裝配,而 @Resource 預設按照 Bean 執行個體名稱進行裝配。

@Resource 中有兩個重要屬性:name 和 type。

Spring 将 name 屬性解析為 Bean 執行個體名稱,type 屬性解析為 Bean 執行個體類型。如果指定 name 屬性,則按執行個體名稱進行裝配;如果指定 type 屬性,則按 Bean 類型進行裝配。

如果都不指定,則先按 Bean 執行個體名稱裝配,如果不能比對,則再按照 Bean 類型進行裝配;如果都無法比對,則抛出 NoSuchBeanDefinitionException 異常。

@Value 給普通屬性指派。

注解注入Bean

配置注解掃描和注解掃描規則幾種方式

方式一:指定包路徑掃描

使用context命名空間,通知Spring掃描指定目錄進行注解解析。(不同源的包用逗号隔開)

<context:component-scan base-package="com.tmsklst.annotation,
                                      com.tmsklst1.dao,
                                      com.tmsklst2.service" />
           
方式二:規則掃描定向注解

設定use-default-filters屬性為false:不使用預設掃描規則,自定義配置規則。

通過include-filter标簽指定注解:

  • type:annotation(注解)。
  • expression:注解包全路徑。
    <context:component-scan base-package="com.tmsklst" use-default-filters="false">
    	<context:include-filter type="annotation"
               expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
               
方式三:規則掃描反向注解(過濾注解)

通過exclude-filter标簽指定需要無視的注解:

  • type:annotation(注解)。
  • expression:注解包全路徑。(如果指定為Component,則該包下的Component注解不解析)
    <context:component-scan base-package="com.tmsklst">
    	<context:exclude-filter type="annotation"
            	expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
               

注解Bean測試

  1. 使用context命名空間,通知Spring掃描指定目錄進行注解解析。
    <context:component-scan base-package="com.tmsklst" use-default-filters="false" >
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
               
  2. 建立UserService類,在類上方使用注解。注解裡的value值預設為類的名稱,且首字母小寫。

    注解等同于:

    <bean id="userService" class="com.tmsklst.service.UserService" />

    @Component(value = "userService")
    public class UserService {
        public void add() {
            System.out.println("service add……");
        }
    }
               
  3. 測試
    public void test1() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
               

注解屬性注入

@Autowired根據類型來注入屬性值 (service和dao為案例)

  1. Service層:建立UserService類, 屬性上的 @Autowired注解會更具類型(UserDao)去Dao層找到相應的類。
    @Service
    public class UserService {
        @Autowired
        private UserDao userDao;
        
        public void add() {
            System.out.println("service add……");
            userDao.add();
        }
    }
               
  2. Dao層:建立UserDao實作類,别忘了給實作類裝配bean(@Repository)
    @Repository
    public class UserDaoImpl implements UserDao {
        @Override
        public void add() {
            System.out.println("UserDao 實作類一 add…………");
        }
    }
               

@Qualifier根據Bean名稱注入屬性,與@Autowired 注解配合使用(service和dao為案例)

  1. Service層:建立UserService類, 屬性上的 @Autowired注解會更具類型(UserDao)去Dao層找到相應的類,當UserDao有多個實作類時,則加上@Qualifier注解,而value值是目标Bean名稱。
    @Service
    public class UserService {
    
        @Autowired
        @Qualifier(value = "userDao2")
        private UserDao userDao;
        
        public void add() {
            System.out.println("service add……");
            userDao.add();
        }
    }
               
  2. Dao層:建立UserDao實作類,多個實作類需要定義value值(@Repository)
    // 實作類一
    @Repository(value = "userDaol")
    public class UserDaoImpl implements UserDao {
        @Override
        public void add() {
            System.out.println("UserDao 實作類一 add…………");
        }
    }
    // 實作類二
    @Repository(value = "userDao2")
    public class UserDaoImpl2 implements UserDao {
        @Override
        public void add() {
            System.out.println("UserDao 實作類二 add()…………");
        }
    }
               
  3. 測試
    public void test1() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
    /**
    [email protected]
    service add……
    UserDao 實作類二 add()…………
    */
               

@Resource根據Bean名稱注入屬性,預設按照 Bean 執行個體名稱進行裝配

@Service
public class UserService {

    @Resource(name = "userDao")
    private UserDao userDao;

    public void add() {
        System.out.println("service add……");
        userDao.add();
    }
}
           

Spring 使用配置類代替配置檔案

建立一個SpringConfig類,使用@Configuration标注目前類為配置類,@ComponentScan指向解析注解的包。

@Configuration
@ComponentScan(basePackages = {"com.tmsklst5"})
public class SpringConfig {
}
           

AOP 面向切面程式設計

  • 利用AOP(Aspect Oriented Programming)可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
  • AOP 采取橫向抽取機制,取代了傳統縱向繼承體系的重複性代碼,其應用主要展現在事務處理、日志管理、權限控制、異常處理等方面。

AOP JDK動态代理

  • JDK 動态代理是通過 JDK 中的 java.lang.reflect.Proxy 類實作的。
  • static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

    :傳回指定接口的代理類執行個體,該接口将方法調用分派給指定的調用處理程式。
    • ClassLoader loader 參數:類的加載器。
    • Class<?>[] interfaces參數:增強方法所在類的實作接口,支援多個接口。
    • InvocationHandler h參數:實作InvocationHandler接口建立代理對象,增強代碼部分。

執行個體:

  1. 建立接口UserDao,在UserDao添加兩個方法,如下所示。
    public interface UserDao {
        int add(int num1,int num2);
        String update(String name);
    }
               
  2. 建立UserDao的實作類UserDaoimpl,如下所示。
    public class UserDaoimpl implements UserDao {
        @Override
        public int add(int num1, int num2) {
            System.out.println("第二:我是add方法原有邏輯……");
            return num1 + num2;
        }
        @Override
        public String update(String name) {
            System.out.println("第二:我是uodate方法原有邏輯……");
            return name;
        }
    }
               
  3. 建立JDKProxy類實作JDK動态代理。
    public class JDKProxy {
        public static void main(String[] args) {
            /* 1.擷取需要擴充功能的接口 */
            Class[] interfaces = {UserDao.class};
            /* 2.建立接口實作類對象 */
            UserDao userDaoimpl = new UserDaoimpl();
            /*
                3.使用Proxy類中的newProxyInstance方法動态代理。
                參數:類的加載器、接口、實作了InvocationHandler接口的類對象,此處也可用匿名InvocationHandler接口。
                傳回新的執行個體。
            */
            UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoimpl));
            /* 8.使用新的執行個體調用方法 add()與 update() */
            int add = userDaoProxy.add(10, 10);
            System.out.println("第四:addResult:" + add);
            String update = userDaoProxy.update("托馬斯·克裡斯特");
            System.out.println("第四:updateResult:" + update);
        }
    }
    
    /* 4.建立實作了InvocationHandler接口類 */
    class UserDaoProxy implements InvocationHandler {
        /* 5.接收目标類對象 */
        private Object obj;
        /* 6.建立有參構造器,用于接收目标類對象 */
        public UserDaoProxy(Object obj) {
            this.obj = obj;
        }
    
        /* 7.重寫invoke方法用于增強邏輯 */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //方法之前處理
            if ("add".equals(method.getName())) {
                System.out.println("第一:我是add方法之前執行…… " + method.getName() + ":傳遞參數" + Arrays.asList(args));
            }
            if ("update".equals(method.getName())) {
                System.out.println("第一:我是update方法之前執行…… " + method.getName() + ":傳遞參數" + Arrays.asList(args));
            }
            /* 9.調用invoke方法被增強的方法執行,參數:目标類對象、參數 */
            Object invoke = method.invoke(obj, args);
    
            //方法之後處理
            System.out.println("第三:方法之後執行……" + obj);
            return invoke;
        }
    }
               

AspectJ開發AOP

AspectJ 是一個基于 Java 語言的 AOP 架構,它擴充了 Java 語言。Spring 2.0 以後,新增了對 AspectJ 方式的支援,新版本的 Spring 架構,建議使用 AspectJ 方式開發 AOP。

AspectJ 開發AOP的兩種方式:

  • 基于XML的聲明方式。
  • 基于Annotation的聲明方式。
  • 除了Spring Aop的jar包以外,還要導入與 AspectJ 相關jar包:
    • spring-aspects-5.2.6.RELEASE:

      Spring 為 AspectJ 提供的實作。
    • com.springsource.net.sf.cglib-2.2.0

    • com.springsource.org.aopalliance-1.0.0

    • com.springsource.org.aspectj.weaver-1.6.8.RELEASE:

      AspectJ 提供的規範。

AspectJ 注解操作

AspectJ 架構為 AOP 開發提供了另一種開發方式——基于 Annotation 的聲明式。AspectJ 允許使用注解定義切面、切入點和增強處理,而 Spring 架構則可以識别并根據這些注解生成 AOP 代理。

AspectJ 注解介紹

名稱 描述
@Aspect 定義一個切面。
@Before 定義前置通知。(在被增強的方法之前執行)
@AfterReturning 定義後置通知。(在程式正常執行後執行)
@AfterThrowing 定義異常通知。(有異常時執行)
@After 定義最終通知。(不管是否異常,都執行)
@Around 定義環繞通知。(在被增強的方法前後執行)被此注解所定義的方法必須攜帶ProceedingJoinPoint類型的參數,通過ProceedingJoinPoint調用proceed()方法,執行被增強的方法。
@DeclareParents 用于定義引介通知。
@Order 在有多個切面的情況下,設定優先級。數值越小,優先級越高。
@Pointcut 對相同切入點作抽取。(對切入點表達式做封裝)在其他切入點value值中調用被此注解标示的方法。

AspectJ 注解切入點表達式介紹

  • 文法結構:@Before(value = “execution(【權限修飾符】【傳回值類型】【目标類全路徑】【方法名(【參數清單】)】)”)。
    • *通配符:所有的。
    • 沒有傳回值類型用空格代替。
  • 執行個體1:對 aop.tmsklst.aspectJannotation.User 類的add方法增強。

    execution(* aop.tmsklst.aspectJannotation.User.add(..))

  • 執行個體2:對 aop.tmsklst.aspectJannotation.User 類的所有方法增強。

    execution(* aop.tmsklst.aspectJannotation.User.*(..))

  • 執行個體3:對 aop.tmsklst.aspectJannotation 包下的所有方法增強。

    execution(* aop.tmsklst.aspectJannotation.*.*(..))

AspectJ 注解的使用

  1. 在配置檔案中使用context名稱空間啟用Annotation掃描,在使用aop名稱空間開啟AspectJ切面。
    <beans xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <!-- Annotation掃描 -->
        <context:component-scan base-package="aop.tmsklst"></context:component-scan>
        <!-- 開啟AspectJ生成切面 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
               
    或 使用配置類替換配置檔案。
    @ComponentScan
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class ConfigAop {
    }
               
  2. 被增強類
    @Component
    public class User {
        public void add() {
    //        int i = 1/0; //制造異常
            System.out.println("add......");
        }
    }
               
  3. 建立切面類,使用@Aspect注解表示目前類是切面類,用@Order設定優先級。
    @Aspect //定義一個切面
    @Order(1)//在有多個切面的情況下,設定優先級
    @Component
    public class UserProxy {
        //相同切入點抽取
        @Pointcut(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..) )")
        public void pointcut() {
    
        }
        //@Before 注解表示作為前置通知
        @Before(value = "pointcut())")
        public void defore() {
            System.out.println("defore....");
        }
        //最終通知:不管在掃描情況下都執行
        @After(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..))")
        public void after() {
            System.out.println("after....");
        }
        //後置通知:在程式正常執行後執行
        @AfterReturning(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..))")
        public void afterReturning() {
            System.out.println("afterReturning....");
        }
        //異常通知:有異常時執行
        @AfterThrowing(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..))")
        public void afterThrowing() {
            System.out.println("afterThrowing....");
        }
        //環繞通知
        @Around(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("環繞之前....");
            
            //被增強的方法執行
            proceedingJoinPoint.proceed();
    
            System.out.println("環繞之後....");
        }
    }
               
  4. 建立第二個切面類,使用@Aspect注解表示目前類是切面類,用@Order設定優先級。
    @Aspect //定義一個切面
    @Order(2)//在有多個切面的情況下,設定優先級
    @Component
    public class PersonProxy {
        @Before(value = "execution(* aop.tmsklst.aspectJannotation.User.add(..))")
        public void before() {
            System.out.println("PersonProx before....");
        }
    }
               
  5. 測試
    public void testAno1() {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        User user = context.getBean("user", User.class);
        user.add();
    }
    /* 執行結果                                  異常結果
    環繞之前....								 環繞之前....
    defore....							       defore....
    PersonProx before....					   PersonProx before....
    add......								   after....
    環繞之後....								afterThrowing....
    after....
    afterReturning....
    */
               

AspectJ 配置檔案操作

  1. 被增強類
    public class Person {
        public void add() {
            System.out.println("add....");
        }
    }
               
  2. 切面類
    public class PersonProxy {
        public void before() {
            System.out.println("before....");
        }
    }
               
  3. 在配置檔案中添加aop名稱空間。
    <beans xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <!-- 配置AOP增強 -->
        <aop:config>
            <!-- 切入點 -->
            <aop:pointcut id="p" expression="execution(* aop.tuoyingtao.aspectJxml.Person.add(..))"></aop:pointcut>
            <!-- 切面 -->
            <aop:aspect ref="personProxy">
                <!-- 增強方法 -->
                <aop:before method="before" pointcut-ref="p"></aop:before>
            </aop:aspect>
        </aop:config>
               

Spring JDBCTemplate操作資料庫

什麼是JDBCTemplate?

它是Spring架構對JDBC的封裝,使用JDBCTemplate更友善實作對資料庫操作。

導入相關jar包

  • druid-1.1.9:連結池
  • mysql-connector-java-5.1.7-bin:資料庫連接配接
  • spring-tx-5.2.6.RELEASE:事務處理
  • spring-jdbc-5.2.6.RELEASE:spring對jdbc的封裝

JDBCTemplate使用執行個體

  1. jdbc.properties 配置資料庫連接配接基本資訊。
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/user_db
    username=root
    passwprd=root
               
  2. Spring 配置檔案添加context名稱空間,引入jdbc.properties配置檔案,啟用注解解析,建立JdbcTemplate對象,并注傳入連結接池。
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:property-placeholder location="jdbc.properties" />
        <context:component-scan base-package="aop2.jdbctemplate"/>
    
        <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${driverClassName}" />
            <property name="url" value="${url}" />
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </bean>
    
        <!-- JdbcTemplate對象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注傳入連結接池 -->
            <property name="dataSource" ref="druidDataSource"/>
        </bean>
    </beans>
               
  3. BookDaoImpl類實作BookDao接口,并注入JdbcTemplate對象。
    @Repository
    public class BookDaoImpl implements BookDao {
        //注入JDBCTemplate
        @Autowired
        private JdbcTemplate jdbcTemplate;
    }
               
  4. 在BookService類中注入BookDaoImpl類。
    @Service
    public class BookService {
        //注入dao
        @Autowired
        private BookDao bookDao;
    }
               

JDBCTemplate 操作資料庫(增、删、改)

  1. 建立Book類
    public class Book {
        private Integer id;
        private String userName;
        private String status;
    	…………
    }
               
  2. service層
    @Service
    public class BookService {
        //注入dao
        @Autowired
        private BookDao bookDao;
    	// 添加資料
        public void addBook(Book book) {
            bookDao.add(book);
        }
        //修改資料
        public void amendBook(Book book) {
            bookDao.amend(book);
        }
    	//删除資料
        public void deleteBook(Integer id) {
            bookDao.delete(id);
        }
    }
               
  3. dao層
    @Repository
    public class BookDaoImpl implements BookDao {
        //注入JDBCTemplate
        @Autowired
        private JdbcTemplate jdbcTemplate;
        //插入資料
        @Override
        public void add(Book book) {
            String sql = "insert into t_book(id,userName,status) values(?,?,?)";
            Object[] args = {book.getId(),book.getUserName(),book.getStatus()};
            int update = jdbcTemplate.update(sql, args);
            System.out.println(update);
        }
        //修改資料
        @Override
        public void amend(Book book) {
            String sql = "update t_book set userName = ?, status = ? where id = ?";
            Object[] args = {book.getUserName(),book.getStatus(),book.getId()};
            int update = jdbcTemplate.update(sql, args);
            System.out.println(update);
        }
    	//删除資料
        @Override
        public void delete(Integer id) {
            String sql = "delete from t_book where id = ?";
            int update = jdbcTemplate.update(sql, id);
            System.out.println(update);
        }
    }
               
  4. 測試
    //插入資料
    public void jdbcTemplateTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.addBook(new Book(null,"托馬斯·克裡斯特","true"));
    }
    //修改資料
    public void jdbcTemplateTest2() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.amendBook(new Book(1,"Tom","false"));
    }
    //删除資料
    public void jdbcTemplateTest3() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.deleteBook(1);
    }
               

JDBCTemplate 操作資料庫(查詢、批量操作)

  1. service層
    @Service
    public class BookService {
        //注入dao
        @Autowired
        private BookDao bookDao;
    
        public Integer findCount() {
           return bookDao.countTotal();
        }
    
        public Book findBook(Integer id) {
            return bookDao.findBookInfo(id);
        }
    
        public List<Book> findAll() {
            return bookDao.findAllList();
        }
    
        public void batcAdd(List<Object[]> batchArgs) {
            bookDao.batchAddBook(batchArgs);
        }
    }
               
  2. dao層
    @Repository
    public class BookDaoImpl implements BookDao {
        //注入JDBCTemplate
        @Autowired
        private JdbcTemplate jdbcTemplate;
    	//總共多少條記錄
        @Override
        public Integer countTotal() {
            String sql = "select count(*) from t_book";
            Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
            return integer;
        }
    	//查詢詳情
        @Override
        public Book findBookInfo(Integer id) {
            String sql = "select id, userName, status from t_book where id = ?";
            Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
            return book;
        }
    	//查詢清單
        @Override
        public List<Book> findAllList() {
            String sql = "select id,userName,status from t_book";
            List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
            return query;
        }
    	//批量添加(删除、修改類似)
        @Override
        public void batchAddBook(List<Object[]> batchArgs) {
            String sql = "insert into t_book(id,userName,status) values(?,?,?)";
            int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
            System.out.println(Arrays.toString(ints));
        }
    
    }
               
  3. 測試
    @Test
    public void jdbcTemplateTest4() {
        Integer count = bookService.findCount();
        System.out.println(count);
    }
    @Test
    public void jdbcTemplateTest6() {
        Book book = bookService.findBook(2);
        System.out.println(book);
    }
    @Test
    public void jdbcTemplateTest7() {
        List<Book> all = bookService.findAll();
        Iterator<Book> iterator = all.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    @Test
    public void jdbcTemplateTest8() {
        List<Object[]> list = new ArrayList<>();
        Object[] o1 = {null,"name11","true"};
        Object[] o2 = {null,"name22","false"};
        Object[] o3 = {null,"name33","true"};
        list.add(o1);
        list.add(o2);
        list.add(o3);
        bookService.batcAdd(list);
    }
               

Spring 事務操作

事務是邏輯上的一組操作,要麼都執行,要麼都不執行。

事務的特性(ACID)

原子性(Atomicity):原子性是指事務是一個不可分割的工作機關,事務中的操作要麼都發生,要麼都不發生。一緻性(Consistency):事務必須使資料庫從一個一緻性狀态變換到另外一個一緻性狀态。

隔離性(Isolation):事務的隔離性是指一個事務的執行不能被其他事務幹擾,即一個事務内部的操作及使用的資料對并發的其他事務是隔離的,并發執行的各個事務之間不能互相幹擾。

持久性(Durability):持久性是指一個事務一旦被送出,它對資料庫中資料的改變就是永久性的,接下來的其他操作和資料庫故障不應該對其有任何影響。

Spring事務管理API

PlatformTransactionManager 接口介紹

PlatformTransactionManager接口表示事務管理器,為各個平台如JDBC、Hibernate等都提供了對應的事務管理器,更具不同的架構提供不同的實作類。

PlatformTransactionManager 接口中定義的三個方法:

public interface PlatformTransactionManager extends TransactionManager {
    //根據指定的傳播行為,傳回目前活動的事務或建立一個新事務。
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
	//送出事務
    void commit(TransactionStatus var1) throws TransactionException;
	//對執行的事務進行復原
    void rollback(TransactionStatus var1) throws TransactionException;
}
           

PlatformTransactionManager 接口對應不同架構的實作類

事務 描述

org.springframework.jdbc.datasource.DataSourceTransactionManager

使用Spring JDBC或者MyBatis進行持久化資料時使用。

org.springframework.orm.hibernate5.HibernateTransactionManager

使用Hibernate進行資料持久化時使用。

org.springframework.orm.jpa.JpaTransactionManager

使用JPA進行資料持久化時使用。

org.springframework.transaction.jta.JtaTransactionManager

使用JTA實作管理事務,在一個事務跨越多個資源時使用。
Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

當我們使用JDBC或Mybatis進行資料持久化操作時,xml配置操作如下:

<!-- 事務管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入資料源 -->
    <property name="dataSource" ref="DruidDataSource"/>
</bean>
           

TransactionDefinition 接口介紹

事務管理器接口 PlatformTransactionManager 通過

getTransaction(TransactionDefinition var1)

方法來得到一個事務,這個方法裡面的參數是 TransactionDefinition類 ,這個類就定義了一些基本的事務屬性。

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

@Transactional 注解

@Transactional 注解定義在類上表示目前類中所有方法都添加事務。若在方法上,則表示為此方法添加事務。

propagation 事務傳播行為:多事務方法直接進行調用,這個過程中事務是如何進行管理的。

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

ioslation 事務隔離級别:事務有特性成為隔離性,多事務操作之間不會産生影響。不考慮隔離性産生很多問題(髒讀、不可重複讀、幻讀)

隔離級别 描述

READ UNCOMMITTED

(讀未送出資料)

允許事務讀取未被其它事務送出更改。髒讀、不可重複讀以及幻讀的問題都會出現。

READ COMMITED

(讀已送出資料)

隻允許事務讀取已經被其它事務送出的變更,可以避免髒讀。但不可重複讀和幻讀問題仍然可能出現。

REPEATABLE READ

(可重複讀)

確定事務可以多次從一個字段中讀取相同的值。

在這個事務持續期間,禁止其他事務對這個字段進行更新,可以避免髒讀和不可重複讀。但幻讀的問題仍然存在。

SERIALIZABLE

(串行化)

確定事務可以從一個表中讀取相同的值。

在這個事務持續期間。禁止其他事務對該表執行插入、更新、删除操作,所有并發問題都可以避免,但性能十分低下。

timeout 逾時時間:事務需要在一定時間内進行送出,如果不送出進行復原。預設值是 -1(時間以秒機關計算)

readOnly 是否隻讀:讀:查詢操作,寫:添加修改删除操作。預設值 false。

rollbackFor 復原:設定出現哪些異常進行事務復原。

noRollbackFor 不復原:設定出現哪些異常不進行事務復原。

案例:

  1. 注解配置中添加tx命名空間
    <beans xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!-- 事務管理器 -->
        <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入資料源 -->
            <property name="dataSource" ref="DruidDataSource"/>
        </bean>
        <!-- 開啟事務注解 -->
        <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
               
    或 使用xml配置
    <!-- 事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入資料源 -->
        <property name="dataSource" ref="DruidDataSource"/>
    </bean>
    
    <!-- xml配置通知 -->
    <tx:advice id="txadvice">
        <!-- 配置事務參數 -->
        <tx:attributes>
            <tx:method name="account*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置切入點和切面 -->
    <aop:config>
        <!-- 配置切入點 -->
        <aop:pointcut id="pt" expression="execution(* spring.tm.service.UserService.*(..))"/>
        <!-- 配置切面 -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>
               
  2. service層(如果使用xml配置就需要添加@Transactional注解)
    @Service
    @Transactional(readOnly = false,timeout = 1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public class UserService {
    
        @Autowired
        private UserDao userDaoImpl;
    
        public void accountMoney(Integer addId,Integer reduceId, BigDecimal money) {
            userDaoImpl.addMoney(addId,money);
            //模拟異常
            int i = 10/0;
            userDaoImpl.reduceMoney(reduceId,money);
        }
    }
               

Spring使用配置類代替xml配置檔案

@Configuration//配置類
@ComponentScan(basePackages = "spring.tm")//元件掃描
@EnableTransactionManagement //開啟事務
public class AnnotationConfig {
    //建立資料庫連結池
    @Bean
    public DataSource getDruidDataSource() {
        try {
            Properties properties = new Properties();
            InputStream resourceAsStream =
                    ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            properties.load(resourceAsStream);
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            return dataSource;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 建立JdbcTemplate對象
     * @param dataSource 在IOC容器中更具類型找到DataSource對象
     * @return
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    /**
     * 建立事務管理器
     * @param dataSource 在IOC容器中更具類型找到DataSource對象
     * @return
     */
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
           

Spring5 架構新功能

整個 Spring5 架構的代碼基于 Java8,運作時相容 JDK9,許多不建議使用的類和方法在代碼庫中已删除。

Spring5 架構日志

Spring5 已經移除 Log4jConfigListener,官方建議使用 Log4j2。

1. 導入jar包

Spring5 架構一篇就夠(IOC容器、AOP面向切面程式設計、JDBCTemplate、Spring事務等)

2. 建立log4j2.xml配置檔案(名字是固定的)

<?xml version="1.0" encoding="UTF-8"?>
<!--日志級别以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration後面的status用于設定log4j2自身内部的資訊輸出,可以不設定,當設定成trace時,可以看到log4j2内部各種詳細輸出-->
<configuration status="INFO">
    <!--先定義所有的appender-->
    <appenders>
        <!--輸出日志資訊到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志輸出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然後定義logger,隻有定義了logger并引入的appender,appender才會生效-->
    <!--root:用于指定項目的根日志,如果沒有單獨指定Logger,則會使用root作為預設的日志輸出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>
           

Spring5 架構核心容器支援@Nullable 注解

@Nullable 注解可以使用在方法上面,屬性上面,參數上面,表示方法傳回可以為空,屬性值可以 為空,參數值可以為空

注解用在方法上面,方法傳回值可以為空。

@Nullable
String getId()
           

注解使用在方法參數裡面,方法參數可以為空。

注解使用在屬性上面,屬性值可以為空。

@Nullable
private String id;
           

Spring5 核心容器支援函數式風格 GenericApplicationContext

函數式風格建立對象,并交給Spring 進行管理。

public void genericApplicationContextTest() {
    //1.建立GenericApplicationContext對象
    GenericApplicationContext genericApplicationContext = new GenericApplicationContext();
    //2.調用context的方法對象注冊
    genericApplicationContext.refresh();
    genericApplicationContext.registerBean(User.class,() -> new User());
    genericApplicationContext.registerBean("myUser",User.class,() -> new User());
    //3.擷取在spring注冊的對象
    User user = (User) genericApplicationContext.getBean("spring.tm.pojo.User");
    User user2 = (User) genericApplicationContext.getBean("myUser");
    System.out.println(user);
    System.out.println(user2);
}
           

Spring5 支援整合 JUnit5

整合 JUnit4

@RunWith(SpringJUnit4ClassRunner.class)//單元測試架構
@ContextConfiguration("classpath:ApplicationContext2.xml")//加載配置檔案
public class JTest4 {
    @Autowired
    private UserService userService;
    @Test
    public void test() {
        userService.accountMoney(1001,1002,new BigDecimal(200));
    }
}
           

整合 JUnit5

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath*:ApplicationContext2.xml")
public class JTest5 {
    @Autowired
    private UserService userService;
    @Test
    public void test() {
        userService.accountMoney(1001,1002,new BigDecimal(300));
    }
}
           

使用複合注解替代上面兩個注解完成整合。

@SpringJUnitConfig(locations = "classpath*:spring/tm/ApplicationContext2.xml")
public class JTest5 {
    @Autowired
    private UserService userService;
    @Test
    public void test() {
        userService.accountMoney(1001,1002,new BigDecimal(300));
    }
}
           

繼續閱讀