天天看點

關于Spring架構的幾個重點,你了解多少?

前言

本文選取了 Spring 架構中比較重點的幾個來介紹。 Spring 架構的誕生是為了使開發更加高效簡潔,同時減少耦合程度,主要還是思想上的一個轉變,想要深入了解 Spring 架構的,可以查閱 Spring 官方文檔,值得一看。

初識Spring

簡介

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

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

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

官網:spring.io/

官方下載下傳位址:repo.spring.io/libs-releas…

GitHub:github.com/spring-proj…

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.3</version>
</dependency>
複制代碼           

組成

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

組成 Spring 架構的每個子產品(或元件)都可以單獨存在,或者與其他一個或多個子產品聯合實作。每個子產品的功能如下:

關于Spring架構的幾個重點,你了解多少?

核心容器(Spring Core)

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

應用上下文(Spring Context)

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

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

  通過配置管理特性,Spring AOP 子產品直接将面向方面的程式設計功能內建到了 Spring 架構中。是以,可以很容易地使 Spring 架構管理的任何對象支援 AOP。Spring AOP 子產品為基于 Spring 的應用程式中的對象提供了事務管理服務。通過使用 Spring AOP,不用依賴 EJB 元件,就可以将聲明性事務管理內建到應用程式中。

JDBC和DAO子產品(Spring DAO)

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

對象實體映射(Spring ORM)

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

Web子產品(Spring Web)

  Web上下文子產品建立在應用程式上下文子產品之上,為基于web的應用程式提供了上下文。是以 Spring 架構支援與 Struts 內建,web 子產品還簡化了處理多部分請求以及将請求參數綁定到域對象的工作。

MVC子產品(Spring Web MVC)

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

建立

建立一個 Maven 項目,在 pom.xml 中進行配置,

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.3</version>
</dependency>
複制代碼           

目錄

關于Spring架構的幾個重點,你了解多少?

先編寫一個 User 實體類,User.java,

package com.idiot.pojo;

public class User{
    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Hello{" +
                "name='" + name + '\'' +
                '}';
    }
}
複制代碼           

再編寫我們的Spring檔案,這裡命名為beans.xml,

配置檔案可在官方文檔中擷取,

關于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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--使用Spring來建立對象,在Spring中這些都稱為Bean-->
    <!--bean就是java對象 , 由Spring建立和管理-->
    <bean id="helloSpring" class="com.idiot.pojo.User">
        <property name="name" value="Spring"/>
    </bean>
</beans>
複制代碼           

最後進行測試,MyTest.java,

import com.idiot.pojo.User;
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中管理,我們要使用的話,直接從裡面取出來
        User helloSpring = (User) context.getBean("helloSpring");
        System.out.println(helloSpring.toString());
    }
}
複制代碼           

運作結果

關于Spring架構的幾個重點,你了解多少?

配置說明

别名

如果添加了别名,我們也可以使用别名擷取這個對象,原名也是可以使用的,在 beans.xml 中進行配置,

<alias name="helloSpring" alias="helloAlias"/>
<bean id="helloSpring" class="com.idiot.pojo.User">
    <property name="name" value="Spring"/>
</bean>
複制代碼           
關于Spring架構的幾個重點,你了解多少?

Bean的配置

bean 就是 java 對象,由 Spring 建立和管理,

  • id:bean 的唯一辨別符,也就相當于對象名,
  • class:bean 對象所對應的全限定名:包名+類型,
  • name:也是别名,而且 name 可以同時取多個别名,可以用逗号,分号,空格隔開,

如果沒有配置 id,name 就是預設辨別符,

如果配置了 id,又配置了 name,那麼 name 是别名,

如果不配置 id 和 name,可以根據 applicationContext.getBean(.class) 擷取對象,

import

import 一般用于團隊開發使用,它可以将多個配置檔案導入合并成為一個,

假設現在項目中有多個人開發,其中三個人負責不同的類的開發,不同的類需要注冊在不同的bean配置檔案中,我們可以利用import将所有人的beans.xml合并成一個總的,即applicationContext.xml,

<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
複制代碼           

使用的時候直接使用總配置applicationContext.xml即可。

思考

  • User對象是誰建立的?
    • User對象是由Spring建立的,
    • <bean id="helloSpring" class="com.idiot.pojo.User"> <property name="name" value="Spring"/> </bean> 複制代碼類型 變量名 = new 類型(); User user = new User(); id = 變量名 class = new的對象 property相當于給對象中的屬性設值 複制代碼
  • User 對象的屬性是怎麼設定的? User 對象的屬性是由Spring容器設定的,

這個過程就叫控制反轉:

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

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

IOC是一種程式設計思想,由主動的程式設計變成被動的接收,可以通過newClassPathXmlApplicationContext去浏覽一下底層源碼,

IOC

本質

控制反轉IoC(Inversion of Control),是一種設計思想,是一種通過描述(XML或注解)并通過第三方去生産或擷取特定對象的方式,在 Spring 中實作控制反轉的是 IoC 容器,其實作方法是依賴注入(Dependency Injection,DI)也有人認為 DI 隻是 IoC 的另一種說法。沒有 IoC 的程式中 , 我們使用面向對象程式設計 , 對象的建立與對象間的依賴關系完全寫死在程式中,對象的建立由程式自己控制,控制反轉後将對象的建立轉移給第三方,個人認為所謂控制反轉就是:獲得依賴對象的方式反轉了。

關于Spring架構的幾個重點,你了解多少?

IoC 是 Spring 架構的核心内容,使用多種方式完美的實作了 IoC,可以使用 XML 配置,也可以使用注解,新版本的 Spring 也可以零配置實作 IoC。

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

理論推導

首先建立一個Maven項目,然後建立接口和實作類,目錄總覽如下,

關于Spring架構的幾個重點,你了解多少?

UserDao.java 接口,

package com.idiot.dao;

public interface UserDao {
    public void getUser();
}
複制代碼           

Dao 的實作類,UserDaoImpl.java,

package com.idiot.dao;

public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("擷取使用者資料");
    }
}
複制代碼           

UserService 的接口,UserService.java,

package com.idiot.service;

public interface UserService {
    public void getUser();
}
複制代碼           

Service 的實作類,UserServiceImpl.java,

package com.idiot.service;

import com.idiot.dao.UserDao;
import com.idiot.dao.UserDaoImpl;

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

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
複制代碼           

測試一下,MyTest.java,

import com.idiot.service.UserService;
import com.idiot.service.UserServiceImpl;
import org.junit.Test;

public class MyTest {
    @Test
    public void test(){
        //使用者實際調用的是業務層,Dao層他們不需要接觸!
        UserService service = new UserServiceImpl();
        service.getUser();
    }
}
複制代碼           

這裡要使用 @Test,需要導入 junit.jar,

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
複制代碼           

運作結果如下,

關于Spring架構的幾個重點,你了解多少?

現在增加一個 Dao 的實作類,UserDaoOracleImpl.java,

package com.idiot.dao;

public class UserDaoOracleImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("Oracle擷取使用者資料");
    }
}
複制代碼           

如果使用者想去調用 Oracle 這個實作類,則我們必須去源代碼去修改代碼,即在 service 實作類裡面修改對應的實作,

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

   @Override
   public void getUser() {
       userDao.getUser();
  }
}
複制代碼           

再加一個 Mysql 的實作類也是一樣的,UserDaoSqlserverImpl.java,

package com.idiot.dao;

public class UserDaoSqlserverImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("MySql擷取使用者資料");
    }
}
複制代碼           

現在我們這個程式的代碼少,修改起來是不麻煩的,但假設修改需求非常大 , 修改源碼這種方式就根本不适用了, 每次變動都需要修改大量代碼,這樣子的成本代價是十分昂貴的。

如何解決這樣的問題?

我們将使用一個 set 接口實作,使之發生革命性的變化!

UserServiceImpl.java

package com.idiot.service;

import com.idiot.dao.UserDao;

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

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
複制代碼           

MyTest.java

@Test
public void test(){
    UserServiceImpl service = new UserServiceImpl();
    //Mysql實作
    service.setUserDao( new UserDaoSqlserverImpl() );
    service.getUser();
    
    //Oracle實作
    service.setUserDao( new UserDaoOracleImpl() );
    service.getUser();
}
複制代碼           
關于Spring架構的幾個重點,你了解多少?

與使用 set 前的代碼進行對比,這已經發生了根本性的變化,以前所有東西都是由程式主動去進行控制建立 , 而使用了 set 注入之後,程式不再具有主動性,而是變成了被動接受的對象,即把主動權交給了調用者,程式則隻負責提供一個接口,

關于Spring架構的幾個重點,你了解多少?

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

建立對象方式

1. 使用無參構造建立對象(預設)

目錄

關于Spring架構的幾個重點,你了解多少?

User.java

package com.idiot.pojo;

public class User {
    private String name;

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

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

    public void show(){
        System.out.println("Name:"+ name );
    }
}
複制代碼           

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="user" class="com.idiot.pojo.User">
        <property name="name" value="Hello Spring!"/>
    </bean>
</beans>
複制代碼           

MyTest.java

import com.idiot.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        User user = (User) context.getBean("user");
        user.show();
    }
}
複制代碼           

運作結果

關于Spring架構的幾個重點,你了解多少?

此時,将無參構造改成有參構造再運作,

public User(String name) {
    System.out.println("User無參構造");
}
複制代碼           

發現程式報錯,沒有辦法進行初始化,Bean 初始化失敗,

關于Spring架構的幾個重點,你了解多少?

2. 使用有參構造建立對象

有參構造器,

public User(String name) {
    this.name = name;
}
複制代碼           

①下标指派

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg index="0" value="idiot"/>
</bean>
複制代碼           

②類型指派

如果有兩個及以上的相同類型,就無法判斷把值給誰了,是以不建議使用!

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg type="java.lang.String" value="idiot.."/>
</bean>
複制代碼           

③參數名指派

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg name="name" value="Idiot"/>
</bean>
複制代碼           

總結:在配置檔案加載的時候,容器中管理的對象就已經初始化了!

DI依賴注入

構造器注入

初始Spring的建立中已講,忘了的話回頭看一下。

Set注入

  • 依賴注入:set 注入, 依賴:bean 對象的建立依賴于容器, 注入:bean 對象中的所有屬性由容器來注入,

【環境搭建】

  1. 複雜類型
  2. 真實測試對象

複雜類型

Address.java

package com.idiot.pojo;

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
}
複制代碼           

如果在 Address 類中不添加 toString 方法,則會導緻輸出的不是 String 值,而是類似于這樣子的 com.idiot.pojo.Address@319b92f3,

Student.java

package com.idiot.pojo;

import java.util.*;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobby;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    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> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", \naddress=" + address +
                ", \nbooks=" + Arrays.toString(books) +
                ", \nhobby=" + hobby +
                ", \ncard=" + card +
                ", \ngames=" + games +
                ", \nwife='" + wife + '\'' +
                ", \ninfo=" + info +
                '}';
    }
}
複制代碼           

真實測試對象

1. 常量注入

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
</bean>
複制代碼           

2. Bean注入

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

<bean id="addr" class="com.idiot.pojo.Address">
    <property name="address" value="浙江"/>
</bean>

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
    <property name="address" ref="addr"/>
</bean>
複制代碼           

3. 數組注入

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
    <property name="address" ref="addr"/>
    <property name="books">
        <array>
            <value>C++</value>
            <value>Java</value>
            <value>Python</value>
        </array>
    </property>
</bean>
複制代碼           

4. List注入

<property name="hobby">
    <list>
        <value>sing</value>
        <value>climb</value>
    </list>
</property>
複制代碼           

5. Map注入

<property name="card">
    <map>
        <entry key="中國電信" value="10000"/>
        <entry key="中國移動" value="10086"/>
    </map>
</property>
複制代碼           

6. Set注入

<property name="games">
    <set>
        <value>堡壘之夜</value>
        <value>皇室戰争</value>
        <value>王者榮耀</value>
    </set>
</property>
複制代碼           

7. Null注入

<property name="wife"><null/></property>
複制代碼           

8. Properties注入

<property name="info">
    <props>
        <prop key="學号">123456</prop>
        <prop key="性别">man</prop>
        <prop key="姓名">idiot</prop>
    </props>
</property>
複制代碼           

下面是完整的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="addr" class="com.idiot.pojo.Address">
        <property name="address" value="浙江"/>
    </bean>

    <bean id="student" class="com.idiot.pojo.Student">
        <property name="name" value="idiot"/>
        <property name="address" ref="addr"/>

        <property name="books">
            <array>
                <value>C++</value>
                <value>Java</value>
                <value>Python</value>
            </array>
        </property>

        <property name="hobby">
            <list>
                <value>sing</value>
                <value>climb</value>
            </list>
        </property>

        <property name="card">
            <map>
                <entry key="中國電信" value="10000"/>
                <entry key="中國移動" value="10086"/>
            </map>
        </property>

        <property name="games">
            <set>
                <value>堡壘之夜</value>
                <value>皇室戰争</value>
                <value>王者榮耀</value>
            </set>
        </property>

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

        <property name="info">
            <props>
                <prop key="學号">123456</prop>
                <prop key="性别">man</prop>
                <prop key="姓名">idiot</prop>
            </props>
        </property>
    </bean>
</beans>
複制代碼           

以及測試MyTest.java,

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

    Student student = (Student) context.getBean("student");

    System.out.println(student.toString());
}
複制代碼           

運作結果

關于Spring架構的幾個重點,你了解多少?

其他注入

P命名空間注入

需要在頭檔案中加入限制檔案xmlns:p="http://www.springframework.org/schema/p",且實體類中要存在無參構造器,

<!--p命名空間注入,可以直接注入屬性的值:property-->
<bean id="userP" class="com.idiot.pojo.User" p:name="idiot" p:age="3"/>
複制代碼           

MyTest.java

@Test
public void test02(){
    ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
    User user = context.getBean("userP", User.class);
    System.out.println(user);
}
複制代碼           

C命名空間注入

雷同于P命名空間注入,

需要在頭檔案中加入限制檔案xmlns:c="http://www.springframework.org/schema/c",且實體類中要存在有參構造器,

<!--c命名空間注入,通過構造器注入:construct-args-->
<bean id="userC" class="com.idiot.pojo.User" c:age="3" c:name="idiot"/>
複制代碼           

AOP

什麼是AOP?

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

關于Spring架構的幾個重點,你了解多少?

AOP在Spring中的作用

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

  • 橫切關注點:跨越應用程式多個子產品的方法或功能。與業務邏輯無關的,但是需要我們關注的部分,就是橫切關注點。如日志 , 安全 , 緩存 , 事務等等...
  • 切面(ASPECT):橫切關注點被子產品化的特殊對象。即它是一個類。
  • 通知(Advice):切面必須要完成的工作。即它是類中的一個方法。
  • 目标(Target):被通知對象。
  • 代理(Proxy):向目标對象應用通知之後建立的對象。
  • 切入點(PointCut):切面通知執行的“地點”的定義。
  • 連接配接點(JointPoint):與切入點比對的執行點。
關于Spring架構的幾個重點,你了解多少?

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

通知類型 連接配接點 實作接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
後置通知 方法後 org.springframework.aop.AfterReturningAdvice
環繞通知 方法前後 org.aopalliance.intercept.MethodInterceptor
異常抛出通知 方法抛出異常 org.springframework.aop.ThrowsAdvice
引介通知 類中增加新的方法屬性 org.springframework.aop.IntroductionInterceptor

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

使用Spring實作AOP

使用AOP織入,需要導入一個依賴包,

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

法一:通過Spring API實作

編寫業務接口 UserService.java,

package com.idiot.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void search();
}
複制代碼           

再編寫實作類 UserServiceImpl.java,

package com.idiot.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 search() {
        System.out.println("查詢使用者");
    }
}
複制代碼           

然後開始 AOP 環節,我們編寫兩個通知類 , 前置通知 BeforeLog.java, 後置通知 AfterLog.java,

BeforeLog.java

package com.idiot.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    //method : 要執行的目标對象的方法
    //args : 被調用的方法的參數
    //target : 目标對象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println( target.getClass().getName() + "的" + method.getName() + "方法被執行了");
    }
}
複制代碼           

AfterLog.java

package com.idiot.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

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 切入實作,注意導入限制, 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"
       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="userService" class="com.idiot.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.idiot.log.BeforeLog"/>
    <bean id="afterLog" class="com.idiot.log.AfterLog"/>

    <!--aop的配置-->
    <aop:config>
        <!--切入點  expression:表達式比對要執行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.idiot.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 是一個表達式,其中 expression="execution()" 這是固定的,execution()的括号裡填寫的參數是要執行的位置,

這是如何判斷的呢?

大概是:修飾詞 -> 傳回值 -> 類名 -> 方法名 -> 參數值,

* com.idiot.service.UserServiceImpl.*(..),第一個*表示類名前面可以是任意的修飾詞和傳回值,第二個*則表示任意的方法名,..表示任意幾個參數,

測試 MyTest.java,

import com.idiot.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意:動态代理代理的是接口
        UserService userService = context.getBean("userService",UserService.class);
        userService.search();
    }
}
複制代碼           

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

法二:自定義類實作

目标業務類不變依舊是 userServiceImpl.java,

  1. 自定義編寫切入類 DiyPointcut.java,
package com.idiot.diy;

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

    public void after(){
        System.out.println("---------方法執行後---------");
    }
}
複制代碼           
  1. 編寫配置檔案 applicationContext.xml,
<!--注冊bean-->
<bean id="userService" class="com.idiot.service.UserServiceImpl"/>
<bean id="diy" class="com.idiot.diy.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
    <!--使用AOP的标簽實作-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPointcut" expression="execution(* com.idiot.service.UserService.*(..))"/>
        <aop:before pointcut-ref="diyPointcut" method="before"/>
        <aop:after pointcut-ref="diyPointcut" method="after"/>
    </aop:aspect>
</aop:config>
複制代碼           
  1. 測試 MyTest.java,
import com.idiot.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意:動态代理代理的是接口
        UserService userService = context.getBean("userService",UserService.class);
        userService.search();
    }
}
複制代碼           
關于Spring架構的幾個重點,你了解多少?

法三:使用注解實作

  1. 編寫一個注解實作的自定義類 AnnotationPointcut.java,
package com.idiot.diy;

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.idiot.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法執行前---------");
    }

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

    @Around("execution(* com.idiot.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("環繞前");

        //執行目标方法proceed
        Object proceed = jp.proceed();

        System.out.println("環繞後");
    }
}
複制代碼           
  1. 在 Spring 配置檔案中注冊 bean,并增加支援注解的配置,
<bean id="annotationPointcut" class="com.idiot.diy.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
複制代碼           
關于Spring架構的幾個重點,你了解多少?

aop:aspectj-autoproxy 說明:

通過 aop 命名空間的 <aop:aspectj-autoproxy /> 聲明自動為 Spring 容器中那些配置 @aspect 切面的 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動态代理。

整合Mybatis

回顧Mybatis

關于Spring架構的幾個重點,你了解多少?

先是導入依賴包,配置倉庫,同時不要忘記配置 Maven 靜态資源過濾, pom.xml

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

    <artifactId>spring-10-mybatis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

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

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

        <dependency>
            <groupId>org.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>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.2</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!--配置Maven靜态資源過濾問題-->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>
複制代碼           

編寫實體類 User.java,

package com.idiot.pojo;

import lombok.Data;

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
複制代碼           

再編寫接口 UserMapper.java 及其映射檔案 UserMapper.xml,

UserMapper.java

package com.idiot.mapper;

import com.idiot.pojo.User;

import java.util.List;

public interface UserMapper {
    public List<User> selectUser();
}
複制代碼           

UserMapper.xml

<?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.idiot.mapper.UserMapper">

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

</mapper>
複制代碼           

緊接着編寫核心配置檔案 mybatis-config.xml 以及外部配置檔案 db.properties,

mybatis-config.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入外部配置檔案-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="pwd" value="123456"/>
    </properties>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.idiot.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.idiot.mapper.UserMapper"/>
    </mappers>

</configuration>
複制代碼           

db.properties

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3307/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
複制代碼           

最後進行測試,編寫測試類 MyTest.java,這裡沒有像之前那樣特意的去編寫工具類了,然是直接在測試類中進行 SqlSession 的一系列操作,

import com.idiot.mapper.UserMapper;
import com.idiot.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest {
    @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(true);

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

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

        sqlSession.close();
    }
}
複制代碼           
關于Spring架構的幾個重點,你了解多少?

MyBatis-Spring

什麼是 MyBatis-Spring?

MyBatis-Spring 就是幫助你将 MyBatis 代碼無縫地整合到 Spring 中。

MyBatis-Spring 需要以下版本:

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

是以需要在 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。

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

需要注意的是,這個配置檔案并不需要是一個完整的 MyBatis 配置。确切地說,任何環境配置,資料源和 MyBatis 的事務管理器都會被忽略。SqlSessionFactoryBean 會建立它自有的 MyBatis 環境配置(Environment),并按要求設定自定義環境的值。

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

模闆(Template)可以參與到 Spring 的事務管理中,并且由于其是線程安全的,可以供多個映射器類使用,你應該總是用 SqlSessionTemplate 來替換 MyBatis 預設的 DefaultSqlSession 實作。在同一應用程式中的不同類之間混雜使用可能會引起資料一緻性的問題。

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

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

現在這個bean就可以直接注入到Mapper bean中,

package com.idiot.mapper;

import com.idiot.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl 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();
    }
}
複制代碼           

接下來注入 SqlSessionTemplate:

<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
複制代碼           

整合一

  1. 引入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
       http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
複制代碼           
  1. 配置資料源替換 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:3307/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
複制代碼           
  1. 配置 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/idiot/mapper/*.xml"/>
</bean>
複制代碼           
  1. 注冊 sqlSessionTemplate,關聯 sqlSessionFactory,
<!--注冊sqlSessionTemplate , 關聯sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--利用構造器注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
複制代碼           
  1. 增加 Mapper 接口的實作類 UserMapperImpl.java,私有化 sqlSessionTemplate,
package com.idiot.mapper;

import com.idiot.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl 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();
    }
}
複制代碼           
  1. 注冊 bean 實作,
<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
複制代碼           
  1. 再來看看我們 Mybatis 的配置檔案,發現所有内容都可以被 Spring 整合,
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.idiot.pojo"/>
    </typeAliases>
</configuration>
複制代碼           
  1. 測試,
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper mapper = (UserMapper) context.getBean("userMapper");
    List<User> user = mapper.selectUser();
    System.out.println(user);
}
複制代碼           
關于Spring架構的幾個重點,你了解多少?

整合二

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

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

關于Spring架構的幾個重點,你了解多少?

修改 UserMapperImpl.java,

package com.idiot.mapper;

import com.idiot.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
複制代碼           

修改 applicationContext.xml 中 bean 的配置,

<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
複制代碼           

測試,

關于Spring架構的幾個重點,你了解多少?

總結 : 整合到 Spring 以後可以完全不要 mybatis 的配置檔案,除了這些方式可以實作整合之外,我們還可以使用注解來實作,

後記

以上内容隻是簡略的有重點的描述了 Spring 架構,要真正掌握還是需要自己繼續深入學習的,謹記面向百度程式設計!