Spring學習筆記

筆記整理來源於 B站UP主 狂神說 //www.bilibili.com/video/BV1WE411d7Dv

筆記源碼://gitee.com/antia11/MyStudy/tree/master/Java/Spring-Study

1、概述及IOC理論推導

image-20210130121518086

1.1、概述

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

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

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

很難想像Rod Johnson的學歷 , 他是悉尼大學的博士,然而他的專業不是電腦,而是音樂學。

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

官網 : //spring.io/

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

GitHub : //github.com/spring-projects

優點

1、Spring是一個開源免費的框架 , 容器 .

2、Spring是一個輕量級的框架 , 非侵入式的 .

3、控制反轉 IoC , 面向切面 Aop

4、對事物的支援 , 對框架的支援

一句話概括:

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

組成

image-20210130121931548

Spring 框架是一個分層架構,由 7 個定義良好的模組組成。Spring 模組構建在核心容器之上,核心容器定義了創建、配置和管理 bean 的方式 .

image-20210130122008474

組成 Spring 框架的每個模組(或組件)都可以單獨存在,或者與其他一個或多個模組聯合實現。每個模組的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要組件是 BeanFactory,它是工廠模式的實現。BeanFactory 使用控制反轉(IOC) 模式將應用程式的配置和依賴性規範與實際的應用程式程式碼分開。
  • Spring 上下文:Spring 上下文是一個配置文件,向 Spring 框架提供上下文資訊。Spring 上下文包括企業服務,例如 JNDI、EJB、電子郵件、國際化、校驗和調度功能。
  • Spring AOP:通過配置管理特性,Spring AOP 模組直接將面向切面的編程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支援 AOP的對象。Spring AOP 模組為基於 Spring 的應用程式中的對象提供了事務管理服務。通過使用 Spring AOP,不用依賴組件,就可以將聲明性事務管理集成到應用程式中。
  • Spring DAO:JDBC DAO 抽象層提供了有意義的異常層次結構,可用該結構來管理異常處理和不同資料庫供應商拋出的錯誤消息。異常層次結構簡化了錯誤處理,並且極大地降低了需要編寫的異常程式碼數量(例如打開和關閉連接)。Spring DAO 的面向 JDBC 的異常遵從通用的 DAO 異常層次結構。
  • Spring ORM:Spring 框架插入了若干個 ORM 框架,從而提供了 ORM 的對象關係工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有這些都遵從 Spring 的通用事務和 DAO 異常層次結構。
  • Spring Web 模組:Web 上下文模組建立在應用程式上下文模組之上,為基於 Web 的應用程式提供了上下文。所以,Spring 框架支援與 Jakarta Struts 的集成。Web 模組還簡化了處理多部分請求以及將請求參數綁定到域對象的工作。
  • Spring MVC 框架:MVC 框架是一個全功能的構建 Web 應用程式的 MVC 實現。通過策略介面,MVC 框架變成為高度可配置的,MVC 容納了大量視圖技術,其中包括 JSP、Velocity、Tiles、iText 和 POI。

拓展

Spring Boot與Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置腳手架,可以基於Spring Boot 快速開發單個微服務;
  • Spring Cloud是基於Spring Boot實現的;
  • Spring Boot專註於快速、方便集成的單個微服務個體,Spring Cloud關注全局的服務治理框架;
  • Spring Boot使用了約束優於配置的理念,很多集成方案已經幫你選擇好了,能不配置就不配置 , Spring Cloud很大的一部分是基於Spring Boot來實現,Spring Boot可以離開Spring Cloud獨立使用開發項目,但是Spring Cloud離不開Spring Boot,屬於依賴的關係。
  • SpringBoot在SpringClound中起到了承上啟下的作用,如果你要學習SpringCloud必須要學習SpringBoot。

image-20210130122153962

1.2、IOC基礎

新建一個空白的maven項目

分析實現

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

1、先寫一個UserDao介面

public interface UserDao {
   public void getUser();
}

2、再去寫Dao的實現類

public class UserDaoImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("獲取用戶數據");
  }
}

3、然後去寫UserService的介面

public interface UserService {
   public 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 UserDaoMySqlImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("MySql獲取用戶數據");
  }
}

緊接著我們要去使用MySql的話 , 我們就需要去service實現類裡面修改對應的實現

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoMySqlImpl();

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

在假設, 我們再增加一個Userdao的實現類 .

public class UserDaoOracleImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("Oracle獲取用戶數據");
  }
}

那麼我們要使用Oracle , 又需要去service實現類裡面修改對應的實現 . 假設我們的這種需求非常大 , 這種方式就根本不適用了, 甚至反人類對吧 , 每次變動 , 都需要修改大量程式碼 . 這種設計的耦合性太高了, 牽一髮而動全身 .

那我們如何去解決呢 ?

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

public class UserServiceImpl implements UserService {
   private UserDao userDao;
// 利用set實現
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
  }

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

現在去我們的測試類里 , 進行測試 ;

@Test
public void test(){
   UserServiceImpl service = new UserServiceImpl();
   service.setUserDao( new UserDaoMySqlImpl() );
   service.getUser();
   //那我們現在又想用Oracle去實現呢
   service.setUserDao( new UserDaoOracleImpl() );
   service.getUser();
}

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

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

1.3、IOC本質

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

img

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

Spring容器在初始化時先讀取配置文件,根據配置文件或元數據創建與組織對象存入容器中,程式使用時再從Ioc容器中取出需要的對象。

img

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

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

2、快速上手Spring

2.1、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 name;

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

   public void show(){
       System.out.println("Hello,"+ name );
  }
}

2.編寫spring文件 , 這裡命名為beans.xml

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

   <!--bean就是java對象 , 由Spring創建和管理-->
   <bean id="hello" class="com.kuang.pojo.Hello">
       <property name="name" value="Spring"/>
   </bean>

</beans>

3.進行測試 .

@Test
public void test(){
   //解析beans.xml文件 , 生成管理相應的Bean對象
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //getBean : 參數即為spring配置文件中bean的id .
   Hello hello = (Hello) context.getBean("hello");
   hello.show();
}

思考

  • Hello 對象是誰創建的 ? 【Hello 對象是由 Spring 創建的】
  • Hello 對象的屬性是怎麼設置的 ? 【hello 對象的屬性是由 Spring 容器設置的】

這個過程就叫控制反轉 :

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

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

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

可以通過 newClassPathXmlApplicationContext 去瀏覽一下底層源碼 .

修改案例一

我們在案例一中, 新增一個Spring配置文件beans.xml

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

   <bean id="MysqlImpl" class="com.kuang.dao.impl.UserDaoMySqlImpl"/>
   <bean id="OracleImpl" class="com.kuang.dao.impl.UserDaoOracleImpl"/>

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

</beans>

測試!

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

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

2.2、IOC創建對象方式

通過無參構造方法來創建

1.User.java

public class User {

   private String name;

   public User() {
       System.out.println("user無參構造方法");
  }

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

   public void show(){
       System.out.println("name="+ name );
  }

}

2.beans.xml

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

   <bean id="user" class="com.kuang.pojo.User">
       <property name="name" value="kuangshen"/>
   </bean>

</beans>

3.測試類

@Test
public void test(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //在執行getBean的時候, user已經創建好了 , 通過無參構造
   User user = (User) context.getBean("user");
   //調用對象的方法 .
   user.show();
}

結果可以發現,在調用 show 方法之前,User 對象已經通過無參構造初始化了!

通過有參構造方法來創建

1.UserT. java

public class UserT {

   private String name;

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

   public void show(){
       System.out.println("name="+ name );
  }
}

2.beans.xml 有三種方式編寫

<!-- 第一種根據index參數下標設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
   <!-- index指構造方法 , 下標從0開始 -->
   <constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二種根據參數名字設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
   <!-- name指參數名 -->
   <constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三種根據參數類型設置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
   <constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>

3.測試

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

結論:在配置文件載入的時候。其中管理的對象都已經初始化了!

2.3、Spring配置

別名

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

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

Bean的配置

<!--bean就是java對象,由Spring創建和管理-->

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

class是bean的全限定名=包名+類名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>

import

團隊的合作通過import來實現 .

<import resource="{path}/beans.xml"/>

3、依賴注入(DI)

概念

  • 依賴注入(Dependency Injection,DI)。
  • 依賴 : 指Bean對象的創建依賴於容器 . Bean對象的依賴資源 .
  • 注入 : 指Bean對象所依賴的資源 , 由容器來設置和裝配 .

Set 注入 (重點)

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

測試pojo類 :

Address.java

public class Address {

    private String address;

    public String getAddress() {
        return address;
    }

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

Student.java

package com.kuang.pojo;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
 public class Student {
 
     private String name;
     private Address address;
     private String[] books;
     private List<String> hobbys;
     private Map<String,String> card;
     private Set<String> games;
     private String wife;
     private Properties info;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAddress(Address address) {
         this.address = address;
    }
 
     public void setBooks(String[] books) {
         this.books = books;
    }
 
     public void setHobbys(List<String> hobbys) {
         this.hobbys = hobbys;
    }
 
     public void setCard(Map<String, String> card) {
         this.card = card;
    }
 
     public void setGames(Set<String> games) {
         this.games = games;
    }
 
     public void setWife(String wife) {
         this.wife = wife;
    }
 
     public void setInfo(Properties info) {
         this.info = info;
    }
 
     public void show(){
         System.out.println("name="+ name
                 + ",address="+ address.getAddress()
                 + ",books="
        );
         for (String book:books){
             System.out.print("<<"+book+">>\t");
        }
         System.out.println("\n愛好:"+hobbys);
 
         System.out.println("card:"+card);
 
         System.out.println("games:"+games);
 
         System.out.println("wife:"+wife);
 
         System.out.println("info:"+info);
 
    }
 }

3.1、常量注入

<bean id="student" class="com.kuang.pojo.Student">
    <property name="name" value="小明"/>
</bean>

測試:

@Test
 public void test01(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 
     Student student = (Student) context.getBean("student");
 
     System.out.println(student.getName());
 
 }

3.2、Bean注入

注意點:這裡的值是一個引用,ref

 <bean id="addr" class="com.kuang.pojo.Address">
     <property name="address" value="重慶"/>
 </bean>
 
 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
 </bean>

3.3、數組注入

<bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
     <property name="books">
         <array>
             <value>西遊記</value>
             <value>紅樓夢</value>
             <value>水滸傳</value>
         </array>
     </property>
 </bean>

3.4、List注入

 <property name="hobbys">
     <list>
         <value>聽歌</value>
         <value>看電影</value>
         <value>爬山</value>
     </list>
 </property>

3.5、Map注入

<property name="card">
    <map>
        <entry key="中國郵政" value="456456456465456"/>
        <entry key="建設" value="1456682255511"/>
    </map>
</property>

3.6、set注入

<property name="games">
    <set>
        <value>LOL</value>
        <value>BOB</value>
        <value>COC</value>
    </set>
</property>

3.7、Null注入

<property name="wife"><null/></property>

3.8、Properties注入

<property name="info">
    <props>
        <prop key="學號">20190604</prop>
        <prop key="性別">男</prop>
        <prop key="姓名">小明</prop>
    </props>
</property>

測試結果:

image-20210130125254783

3.9、p命名和c命名注入

User.java :【注意:這裡沒有有參構造器!】

public class User {
     private String name;
     private int age;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAge(int age) {
         this.age = age;
    }
 
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + '\'' +
                 ", age=" + age +
                 '}';
    }
 }

1.P命名空間注入 : 需要在頭文件中加入約束文件

 導入約束 : xmlns:p="//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="//www.springframework.org/schema/c"
 <!--C(構造: Constructor)命名空間 , 屬性依然要設置set方法-->
 <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

發現問題:爆紅了,剛才我們沒有寫有參構造!

解決:把有參構造器加上,這裡也能知道,c 就是所謂的構造器注入!

測試程式碼:

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

4、Bean的作用域

在Spring中,那些組成應用程式的主體及由Spring IoC容器所管理的對象,被稱之為bean。簡單地講,bean就是由IoC容器初始化、裝配及管理的對象 .

image-20210130125601234

幾種作用域中,request、session作用域僅在基於web的應用中使用(不必關心你所採用的是什麼web應用框架),只能用在基於web的Spring ApplicationContext環境。

4.1、Singleton

當一個bean的作用域為Singleton,那麼Spring IoC容器中只會存在一個共享的bean實例,並且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。Singleton是單例類型,就是在創建起容器時就同時自動創建了一個bean的對象,不管你是否使用,他都存在了,每次獲取到的對象都是同一個對象。注意,Singleton作用域是Spring中的預設作用域。要在XML中將bean定義成singleton,可以這樣配置:

 <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
 @Test
 public void test03(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     User user2 = (User) context.getBean("user");
     System.out.println(user==user2);
 }

4.2、Prototype

當一個bean的作用域為Prototype,表示一個bean定義對應多個對象實例。Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程式的方式調用容器的getBean()方法)時都會創建一個新的bean實例。Prototype是原型類型,它在我們創建容器的時候並沒有實例化,而是當我們獲取bean的時候才會去創建一個對象,而且我們每次獲取到的對象都不是同一個對象。根據經驗,對有狀態的bean應該使用prototype作用域,而對無狀態的bean則應該使用singleton作用域。在XML中將bean定義成prototype,可以這樣配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

4.3、Request

當一個bean的作用域為Request,表示在一次HTTP請求中,一個bean定義對應一個實例;即每個HTTP請求都會有各自的bean實例,它們依據某個bean定義創建而成。該作用域僅在基於web的Spring ApplicationContext情形下有效。考慮下面bean定義:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

針對每次HTTP請求,Spring容器會根據loginAction bean的定義創建一個全新的LoginAction bean實例,且該loginAction bean實例僅在當前HTTP request內有效,因此可以根據需要放心的更改所建實例的內部狀態,而其他請求中根據loginAction bean定義創建的實例,將不會看到這些特定於某個請求的狀態變化。當處理請求結束,request作用域的bean實例將被銷毀。

4.4、Session

當一個bean的作用域為Session,表示在一個HTTP Session中,一個bean定義對應一個實例。該作用域僅在基於web的Spring ApplicationContext情形下有效。考慮下面bean定義:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

針對某個HTTP Session,Spring容器會根據userPreferences bean定義創建一個全新的userPreferences bean實例,且該userPreferences bean僅在當前HTTP Session內有效。與request作用域一樣,可以根據需要放心的更改所創建實例的內部狀態,而別的HTTP Session中根據userPreferences創建的實例,將不會看到這些特定於某個HTTP Session的狀態變化。當HTTP Session最終被廢棄的時候,在該HTTP Session作用域內的bean也會被廢棄掉。

5、自動裝配

5.1、Bean的自動裝配

自動裝配說明

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

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

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

這裡我們主要講第三種:自動化的裝配bean。

Spring的自動裝配需要從兩個角度來實現,或者說是兩個操作:

  1. 組件掃描(component scanning):spring會自動發現應用上下文中所創建的bean;
  2. 自動裝配(autowiring):spring自動滿足bean之間的依賴,也就是我們說的IoC/DI;

組件掃描和自動裝配組合發揮巨大威力,使得顯示的配置降低到最少。

推薦不使用自動裝配xml配置 , 而使用註解 .

測試環境搭建

1.新建一個項目

2.新建兩個實體類,Cat Dog 都有一個叫的方法

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}

3.新建一個用戶類 User

public class User {
   private Cat cat;
   private Dog dog;
   private String str;
}

4.編寫Spring配置文件

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

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

   <bean id="user" class="com.kuang.pojo.User">
       <property name="cat" ref="cat"/>
       <property name="dog" ref="dog"/>
       <property name="str" value="qinjiang"/>
   </bean>
</beans>

5.測試

public class MyTest {
   @Test
   public void testMethodAutowire() {
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       User user = (User) context.getBean("user");
       user.getCat().shout();
       user.getDog().shout();
  }
}

結果正常輸出,環境OK

ByName

autowire byName (按名稱自動裝配)

由於在手動配置xml過程中,常常發生字母缺漏和大小寫等錯誤,而無法對其進行檢查,使得開發效率降低。

採用自動裝配將避免這些錯誤,並且使配置簡單化。

測試:

1.修改bean配置,增加一個屬性 autowire="byName"

<bean id="user" class="com.kuang.pojo.User" autowire="byName">
   <property name="str" value="qinjiang"/>
</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. 如果有,就取出注入;如果沒有,就報空指針異常。

ByType

autowire byType (按類型自動裝配)

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

NoUniqueBeanDefinitionException

測試:

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

2.測試,正常輸出

3.在註冊一個cat 的bean對象!

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

<bean id="user" class="com.kuang.pojo.User" autowire="byType">
   <property name="str" value="qinjiang"/>
</bean>

4、測試,報錯:NoUniqueBeanDefinitionException

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

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

5.2、使用註解

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

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

1.在spring配置文件中引入context文件頭

xmlns:context="//www.springframework.org/schema/context"
//www.springframework.org/schema/context
//www.springframework.org/schema/context/spring-context.xsd

2.開啟屬性註解支援!

<context:annotation-config/>

@Autowired

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

測試:

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

public class User {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   private String str;

   public Cat getCat() {
       return cat;
  }
   public Dog getDog() {
       return dog;
  }
   public String getStr() {
       return str;
  }
}

2、此時配置文件內容

<context:annotation-config/>

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>

3、測試,成功輸出結果!

【科普時間】

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

//如果允許對象為null,設置required = false,默認為true
@Autowired(required = false)
private Cat cat;

@Qualifier

  • @Autowired是根據類型自動裝配的,加上@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;

測試,成功輸出!

@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查找,成功。

5.3、小結

@Autowired與@Resource異同:

1、@Autowired與@Resource都可以用來裝配bean。都可以寫在欄位上,或寫在setter方法上。

2、@Autowired默認按類型裝配(屬於spring規範),默認情況下必須要求依賴對象必須存在,如果要允許null 值,可以設置它的required屬性為false,如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用

3、@Resource(屬於J2EE復返),默認按照名稱進行裝配,名稱可以通過name屬性進行指定。如果沒有指定name屬性,當註解寫在欄位上時,默認取欄位名進行按照名稱查找,如果註解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。

它們的作用相同都是用註解方式注入對象,但執行順序不同。@Autowired先byType,@Resource先byName。

6、使用註解開發

說明

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

image-20210130131308882

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

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

</beans>

6.1、Bean的實現

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

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

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

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

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

3、測試

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

6.2、屬性注入

使用註解注入屬性

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

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

6.3、衍生註解

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

@Component三個衍生註解

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

  • @Controller:web層
  • @Service:service層
  • @Repository:dao層

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

6.4、作用域

@scope

  • singleton:默認的,Spring會採用單例模式創建這個對象。關閉工廠 ,所有的對象都會銷毀。
  • prototype:多例模式。關閉工廠 ,所有的對象不會銷毀。內部的垃圾回收機制會回收
@Controller("user")
@Scope("prototype")
public class User {
   @Value("秦疆")
   public String name;
}

6.5、基於Java類進行配置

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

測試:

1、編寫一個實體類,Dog

@Component  //將這個類標註為Spring的一個組件,放到容器中!
public class Dog {
   public String name = "dog";
}

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

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

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

}

3、測試

@Test
public void test2(){
   ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
   Dog dog = (Dog) applicationContext.getBean("dog");
   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中還會大量看到,我們需要知道這些註解的作用即可!

6.6、小結

XML與註解比較

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

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

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

作用:

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

7、靜態/動態代理模式

代理模式

為什麼要學習代理模式,因為AOP的底層機制就是動態代理!

代理模式:

  • 靜態代理
  • 動態代理

學習aop之前 , 我們要先了解一下代理模式!

image-20210130132333533

7.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();
  }
}

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

靜態代理的好處:

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

缺點 :

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

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

7.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:縱向開發,橫向開發

image-20210130133246417

7.3、動態代理

  • 動態代理的角色和靜態代理的一樣 .

  • 動態代理的代理類是動態生成的 . 靜態代理的代理類是我們提前寫好的

  • 動態代理分為兩類 : 一類是基於介面動態代理 , 一類是基於類的動態代理

    • 基於介面的動態代理—-JDK動態代理
    • 基於類的動態代理–cglib
    • 現在用的比較多的是 javasist 來生成動態代理 . 百度一下javasist
    • 我們這裡使用JDK的原生程式碼來實現,其餘的道理都是一樣的!

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

核心 : InvocationHandlerProxy ,打開JDK幫助文檔看看

【InvocationHandler:調用處理程式】

image-20210130133353757

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

【Proxy : 代理】

image-20210130133617294

image-20210130133654339

//生成代理類
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();
  }

}

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

7.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();
  }
}

7.5、動態代理的好處

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

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

8、AOP

8.1、什麼是AOP

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

image-20210130143712471

8.2、Aop在Spring中的作用

提供聲明式事務;允許用戶自定義切面

以下名詞需要了解下:

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

image-20210130143742257

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

image-20210130143822935

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

8.3、使用Spring實現AOP

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

<!-- //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 search();

}
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加用戶");
  }

   @Override
   public void delete() {
       System.out.println("刪除用戶");
  }

   @Override
   public void update() {
       System.out.println("更新用戶");
  }

   @Override
   public void search() {
       System.out.println("查詢用戶");
  }
}

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

public class Log implements MethodBeforeAdvice {

   //method : 要執行的目標對象的方法
   //objects : 被調用的方法的參數
   //Object : 目標對象
   @Override
   public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被執行了");
  }
}
public class AfterLog implements AfterReturningAdvice {
   //returnValue 返回值
   //method被調用的方法
   //args 被調用的方法的對象的參數
   //target 被調用的目標對象
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
       System.out.println("執行了" + target.getClass().getName()
       +"的"+method.getName()+"方法,"
       +"返回值:"+returnValue);
  }
}

最後去spring的文件中註冊 , 並實現aop切入實現 , 注意導入約束 .

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

   <!--註冊bean-->
   <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
   <bean id="log" class="com.kuang.log.Log"/>
   <bean id="afterLog" class="com.kuang.log.AfterLog"/>

   <!--aop的配置-->
   <aop:config>
       <!--切入點 expression:表達式匹配要執行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <!--執行環繞; advice-ref執行方法 . pointcut-ref切入點-->
       <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("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.search();
  }
}

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

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

第二種方式

自定義類來實現Aop

目標業務類不變依舊是userServiceImpl

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

public class DiyPointcut {

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

去spring中配置

<!--第二種方式自定義實現-->
<!--註冊bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
   <!--第二種方式:使用AOP的標籤實現-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

測試:

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

第三種方式

使用註解實現

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

package com.kuang.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

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

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

   @Around("execution(* com.kuang.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.kuang.config.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動態代理。

9、整合MyBatis

9.1、步驟

1、導入相關jar包

junit

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

mybatis

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

mysql-connector-java

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

spring相關

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

aspectJ AOP 織入器

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

mybatis-spring整合包 【重點】

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

配置Maven靜態資源過濾問題!

<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>true</filtering>
       </resource>
   </resources>
</build>

編寫配置文件

程式碼實現

9.2、回憶MyBatis

編寫pojo實體類

package com.kuang.pojo;

public class User {
   private int id;  //id
   private String name;   //姓名
   private String pwd;   //密碼
}

實現mybatis的配置文件

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

   <typeAliases>
       <package name="com.kuang.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?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>

   <mappers>
       <package name="com.kuang.dao"/>
   </mappers>
</configuration>

UserDao介面編寫

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

介面對應的Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">

 <select id="selectUser" resultType="User">
     select * from user
  </select>

</mapper>

測試類

@Test
public void selectUser() throws IOException {

   String resource = "mybatis-config.xml";
   InputStream inputStream = Resources.getResourceAsStream(resource);
   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();

   UserMapper mapper = sqlSession.getMapper(UserMapper.class);

   List<User> userList = mapper.selectUser();
   for (User user: userList){
       System.out.println(user);
  }

   sqlSession.close();
}

9.3、MyBatis-Spring學習

引入Spring之前需要了解mybatis-spring包中的一些重要類;

//www.mybatis.org/spring/zh/index.html

image-20210130145016081

什麼是 MyBatis-Spring?

MyBatis-Spring 會幫助你將 MyBatis 程式碼無縫地整合到 Spring 中。

知識基礎

在開始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 這兩個框架和有關它們的術語。這很重要

MyBatis-Spring 需要以下版本:

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring 框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

如果使用 Maven 作為構建工具,僅需要在 pom.xml 中加入以下程式碼即可:

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

要和 Spring 一起使用 MyBatis,需要在 Spring 應用上下文中定義至少兩樣東西:一個 SqlSessionFactory 和至少一個數據映射器類。

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean來創建 SqlSessionFactory。要配置這個工廠 bean,只需要把下面程式碼放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 	<property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一個 DataSource(數據源)。這可以是任意的 DataSource,只需要和配置其它 Spring 資料庫連接一樣配置它就可以了。

在基礎的 MyBatis 用法中,是通過 SqlSessionFactoryBuilder 來創建 SqlSessionFactory 的。而在 MyBatis-Spring 中,則使用 SqlSessionFactoryBean 來創建。

在 MyBatis 中,你可以使用 SqlSessionFactory 來創建 SqlSession。一旦你獲得一個 session 之後,你可以使用它來執行映射了的語句,提交或回滾連接,最後,當不再需要它的時候,你可以關閉 session。

SqlSessionFactory有一個唯一的必要屬性:用於 JDBC 的 DataSource。這可以是任意的 DataSource 對象,它的配置方法和其它 Spring 資料庫連接是一樣的。

一個常用的屬性是 configLocation,它用來指定 MyBatis 的 XML 配置文件路徑。它在需要修改 MyBatis 的基礎配置非常有用。通常,基礎配置指的是 < settings> 或 < typeAliases>元素。

需要注意的是,這個配置文件並不需要是一個完整的 MyBatis 配置。確切地說,任何環境配置(),數據源()和 MyBatis 的事務管理器()都會被忽略。SqlSessionFactoryBean 會創建它自有的 MyBatis 環境配置(Environment),並按要求設置自定義環境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作為 SqlSession 的一個實現,這意味著可以使用它無縫代替你程式碼中已經在使用的 SqlSession。

模板可以參與到 Spring 的事務管理中,並且由於其是執行緒安全的,可以供多個映射器類使用,你應該總是用 SqlSessionTemplate 來替換 MyBatis 默認的 DefaultSqlSession 實現。在同一應用程式中的不同類之間混雜使用可能會引起數據一致性的問題。

可以使用 SqlSessionFactory 作為構造方法的參數來創建 SqlSessionTemplate 對象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
 	<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

現在,這個 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一個 SqlSession 屬性,就像下面這樣:

public class UserDaoImpl implements UserDao {

     private SqlSession sqlSession;

     public void setSqlSession(SqlSession sqlSession) {
       this.sqlSession = sqlSession;
     }

     public User getUser(String userId) {
       return sqlSession.getMapper...;
     }
}

按下面這樣,注入 SqlSessionTemplate:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
 	<property name="sqlSession" ref="sqlSession" />
</bean>

9.4、整合實現一

1、引入Spring配置文件beans.xml

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

2、配置數據源替換mybaits的數據源

<!--配置數據源:數據源有非常多,可以使用第三方的,也可使使用Spring的-->
<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=utf8"/>
   <property name="username" value="root"/>
   <property name="password" value="123456"/>
</bean>

3、配置SqlSessionFactory,關聯MyBatis

<!--配置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/kuang/dao/*.xml"/>
</bean>

4、註冊sqlSessionTemplate,關聯sqlSessionFactory;

<!--註冊sqlSessionTemplate , 關聯sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <!--利用構造器注入-->
   <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

5、增加Dao介面的實現類;私有化sqlSessionTemplate

public class UserDaoImpl implements UserMapper {

   //sqlSession不用我們自己創建了,Spring來管理
   private SqlSessionTemplate sqlSession;

   public void setSqlSession(SqlSessionTemplate sqlSession) {
       this.sqlSession = sqlSession;
  }

   public List<User> selectUser() {
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       return mapper.selectUser();
  }
   
}

6、註冊bean實現

<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
   <property name="sqlSession" ref="sqlSession"/>
</bean>

7、測試

@Test
public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserMapper mapper = (UserMapper) context.getBean("userDao");
    List<User> user = mapper.selectUser();
    System.out.println(user);
}

結果成功輸出!現在我們的Mybatis配置文件的狀態!發現都可以被Spring整合!

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "//mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <typeAliases>
       <package name="com.kuang.pojo"/>
   </typeAliases>
</configuration>

9.5、整合實現二

mybatis-spring1.2.3版以上的才有這個 .

官方文檔截圖 :

dao繼承Support類 , 直接利用 getSqlSession() 獲得 , 然後直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且對事務的支援更加友好 . 可跟蹤源碼查看

image-20210130145320056

測試:

1、將我們上面寫的UserDaoImpl修改一下

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
   public List<User> selectUser() {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.selectUser();
  }
}

2、修改bean的配置

<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
   <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3、測試

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

總結 : 整合到spring以後可以完全不要mybatis的配置文件,除了這些方式可以實現整合之外,我們還可以使用註解來實現,這個等我們後面學習SpringBoot的時候還會測試整合!

10、聲明式事務

10.1、回顧事務

  • 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎!
  • 事務管理是企業級應用程式開發中必備技術,用來確保數據的完整性和一致性。

事務就是把一系列的動作當成一個獨立的工作單元,這些動作要麼全部完成,要麼全部不起作用。

事務四個屬性ACID

  1. 原子性(atomicity)

    • 事務是原子性操作,由一系列動作組成,事務的原子性確保動作要麼全部完成,要麼完全不起作用
  2. 一致性(consistency)

    • 一旦所有事務動作完成,事務就要被提交。數據和資源處於一種滿足業務規則的一致性狀態中
  3. 隔離性(isolation)

    • 可能多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞
  4. 持久性(durability)

    • 事務一旦完成,無論系統發生什麼錯誤,結果都不會受到影響。通常情況下,事務的結果被寫到持久化存儲器中

10.2、測試

將上面的程式碼拷貝到一個新項目中

在之前的案例中,我們給userDao介面新增兩個方法,刪除和增加用戶;

//添加一個用戶
int addUser(User user);

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

mapper文件,我們故意把 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>

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

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {

   //增加一些操作
   public List<User> selectUser() {
       User user = new User(4,"小明","123456");
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       mapper.addUser(user);
       mapper.deleteUser(4);
       return mapper.selectUser();
  }

   //新增
   public int addUser(User user) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.addUser(user);
  }
   //刪除
   public int deleteUser(int id) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.deleteUser(id);
  }

}

測試

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

報錯:sql異常,delete寫錯了

結果 :插入成功!

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

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

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

10.3、Spring中的事務管理

Spring在不同的事務管理API之上定義了一個抽象層,使得開發人員不必了解底層的事務管理API就可以使用Spring的事務管理機制。Spring支援編程式事務管理和聲明式的事務管理。

編程式事務管理

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

聲明式事務管理

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

使用Spring管理事務,注意頭文件的約束導入 : tx

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

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

事務管理器

  • 無論使用Spring的哪種事務管理策略(編程式或者聲明式)事務管理器都是必須的。
  • 就是 Spring的核心事務管理抽象,管理封裝了一組獨立於技術的方法。

JDBC事務

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

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

<!--配置事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <!--配置哪些方法使用什麼樣的事務,配置事務的傳播特性-->
       <tx:method name="add" propagation="REQUIRED"/>
       <tx:method name="delete" propagation="REQUIRED"/>
       <tx:method name="update" propagation="REQUIRED"/>
       <tx:method name="search*" propagation="REQUIRED"/>
       <tx:method name="get" read-only="true"/>
       <tx:method name="*" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>

spring事務傳播特性:

事務傳播行為就是多個事務方法相互調用時,事務如何在這些方法間傳播。spring支援7種事務傳播行為:

  • propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇。
  • propagation_supports:支援當前事務,如果沒有當前事務,就以非事務方法執行。
  • propagation_mandatory:使用當前事務,如果沒有當前事務,就拋出異常。
  • propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
  • propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • propagation_never:以非事務方式執行操作,如果當前事務存在則拋出異常。
  • propagation_nested:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作

Spring 默認的事務傳播行為是 PROPAGATION_REQUIRED,它適合於絕大多數的情況。

假設 ServiveX#methodX() 都工作在事務環境下(即都被 Spring 事務增強了),假設程式中存在如下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這 3 個服務類的 3 個方法通過 Spring 的事務傳播機制都工作在同一個事務中。

就好比,我們剛才的幾個方法存在調用,所以會被放在一組事務當中!

配置AOP

導入aop的頭文件!

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

進行測試

刪掉剛才插入的數據,再次測試!

@Test
public void test2(){
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserMapper mapper = (UserMapper) context.getBean("userDao");
   List<User> user = mapper.selectUser();
   System.out.println(user);
}

思考問題?

為什麼需要配置事務?

  • 如果不配置,就需要我們手動提交控制事務;
  • 事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎!