天天看點

Spring 架構介紹和使用

作者:運維開發故事

本文主要是對 Spring 的一個基本使用,建議閱讀時間 5min。

曆史的選擇

Spring 作為一個基礎的架構,是在 Java EE 開發曆史中,是成千上萬公司選擇。單獨使用 Spring 的非常少了,很多都是用 Spring-Boot/Spring-Cloud 來開發,但是 Spring 基礎依然是我們使用的基石。我們将一起來聊一聊 Spring 的基本使用。首先我們一起來了解一下 Spring 架構整體架構圖如下:

Spring 架構介紹和使用
  1. 資料通路/內建,包括 JDBC 、ORM、OXM、JMS 和 Transaction 子產品;
  2. WEB 子產品,包括 WebSocket、Servlet、Web、Porlet 子產品;
  3. 核心容器,包括 Bean 子產品、Core 子產品、Context 子產品 和 SpEL 子產品;
  4. 其他部分,包括:AOP、Test 等子產品

Spring 同類架構

  • Micronaut
  • Quarkus

Spring 核心功能

核心功能:控制反轉(IOC) 、AOP 非核心功能:事件驅動、國際化、資源管理,資料綁定、類型轉換 、SpEL、單元測試等。

PS:核心功能,在本文會有使用實踐。

Spring Bean 容器

控制反轉(IOC)是 Spring 架構的核心功能之一,其本質的就是将使用者建立 Bean 的過程賦予給 IOC 容器去完成,實作 Bean 建立權利的反轉為容器來建立 Bean 和依賴 Bean 。

Spring 架構介紹和使用

Bean 建立

Spring 容器建立 Bean 隻需要三個步驟:

  1. 定義 Bean
  2. 建立 Bean 容器/Bean 工廠
  3. 擷取 Bean 對象

舉一個例子:

public class TestMain {

public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
Student student = applicationContext.getBean(Student.class);
student.study;
student.sleep;
student.study;
}
}

@Configuration
@Import(Student.class)
class AppConfig {

}

@Component
class Student {

private String name;

private Integer source;

public void study {
System.out.println("學習中...");
}

public void sleep {
System.out.println("休息中...");
}
// setter getter
}
           

運作上面的代碼我們可以得到一下結果:

學習中...
休息中...
學習中...
           

上面的代碼執行什麼呢?其實我們可以将

ApplicationContext

了解為 Spring 容器對象,然後我們在 AppConfig 配置類中去定義 Spring 容器去幫助我們加載那些 Bean ,最後我們通過 getBean 方法擷取我們注冊的 Bean 對象。如下圖:

Spring 架構介紹和使用

在這個過程中使用到那些關鍵的接口/類呢?

  • BeanFactory 是 Bean 的抽象工廠,也就是我們

    ApplicationContext

    的一個父接口。
  • BeanDefinition 是 Bean 的定義資訊, 比如 beanName, className, isAbstract 等 Bean 定義資訊。

注入依賴 (DI)

Spring IOC 容器主要是解決了 Bean 的建立和依賴管理的問題。我們常見的有兩種依賴注入方式:

  1. 屬性注入
  2. 構造方法注入

屬性注入

通過成員屬性的方式實作 Bean 的自動注入

@Component
class Student {
@Autowired
private Address address;
// ...
}

@Component
class Address {
// ...
}
           
  • 通過

    @Component

    可以将 Student 、Address 類标記為一個 bean 對象
  • 通過

    @Autowired

    可以将依賴 Bean 自動注入進來。

構造方法注入

通過構造方法實作 Bean 的自動注入

@Component
class Student {

public Student(Address address) {
this.address = address;
}
}

@Component
class Address {
// ...
}
           

Spring 的 IOC 解決了什麼問題?

  1. 容器化,Spring包含并管理應用中對象的生命周期和配置(配置成單例還是原型,以及什麼時候使用什麼時候銷毀)。
  2. 友善解耦,簡化開發,Spring就是一個大工廠,可以将所有對象建立和依賴關系維護交給Spring管理,實作松耦合。符合高内聚低耦合的思想,這個特性也叫IOC(控制反轉)。
  3. AOP程式設計的支援,Spring提供面向切面的程式設計,可以友善的實作對程式進行權限攔截、運作監控等功能,是通過動态代理和CGlib實作的,底層原理是反射。
  4. 聲明式事務的支援,通過AOP來實作。不需通過程式設計的方式而進行管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,隻需在配置檔案中做相關的事務規則聲明,便可将事務規則應用到業務邏輯中。
  5. 友善程式的測試,Spring對Junit4的支援,可以通過注解友善的測試Spring程式。
  6. 友善內建各種優秀架構,Spring不排斥各種優秀的開源架構,其内部提供了對各種優秀架構的直接支援(如Struts2、Hibernate、MyBatis等)。
  7. 異常處理,Spring提供友善的API把具體技術相關的異常轉化為一緻的unchecked異常(比如由JDBC、Hibernate或者JDO抛出的異常)。SpringMVC也有一個異常集中處理的思想,将異常抛給SpringMVC架構,由架構來處理異常。
  8. 降低JavaEE API的使用難度,Spring對JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠端調用等),都提供了封裝,使這些API應用難度大大降低。

Spring AOP 面向切面

AOP(Aspect Oriented Programming)是面向切面的意思。

了解 AOP

Java 是一個面向對象(OOP)的程式設計語言,但是它有一個弊端就是需要為多個不具有繼承關系的對象引入一個公共行為時,例如:日志記錄、權限驗證、事務管理、通路統計等公共行為,這樣不便于維護,而且有大量重複代碼,AOP 可以實作和 AOP 的互補。

舉個例子:我們有兩個邏輯登入業務、訂單業務,需要在他們調用前後進行:權限驗證、日志記錄等公共邏輯。

  1. 通過 OOP 的方式實作我們需要做一個邏輯模闆:權限驗證,具體邏輯(登入、訂單),日志記錄。
  2. 通過 AOP 的方式實作我們隻需針對具體邏輯(登入、訂單)前後做一個自定義切點,進行權限驗證、日志記錄。

如下圖:

Spring 架構介紹和使用

經過 AOP 方式處理過後,我們可以減少公共對象的引用、通過非繼承的方式來處理切入邏輯的攔截,實作公共邏輯和業務的邏輯的松耦合關系。

AOP 實作

Spring 通過代理的方式去實作 AOP,Java 代理的兩種模式:靜态代理、動态代理。

  • 靜态代理:靜态代理是指在程式運作前,可以了解為是 .java 檔案編譯後就存在代理類的位元組碼 .class 檔案。
  • 動态代理:動态代理指在程式運作期間通過 JVM 反射等動态機制,在運作期生成代理對象确定代理邏輯。

Spring 的兩種代理模式:

  • JDK 代理:核心類 JdkDynamicAopProxy。
  • GCLIB 代理:核心類 ObjenesisCglibAopProxy。

兩種代理的選擇:如果 Bean 實作了接口就采用 JDK 代理, 如果沒有實作就采用 GCLIB 代理。

AOP 使用

假設已經有一個 UserService 類提供了登入業務,我們需要對該業務做一個【權限驗證】、【日志記錄】這兩個公共邏輯,在不修改 UserService 類代碼的前提下就可以通過 AOP 來解決。示例如下:

// 1. 測試類
public class AopTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AopConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.login("admin", "123456");
}
}

// 2. 配置類
@EnableAspectJAutoProxy
@Configuration
@Import({UserService.class, ValidateAspect.class})
class AopConfig {


}

// 業務類
@Component
class UserService {
public String login(String username, String password) {
System.out.println("username:" + username + ",password:" + password);
return "ok";
}
}

// Aspect
@Aspect
@Component
class ValidateAspect {

@Pointcut("execution(public * io.zhengsh.simu.spring.UserService.*(..))")
public void servicePoint {
// Do nothing
}


@Around("servicePoint")
public Object doAroundService(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("validate param invoke !!!");
return joinPoint.proceed;
}
}


           

maven 依賴

<!-- Spring核心依賴 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring beans包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring 容器包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

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

參考文檔

  • Spring 官方文檔
  • 駱駝整理說-Spring AOP
  • Java-為什麼使用Spring架構