1. 程式人生 > 遊戲 >滑板模擬遊戲《Session: Skate Sim》9月22日推出

滑板模擬遊戲《Session: Skate Sim》9月22日推出

Spring學習

1. Spring

1.1 簡介

  • Spring : 春天 —>給軟體行業帶來了春天

  • 2002年,Rod Jahnson首次推出了Spring框架雛形interface21框架。

  • 2004年3月24日,Spring框架以interface21框架為基礎,經過重新設計,釋出了1.0正式版。

  • 很難想象Rod Johnson的學歷 , 他是悉尼大學的博士,然而他的專業不是計算機,而是音樂學。

  • Spring理念 : 使現有技術更加實用 . 本身就是一個大雜燴 , 整合現有的框架技術

官網 :https://docs.spring.io/spring-framework/docs/current/reference/html/overview.html#overview

官方下載地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/

GitHub : https://github.com/spring-projects

配置檔案:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>6.0.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.2</version>
</dependency>

1.2 優點

  1. Spring是一個開源免費的框架 , 容器;
  2. Spring是一個輕量級的框架 , 非侵入式的;
  3. 控制反轉(IoC) , 面向切面程式設計(Aop)
  4. 支援事務的處理 , 對框架整合的支援;

一句話概括:

Spring是一個輕量級的控制反轉(IoC)和麵向切面(AOP)的容器(框架)。

1.3 組成

1.4 擴充套件

  • Spring Boot
    • 一個快速開發的腳手架
    • 基於SpringBoot可以快速的開發單個微服務
    • 約定大於配置
  • Spring Cloud
    • SpringCloud是基於SpringBoot實現的。

因為現在大多數公司都在使用SpringBoot進行快速開發,學習SpringBoot的前提,需要完全掌握Spring及SpringMVC,承上啟下的作用。

弊端:發展了太久,違背了原來的理念!配置十分繁瑣,人稱:“配置地獄”

2. IOC理論推導

2.1 IoC基礎

新建一個空白的maven專案

分析實現

我們先用我們原來的方式寫一段程式碼 .

  1. 先寫一個UserDao介面

    public interface UserDao {
        void getUser();
    }
    
  2. 再去寫Dao的實現類

    public class UserDaoImpl implements UserDao {
        @Override
        public void getUser() {
            System.out.println("UserDaoImpl");
        }
    }
    
  3. 然後去寫UserService的介面

    public interface UserService {
        void getUser();
    }
    
  4. 最後寫Service的實現類

    public class UserServiceImpl implements UserService {
       private UserDao userDao = new UserDaoImpl();
    
       @Override
       public void getUser() {
           userDao.getUser();
      }
    }
    
  5. 測試一下

    @Test
    public void test(){
       UserService service = new UserServiceImpl();
       service.getUser();
    }
    

    這是我們原來的方式 , 開始大家也都是這麼去寫的對吧 . 那我們現在修改一下 .

    • 把Userdao的實現類增加一個

      public class UserDaoImpl2 implements UserDao {
          @Override
          public void getUser() {
              System.out.println("UserDaoImpl2");
          }
      }
      
    • 緊接著我們要去使用UserDaoImpl2的話 , 我們就需要去service實現類裡面修改對應的實現

      public class UserServiceImpl implements UserService {
         private UserDao userDao = new UserDaoImpl2();
      
         @Override
         public void getUser() {
             userDao.getUser();
        }
      }
      
    • 再假設, 我們再增加一個Userdao的實現類

      public class UserDaoOracleImpl implements UserDao {
         @Override
         public void getUser() {
             System.out.println("Oracle獲取使用者資料");
        }
      }
      
    • 那麼我們要使用Oracle , 又需要去service實現類裡面修改對應的實現 . 假設我們的這種需求非常大 , 這種方式就根本不適用了, 甚至反人類對吧 , 每次變動 , 都需要修改大量程式碼 。這種設計的耦合性太高了, 牽一髮而動全身

  6. 那我們如何去解決呢 ?

    • 我們可以在需要用到他的地方 , 不去實現它 , 而是留出一個介面 , 利用set , 我們去程式碼裡修改下

      public class UserServiceImpl implements UserService {
          private static UserDao userDao;
      
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          @Override
          public void getUser() {
              userDao.getUser();
          }
      }
      
    • 現在去我們的測試類裡 , 進行測試

      @Test
      public void test() {
          UserServiceImpl userService = new UserServiceImpl();
          userService.setUserDao(new UserDaoImpl2());
      }
      

​ 大家發現了區別沒有 ? 可能很多人說沒啥區別。但是同學們 , 他們已經發生了根本性的變化 , 很多地方都不一樣了。仔細去思考一下 , 以前所有東西都是由程式去進行控制建立 , 而現在是由我們自行控制建立物件 , 把主動權交給了呼叫者。程式不用去管怎麼建立,怎麼實現了。它只負責提供一個介面。

​ 這種思想 , 從本質上解決了問題 , 我們程式設計師不再去管理物件的建立了 , 更多的去關注業務的實現 . 耦合性大大降低 . 這也就是IOC的原型 !

2.2 IOC本質

控制反轉IoC(Inversion of Control),是一種設計思想,DI(依賴注入)是實現IoC的一種方法,也有人認為DI只是IoC的另一種說法。沒有IoC的程式中 , 我們使用面向物件程式設計 , 物件的建立與物件間的依賴關係完全硬編碼在程式中,物件的建立由程式自己控制,控制反轉後將物件的建立轉移給第三方,個人認為所謂控制反轉就是:獲得依賴物件的方式反轉了。

IoC是Spring框架的核心內容,使用多種方式完美的實現了IoC,可以使用XML配置,也可以使用註解,新版本的Spring也可以零配置實現IoC。

Spring容器在初始化時先讀取配置檔案,根據配置檔案或元資料建立與組織物件存入容器中,程式使用時再從Ioc容器中取出需要的物件。

採用XML方式配置Bean的時候,Bean的定義資訊是和實現分離的,而採用註解的方式可以把兩者合為一體,Bean的定義資訊直接以註解的形式定義在實現類中,從而達到了零配置的目的。

控制反轉是一種通過描述(XML或註解)並通過第三方去生產或獲取特定物件的方式。在Spring中實現控制反轉的是IoC容器,其實現方法是依賴注入(Dependency Injection,DI)。

3. HelloSpring

匯入Jar包

注 : spring 需要匯入commons-logging進行日誌記錄 . 我們利用maven , 他會自動下載對應的依賴項 .

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>

編寫程式碼

  1. 編寫一個Hello實體類

    public class Hello {
        private String str;
    
        public String getStr() {
            return str;
        }
    
        public void setStr(String str) {
            this.str = str;
        }
    
        @Override
        public String toString() {
            return "Hello{" +
                    "str='" + str + '\'' +
                    '}';
        }
    }
    
  2. 編寫我們的spring檔案 , 這裡我們命名為beans.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">
    
        <!--bean就是java物件,由spring建立和管理-->
        <bean id="hello" class="com.LEEZ.pojo.Hello">
            <property name="str" value="Hello World"/>
        </bean>
    
    </beans>
    
  3. 測試

    @Test
    public void test() {
        //解析beans.xml檔案 , 生成管理相應的Bean物件
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //解析beans.xml檔案 , 生成管理相應的Bean物件
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello);
    }
    

思考

  • Hello 物件是誰建立的 ?
    • 【hello 物件是由Spring建立的】
  • Hello 物件的屬性是怎麼設定的 ?
    • 【hello 物件的屬性是由Spring容器設定的】

這個過程就叫控制反轉 :

  • 控制 : 誰來控制物件的建立 , 傳統應用程式的物件是由程式本身控制建立的 , 使用Spring後 , 物件是由Spring來建立的
  • 反轉 : 程式本身不建立物件 , 而變成被動的接收物件 .

依賴注入 : 就是利用set方法來進行注入的.

IOC是一種程式設計思想,由主動的程式設計變成被動的接收

可以通過newClassPathXmlApplicationContext去瀏覽一下底層原始碼

修改案例一

我們在案例一中, 新增一個Spring配置檔案beans.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">

    <!--bean就是java物件,由spring建立和管理-->
    <bean id="userDaoImpl" class="com.LEEZ.dao.UserDaoImpl"/>
    <bean id="userDaoImpl2" class="com.LEEZ.dao.UserDaoImpl2"/>

    <bean id="userServiceImpl" class="com.LEEZ.service.UserServiceImpl">
        <!--注意: 這裡的name並不是屬性 , 而是set方法後面的那部分 , 首字母小寫-->
        <!--引用另外一個bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="userDaoImpl2"/>
    </bean>
</beans>

測試

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
    userServiceImpl.getUser();
}

OK , 到了現在 , 我們徹底不用再程式中去改動了 , 要實現不同的操作 , 只需要在xml配置檔案中進行修改 , 所謂的IoC,一句話搞定 : 物件由Spring 來建立 , 管理 , 裝配 !

4. IOC建立物件方式

通過無參構造方法來建立

  1. User.java

    package com.LEEZ.pojo;
    
    public class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public User() {
            System.out.println("進入了無參構造");
        }
    
        public void show() {
            System.out.println("name:"+ this.name);
        }
    }
    
  2. beans.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">
    
        <!--bean就是java物件,由spring建立和管理-->
        <bean id="user" class="com.LEEZ.pojo.User">
            <property name="name" value="LEEZ"/>
        </bean>
    
    </beans>
    
  3. 測試類

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        user.show();
    }
    

結果可以發現,在呼叫show方法之前,也就是context.getBean時,User物件已經通過無參構造初始化了!

通過有參構造來建立

  1. User.java

    package com.LEEZ.pojo;
    
    public class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public User() {
            System.out.println("進入了無參構造");
        }
    
        public User(String name){
            this.name = name;
        }
    
        public void show() {
            System.out.println("name:"+ this.name);
        }
    }
    
  2. beans.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">
    
        <!--bean就是java物件,由spring建立和管理-->
        <!--無參構造初始化物件-->
        <bean id="user0" class="com.LEEZ.pojo.User">
            <property name="name" value="LEEZ"/>
        </bean>
    
        <!--有參構造初始化物件-->
        <!--初始化物件的第一種方式,通過構造器下標賦值-->
        <bean id="user1" class="com.LEEZ.pojo.User">
            <constructor-arg index="0" value="CV攻城獅"/>
        </bean>
    
        <!--初始化物件的第二種方式,通過引數型別,不建議使用-->
        <bean id="user2" class="com.LEEZ.pojo.User">
            <constructor-arg type="java.lang.String" value="CV攻城獅-LEEZ"/>
        </bean>
    
        <!--初始化物件的第三種方式,直接通過引數名來設定-->
        <bean id="user3" class="com.LEEZ.pojo.User">
            <constructor-arg name="name" value="leez-cv"/>
        </bean>
    </beans>
    
  3. 測試

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user0 = (User) context.getBean("user0");
        user0.show();
    
        User user1 = (User) context.getBean("user1");
        user1.show();
    
        User user2 = (User) context.getBean("user2");
        user2.show();
    
        User user3 = (User) context.getBean("user3");
        user3.show();
    }
    

結論:在配置檔案載入的時候,其中管理的物件都已經初始化了!即new ClassPathXmlApplicationContext("beans.xml")時,beans.xml中所有的bean都已經被初始化了

5. Spring配置

別名

alias 設定別名 , 為bean設定別名 , 可以設定多個別名

<!--設定別名:在獲取Bean的時候可以使用別名獲取-->
<alias name="user0" alias="userNew"/>

Bean的配置

<!--bean就是java物件,由Spring建立和管理-->

<!--
   id 是bean的識別符號,要唯一,如果沒有配置id,name就是預設識別符號
   如果配置id,又配置了name,那麼name是別名
   name可以設定多個別名,可以用逗號,分號,空格隔開
   如果不配置id和name,可以根據applicationContext.getBean(.class)獲取物件;

 class是bean的全限定名=包名+類名
-->
<bean id="user0" name="u0 uu,U0;uu0" class="com.LEEZ.pojo.User">
   <property name="name" value="LEEZ"/>
</bean>

import

團隊的合作通過import來實現,將多個beans.xml匯入到最終的applicationContext.xml

<import resource="beans.xml"/>

6. DI注入

概念

  • 依賴注入(Dependency Injection,DI)。
  • 依賴 : 指Bean物件的建立依賴於容器,Bean物件的依賴資源;
  • 注入 : 指Bean物件所依賴的資源 , 由容器來設定和裝配;

6.1 構造器注入

我們在之前的案例已經講過了

6.2 Set 注入 (重點)

要求被注入的屬性 , 必須有set方法 , set方法的方法名由set + 屬性首字母大寫 , 如果屬性是boolean型別 , 沒有set方法 , 是 is .

測試pojo類 :

Student.java

@Data
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
}

Address.java

@Data
public class Address {
    private String address;
}

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">
    <bean id="address" class="com.LEEZ.pojo.Address">
        <property name="address" value="五邑大學"/>
    </bean>

    <bean id="student" class="com.LEEZ.pojo.Student">
        <!--第一種,普通值注入,直接value-->
        <property name="name" value="LEEZ"/>
        <!--第二種,Bean注入,ref-->
        <property name="address" ref="address"/>
        <!--第三種,陣列-->
        <property name="books">
            <array>
                <value>《紅樓夢》</value>
                <value>《三國演義》</value>
                <value>《西遊記》</value>
                <value>《水滸傳》</value>
            </array>
        </property>
        <!--第四種,List集合-->
        <property name="hobbies">
            <list>
                <value>聽歌</value>
                <value>打程式碼</value>
                <value>打遊戲</value>
            </list>
        </property>
        <!--第五種,map集合-->
        <property name="card">
            <map>
                <entry key="身份證" value="111122233334445555"/>
                <entry key="性別" value="男"/>
            </map>
        </property>
        <!--第六種,set集合-->
        <property name="games">
            <set>
                <value>塞爾達</value>
                <value>寶可夢</value>
            </set>
        </property>
        <!--第七種,null值-->
        <property name="wife">
            <null/>
        </property>
        <!--第八種,properties-->
        <property name="info">
            <props>
                <prop key="driver">com.mysql.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT</prop>
                <prop key="username">root</prop>
                <prop key="password"/>
            </props>
        </property>
   </bean>
</beans>

6.3 p命名和c命名注入

  1. P名稱空間注入 : 需要在標頭檔案中加入約束檔案

     匯入約束 : xmlns:p="http://www.springframework.org/schema/p"
     
     <!--P(屬性: properties)名稱空間 , 屬性依然要設定set方法-->
     <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
    
  2. c 名稱空間注入 : 需要在標頭檔案中加入約束檔案

    匯入約束 : xmlns:c="http://www.springframework.org/schema/c"
     <!--C(構造: Constructor)名稱空間 , 屬性依然要設定set方法-->
     <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
    

結論:p命名注入本質是set注入,c命名注入本質是構造器注入。

6.4 bean的作用域

  1. 單例模式(spring預設)

    <bean id="student" class="com.LEEZ.pojo.Student" scope="singleton"/>
    
  2. 原型模式:每次從容器中get的時候,都會產生一個新物件

    <bean id="student" class="com.LEEZ.pojo.Student" scope="prototype"/>
    
  3. 其餘的request、session、application這些只能在web開發中使用

7. Bean自動裝配

  • 自動裝配是使用spring滿足bean依賴的一種方法
  • spring會在應用上下文中為某個bean尋找其依賴的bean。

Spring中bean有三種裝配機制,分別是:

  1. 在xml中顯式配置;
  2. 在java中顯式配置;
  3. 隱式的bean發現機制和自動裝配。

7.1 測試環境

  1. 新建專案

  2. Cat.java和Dog.java

    public class Cat {
        public void shout() {
            System.out.println("喵喵喵");
        }
    }
    
    public class Dog {
        public void shout() {
            System.out.println("汪汪汪");
        }
    }
    
  3. People.java

    @Data
    public class People {
        private String name;
        private Cat cat;
        private Dog dog;
    }
    
  4. 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">
    
        <bean id="cat" class="com.LEEZ.pojo.Cat"/>
        <bean id="dog" class="com.LEEZ.pojo.Dog"/>
    
        <bean id="people" class="com.LEEZ.pojo.People">
            <property name="name" value="LEEZ"/>
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>
        </bean>
    </beans>
    
  5. 測試

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        People people = context.getBean(People.class);
        people.getCat().shout();
        people.getDog().shout();
    }
    

7.2 ByName自動裝配

  1. applicationContext.xml在bean加上autowire=“byName”

    <bean id="people" class="com.LEEZ.pojo.People" autowire="byName">
        <property name="name" value="LEEZ"/>
    </bean>
    
  2. 再次測試,結果依舊成功輸出!

  3. 我們將 cat 的bean id修改為 catXXX

  4. 再次測試, 執行時報空指標java.lang.NullPointerException。因為按byName規則找不對應set方法,真正的setCat就沒執行,物件就沒有初始化,所以呼叫時就會報空指標錯誤。

小結:

當一個bean節點帶有 autowire byName的屬性時。

  1. 將查詢其類中所有的set方法名,例如setCat,獲得將set去掉並且首字母小寫的字串,即cat。
  2. 去spring容器中尋找是否有此字串名稱id的物件。
  3. 如果有,就取出注入;如果沒有,就報空指標異常。

7.3 ByType

autowire byType (按型別自動裝配)

使用autowire byType首先需要保證:同一型別的物件,在spring容器中唯一。如果不唯一,會報不唯一的異常。

NoUniqueBeanDefinitionException

測試:

  1. 將user的bean配置修改一下 : autowire=“byType”

    <bean id="people" class="com.LEEZ.pojo.People" autowire="byType">
        <property name="name" value="LEEZ"/>
    </bean>
    
  2. 測試,正常輸出

  3. 在註冊一個cat 的bean物件!

    <bean id="cat" class="com.LEEZ.pojo.Cat"/>
    <bean id="cat2" class="com.LEEZ.pojo.Cat"/>
    <bean id="dog" class="com.LEEZ.pojo.Dog"/>
    
    <bean id="people" class="com.LEEZ.pojo.People" autowire="byType">
        <property name="name" value="LEEZ"/>
    </bean>
    
  4. 測試,報錯:NoUniqueBeanDefinitionException

  5. 刪掉cat2,將cat的bean名稱改掉!測試!因為是按型別裝配,所以並不會報異常,也不影響最後的結果。甚至將id屬性去掉,也不影響結果。

這就是按照型別自動裝配!

7.4 使用註解實現自動裝配

jdk1.5開始支援註解,spring2.5開始全面支援註解。

準備工作:利用註解的方式注入屬性。

  1. 在spring配置檔案中引入context檔案頭

    xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    
  2. 開啟屬性註解支援

    <context:annotation-config/>
    
  3. @Autowired

    • @Autowired是按型別自動轉配的,不支援id匹配。
    • 需要匯入 spring-aop的包!

測試:

  1. 將User類中的set方法去掉,使用@Autowired註解

    public class People {
       @Autowired
       private Cat cat;
       @Autowired
       private Dog dog;
       private String name;
    
       public Cat getCat() {
           return cat;
      }
       public Dog getDog() {
           return dog;
      }
       public String getName() {
           return name;
      }
    }
    
  2. 此時配置檔案內容

    <context:annotation-config/>
    
    <bean id="dog" class="com.LEEZ.pojo.Dog"/>
    <bean id="cat" class="com.LEEZ.pojo.Cat"/>
    <bean id="people" class="com.LEEZ.pojo.People"/>
    
  3. 測試,成功輸出結果!

注意:

  • @Autowired(required=false) 說明:false,物件可以為null;true,物件必須存物件,不能為null。

    //如果允許物件為null,設定required = false,預設為true
    @Autowired(required = false)
    private Cat cat;
    
  • @Nullable說明:如果欄位標記了這個註解,說明這個欄位可以為null。

    @Nullable
    private Cat cat;
    

7.6 @Qualifier

  • @Autowired預設是根據型別自動裝配的,當有多個相同型別時,則根據ByName進行自動裝配,若名字也相同時,加上@Qualifier則可以根據特定的byName方式自動裝配
  • @Qualifier不能單獨使用。

測試實驗步驟:

  1. 配置檔案修改內容,保證型別存在物件。且名字不為類的預設名字!

    <bean id="dog1" class="com.kuang.pojo.Dog"/>
    <bean id="dog2" class="com.kuang.pojo.Dog"/>
    <bean id="cat1" class="com.kuang.pojo.Cat"/>
    <bean id="cat2" class="com.kuang.pojo.Cat"/>
    
  2. 沒有加Qualifier測試,直接報錯

  3. 在屬性上新增Qualifier註解

    @Autowired
    @Qualifier(value = "cat2")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    

    測試,成功輸出!

7.7 @Resource

  • @Resource如有指定的name屬性,先按該屬性進行byName方式查詢裝配;
  • 其次再進行預設的byName方式進行裝配;
  • 如果以上都不成功,則按byType的方式自動裝配。
  • 都不成功,則報異常。

實體類

public class User {
   //如果允許物件為null,設定required = false,預設為true
   @Resource(name = "cat2")
   private Cat cat;
   @Resource
   private Dog dog;
   private String str;
}

beans.xml

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User"/>

測試:結果OK

配置檔案2:beans.xml , 刪掉cat2

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>

實體類上只保留註解

@Resource
private Cat cat;
@Resource
private Dog dog;

結果:OK

結論:先進行byName查詢,失敗;再進行byType查詢,成功。

小結

@Autowired與@Resource異同:

  1. @Autowired與@Resource都可以用來裝配bean。都可以寫在欄位上,或寫在setter方法上。
  2. @Autowired預設按型別裝配(屬於spring規範),預設情況下必須要求依賴物件必須存在,如果要允許null 值,可以設定它的required屬性為false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用
  3. @Autowired預設按型別裝配(屬於spring規範),預設情況下必須要求依賴物件必須存在,如果要允許null 值,可以設定它的required屬性為false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用
  4. 它們的作用相同都是用註解方式注入物件,但執行順序不同。@Autowired先byType,@Resource先byName。

8. 使用註解開發

8.1 說明

在spring4之後,想要使用註解形式,必須得要引入aop的包

在配置檔案當中,還得要引入一個context約束

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

    <context:annotation-config/>
</beans>

8.2 Bean的實現

我們之前都是使用 bean 的標籤進行bean注入,但是實際開發中,我們一般都會使用註解!

1、配置掃描哪些包下的註解

<!--指定註解掃描包-->
<context:component-scan base-package="com.LEEZ.pojo"/>

2、在指定包下編寫類,增加註解

// 相當於配置檔案中 <bean id="user" class="當前註解的類"/>
@Component("user")
public class User {
   public String name = "LEEZ";
}

3、測試

@Test
public void test(){
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean(User.class);
        System.out.println(user.name);
    }
}

8.3 屬性注入

使用註解注入屬性

1、可以不用提供set方法,直接在直接名上新增@value(“值”)

// 相當於配置檔案中 <bean id="user" class="當前註解的類"/>
@Component("user")
public class User {
   @Value("LEEZ")
   // 相當於配置檔案中 <property name="name" value="LEEZ"/>
   public String name;
}

2、如果提供了set方法,在set方法上新增@value(“值”);

@Component("user")
public class User {

    public String name;

    @Value("LEEZ")
    public void setName(String name) {
        this.name = name;
    }
}

衍生註解

我們這些註解,就是替代了在配置檔案當中配置步驟而已!更加的方便快捷!

@Component三個衍生註解

為了更好的進行分層,Spring可以使用其它三個註解,功能一樣,目前使用哪一個功能都一樣。

  • dao層:@Respository
  • service層:@Service
  • servlet層(controller層):@Controller

寫上這些註解,就相當於將這個類交給Spring管理裝配了!

8.4 自動裝配註解

在Bean的自動裝配已經講過了,可以回顧!

8.5 作用域

@scope

  • singleton:預設的,Spring會採用單例模式建立這個物件。關閉工廠 ,所有的物件都會銷燬。
  • prototype:多例模式。關閉工廠 ,所有的物件不會銷燬。內部的垃圾回收機制會回收
@Controller("user")
@Scope("prototype")
public class User {
   @Value("LEEZ")
   public String name;
}

8.6 小結

XML與註解比較

  • XML可以適用任何場景 ,結構清晰,維護方便
  • 註解不是自己提供的類使用不了,開發簡單方便

xml與註解整合開發 :推薦最佳實踐

  • xml管理Bean
  • 註解完成屬性注入
  • 使用過程中, 可以不用掃描,掃描是為了類上的註解
<context:annotation-config/>  

作用:

  • 進行註解驅動註冊,從而使註解生效
  • 用於啟用那些已經在spring容器裡註冊過的bean上面的註解,也就是顯示的向Spring註冊
  • 如果不掃描包,就需要手動配置bean
  • 如果不加註解驅動,則注入的值為null!

8.7 基於Java類進行配置

JavaConfig 原來是 Spring 的一個子專案,它通過 Java 類的方式提供 Bean 的定義資訊,在 Spring4 的版本, JavaConfig 已正式成為 Spring4 的核心功能 。

測試:

1、編寫一個實體類,Dog

public class Dog {
    @Value("旺財")
    public String name;
}

2、新建一個config配置包,編寫一個MyConfig配置類

@Configuration//代表這是一個配置類
public class MyConfig {

    @Bean//通過方法註冊一個bean,這裡的返回值就Bean的型別,方法名就是bean的id!
    public Dog dog() {
        return new Dog();
    }
}

3、測試

@Test
public void test() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
    Dog dog = applicationContext.getBean(Dog.class);
    System.out.println(dog.name);
}

4、成功輸出結果!

匯入其他配置如何做呢?

1、我們再編寫一個配置類!

@Configuration  //代表這是一個配置類
public class MyConfig2 {
}

2、在之前的配置類中我們來選擇匯入這個配置類

@Configuration
@Import(MyConfig2.class)  //匯入合併其他配置類,類似於配置檔案中的 inculde 標籤
public class MyConfig {

   @Bean
   public Dog dog(){
       return new Dog();
  }

}

關於這種Java類的配置方式,我們在之後的SpringBoot 和 SpringCloud中還會大量看到,我們需要知道這些註解的作用即可!

9. 代理模式

為什麼要學習代理模式? 因為這就是Spring AOP的底層。

代理模式的分類:

  • 靜態代理
  • 動態代理

9.1 靜態代理

靜態代理角色分析

  • 抽象角色 : 一般使用介面或者抽象類來實現
  • 真實角色 : 被代理的角色
  • 代理角色 : 代理真實角色 ; 代理真實角色後 , 一般會做一些附屬的操作 .
  • 客戶 : 使用代理角色來進行一些操作 .

程式碼實現

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真實角色

//真實角色: 房東,房東要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy . java 即代理角色

//代理角色:中介
public class Proxy implements Rent {

   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("帶房客看房");
  }
   //收中介費
   public void fare(){
       System.out.println("收中介費");
  }
}

Client . java 即客戶

//客戶類,一般客戶都會去找代理!
public class Client {
    public static void main(String[] args) {
        //房東要出租房子
        Host host = new Host();
        //中介代理房東
        Proxy proxy = new Proxy(host);
        //你找中介租房,即中介代理房東出租房子給你
        proxy.rent();
    }
}

分析:在這個過程中,你直接接觸的就是中介,就如同現實生活中的樣子,你看不到房東,但是你依舊租到了房東的房子通過代理,這就是所謂的代理模式,程式源自於生活,所以學程式設計的人,一般能夠更加抽象的看待生活中發生的事情。

靜態代理的好處:

  • 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情 .
  • 公共的業務由代理來完成 . 實現了業務的分工 ,
  • 公共業務發生擴充套件時變得更加集中和方便 .

缺點 :

  • 類多了 , 多了代理類 , 工作量變大了 . 開發效率降低 .

我們想要靜態代理的好處,又不想要靜態代理的缺點,所以 , 就有了動態代理 !

9.2 靜態代理再理解

同學們練習完畢後,我們再來舉一個例子,鞏固大家的學習!

練習步驟:

1、建立一個抽象角色,比如咱們平時做的使用者業務,抽象起來就是增刪改查!

//抽象角色:增刪改查業務
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我們需要一個真實物件來完成這些增刪改查操作

//真實物件,完成增刪改查操作的人
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一個使用者");
  }

   public void delete() {
       System.out.println("刪除了一個使用者");
  }

   public void update() {
       System.out.println("更新了一個使用者");
  }

   public void query() {
       System.out.println("查詢了一個使用者");
  }
}

3、需求來了,現在我們需要增加一個日誌功能,怎麼實現!

  • 思路1 :在實現類上增加程式碼 【麻煩!】
  • 思路2:使用代理來做,能夠不改變原來的業務情況下,實現此功能就是最好的了!

4、設定一個代理類來處理日誌!代理角色

//代理角色,在這裡面增加日誌的實現
public class UserServiceProxy implements UserService {
   private UserServiceImpl userService;

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

   public void add() {
       log("add");
       userService.add();
  }

   public void delete() {
       log("delete");
       userService.delete();
  }

   public void update() {
       log("update");
       userService.update();
  }

   public void query() {
       log("query");
       userService.query();
  }

   public void log(String msg){
       System.out.println("執行了"+msg+"方法");
  }

}

5、測試訪問類:

public class Client {
   public static void main(String[] args) {
       //真實業務
       UserServiceImpl userService = new UserServiceImpl();
       //代理類
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理類實現日誌功能!
       proxy.setUserService(userService);

       proxy.add();
  }
}

OK,到了現在代理模式大家應該都沒有什麼問題了,重點大家需要理解其中的思想;

我們在不改變原來的程式碼的情況下,實現了對原有功能的增強,這是AOP中最核心的思想

聊聊AOP:縱向開發,橫向開發

9.3 動態代理

  • 動態代理的角色和靜態代理的一樣 .
  • 動態代理的代理類是動態生成的 . 靜態代理的代理類是我們提前寫好的
  • 動態代理分為兩類 : 一類是基於介面動態代理 , 一類是基於類的動態代理
    • 基於介面的動態代理----JDK動態代理
    • 基於類的動態代理–cglib
    • 現在用的比較多的是 javasist 來生成動態代理 . 百度一下javasist
    • 我們這裡使用JDK的原生程式碼來實現,其餘的道理都是一樣的!、

JDK的動態代理需要了解兩個類

核心 : InvocationHandler 和 Proxy , 開啟JDK幫助文件看看

【InvocationHandler:呼叫處理程式】

Object invoke(Object proxy, 方法 method, Object[] args);
//引數
//proxy - 呼叫該方法的代理例項
//method -所述方法對應於呼叫代理例項上的介面方法的例項。方法物件的宣告類將是該方法宣告的介面,它可以是代理類繼承該方法的代理介面的超級介面。
//args -包含的方法呼叫傳遞代理例項的引數值的物件的陣列,或null如果介面方法沒有引數。原始型別的引數包含在適當的原始包裝器類的例項中,例如java.lang.Integer或java.lang.Boolean 。

【Proxy : 代理】

//生成代理類
public Object getProxy(){
   return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                 rent.getClass().getInterfaces(),this);
}

程式碼實現

抽象角色和真實角色和之前的一樣!

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真實角色

//真實角色: 房東,房東要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

ProxyInvocationHandler. java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {
   private Rent rent;

   public void setRent(Rent rent) {
       this.rent = rent;
  }

   //生成代理類,重點是第二個引數,獲取要代理的抽象角色!之前都是一個角色,現在可以代理一類角色
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
  }

   // proxy : 代理類 method : 代理類的呼叫處理程式的方法物件.
   // 處理代理例項上的方法呼叫並返回結果
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       seeHouse();
       //核心:本質利用反射實現!
       Object result = method.invoke(rent, args);
       fare();
       return result;
  }

   //看房
   public void seeHouse(){
       System.out.println("帶房客看房");
  }
   //收中介費
   public void fare(){
       System.out.println("收中介費");
  }

}

Client . java

//租客
public class Client {

   public static void main(String[] args) {
       //真實角色
       Host host = new Host();
       //代理例項的呼叫處理程式
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setRent(host); //將真實角色放置進去!
       Rent proxy = (Rent)pih.getProxy(); //動態生成對應的代理類!
       proxy.rent();
  }

}

核心:一個動態代理 , 一般代理某一類業務 , 一個動態代理可以代理多個類,代理的是介面!、

9.4 深化理解

我們來使用動態代理實現代理我們後面寫的UserService!

我們也可以編寫一個通用的動態代理實現的類!所有的代理物件設定為Object即可!

public class ProxyInvocationHandler implements InvocationHandler {
   private Object target;

   public void setTarget(Object target) {
       this.target = target;
  }

   //生成代理類
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               target.getClass().getInterfaces(),this);
  }

   // proxy : 代理類
   // method : 代理類的呼叫處理程式的方法物件.
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       log(method.getName());
       Object result = method.invoke(target, args);
       return result;
  }

   public void log(String methodName){
       System.out.println("執行了"+methodName+"方法");
  }

}

測試!

public class Test {
   public static void main(String[] args) {
       //真實物件
       UserServiceImpl userService = new UserServiceImpl();
       //代理物件的呼叫處理程式
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setTarget(userService); //設定要代理的物件
       UserService proxy = (UserService)pih.getProxy(); //動態生成代理類!
       proxy.delete();
  }
}

測試,增刪改查,檢視結果!

1、動態代理的好處

靜態代理有的它都有,靜態代理沒有的,它也有!

  • 可以使得我們的真實角色更加純粹 . 不再去關注一些公共的事情 .
  • 公共的業務由代理來完成 . 實現了業務的分工 ,
  • 公共業務發生擴充套件時變得更加集中和方便 .
  • 一個動態代理 , 一般代理某一類業務
  • 一個動態代理可以代理多個類,代理的是介面!

10. AOP就這麼簡單

上一講中我們講解了代理模式,這是AOP的基礎,一定要先搞懂它

狂神說Spring06:靜態/動態代理模式

那我們接下來就來聊聊AOP吧!

10.1 什麼是AOP

AOP(Aspect Oriented Programming)意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

Aop在Spring中的作用

提供宣告式事務;允許使用者自定義切面

以下名詞需要了解下:

  • 橫切關注點:跨越應用程式多個模組的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日誌 , 安全 , 快取 , 事務等等 …
  • 切面(ASPECT):橫切關注點 被模組化 的特殊物件。即,它是一個類。
  • 通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。
  • 目標(Target):被通知物件。
  • 代理(Proxy):向目標物件應用通知之後建立的物件。
  • 切入點(PointCut):切面通知 執行的 “地點”的定義。
  • 連線點(JointPoint):與切入點匹配的執行點。

SpringAOP中,通過Advice定義橫切邏輯,Spring中支援5種類型的Advice:

即 Aop 在 不改變原有程式碼的情況下 , 去增加新的功能 .

10.2 使用Spring實現Aop

【重點】使用AOP織入,需要匯入一個依賴包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

第一種方式

通過 Spring API 實現

首先編寫我們的業務介面和實現類

public interface UserService {
    public void add();

    public void delete();

    public void update();

    public void query();
}
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("add");
    }

    @Override
    public void delete() {
        System.out.println("delete");
    }

    @Override
    public void update() {
        System.out.println("update");
    }

    @Override
    public void query() {
        System.out.println("query");
    }
}

然後去寫我們的增強類 , 我們編寫兩個 , 一個前置增強 一個後置增強

public class Log implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + " 執行了 " + method.getName() + "方法");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName() + "方法執行了,返回了" + returnValue);
    }
}

最後去spring的檔案中註冊 , 並實現aop切入實現 , 注意匯入約束 .

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

    <context:annotation-config/>

    <bean id="userServiceImpl" class="com.LEEZ.service.UserServiceImpl"/>
    <bean id="log" class="com.LEEZ.log.Log"/>
    <bean id="afterLog" class="com.LEEZ.log.AfterLog"/>

    <!--方法一:使用原生Spring api介面-->
    <!--配置aop,需要匯入aop的約束-->
    <aop:config>
        <!--切入點:expression 表示式,execution(* * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.LEEZ.service.UserServiceImpl.*(..))"/>

        <!--執行環繞增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

測試

public class MyTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //動態代理代理的是介面,不是實現類
        UserService userService = context.getBean(UserService.class);
        userService.add();
    }
}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解這一塊 .

Spring的Aop就是將公共的業務 (日誌 , 安全等) 和領域業務結合起來 , 當執行領域業務時 , 將會把公共業務加進來 . 實現公共業務的重複利用 . 領域業務更純粹 , 程式猿專注領域業務 , 其本質還是動態代理 .

第二種方式

自定義類來實現Aop

目標業務類不變依舊是userServiceImpl

第一步 : 寫我們自己的一個切入類

public class DiyPointcut {

   public void before(){
       System.out.println("---------方法執行前---------");
  }
   public void after(){
       System.out.println("---------方法執行後---------");
  }
   
}

去spring中配置

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

    <context:annotation-config/>

    <bean id="userServiceImpl" class="com.LEEZ.service.UserServiceImpl"/>
    <bean id="log" class="com.LEEZ.log.Log"/>
    <bean id="afterLog" class="com.LEEZ.log.AfterLog"/>

    <!--方法一:使用原生Spring api介面-->
    <!--配置aop,需要匯入aop的約束-->
<!--    <aop:config>-->
<!--        &lt;!&ndash;切入點:expression 表示式,execution(* * * * *)&ndash;&gt;-->
<!--        <aop:pointcut id="pointcut" expression="execution(* com.LEEZ.service.UserServiceImpl.*(..))"/>-->

<!--        &lt;!&ndash;執行環繞增加&ndash;&gt;-->
<!--        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>-->
<!--        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>-->
<!--    </aop:config>-->

    <!--方法二:自定義介面-->
    <bean id="diy" class="com.LEEZ.Diy.DiyPointCut"/>

    <aop:config>
        <aop:aspect ref="diy">
            <aop:pointcut id="pointcut" expression="execution(* com.LEEZ.service.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

測試:

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
  }
}

第三種方式

使用註解實現

第一步:編寫一個註解實現的增強類

@Aspect
public class AnnotationPointcut {
   @Before("execution(* com.LEEZ.service.UserServiceImpl.*(..))")
   public void before(){
       System.out.println("---------方法執行前---------");
  }

   @After("execution(* com.LEEZ.service.UserServiceImpl.*(..))")
   public void after(){
       System.out.println("---------方法執行後---------");
  }

   @Around("execution(* com.LEEZ.service.UserServiceImpl.*(..))")
   public void around(ProceedingJoinPoint jp) throws Throwable {
       System.out.println("環繞前");
       System.out.println("簽名:"+jp.getSignature());
       //執行目標方法proceed
       Object proceed = jp.proceed();
       System.out.println("環繞後");
       System.out.println(proceed);
  }
}

第二步:在Spring配置檔案中,註冊bean,並增加支援註解的配置

<!--第三種方式:註解實現-->
<bean id="annotationPointcut" class="com.LEEZ.Diy.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:說明

通過aop名稱空間的<aop:aspectj-autoproxy />宣告自動為spring容器中那些配置@aspectJ切面的bean建立代理,織入切面。當然,spring 在內部依舊採用AnnotationAwareAspectJAutoProxyCreator進行自動代理的建立工作,但具體實現的細節已經被<aop:aspectj-autoproxy />隱藏起來了

<aop:aspectj-autoproxy />有一個proxy-target-class屬性,預設為false,表示使用jdk動態代理織入增強,當配為<aop:aspectj-autoproxy  poxy-target-class="true"/>時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設定為false,如果目標類沒有宣告介面,則spring將自動使用CGLib動態代理。

到了這裡,AOP的思想和使用相信大家就沒問題了!

11. 整合Mybatis

步驟:

  • 1.匯入相關jar包
    • Junit
    • mybatis
    • MySQL
    • spring相關
    • aop織入
    • mybatis-spring 【new】

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringStudy</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>Spring-01-test</module>
        <module>spring-02-helloSpring</module>
        <module>spring-03-ioc2</module>
        <module>spring-04-di</module>
        <module>spring-05</module>
        <module>spring-06</module>
        <module>spring-07</module>
        <module>spring-08</module>
        <module>spring-09</module>
    </modules>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.23</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

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

        <!--spring操作資料庫的話,還需要spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
    </dependencies>

</project>

maven 靜態資源匯入問題

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  • 2.編寫配置檔案
  • 3.測試

11.1 回憶Mybatis

1.編寫實體類

@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}

2.編寫核心配置檔案
mybatis-config.xml

<?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核心配置檔案-->
<configuration>
    
    <typeAliases>
        <package name="com.LEEZ.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.LEEZ.mapper.UserMapper"/>
    </mappers>
</configuration>

3.編寫介面
UserMapper

public interface UserMapper {
    List<User> selectUser();
}

4.編寫Mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.LEEZ.mapper.UserMapper">
        <select id="selectUser" resultType="user">
            select * from mybatis.user;
        </select>
</mapper>

5.測試

@Test
public void test() throws IOException {
    String resources = "mybatis-config.xml";
    InputStream in = Resources.getResourceAsStream(resources);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
    //true為開啟自動提交事務
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = userMapper.selectUser();
    for (User user : users) {
        System.out.println(user);
    }
}

注意點:

  1. 找不到類就是maven資源匯出問題 target中沒有匯出檔案
  2. 編寫完mapper.xml檔案後,一定要去mybatis-config.xml中繫結mapper
  3. 資料庫UTC設定 Asia/Shanghai
  4. 工具類放下下面
//sqlSessionFactory -->sqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try{
            //使用mybatis第一步:獲取sqlSessionFactory物件
            String resource = "mybatis-config.xml";//注意
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    //
    public static SqlSession getSqlSession(){
         return sqlSessionFactory.openSession(true);
    }

}

11.2 Mybatis-spring

方式一

1.編寫資料來源配置
2.sqlSessionFactory
3.sqlSessionTemplete

  • spring-dao.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"
       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:annotation-config/>

    <!--Datasource:使用spring的資料來源替換mybatis的配置  c3p0 dbcp druid
     我們這裡使用spring提供的jdbc:org.springframework.jdbc.datasource-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>

    <!--建立sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--繫結mybatis配置檔案-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/LEEZ/mapper/*.xml"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate這個類沒有set方法,但有構造方法,因此通過構造方法注入屬性-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>

4.給介面新增實現類

public class UserMapperImpl implements UserMapper {

    private SqlSessionTemplate sqlSessionTemplate;

    //要想通過spring注入屬性,必須有set方法
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    @Override
    public List<User> selectUser() {
        UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();
        return users;
    }
}

5.將自己寫的實現類,注入到spring中

<bean id="userMapper" class="com.LEEZ.mapper.UserMapperImpl">
    <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>

6.測試

@Test
public void test1() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapper userMapper = context.getBean(UserMapper.class);
    List<User> users = userMapper.selectUser();
    for (User user : users) {
        System.out.println(user);
    }
}

注意:

  1. 其實在我的理解裡,寫實現類就是將之前測試類裡的程式碼分離了出來,但這樣的好處在於,可以將各個Mapper寫進Spring的容器中,由Spring同一管理

  2. 所以,雖然多了些程式碼量,但複用管理成本降低了。

  3. 二者的程式碼區別:

    1. //提取出來作為實現類的測試
      @Test
      public void test1() {
          ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
          UserMapper userMapper = context.getBean(UserMapper.class);
          List<User> users = userMapper.selectUser();
          for (User user : users) {
              System.out.println(user);
          }
      }
      
    2. //沒有提取出來作為實現類的測試,僅僅是多了獲取sqlSession和mapper的過程
      @Test
      public void test2() {
          ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
          SqlSessionTemplate sqlSessionTemplate = (SqlSessionTemplate) context.getBean("sqlSessionTemplate");
          UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
          List<User> users = mapper.selectUser();
          for (User user : users) {
              System.out.println(user);
          }
      }
      

方式二 SqlSessionDaoSupport

spring-dao.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"
       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:annotation-config/>

    <!--Datasource:使用spring的資料來源替換mybatis的配置  c3p0 dbcp druid
     我們這裡使用spring提供的jdbc:org.springframework.jdbc.datasource-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>

    <!--建立sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--繫結mybatis配置檔案-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/LEEZ/mapper/*.xml"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate這個類沒有set方法,但有構造方法,因此通過構造方法注入屬性-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <bean id="userMapper" class="com.LEEZ.mapper.UserMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
    </bean>
</beans>

實現類:UserMapperImpl2

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectUser() {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

將新寫的實現類注入到spring-dao.xml

<bean id="userMapper2" class="com.LEEZ.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

測試:

@Test
public void test3() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapperImpl2 userMapper2 = (UserMapperImpl2) context.getBean("userMapper2");
    List<User> users = userMapper2.selectUser();
    for (User user : users) {
        System.out.println(user);
    }
}

12. 宣告式事務

12.1 回顧事務

  • 把一組業務當成一個業務來做,要麼同時成功,要麼同時失敗;
  • 事務在專案開發中,十分重要,涉及到資料的一致性問題,不能馬虎;
  • 確保完整性和一致性

事務的ACID原則:

  • 原子性:
    • 事務是原子性操作,要麼同時全部完成,要麼完全不起作用。
  • 一致性:
    • 一旦所有事務動作完成,事務就要被提交,資料和資源處於一種滿足業務規格的一致性狀態
    • 事務提交前和提交後,資料的完整性不變
  • 隔離性:
    • 多個事務同時處理相同的資料,每個事務都應該與其他事務隔離開來,防止資料損壞
  • 永續性
    • 事務一旦完成,無論系統發生什麼錯誤,結果都不會收到影響,通常情況下,事務一旦被提交,就會被持久化到資料庫中。

測試:

將上面的程式碼拷貝到一個新專案中
在之前的案例中,我們給userMapper介面新增兩個方法,刪除和增加使用者;

//新增一個使用者
int addUser(User user);

//根據id刪除使用者
int deleteUser(int id);

UserMapper檔案,我們故意把 deletes 寫錯,測試!

<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>

編寫介面的UserMapperImpl實現類,在實現類中,我們去操作一波

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

    //增加一些操作
    public List<User> selectUser() {
        User user = new User(5, "小王", "185161");
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(5);

        return mapper.selectUser();
    }
    
    //新增
    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    //刪除
    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

測試

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}

報錯:sql異常,delete寫錯了

結果 :資料庫結果顯示插入成功!

沒有進行事務的管理;我們想讓他們都成功才成功,有一個失敗,就都失敗,我們就應該需要事務!

以前我們都需要自己手動管理事務,十分麻煩!

但是Spring給我們提供了事務管理,我們只需要配置即可;

13.2、Spring中的事務管理

spring在不同的事務管理API之上定義了一個抽象層,是的開發人員不必瞭解底層的事務管理API就可以使用spring的事務管理機制。spring支援程式設計式事務管理和宣告式的事務管理

程式設計式事務管理

  • 將事務管理程式碼嵌入到業務方法中來控制事務的提交和回滾
  • 缺點:必須在每個事務的操作業務邏輯中包含額外的事務管理程式碼

宣告式事務管理

  • 一般情況下比程式設計式事務管理好用
  • 將事務管理程式碼從業務方法中分離出來,以宣告的方式來實現事務管理
  • 將事務管理作為橫切關注點,通過aop方法模組化。spring中通過spring aop框架支援宣告式事務管理

1、使用spring管理實務,注意標頭檔案的約束匯入:tx

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

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

2、JDBC事務

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

3、配置好事務管理器後,我們需要去配置事務的通知

<!--結合AOP實現事務的織入-->
<!--配置事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--給那些方法配置事務-->
    <!--配置事務的傳播特性: new -->
    <tx:attributes>
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

spring事務傳播特性:

事務傳播行為就是多個事務方法相互呼叫的時候,事務如何在這些方法間傳播。spring支援7中事務傳播行為:

  • propagation_requierd:如果沒有當前事務,就新建一個事務,如果已存在一個事務中,加入這個事務;
  • propagation_support:支援當前事務,如果沒有當前事務,就以非事務方法執行
  • propagation_mandatory:支援當前事務,如果沒有當前事務就丟擲異常
  • propagation_requierd_new:不支援當前事務,新建事務,如果當前存在任務,就把當前任務掛起。
  • propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • propagation_never:以非事務方式執行,如果當前存在事務,就丟擲異常
  • propagation_nested:如果當前存在事務,就在巢狀事務內執行,如果當前沒有事務,則執行與propagation_required類似的操作。

spring預設的事務傳播行為是;propagation_required,它適用於絕大多數的情況。

就好比,我們剛才的幾個方法存在呼叫,所以會被放在一組事務中。

4、配置AOP,匯入aop的標頭檔案

<!--配置事務切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

5、刪掉剛才插入的資料,再次測試

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}

思考:

為什麼需要事務?

  • 如果不配置事務,可能存在資料提交不一致的情況;
  • 如果我們不在spring中配置宣告式事務,我們就需要在程式碼中手動配置事務
  • 事務在專案開發中十分重要,涉及到資料的一致性和完整性的問題。