1. 程式人生 > 實用技巧 >spring5自學之路 狂神說

spring5自學之路 狂神說

1. Spring介紹

1.1 簡介

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

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

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

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

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

官網 : http://spring.io/

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

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

1.2 優點

  1. spring 是一個開源免費的框架(容器)
  2. spring 是輕量級的、非侵入式的框架
  3. 控制反轉(IOC)、面向切面(AOP)程式設計
    1. Inversion of Control
    2. Aspect-Oriented Programming
  4. 支援事務處理,對框架整合的支援

總結:spring就是一個輕量級的控制反轉和麵向切面程式設計的框架

1.3 組成

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

組成 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。

1.4 擴充套件

組成 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。

2. IOC

2.1 引入IOC

新建一個空白的maven專案,使用這個專案來說明IOC原理

先按照原來的方式寫一段程式碼

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的原型 !

NOTE:

  1. 之前,是程式主動建立物件,控制權在程式手裡
  2. 使用set注入之後,程式不再具備主動性,而是變成了被動的接受物件,使用者獲取了程式的主動性

2.2 IOC本質

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

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

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

Your application classes are combined with configuration metadata so that, after the ApplicationContext is created and initialized, you have a fully configured and executable system or application.

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

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

程式碼測試

  1. hello.java

    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. 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
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="hello" class="Hello">
            <property name="str" value="spring"/>
        </bean>
    </beans>
    

    The id attribute is a string that identifies the individual bean definition.

    The class attribute defines the type of the bean and uses the fully qualified classname.

  3. 測試程式碼

    @Test
    public void helloTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Hello hello = (Hello) applicationContext.getBean("hello");
        System.out.println(hello.toString());
    }
    

思考

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

這個過程就叫控制反轉 :

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

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

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

2.3 IOC建立物件的方式

  1. 預設通過無參構造建立物件

  2. 建立物件的時間:在拿到容器的時候所有.xml檔案中配置的物件就建立完成了,且通過同一個id建立的物件是一樣的

     ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    
  3. 通過有參構造的方式創造物件的三種方式

    1. 通過引數索引

      <bean class="User" id="user">
              <constructor-arg index="0" value="spring"/>
      </bean>
      
    2. 通過引數型別{不推薦}

      <bean class="User" id="user">
      	<constructor-arg type="java.lang.String" value="spring"/>
      </bean>
      
    3. 通過引數的名稱{推薦}

      <bean class="User" id="user">
              <constructor-arg name="name" value="spring"/>
      </bean>
      

2.4 spring配置

2.4.1 alias

alias : 為bean設定別名 ,id可以傳入userNew取物件

<alias name="userT" alias="userNew"/>

2.4.2 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>

2.4.3 import

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

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

3. spring注入

3.1 簡介

依賴注入(DI: dependency injection):

  1. 依賴:bean物件的建立依賴於容器
  2. 注入:指bean對所依賴的資源,由容器設定和裝配

3.2 注入方式

3.2.1 構造器注入

<bean class="User" id="user">
        <constructor-arg name="name" value="spring"/>
</bean>

3.2.2 set注入{重點}

要求被注入的屬性 , 必須有set方法

環境搭建

pojo類

@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;
}
@Data
public class Address {
    private String address;
}

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 id="stu" class="com.iandf.pojo.Student">
        <!--value注入-->
        <property name="name" value="黃鶴"/>
        <!--bean注入-->
        <property name="address" ref="address"/>
        <!--陣列-->
        <property name="books">
            <array>
                <value>紅樓夢</value>
                <value>水滸傳</value>
                <value>三國演義</value>
            </array>
        </property>
        <!--list-->
        <property name="hobbies">
            <list>
                <value>敲程式碼</value>
                <value>看電影</value>
            </list>
        </property>
        <!--map-->
        <property name="card">
            <map>
                <entry key="學號" value="123456"/>
            </map>
        </property>
        <!--set-->
        <property name="games">
            <set>
                <value>lol</value>
                <value>王者</value>
            </set>
        </property>
        <!--null-->
        <property name="wife">
            <null/>
        </property>
        <!--properties-->
        <property name="info">
            <props>
                <prop key="銀行卡">123414564</prop>
            </props>
        </property>
    </bean>
    <bean id="address" class="com.iandf.pojo.Address">
        <property name="address" value="湖南"/>
    </bean>
</beans>

測試類

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student stu = (Student) context.getBean("stu");
System.out.println(stu.toString());

結果

/*Student(name=黃鶴
        , address=Address(address=湖南)
        , books=[紅樓夢, 水滸傳, 三國演義]
        , hobbies=[敲程式碼, 看電影]
        , card={學號=123456}
        , games=[lol, 王者]
        , wife=null
        , info={銀行卡=123414564})*/

3.2.3 擴充套件注入

Spring supports extensible configuration formats with namespaces, which are based on an XML Schema definition

xml shortcut with the p-namespace 相當於property標籤

  1. 匯入名稱空間

     xmlns:p="http://www.springframework.org/schema/p"
    
  2. 使用

     <bean id="user" class="com.iandf.pojo.User" p:id="123" p:name="黃鶴"/>
    

xml shortcut with c-namespace 相當於constructor-arg標籤

  1. 匯入名稱空間

     xmlns:c="http://www.springframework.org/schema/c"
    
  2. 使用

     <bean id="user2" class="com.iandf.pojo.User" c:id="456" c:name="利誘">
    

4. bean作用域

  1. 單例模式(預設的):spring容器為每一個bean分配一個物件

  2. 原型模式:每一次從spring容器中獲取到的物件都是新的

     <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
    
  3. request、session、...都是在web應用中使用的

5. 自動裝配

自動裝配說明

  • 自動裝配是使用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配置 , 而使用註解 .

5.1 使用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="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="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 使用註解自動配置

5.2.1 配置環境

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

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

5.2.2 @autowired

@autowired預設是byType,byType找不到再使用byName

如果出現多個bean且byName找不到的情況下可以使用@Qualifier("xxx") xxx為bean的id

    @Autowired
    @Qualifier("cat1")
    private Cat cat;
    @Autowired
    @Qualifier("dog1")
    private Dog dog;
    <bean id="dog2" class="Dog"/>
    <bean id="dog1" class="Dog"/>
    <bean id="cat1" class="Cat"/>
    <bean id="cat2" class="Cat"/>
    <bean id="person" class="Person">
        <property name="name" value="huang"/>
    </bean>

5.2.3 @Resource

@Resource預設是byName,byType找不到再byType

byName和byType都找不到的情況下使用 @Resource(name = "xxx") xxx為bean的id

    @Resource(name = "cat1")
    private Cat cat;
    @Resource
    private Dog dog;
    <bean id="dog" class="Dog"/>
    <bean id="cat1" class="Cat"/>
    <bean id="cat2" class="Cat"/>
    <bean id="person" class="Person">
        <property name="name" value="huang"/>
    </bean>

5.2.4 小結

@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. 使用註解開發

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

配置環境

在配置檔案當中,還得要引入一個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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.iandf.pojo"/>

</beans>

6.1 使用註解實現bean配置和屬性的注入

@Component(value = "user")//預設就是user
public class User {
    @Value("iandf")//優先使用
    private String name = "aaa";
}

@Component("user")
// 相當於配置檔案中 <bean id="user" class="當前註解的類"/>
@Value("iandf")
// 相當於配置檔案中 <property name="name" value="iandf"/>,也可以寫在set方法上

6.2 Component的衍生註解

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

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

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

6.3 自動配置註解 看第五章節

6.4 作用域

@scope

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

6.5 小結

XML與註解比較

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

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

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

作用:

  • 進行註解驅動註冊,從而使註解生效

  • 用於啟用那些已經在spring容器裡註冊過的bean上面的註解,也就是顯示的向Spring註冊

  • 如果不掃描包,就需要手動配置bean

  • 如果不加註解驅動,則注入的值為null

7 使用純java進行配置

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

測試

  1. 編寫config類

    @Configuration//代表這是一個配置類,相當於beans.xml,同時將BeansConfig交給spring管理
    public class BeansConfig {
        @Bean//id是方法名,class是返回值型別
        public User getUser(){
            return new User();
        }
    }
    
  2. 編寫一個實體類,使用component標註

    @Component
    public class User {
        private String name = "iandf";
    }
    
  3. 測試 使用純註解方式需要通過AnnotationConfigApplicationContext類拿到容器

    @Test
    public void javaConfigTest(){
        ApplicationContext context = new AnnotationConfigApplicationContext("com.iandf.config");
        BeansConfig beansConfig = context.getBean("beansConfig", BeansConfig.class);
        User user = beansConfig.getUser();
        System.out.println(user.toString());
    }
    @Test
    public void javaConfigTest2() {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.iandf.config");
        User user = context.getBean("getUser", User.class);
        System.out.println(user.toString());
    }
    
  4. 也可以使用@Import(XxxConfig.class) 匯入其他配置類相當於inculde 標籤

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

8. AOP

8.1 什麼是aop

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

8.2 aop在spring中的作用

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

以下名詞需要了解下:

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

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

  • before advice, 在 join point 前被執行的 advice. 雖然 before advice 是在 join point 前被執行, 但是它並不能夠阻止 join point 的執行, 除非發生了異常(即我們在 before advice 程式碼中, 不能人為地決定是否繼續執行 join point 中的程式碼)
  • after return advice, 在一個 join point 正常返回後執行的 advice
  • after throwing advice, 當一個 join point 丟擲異常後執行的 advice
  • after(final) advice, 無論一個 join point 是正常退出還是發生了異常, 都會被執行的 advice.
  • around advice, 在 join point 前和 joint point 退出後都執行的 advice. 這個是最常用的 advice.

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

8.3 使用spring實現aop

需求:使UserServiceImpl 在不改變原始碼的情況下,使它原本的增刪改查實現前置日誌和後置日誌的功能

8.3.1 使用spring api 實現

搭建環境

public interface IUserService {
    void add();
    void delete();
    void update();
    void select();
}
public class UserServiceImpl implements IUserService{
    public void add() {
        System.out.println("新增一個使用者");
    }

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

    public void update() {
        System.out.println("修改一個使用者");
    }

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

Log類

public class BeforeLog implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) {
        System.out.println((target != null ? target.getClass().getName() : null) +"執行了"+method.getName()+"方法");
    }
}
public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println((target != null ? target.getClass().getName() : null) +"執行完了"+method.getName()+"返回值為: "+returnValue);
    }
}

配置檔案

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

    <bean id="userService" class="com.iandf.service.UserServiceImpl"/>
    <bean id="afterLog" class="com.iandf.log.AfterLog"/>
    <bean id="beforeLog" class="com.iandf.log.BeforeLog"/>
    <aop:config>
         <!--切入點 expression:表示式匹配要執行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.iandf.service.UserServiceImpl.*(..))"/>
        <!--執行環繞; advice-ref執行方法 . pointcut-ref切入點-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

定義切點:

expression="execution(* com.iandf.service.UserServiceImpl.*(..))

對這個表示式分析:

  1. * 表示返回值的方法
  2. com.iandf.service.UserServiceImpl.* 表示UserServiceImpl類中的任意方法
  3. (..)表示任意引數

測試程式碼

@Test
public void LogTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //獲取代理物件,注意代理物件是其介面的實現類
    IUserService userService = context.getBean("userService", IUserService.class);
    userService.add();
}

測試結果

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

8.3.2 使用自定義類實現aop

步驟:

  1. 自己自動一個Log類
  2. 在配置檔案中配置切入面,切入點,通知

log類

public class LogAopDiy {

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

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

配置檔案

<bean id="diy" class="com.iandf.diy.LogAopDiy"/>
<aop:config>
    <aop:aspect ref="diy">
        <!--切入點-->
        <aop:pointcut id="pointcut" expression="execution(* com.iandf.service.UserServiceImpl.*(..))"/>
        <!--通知:要執行的方法-->
        <aop:after method="after" pointcut-ref="pointcut"/>
        <aop:before method="before" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

測試結果

8.3.3 使用註解實現aop

步驟:

  1. 自定義一個帶有註解的Log類
  2. 在配置檔案中配置bean和開啟註解

AnnotationAop

@Aspect//把該類定義成一個切面
public class AnnotationAop {
    @Before(value = "execution(* com.iandf.service.UserServiceImpl.*(..))")//配置切入點
    public void before(){
        System.out.println("方法執行前");
    }

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

    @AfterReturning(value = "execution(* com.iandf.service.UserServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("方法返回結果");
    }

    @Around(value = "execution(* com.iandf.service.UserServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //ProceedingJoinPoint joinPoint與切入點匹配的執行點。
        System.out.println("方法環繞前");
        Object proceed = joinPoint.proceed();//執行目標物件呼叫的方法
        System.out.println("方法環繞後");
        return proceed;
    }
}

配置檔案

<bean id="annotationAop" class="com.iandf.annotation_aop.AnnotationAop"/>
<!--支援註解-->
<aop:aspectj-autoproxy proxy-target-class="false"/>

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 mybatis原生開發

步驟:

  1. 編寫mybatis-config.xml 新增資料來源
  2. 編寫實體類 eg:User
  3. 編寫介面 eg:UserMapper
  4. 編寫介面配置檔案 eg:UserMapper.xml 寫sql語句
  5. 在mybatis-config.xml中新增mapper
  6. 寫測試類

程式碼案例

User、UserMapper 和 UserMapper.xml

@Data
public class User {
    private int id;
    private String name;
    private String password;
}
public interface UserMapper {
    List<User> getUserList();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.iandf.dao.UserMapper">
    <select id="getUserList" resultType="user">
        select * from mybatis.user
    </select>
</mapper>

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>
    <typeAliases>
        <package name="com.iandf.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=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

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

測試方法

@Test
public void getUserListTest(){
    String resource = "mybatis-config.xml";
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    try {
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream(resource));
        SqlSession sqlSession = build.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

9.2 使用spring整合mybatis開發

9.2.1 導包

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 織入器

<!-- https://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.2 使用方式一 基於SqlSessionTemplate

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

SqlSessionFactory還需要一個數據源,所指定的對映器類必須是一個介面,而不是具體的實現類。

在 MyBatis 中,你可以使用 SqlSessionFactory 來建立 SqlSession。一旦你獲得一個 session 之後,你可以使用它來執行映射了的語句,提交或回滾連線,最後,當不再需要它的時候,你可以關閉 session。使用 MyBatis-Spring 之後,你不再需要直接使用 SqlSessionFactory 了,因為你的 bean 可以被注入一個執行緒安全的 SqlSession它能基於 Spring 的事務配置來自動提交、回滾、關閉 session

  1. 編寫實體類User

  2. 在spring-dao.xml配置檔案中配置資料來源 這裡使用的資料來源是spring-jdbc依賴下的DriverManagerDataSource。也可以使用dbcp c3po druid

    <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"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
  3. 編寫UserMapper和UserMapper.xml 和原生開發一樣

  4. 在spring-dao.xml配置檔案中建立SqlSessionFactory

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--關聯Mybatis配置檔案-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--關聯mapper檔案-->
        <property name="mapperLocations" value="classpath:com/iandf/dao/*.xml"/>
    </bean>
    
  5. 在spring-dao.xml配置檔案中建立SqlSessionTemplate

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--沒有set方法,所以只能是構造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
  6. 編寫UserMapper實現類

    public class UserMapperImpl implements UserMapper {
        private SqlSessionTemplate sqlSessionTemplate;
    
        public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
            this.sqlSessionTemplate = sqlSessionTemplate;
        }
    
        public List<User> getUserList() {
            UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
            return mapper.getUserList();
        }
    }
    
  7. 將剛剛寫好的spring-dao.xml和UserMapper實現類整合到專案的配置檔案中

    <?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: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/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
            <import resource="spring-dao.xml"/>
            <bean id="userDao" class="com.iandf.dao.UserMapperImpl">
                <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
            </bean>
    </beans>
    
  8. 編寫測試方

    @Test
    public void springMybatisTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapperImpl userDao = context.getBean("userDao", UserMapperImpl.class);
        for (User user : userDao.getUserList()) {
            System.out.println(user);
        }
    }
    

9.2.3 spring整合mybatis開發和原生mybatis開發的區別

  1. 在基礎的 MyBatis 用法中,是通過 SqlSessionFactoryBuilder 來建立 SqlSessionFactory 的。而在 MyBatis-Spring 中,則使用 SqlSessionFactoryBean 來建立。
  2. 在 MyBatis 中,你可以使用 SqlSessionFactory 來建立 SqlSession。一旦你獲得一個 session 之後,你可以使用它來執行映射了的語句,提交或回滾連線,最後,當不再需要它的時候,你可以關閉 session。而在spring中,使用sqlSessionFactory來建立sqlSessionTemplate,你可以使用它來執行映射了的語句。
  3. SqlSessionTemplate 是 MyBatis-Spring 的核心。作為 SqlSession 的一個實現,這意味著可以使用它無縫代替你程式碼中已經在使用的 SqlSession。模板可以參與到 Spring 的事務管理中,並且由於其是執行緒安全的,可以供多個對映器類使用,你應該總是用 SqlSessionTemplate 來替換 MyBatis 預設的 DefaultSqlSession 實現。在同一應用程式中的不同類之間混雜使用可能會引起資料一致性的問題。
  4. spring需要提供一個UserMapper介面的實現類,實現類組合了一個SqlSessionTemplate物件,有這個物件來對事務進行統一的管理

9.2.4 使用方式二 基於SqlSessionDaoSupport

步驟:

  1. 編寫實體類User

  2. 在spring-dao.xml配置檔案中配置資料來源

  3. 編寫UserMapper和UserMapper.xml 和原生開發一樣

  4. 在spring-dao.xml配置檔案中建立SqlSessionFactory

  5. 編寫UserMapper實現類並繼承SqlSessionDaoSupport 不用編寫SqlSessionTemplate

    public class UserMapperImplBySqlSessionDaoSupport extends SqlSessionDaoSupport implements UserMapper {
    
        public List<User> getUserList() {
            //getSqlSession()返回的是SqlSessionTemplate,只是SqlSessionTemplate實現了SqlSession介面
            return getSqlSession().getMapper(UserMapper.class).getUserList();
        }
    }
    
  6. 在applicationContext.xml中註冊UserMapperImplBySqlSessionDaoSupport的bean

    <bean id="userDao2" class="com.iandf.dao.UserMapperImplBySqlSessionDaoSupport">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
    </bean>
    
  7. 測試方法

    @Test
    public void springMybatisTest2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        SqlSessionTemplate sqlSessionTemplate = context.getBean("sqlSessionTemplate", SqlSessionTemplate.class);
        UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
        for (User user : mapper.getUserList()) {
            System.out.println(user);
        }
    }
    

10. spring事務

事務管理分類:

  1. 宣告式事務:使用aop織入事務,對事務進行管理
  2. 程式設計式事務:在程式碼中顯示的執行事務

10.1 使用aop織入事務

  1. 配置生命式事務

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  2. 配置事務通知 需要配置tx名稱空間

    <!--配置事務通知-->
    <tx:advice id="tx_advice" transaction-manager="transactionManager">
        <tx:attributes>
             <!--*:該通知在任意方法上生效,也可以是get*: 以get開頭的所有方法生效-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
  3. 配置切入點

        <!--配置接入點-->
        <aop:config>
            <aop:pointcut id="tx_pointcut" expression="execution(* com.iandf.dao.UserMapper.*(..))"/>
            <aop:advisor advice-ref="tx_advice" pointcut-ref="tx_pointcut"/>
        </aop:config>
    

上面三步做完之後就成功的把事務織入到原來的程式碼中了