spring-5學習筆記

肉食动物123發表於2024-04-03

Spring5-2023/08/23(稍後更新6)

01 初識Spring

1.1 簡介

Spring框架是由於軟體開發的複雜性而建立的。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。

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

歷史:

  • 2002,首次推出了Spring框架的雛形:interface21框架!
  • Spring框架即以interface21為基礎,經過重新設計,並不斷豐富其內涵,於2004年3月24日正式釋出其1.0版本
  • Spring理念:使現有技術更加容易使用,本身是一個大雜燴,整合了現有的許多技術框架
  • 原來的技術路線:SSH:Struct2+Spring+Hibernate
  • 現在的技術路線:SSM:SpringMVC+Spring+Mybatis

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

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

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

1.2 優點

  • Spring是一個開源的免費的框架(容器)

  • Spring是一個輕量級的、非入侵式的框架

  • 控制反轉(IOC)、面向切面程式設計(AOP)

  • 支援事務的處理,對框架整合的支援!

總結一句話:Spring就是一個輕量級的控制反轉(IOC)和麵向切面程式設計(AOP)的框架!

1.3 Spring組成

  1. 核心容器(Spring Core)

      核心容器提供Spring框架的基本功能。Spring以bean的方式組織和管理Java應用中的各個元件及其關係。Spring使用BeanFactory來產生和管理Bean,它是工廠模式的實現。BeanFactory使用控制反轉(IoC)模式將應用的配置和依賴性規範與實際的應用程式程式碼分開。

  2. 應用上下文(Spring Context)

      Spring上下文是一個配置檔案,向Spring框架提供上下文資訊。Spring上下文包括企業服務,如JNDI、EJB、電子郵件、國際化、校驗和排程功能。

  3. Spring面向切面程式設計(Spring AOP)

      透過配置管理特性,Spring AOP 模組直接將面向方面的程式設計功能整合到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何物件支援 AOP。Spring AOP 模組為基於 Spring 的應用程式中的物件提供了事務管理服務。透過使用 Spring AOP,不用依賴 EJB 元件,就可以將宣告性事務管理整合到應用程式中。

  4. JDBC和DAO模組(Spring DAO)

      JDBC、DAO的抽象層提供了有意義的異常層次結構,可用該結構來管理異常處理,和不同資料庫供應商所丟擲的錯誤資訊。異常層次結構簡化了錯誤處理,並且極大的降低了需要編寫的程式碼數量,比如開啟和關閉連結。

  5. 物件實體對映(Spring ORM)

      Spring框架插入了若干個ORM框架,從而提供了ORM物件的關係工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有這些都遵從Spring的通用事物和DAO異常層次結構。

  6. Web模組(Spring Web)

      Web上下文模組建立在應用程式上下文模組之上,為基於web的應用程式提供了上下文。所以Spring框架支援與Struts整合,web模組還簡化了處理多部分請求以及將請求引數繫結到域物件的工作。

  7. MVC模組(Spring Web MVC)

      MVC框架是一個全功能的構建Web應用程式的MVC實現。透過策略介面,MVC框架變成為高度可配置的。MVC容納了大量檢視技術,其中包括JSP、POI等,模型來有JavaBean來構成,存放於m當中,而檢視是一個街口,負責實現模型,控制器表示邏輯程式碼,由c的事情。Spring框架的功能可以用在任何J2EE伺服器當中,大多數功能也適用於不受管理的環境。Spring的核心要點就是支援不繫結到特定J2EE服務的可重用業務和資料的訪問的物件,毫無疑問這樣的物件可以在不同的J2EE環境,獨立應用程式和測試環境之間重用。

1.4 擴充套件

在Spring的官網有這個介紹:現代化的Java開發,就是基於Spring的開發

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

現在大多數公司都在使用SpringBoot進行快速開發,學習SpringBoot的前提,需要完全掌握Spring及SpringMVC!

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

02 IOC理論推導

2.1 初見IOC

1、UserDao介面

package com.wu.dao;

public interface UserDao {

    void getUser();

}

2、UserDaoImpl實現類(現在我們假設,有三個類似結構但功能不同的實現類均實現了UserDao介面)

package com.wu.dao;

public class UserDaoImpl implements UserDao{

    @Override
    public void getUser() {
        System.out.println("預設獲取使用者資料");
    }

}
package com.wu.dao;

public class UserDaoMysqlImpl implements UserDao{

    public void getUser(){
        System.out.println("獲取Mysql預設資料");
    }
    
}
package com.wu.dao;

public class UserDaoOracleImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("Oracle獲取資料!");
    }
    
}

3、UserService業務介面

package com.wu.Service;

public interface UserService {

    void getUser();
    
}

4、UserServiceImpl業務實現類(思考一下,如果每次我們想讓物件例項化時,如果按照以前的這種寫法,如果每次需要更改時,都需要修改對應的語句,這是十分麻煩的)

package com.wu.Service;

import com.wu.dao.UserDao;
import com.wu.dao.UserDaoImpl;
import com.wu.dao.UserDaoMysqlImpl;
import com.wu.dao.UserDaoOracleImpl;

public class UserServiceImpl implements UserService{

    private UserDao userDao = new UserDaoOracleImpl();
    /*private UserDao userDao = new UserDaoMysqlImpl();*/
    /*private UserDao userDao = new UserDaoImpl();*/

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

​ 這個問題的本質在於,現在程式的執行邏輯控制權在程式設計師手上。由於內部較高的耦合度導致當使用者需求發生變化時,需要大面積的修改一些物件例項的語句。這是很不好的。Spring提出的解決思路就是IOC(Inversion Of Control),即將程式執行的邏輯控制權從程式設計師手上解放出來,同時降低內部的耦合度。

​ 來看下面這個ServiceImpl的寫法,重點關注物件例項化部分的程式碼。可以看到在這裡原來的一句話被拆成2句,並且新增了1個方法。利用set方法實現對物件的動態注入,使得程式內部耦合度降低,並在一定程度上減少了程式設計師對程式執行的程式碼直接控制。

package com.wu.Service;

import com.wu.dao.UserDao;
import com.wu.dao.UserDaoImpl;
import com.wu.dao.UserDaoMysqlImpl;
import com.wu.dao.UserDaoOracleImpl;

public class UserServiceImpl implements UserService{

    /*private UserDao userDao = new UserDaoOracleImpl();*/
    /*private UserDao userDao = new UserDaoMysqlImpl();*/
    /*private UserDao userDao = new UserDaoImpl();*/
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao){this.userDao=userDao;}
    
    @Override
    public void getUser() {
        userDao.getUser();
    }
}
  • 之前,程式是主動建立物件!控制權在程式設計師手上!
  • 使用了set注入後,程式不再具有主動性,而是變成了被動的接受物件!

這就是IOC的原型!

2.2 IOC本質

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

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

03 Hello Spring

3.1 模組搭建

1、新建實體類

package com.wu.pojo;

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、新建配置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">

    <!--使用Spring來建立物件,在Spring這些都稱為Bean
    型別 變數名 = new 型別();
    Hello hello = new Hello();

    id = 變數名
    class = new的物件
    property 相當於給物件中的屬性設定一個值!
    -->
    <bean id="hello" class="com.wu.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>

3、編寫測試方法

import com.wu.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    public static void main(String[] args) {
        /*獲取Spring的上下文物件*/
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        /*我們的物件現在都在Spring中管理,我們要使用直接去取出來就可以*/
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}
  • Hello物件是誰建立的?

    hello物件是由Spring建立的

  • Hello物件的屬性是怎麼設定的?

​ hello的屬性由xml檔案進行

這個過程就是控制反轉:

控制:傳統應用程式的物件是由程式本身控制建立的,使用Sring後,物件是由Spring來建立的。

反轉:程式本身不建立物件,而是變成被動的接收物件。

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

IOC是一種程式設計思想,由主動編寫物件改為被動的接收。

總結一句話:在Spring框架中,物件由Spring來建立,管理和裝配。如果想要實現不同的操作,只需在xml配置檔案中進行修改。

04 IOC建立物件的方式

1、如果沒有特殊說明,bean使用無參構造建立物件

來看以下的程式碼,首先建立一個pojo類:

package com.wu.pojo;

public class User {
    
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+name);
    }
    public User(){
        System.out.println("User的無參構造");
    }
}

可以看到我們引入了一個無參構造(當然也可以去掉)。接著再配置一個beans,並在test中編寫一個測試類,執行結果如下

<?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="user" class="com.wu.pojo.User">
        <property name="name" value="某某"/>
    </bean>
</beans>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)context.getBean("user");
        user.show();
    }
}

那麼,如果我們引入的不是無參構造而是有參構造,會發生什麼?結果如下:

可以看到,在沒有進行特殊操作時,class類在建立物件時呼叫的是物件的無參構造類。但如果一定要用到有參構造器,那該如何操作呢?

2、假設我們要使用有參構造建立物件,方法有以下三種

  • 利用下標進行賦值

       <bean id="user" class="com.wu.pojo.User">
            <constructor-arg index="0" value="某某"/>
        </bean>
    
    public User(String name){
        System.out.println(name+"的無參構造");
        this.name=name;
    }
    
  • 透過型別建立(不推薦使用)

        <bean id="user3" class="com.wu.pojo.User">
            <constructor-arg type="java.lang.String" value="wutong"/>
        </bean>
    

    這種方式的弊端在於,如果實體類中有2個型別一樣的變數,如姓名和住址都是String,這個時候就容易出錯。

  • 利用引數名賦值建立(推薦)

        <!--
           第三種,利用變數名賦值來進行建立(推薦)
        -->
        <bean id="user3" class="com.wu.pojo.User">
            <constructor-arg name="name" value="wutong"/>
        </bean>
    

    注:這裡我提出一個問題,那就是這個user物件究竟是什麼時候建立的。狂神認為物件在:

  User user = (User)context.getBean("user1");

​ 已經建立了。但我試著做了如下操作發現了一些問題,如果我們試著不修改原有的bean,而是增加一個bean物件然後執行會發生什麼?

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

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

    <bean id="user1" class="com.wu.pojo.User">
        <constructor-arg index="0" value="某某"/>
    </bean>
</beans>
package com.wu.pojo;
/*物件去掉無參構造器*/
public class User {

    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+name);
    }
/*    public User(){
        System.out.println("User的無參構造");
    }*/
    public User(String name){
        System.out.println(name+"的無參構造");
        this.name=name;
    }
}

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)context.getBean("user1");
        user.show();
    }
}

​ 同樣的錯誤,和前面探討解決有參構造時的錯誤一樣,spring報錯沒有預設(無參)構造體。如果是第二句報錯就很沒邏輯,新建一個User物件是不會報這種錯誤的,然後拿到bean時我甚至都不是拿的那個無參bean而是有參bean,從邏輯上來講有參bean的建立是沒有問題的,此時回到bean.xml檔案中就會發現IDEA提示在那個無參的bean中class有問題。

​ 因此我認為,bean.xml中的物件應當是在執行完:

 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

​ 之後就已經建立了。而所謂的getBean方法正如其表面含義,是拿到bean.xml檔案中一大堆bean物件中的其中一個而已。只要bean.xml中有一個bean的寫法有毛病,那整個全部物件的建立就都會有問題。

​ 總結:作為一名新手,如果試圖規避掉這樣的錯誤,那最好的辦法就是在物件Pojo類中同時新建有參無參兩種構造體。但這種做法無疑將會掩蓋掉可能存在的程式碼業務上的一些問題。如何取捨,需視具體情況而定。

05 Spring配置

5.1、別名

<!--別名,如果新增了別名,也可以使用別名獲取到這個物件-->
<alias name="user" alias="newuser"/>

5.2、Bean配置

<!--
id:bean的唯一識別符號,也就是相當於我們學習過的變數名
class:bean所對應的全限定名(包名+類名)
name:別名,而且name可以同時取多個別名
scope:作用域
···
-->
    <bean id="user2" class="com.wu.pojo.User" name="userB user3,u2;u3">
        <property name="name" value="李某某"/>
    </bean>

5.3、import

這個import,一般用於團隊開發使用,它可以將多個配置檔案,匯入合併成一個。

假設專案中有多人進行開發每個人都負責不同類的開發,不同的類需要註冊在不同的bean中,因此可以利用import在一個xml中將所有人的配置檔案都匯入合併到一個總的。

  • applicationContext.xml

        <import resource="beans.xml"/>
        <import resource="beans2.xml"/>
        <import resource="beans3.xml"/>
    

    需要使用的時候直接用總的就行。

06 依賴注入

6.1 構造器注入

前面講過了

6.2 Set方式注入(重點)

  • 依賴注入:Set注入!
    • 依賴:bean物件的建立依賴於容器
    • 注入:bean物件中的所有屬性,由容器來注入!

【環境搭建】

  1. 複雜型別

    package com.wu.pojo;
    
    public class Address {
    
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    
  2. 真實測試物件

    package com.wu.pojo;
    
    import java.util.*;
    
    public class Student {
    
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobby;
        private Map<String,String> marks;
        private Set<String> games;
        private Properties info;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public String[] getBooks() {
            return books;
        }
    
        public void setBooks(String[] books) {
            this.books = books;
        }
    
        public List<String> getHobby() {
            return hobby;
        }
    
        public void setHobby(List<String> hobby) {
            this.hobby = hobby;
        }
    
        public Map<String, String> getMarks() {
            return marks;
        }
    
        public void setMarks(Map<String, String> marks) {
            this.marks = marks;
        }
    
        public Set<String> getGames() {
            return games;
        }
    
        public void setGames(Set<String> games) {
            this.games = games;
        }
    
        public Properties getInfo() {
            return info;
        }
    
        public void setInfo(Properties info) {
            this.info = info;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", address=" + address +
                    ", books=" + Arrays.toString(books) +
                    ", hobby=" + hobby +
                    ", marks=" + marks +
                    ", games=" + games +
                    ", info=" + info +
                    '}';
        }
    }
    
    
  3. bean配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
           <bean id="student" class="com.wu.pojo.Student">
               <!--第一種,普通值注入,value-->
               <property name="name" value="wt"/>
           </bean>
    </beans>
    
  4. 測試類

    public class test1 {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student.getName());
        }
    
    }
    

【八種注入方式】(重點)

<?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.wu.pojo.Address">
             <property name="address" value="江西南昌"/>
       </bean>
       <bean id="student" class="com.wu.pojo.Student">
           <!--第一種,普通值注入,value-->
           <property name="name" value="wt"/>
           <!--第二種,bean注入,使用ref引用-->
           <property name="address" ref="address"/>
           <!--第三種,陣列注入,使用array標籤-->
           <property name="books">
               <array>
                   <value>《三國演義》</value>
                   <value>《西遊記》</value>
                   <value>《紅樓夢》</value>
                   <value>《水滸傳》</value>
               </array>
           </property>
           <!--第四種,list注入,使用list標籤-->
           <property name="hobby">
               <list>
                   <value>打遊戲</value>
                   <value>看電影</value>
                   <value>釣魚</value>
               </list>
           </property>
           <!--第五種,map注入,使用map標籤-->
           <property name="marks">
               <map>
                   <entry key="math" value="100"/>
                   <entry key="english" value="90"/>
                   <entry key="chinese" value="85"/>
               </map>
           </property>
           <!--第六種,set注入,使用set標籤-->
           <property name="games">
               <set>
                   <value>星際爭霸</value>
                   <value>戰艦世界</value>
               </set>
           </property>
           <!--第七種,properties注入,使用props標籤-->
           <property name="info">
               <props>
                  <prop key="學號">2211936</prop>
                  <prop key="性別">男</prop>
                  <prop key="school">東北大學</prop>
               </props>
           </property>
           <!--第八種,null注入,使用null標籤-->
           <property name="wife">
               <null/>
           </property>
       </bean>
</beans>

6.3 擴充方式注入

除開上面所述的注入方法外,還有兩種注入方式:P名稱空間注入和C名稱空間注入

6.3.1 P命名注入

P命名注入使用,藉助實體類中的set方法實現。

xml配置:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用p名稱空間需要引入上面的xmlns:p-->
    <!--p:property-->
    <bean id="user" class="com.wu.pojo.User"
          p:name="wt" p:age="24" />

</beans>

測試方法:(這裡使用一種新的方法從容器中取出物件,在getBean的同時規定反射物件的型別)(C名稱空間測試方法類似這個)

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        /*User user = (User)context.getBean("user");*/
        User user = context.getBean("user", User.class);
        System.out.println(user.toString());
    }

6.3.2 C命名注入

C命名注入使用,藉助實體類中的有參構造器實現。(回到上面的問題,要想不報錯,有參無參構造器都要建)

xml配置:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用c名稱空間同樣需要先引入"xmlns:c"-->
    <!--c:construct-args-->
    <!--c名稱空間使用有參構造器進行注入-->
    <!--p名稱空間使用set方法進行注入-->
    <bean id="user2" class="com.wu.pojo.User" c:name="wt" c:age="24"/>

</beans>

測試方法:

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        /*User user = (User)context.getBean("user2");*/
        User user = context.getBean("user2", User.class);
        System.out.println(user.toString());
    }

注:P命名和C命名不能直接使用,需要匯入xml約束

xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"

6.4 Bean的作用域

  1. 單例模式(Spring預設機制)

        <!--使用scope約束新生成的物件,其中singleton是預設設定-->
        <!--這樣取出來的物件都是同一個-->
        <bean id="user2" class="com.wu.pojo.User" c:name="wt" c:age="25" scope="singleton"/>
    
     @Test
        public void test1(){
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            User user2 = context.getBean("user2", User.class);
            User user = context.getBean("user2",User.class);
            System.out.println(user2==user);
            System.out.println(user.hashCode());
            System.out.println(user2.hashCode());
        }
    

    可以看到在單例模式下取出來的物件都是同一個。

  2. 原型模式:每次從容器中get的時候都會產生一個新物件!

    <bean id="user3" class="com.wu.pojo.User" scope="prototype">
            <property name="name" value="wt"/>
            <property name="age" value="25"/>
    </bean>
    
     @Test
        public void test2(){
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            User user2 = context.getBean("user3", User.class);
            User user = context.getBean("user3",User.class);
            System.out.println(user2==user);
            System.out.println(user.hashCode());
            System.out.println(user2.hashCode());
        }
    

  1. 其餘的request、session、application,這些個只能在web開發中使用到!

07 Bean的自動裝配

  • 自動裝配是Spring滿足bean依賴的一種方式!
  • Spring會在上下文中自動尋找,並自動給bean裝配屬性!

在Spring中有三種裝配的方式

  1. 在xml中顯示的配置
  2. 在java中顯示的配置
  3. 隱式的自動裝配bean【重要】

7.1 測試環境

環境搭建:一個人有兩個寵物(建立三個實體類:Cat,Dog,People)

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

7.2 ByName自動裝配

    <!--
     byName:會自動在容器上下文中查詢,和自己物件set方法後面的值對應的beanid
    -->
    <bean id="people1" class="com.wu.pojo.People" autowire="byName">
        <property name="name" value="wut"/>
    </bean>

7.3 ByType自動裝配

    <!--
     byType:會自動在容器上下文中查詢,和自己物件set方法後面的物件相同型別的bean
    -->
    <bean id="people2" class="com.wu.pojo.People" autowire="byType">
        <property name="name" value="wut"/>
    </bean>

注:需要注意的是使用byType實現自動裝配時,可以省去bean中的id,但要求同型別的物件只能由一個,適合於複用較多的重複物件。

使用byName實現自動裝配時,由於針對的是beanid,因此不存在同型別物件的數量限制,但要求beanid必須存在。

7.4 使用註解實現裝配(重點)

要使用註解須知:

1、匯入約束,context

2、 配置註解的支援:Core Technologies (spring.io)

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

@Autowired

直接在屬性上使用即可,也可以在set方式上使用,但需要確保相關命名一致。

使用Autowired就可以不用編寫Set方法,前提是這個自動裝配的屬性在IOC(Spring)容器要存在,且符合名字byName!

科普:

@Nullable 欄位標記了這個註解,說明這個欄位可以為null
//如果顯式的定義了Autowired的required屬性為false,說明這個物件可以為null,否則不允許為空
@Autowired(required = false) 

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

測試程式碼:

public class People {

    @Autowired
    private Cat cat;

    @Autowired
    private Dog dog;

    @Autowired(required = false)
    private String name;
    

@Qualifier(value="xxx")

如果@Autowired自動裝配的環境比較複雜,自動裝配無法透過一個註解【@Autowired】完成的時候,我們可以使用@Qualifier(value="xxx")去配置@Autowired的使用,指定一個唯一的bean物件注入!

    @Autowired
    @Qualifier(value = "cat222")
    private Cat cat;

    @Autowired
    @Qualifier(value = "dog111")
    private Dog dog;

    @Autowired(required = false)
    private String name;

@Resource(name="xxx")

  @Resource
    private Cat cat;

    @Resource
    private Dog dog;

小結:

@Resource和@Autowired的區別:

  • 都是用來自動裝配的,都可以放在屬性欄位上
  • @Autowired透過byname的方式實現
  • @Resource預設透過byname的方式實現,如果找不到名字,則透過bytype來實現!如果兩個都找不到的情況下,就會報錯(無唯一物件)【常用】
  • 執行順序不同:
    • @Autowired找byname
    • @Resource先找byname,如果找不到再找bytype

08 使用註解開發

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

8.1 bean

8.2 屬性如何注入

@Component
public class User {

    /*public String name = "吳同";*/

    /*相當於
    <property name="name" value="吳同"/>
    * */
    @Value("張三")
    private String name;

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

    public String getname(){
        return this.name;
    }
}

8.3 衍生的註解

@Component 有幾個衍生註解,在web開發中,會按照mvc三層架構分層!

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

這四個註解功能都是一樣的,都是代表將某個類註冊到Spring中,裝配bean

8.4 自動裝配配置

@Autowired :自動裝配透過型別,名字
  如果Autowired不能唯一自動裝配上屬性,則需要透過@Qualifier(value="xxx")
@Nullable    欄位標記了這個註解,說明這個欄位可以為null
@Resource : 自動裝配透過名字,型別。

8.5 作用域

@Scope("prototype")

比如:singleton 單例模式等

8.6 小結

xml 與 註解:

  • xml更加萬能,適合與任何場合!維護簡單方便
  • 註解 不是自己的類不能使用,維護相對複雜

xml與註解最佳實踐:

  • xml用來管理bean
  • 註解只負責完成屬性的注入
  • 在使用過程中,只需要注意一個問題:必須讓註解生效,就需要開啟註解的支援
    <!--指定需要掃描的包,這個包下的註解就會生效-->
    <context:component-scan base-package="com.wu"/>
    <context:annotation-config/>

09 使用Java的方式配置Spring

現在要完全不適用Spring的xml配置了,全權交給Java來做

JavaConfig是Spring的一個子專案,在Spring4之後,它成了一個核心功能!

1、pojo類

//這裡這個註解的意思就是說明這個類被spring接管了,註冊到了容器中
@Component
public class User {

    private String name;

    public String getName() {
        return name;
    }

    //注入值
    @Value("張三")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

2、新建config

//這個也會被spring容器託管,註冊到容器中,因為他本來就是一個@Component
// @Configuration代表這是一個配置類,類似於之前看的beans.xml
@Configuration
@ComponentScan("com.wu.pojo")
@Import(WuConfig2.class)
public class WuConfig {

    //註冊一個Bean,就相當於我們之前寫的一個bean標籤
    //這個方法的名字,就相當於bean標籤中的id屬性
    //這個方法的返回值,就相當於bean標籤中的class屬性
   @Bean
    public User getUser(){
         return new User();//就是返回要注入到bean的物件!
   }

}

3、測試檔案

public class test {

    public static void main(String[] args) {
        //若完全使用了配置類方式去做,我們就只能透過AnnotationConfig 上下文來獲取容器,透過配置類的class物件載入
        ApplicationContext context = new AnnotationConfigApplicationContext(WuConfig.class);
        User getuser = (User)context.getBean("user");
        System.out.println(getuser.getName());
    }

}

這種純Java的配置方式,在springBoot中隨處可見!

10 代理模式

為什麼要學習代理模式?因為這就是springAOP的底層實現!【SpringAOP和SpringMVC】

代理模式的分類:

  • 靜態代理
  • 動態代理

10.1 靜態代理

角色分析:

  • 抽象角色:一般會使用介面或者抽象類來解決
  • 真實角色:被代理的角色
  • 代理角色:代理真實角色,代理真實角色後,我們一般會做一些附屬操作
  • 客戶:訪問代理物件的人!

程式碼步驟:

  1. 介面

    /*
    * 租房的介面,抽象
    * */
    public interface Rent {
    
        public void rent();
    
    }
    
  2. 真實角色

    /*
    * 房東 真實角色
    * */
    public class Host implements Rent{
    
        @Override
        public void rent() {
            System.out.println("房東要出租房子!");
        }
    }
    
  3. 代理角色

    public class Proxy implements Rent{
    
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            host.rent();
            signContract();
            fare();
        }
    
        /*
        * 看房
        * */
        public void seeHouse(){
            System.out.println("中介帶你看房");
        }
    
        /*籤合同*/
        public void signContract(){
            System.out.println("籤租賃合同");
        }
    
        /*收中介費*/
        public void fare(){
            System.out.println("中介收取手續費");
        }
    
    
    }
    
  4. 客戶端訪問代理角色

        public static void main(String[] args) {
            /*房東要租房子*/
            Host host = new Host();
            /*代理,中介幫房東出租房子,但是同時代理角色會有一些自身的附屬操作*/
            Proxy proxy = new Proxy(host);
            /*不用面對房東,直接找中介就行*/
            proxy.rent();
    
        }
    

代理模式的好處:

  • 可以使真實角色的操作更加純粹!不用去關注一些公共的業務
  • 公共業務就交給代理角色!實現了業務的分工。同時降低了程式碼的耦合性。
  • 公共業務發生擴充套件的時候,方便集中管理!

缺點:

  • 一個真實角色就會產生一個代理角色;程式碼量會翻倍~開發效率會降低。

10.2 加深靜態代理的理解

程式碼:

  • 虛擬物件:

    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("新建了一個使用者");
        }
    
        @Override
        public void delete() {
            System.out.println("刪除了一個使用者");
        }
    
        @Override
        public void update() {
            System.out.println("編輯了一個使用者");
        }
    
        @Override
        public void query() {
            System.out.println("查詢了一個使用者");
        }
    }
    

    注意,如果此時有要求需要在進行上述操作時增加日誌,該如何運作?

    第一,可以考慮在原真實服務類中逐一新增日誌方法,但這不好!如果此類操作較多將導致程式碼工作量急劇增加。有人考慮可以將日誌方法單獨抽出來。但如果系統內部功能耦合比較嚴重的話,可能在意想不到的地方突然呼叫這個方法。簡而言之就是不建議破壞系統的原有業務程式碼。

    第二,此時可以考慮使用代理思路,即原系統業務程式碼不變更,但新增一個代理類,在代理類中新建日誌方法。

  • 代理物件:

    public class UserServiceProxy implements UserService{
    
        private UserService userService;
    
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
           operationLog("add");
           userService.add();
        }
    
        @Override
        public void delete() {
            operationLog("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            operationLog("update");
            userService.update();
        }
    
        @Override
        public void query() {
            operationLog("query");
            userService.query();
        }
    
        //日誌方法
        private void operationLog(String msg){
            System.out.println("[Debug]使用了"+msg+"方法");
        }
    }
    

    ​ 為了簡化程式碼量,我們可以使得代理類的公開方法與原來的方法保持一致。這樣在測試類進行修改時,只需要增加一行方法注入,將原來的方法呼叫改為代理類方法就可以實現新的方法,在不影響原業務邏輯的情況下加上自己的業務需求。

10.3 動態代理

動態代理的底層都是反射

  • 動態代理和靜態代理角色一樣
  • 動態代理的類是動態生成的,不是我們直接寫好的!
  • 動態代理分為兩大類:基於介面的動態代理,基於類的動態代理
    • 基於介面 --- JDK動態代理【本次學習使用】【需要注意!】
    • 基於類:cglib
    • java位元組碼:javassist

需要了解兩個類:Proxy,代理 InvocationHandler:呼叫處理程式

動態代理的好處:

  • 可以使真實角色的操作更加純粹!不用去關注一些公共的業務
  • 公共業務就交給代理角色!實現了業務的分工。同時降低了程式碼的耦合性。
  • 公共業務發生擴充套件的時候,方便集中管理!
  • 一個動態代理類代理的是一個介面,一般就是一類業務
  • 一個動態代理類可以代理多個類,只要是實現了同一個介面

11 AOP

11.1 什麼是AOP

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

11.2 AOP在spring中的作用

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

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

SpringAOP中,透過Advice定義橫切邏輯,Spring中支援5種型別的Adcive:

通知型別 連線點 實現介面
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
後置通知 方法後 org.springframework.aop.AfterReturningAdvice
環繞通知 方法前後 org.aopalliance.
異常丟擲通知 方法丟擲異常 org.springframework.aop.ThrowsAdvice
引介通知 類中新增方法屬性 org.springframework.aop.IntroductionInterceptor

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

11.3 使用spring實現AOP

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

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

方式一:使用Spring的API介面呼叫

首先仍然新建1個service服務,包括1個介面類和1個實現類

package com.wu.service;

public interface UserService {

    public void add();

    public void delete();

    public void update();

    public void select();

}

package com.wu.service;

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 select() {
        System.out.println("查詢了一個使用者");
    }
}

其次新建兩個日誌類:BeforeLog和AfterLog,並分別實現MethodBeforeAdvice和AfterReturningAdvice介面

package com.wu.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {

    //method:要執行的目標物件的方法
    //objects:引數(看原始碼可知這個一般叫args)
    //o:目標物件(同上,這裡一般是target)
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被執行了");
    }
}

package com.wu.log;

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {


    //returnValue:返回值

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行了"+method.getName()+"方法,返回結果為"+returnValue);
    }
}

最後配置xml檔案,實現bean註冊以及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: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-->
    <!--此處需注意,註冊bean應當使用實體物件,而在前面的呼叫中,應當使用spring代理實體物件的介面類-->
    <bean id="userService" class="com.wu.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.wu.log.BeforeLog"/>
    <bean id="afterLog" class="com.wu.log.AfterLog"/>

    <!--方法一:使用原生Spring API介面-->
    <!--配置aop-->
    <aop:config>
        <!--切入點:expression:表示式,execution(要執行的位置!* * * * * ) 星星按順序對應:修飾詞(public)、返回值、類名、方法名、引數-->
        <aop:pointcut id="pointcut" expression="execution(* com.wu.service.UserServiceImpl.*(..))"/>

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



</beans>

測試方法需要注意的是,在xml檔案中我們註冊的是實體物件,但在使用時需要給spring該實體類的代理介面,正如第10章在講代理模式的時候說的,動態代理基於介面的方法!

import com.wu.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //此處需注意
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }

}

方法二:使用自定義方式實現AOP【主要是切面定義】

此時新建的方法不需要實現MethodBeforeAdvice和AfterReturningAdvice等介面,只需要在配置檔案中用spring的aop配置就行。

本質還是代理中的InvocationHandler和Proxy

<?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-->
    <bean id="diy" class="com.wu.diy.Log"/>
    <bean id="userServiceImpl" class="com.wu.service.UserServiceImpl"/>

    <!--自定義切面-->
    <aop:config>
        <!--自定義切入點,ref是需要引入切面的類-->
        <aop:aspect ref="diy">
            <!--切入點-->
            <aop:pointcut id="point" expression="execution(* com.wu.service.UserServiceImpl.*(..))"/>
            <!--通知,如何插入方法中-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

</beans>

方式三:使用註解實現

package com.wu.diy;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.wu.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=====方法執行前=====");
    }

    @After("execution(* com.wu.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====方法執行後=====");
    }
    
    //在環繞增強中,我們可以給定一個引數,代表我們要獲取處理切入的點
    @Around("execution(* com.wu.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("*****環繞前*****");
        Signature signature = jp.getSignature();
        System.out.println(signature);
        Object proceed = jp.proceed();//執行方法
        System.out.println("*****環繞後*****");
        System.out.println(proceed);
    }

}

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: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="userServiceImpl" class="com.wu.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.wu.diy.AnnotationPointCut"/>

    <!--開啟註解支援  JDK預設 false//cglib true-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>

執行結果,觀察幾條語句執行執行先後順序!

12 整合Mybatis

步驟:

  1. 匯入相關jar包
    • junit(測試)
    • mybatis
    • mysql資料庫
    • spring相關的
    • aop織入包
    • mybatis-spring【new】
  2. 編寫配置檔案
  3. 測試

12.1 回憶mybatis

  1. 編寫實體類
  2. 編寫核心配置檔案
  3. 編寫介面
  4. 編寫mapper.xml
  5. 測試

12.2 Mabits-Spring

1.編寫資料來源配置

2.sqlSessionFactory

3.sqlSessionTemplate

4.需要給介面加實現類【】

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

13 宣告式事務

13.1 回顧事務

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

事務ACID原則:

  • 原子性
  • 一致性
  • 隔離性
    • 多個業務可能操作同一個資源,防止資料損壞
  • 永續性
    • 事務一旦提交,無論系統發生什麼問題,結果都不會再被影響,被持久化寫到儲存器中!

13.2 spring中的事務管理

  • 宣告式事務:AOP
  • 程式設計式事務:需要在程式碼中,進行事務的管理

思考:為什麼事務?

  • 如果不配置事務,可能存在資料提交不一致的情況;
  • 如果我們不在spring中去配置事務,就需要在程式碼中手動配置事務!(使用try-catch捕獲異常然後回滾)
  • 事務在專案開發中十分重要,涉及到資料的一致性和完整性問題,不容馬虎!

14 總結