Spring IOC本質、創建Spring程序、自動裝配@Autowire+@Qualifier、使用註解開發詳解

Spring

1、簡介

Spring官網:https://spring.io/

Spring核心:IOC和AOP

Spring的目的:解決企業開發的複雜性,使得JavaEE開發更加容易

Spring框架是一個開放源代碼的 J2EE 應用程序框架,由 Rod Johnson發起,是針對bean的生命週期進行管理的輕量級的**控制反轉(IOC)面向切面(AOP)**的容器框架。

從2002年第一次出現了Spring的一些核心思想,到目前,經過這麼多年發展,Spring已經沒有那麼簡單了,現在學Spring,是爲了更好學習SpringMVC和SpringBoot

Spring可以做的事

在這裏插入圖片描述

2、IOC

1、通過簡單程序理解控制反轉

控制反轉就是向外部提供調用接口,將控制權交給調用者,程序成了被動的接受對象,程序員不再管理對象的創建,降低程序的耦合性,讓程序高效健壯

  1. 創建dao層接口及實現類

    //dao層接口
    public interface UserMapper {
        public void getUser();
    }
    
    //實現類
    public class UserMapperImpl implements UserMapper {
        public void getUser() {
            System.out.println("MySQL獲取用戶數據");
        }
    }
    
  2. 創建service層接口及實現類,調用dao層

    //service層接口
    public interface UserService {
        public void getUser();
    }
    
    //實現類
    public class UserServiceImpl implements UserService {
        private UserMapper mapper;
        //調用dao層接口的實現類得到user的信息
        public void getUser() {
            mapper = new UserMapperImpl();
            mapper.getUser();
        }
    }
    
  3. 當dao層接口的實現類只有一個的時候,這樣寫沒有問題

    但是,如果dao層接口的實現類有多個呢?

    //新增dao層接口實現類
    public class UserMapperOracleImpl implements UserMapper {
        public void getUser() {
            System.out.println("Oracle獲取用戶數據");
        }
    }
    
    //service層接口實現類
    public class UserServiceImpl implements UserService {
        //定義Mapper接口對象
        private UserMapper mapper;
    
        public void getUser() {
            //具體是Mapper接口的哪個實現類就要修改代碼了
            mapper = new UserMapperImpl();//使用MySQL數據庫
            mapper = new UserMapperOracleImpl();//使用Oracle數據庫
            mapper.getUser();
        }
    }
    
  4. 可以看到,當有多個dao層接口的實現類時,我們要修改使用的dao層實現類,就必須要修改service層的代碼,這對於大型的程序來說簡直就是災難,相當於要把底層的代碼大換血一遍。是不可能的,那麼我們來看這種方式

    //service層實現類
    public class UserServiceImpl implements UserService {
        private UserMapper mapper;
    
        //使用set方法,用戶想使用哪個數據庫,就使用set方法調用哪個數據庫
        public void setMapper(UserMapper mapper) {
            this.mapper = mapper;
        }
        public void getUser() {
            mapper.getUser();
        }
    }
    
    //測試類
    public class UserMapperTest {
        @Test
        public void testGetUser() {
            //得到service對象
            UserService service = new UserServiceImpl();
            //通過setMapper方法設置對應的Mapper接口的實現類,將具體實現類的選擇交給調用者去選擇
            //解耦,這樣子就不用再改動底層的代碼
            service.setMapper(new UserMapperImpl());
            //調用dao層接口實現類的方法,得到數據庫中的用戶數據
            service.getUser();
        }
    }
    
  5. 在上面的代碼中,我們將使用什麼數據庫這個問題拋給了調用者去考慮,這樣子不管調用者傳遞的是哪個實現類,我們service層的代碼都不用改變,降低了程序的耦合度

  6. 其實在學習了Spring IOC之後,我們連new dao層接口的實現類都不用了,所有的對象都是使用IOC容器自動獲取的。完全解耦,解決此類問題。

2、IOC本質

IOC(控制反轉)是一種設計思想,當應用了IOC,IOC容器在對象初始化時,不等對象請求就主動將依賴注給它,而不是這個對象自己創建依賴對象,將對象的創建任務轉移給第三方,降低程序的耦合度。

當採用XML方式配置Bean的時候,對象的定義(在類中定義屬性和方法)和創建信息(在<bean>標籤中)是分離的,而採用註解的方式可以把兩者合爲一體,對象的信息直接以註解的形式定義在實現類中,從而達到零配置

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

3、第一個Spring程序

  1. 導入依賴包

    在Maven項目中導入依賴的時候,Maven會自動幫我們導入所有要用到的依賴

    <!--spring框架依賴-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    
    <!-- lombok依賴,簡化類中方法 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
    
  2. 創建Spring的配置文件

    建議命名爲:applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--
    	這裏一個bean標籤對應一個類的對象
    	id對應對象的對象名,是唯一標識對象的
    	class表示對象的類型
    	property標籤中給對象的字段進行賦值
    	-->
        <bean id="hello" class="org.westos.pojo.Hello">
            <property name="name" value="spring"/>
        </bean>
    </beans>
    
  3. 實體類

    package org.westos.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    //簡化類開發的,使用lombok就不用寫get/set/構造器/tostring等方法了
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Hello {
        private String name;
            
        public void show (){
            System.out.println("hello,"+name);
        }
    }
    
  4. 在applicationContext.xml配置文檔中註冊bean

    <!--
     這裏一個bean標籤對應一個類的對象
     id對應對象的對象名,是唯一標識對象的
     class表示對象的類型
     property標籤中給對象的字段進行賦值
     -->
    <bean id="hello" class="org.westos.pojo.Hello">
        <property name="name" value="spring"/>
    </bean>
    
  5. 測試類

    package org.westos.pojo;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.westos.pojo.Hello;
    
    public class HelloTest {
        @Test
        public void testShow() {
            //根據配置文檔獲得連接對象
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            //根據bean標籤中的id獲得對象
            Hello hello = (Hello) context.getBean("hello");
            hello.show();
        }
    }
    
    //輸出結果
    hello,spring
    

問題

  1. hello對象誰創建的

    spring

  2. hello對象誰賦值的

    spring

其實這就是控制反轉,由Spring容器進行對象的創建與注入,我們並沒有在程序中使用new關鍵字創建任何一個對象,對象都是從IOC容器中獲取的。

反轉:程序本身並不創建對象,而只是對象的接收者

控制反轉是一種程序設計思想,由主動的創建變成被動的接收 ,現在我們徹底不用在程序中去改動了 , 要實現不同的操作 , 只需要在xml配置文件中進行修改對象由Spring 來創建、管理 、 裝配

4、IOC創建對象的方式

  1. IOC容器對象創建的時間

    在加載容器的時候(獲得context連接對象時),所有<bean>標籤中的對象就被創建在容器中了,使用getBean()方法只是將對象取出來

  2. 對象創建的兩種方式

    1. 類無參構造創建(默認)

      先使用類的無參構造創建,後使用setter方法初始化

      使用<property>標籤(P命名注入)

    2. 類有參構造創建

      使用類的有參構造創建並初始化

      使用<constructor-arg>標籤(C命名注入),有參構造中的參數由三種寫法

      1. 使用參數名稱(主要使用)
      2. 使用參數的類型
      3. 使用參數下標順序
    3. 如果屬性是引用類型,要使用ref屬性引用別的對象

      如果是基本類型,直接使用value賦值

練習

  1. 創建實體類

    package org.westos.pojo;
    
    public class User {
        private String name;
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println("有參構造執行了");
        }
    
        public User() {
            System.out.println("無參構造執行了");
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
            System.out.println("set方法執行了");
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void show() {
            System.out.println("name:" + name + ",age:" + age);
        }
    }
    
  2. 創建bean

    <bean id="user" class="org.westos.pojo.User">
        <!--使用默認的無參構造進行創建對象-->
        <property name="name" value="張三"/>
        <property name="age" value="23"/>
    
        <!--使用有參構造的參數名進行創建
            <constructor-arg name="name" value="李四"/>
            <constructor-arg name="age" value="24"/>-->
    
        <!--使用有參構造的類型創建
            <constructor-arg type="java.lang.String" value="王五"/>
            <constructor-arg type="int" value="25"/>-->
    
        <!--使用有參構造的索引創建
            <constructor-arg index="0" value="趙六"/>
            <constructor-arg index="1" value="26"/>-->
    </bean>
    
  3. 測試

    package org.westos.pojo;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class UserTest {
    
        @Test
        public void testShow() {
            ApplicationContext context = new ClassPathXmlApplicationContext("text.xml");
            System.out.println("=================");
            User user = (User) context.getBean("user");
            user.show();
        }
    }
    

5、spring配置

  1. <alias>

    可以給bean設置別名,別名可以設置多個,中間使用空格隔開

<alias name="user" alias="user2 user3"/>
  1. <bean>

    用來表示一個對象,一個bean標籤就是一個對象

    id是bean的唯一標誌,只能有一個值

    name 可以有多個值,中間使用空格隔開

<bean id="user"  name="user3 user4" class="org.westos.pojo.User">
    <property name="name" value="張三"/>
    <property name="age" value="23"/>
</bean>
  1. <import>

    用來導入其他xml配置文件,從其他文件中讀取bean標籤

    在多人協作中,每個人負責一個模塊,最後使用import標籤導入進去

<import resource="text.xml"/>

6、屬性注入方式

  1. 創建實體類
package org.westos.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

//使用lombok簡化類的創建
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {

    private String name;
    private Address address;
    private String[] books;
    private List<String> list;
    private Map<String, String> map;
    private Set<String> set;
    private String wife;// null
    private Properties info;
}
  1. 創建配置文件,使用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="ZSaddress" class="org.westos.pojo.Address" scope="prototype">
            <property name="address" value="陝西西安"/>
        </bean>
    
        <bean id="student" class="org.westos.pojo.Student">
            <!--基本類型直接使用value賦值-->
            <property name="name" value="張三"/>
            <!--引用類型使用ref引用-->
            <property name="address" ref="ZSaddress"/>
            <!--數組使用<array>標籤賦值-->
            <property name="books">
                <array>
                    <value>紅樓夢</value>
                    <value>西遊記</value>
                    <value>水滸傳</value>
                    <value>三國演義</value>
                </array>
            </property>
            <!--List集合使用<list>標籤賦值-->
            <property name="list">
                <list>
                    <value>list1</value>
                    <value>list2</value>
                    <value>list3</value>
                </list>
            </property>
            <!--map集合是鍵值對的形式,使用<map>標籤賦值-->
            <property name="map">
                <map>
                    <entry key="k1" value="v1"/>
                    <entry key="k2" value="v2"/>
                </map>
            </property>
            <!--set集合使用<set>標籤賦值-->
            <property name="set">
                <set>
                    <value>set1</value>
                    <value>set2</value>
                </set>
            </property>
            <!--null直接使用null標籤-->
            <property name="wife">
                <null/>
            </property>
            <!--properties配置文件使用<props>標籤賦值-->
            <property name="info">
                <props>
                    <prop key="id">001</prop>
                    <prop key="name">張三</prop>
                </props>
            </property>
        </bean>
        
    </beans>
    
  2. 測試

    package org.westos.pojo;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class StudentTest {
        @Test
        public void test() {
            ApplicationContext context = new ClassPathXmlApplicationContext("student.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student);
        }
    }
    
    //運行結果
    //Student(name=張三, address=org.westos.pojo.Address@eb21112, books=[紅樓夢, 西遊記, 水滸傳, 三國演義], list=[list1, list2, list3], map={k1=v1, k2=v2}, set=[set1, set2], wife=null, info={name=張三, id=001})
    

7、bean的作用域

在bean標籤中的scope屬性中進行配置

由如下幾個值

  1. prototype

    原型,每次創建新對象

  2. singleton

    單例,默認,只有一個對象

  3. request,在web中使用

  4. response,在web中使用

8、自動裝配

手動裝配:對引用類型的參數使用ref屬性手動分別引用

<bean id="user" class="org.westos.entity.User" >
    <property name="name" value="張三"/>
    <!--引用id==cat的對象-->
    <property name="cat" ref="cat"/>
    <!--引用id==dog的對象-->
    <property name="dog" ref="dog"/>
</bean>

<bean id="cat" class="org.westos.entity.Cat">
    <property name="name" value=""/>
</bean>
<bean id="dog" class="org.westos.entity.Dog">
    <property name="name" value=""/>
</bean>

自動裝配:自動化賦值

分爲根據屬性名和屬性類型兩種方式

1、autowire在xml中實現自動裝配

  1. byName

    通過屬性名與bean的id進行自動匹配

    本質上是setter方法,會自動匹配與屬性名相同的id的bean,id與屬性名不一致,就會賦值失敗

  2. byType

    通過類型自動匹配,如果同一個類型有多個bean(對象),程序就不知道使用哪個,就會報錯

        <bean id="user" class="org.westos.entity.User" autowire="byName">
            <property name="name" value="張三"/>
            <!--使用自動注入以後,就不用寫這兩行代碼了。會自動引用
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>-->
        </bean>
    
        <!--當使用屬性名進行自動注入的時候,如果id與屬性名不一致,就會賦值失敗-->
        <bean id="cat1" class="org.westos.entity.Cat">
            <property name="name" value=""/>
        </bean>
        <bean id="dog" class="org.westos.entity.Dog">
            <property name="name" value=""/>
        </bean>
        <!--當使用類型進行自動注入的時候,如果同一類型有bean標籤,那麼就會報錯
        <bean id="dog1" class="org.westos.entity.Dog">
            <property name="name" value="狗"/>
        </bean>-->
    

2、使用註解實現自動裝配

一般在實際的開發中,不會使用xml進行自動配置,而是使用Spring中的註解(@Autowire+@Qualifier)

步驟

  1. 增加context命名空間

  2. 開啓註解支持

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--開啓註解支持-->
        <context:annotation-config/>
    
    </beans>
    
  3. 使用註解自動裝配(spring的)

    1. @Autowired

      required=false,表示允許對象是否爲空,默認爲true

      默認是使用byType的形式查找,找不到或同類型有多個bean就會報錯

    2. @Qualifier(“bean標籤的id”)

      按照指定的標籤id進行匹配查找

    3. @Autowired+@Qualifier(“bean標籤的id”)

      這兩個註解是Spring的,一般組合使用

  4. Resource註解(java的)

    1. 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則拋出異常
      2. 如果只指定了name,則查找匹配的id進行裝配,找不到則拋出異常
      3. 如果指定了type,則查找到類型匹配的唯一bean進行裝配,找不到或者找到多個,都會拋出異常
      4. 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退爲一個原始類型進行匹配,如果匹配則自動裝配;
      5. 一般用在字段上比較多,不和前兩個混合使用

9、使用註解開發

  1. 導入aop的依賴

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    
  2. 配置context約束,並且開啓註解支持

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--開啓註解支持-->
        <context:annotation-config/>
    
    </beans>
    
  3. @Component註解

    在類上添加,該類就相當於一個<bean>標籤,id 就相當於bean標籤裏的 id,class就是該類

  4. 添加掃描包

    <!--添加以後,所有org.westos包下,包括子包下的所有類都會被掃描-->
    <context:component-scan base-package="org.westos" />
    
  5. @Value註解

    都是給屬性進行賦值

    1. 在類屬性上使用
    2. 在setter方法上使用
  6. @Component等價的註解

    與@Component註解的作用完全一致,區別在於約定俗成的使用地點

    1. @Repository,在dao層
    2. @Service,在service層
    3. @Controller,在controller層

註解和xml的最佳實踐

  1. xml

    通過xml管理bean

  2. 註解

    通過註解完成屬性的注入

10、spring新特性

最新SpringBoot推薦摒棄使用xml配置文件,而是完全使用註解進行配置

  1. @Configuration註解

    被修飾的類等價於一個xml文件,爲配置類

  2. @Bean註解

    被修飾的方法等同於一個<bean>標籤,方法名爲id,class爲具體返回對象的類型

  3. @Import(***.class)

    類似於<import>標籤,用來導入其他的配置類

但是目前傳統的SSM框架還是使用xml配置文檔居多

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章