YMNNALFT:用 SpEL 表達自我

工程 | Josh Long | 2021 年 1 月 13 日 | ...

歡迎來到另一期的你可能不需要另一個函式庫(YMNNALFT)!自 2016 年以來,我花了很多時間在 我的 Spring Tips 影片 中闡明(或試圖闡明!)Spring 生態系統中一些更龐大的機會。然而,今天,我帶著不同的精神來到這裡,想要關注那些小的、有時是隱藏的、能做很棒的事情,並且可能會讓你免於額外的第三方依賴及其隱含的複雜性的瑰寶。

您的使用者是否想要一種方便、精簡的方式來客製化您的應用程式的行為?表達式語言專門用於允許對應用程式行為進行輕量級客製化。表達式語言有很多應用。它們可以幫助您評估事物!也許他們可以運行使用者配置的簡單謂詞邏輯。表達式語言可以取消引用環境值、將事物組合在一起、支援範本、客製化訪問控制和授權謂詞、支援客製化的訊息流路由和工作流程事件處理程序邏輯等等。一個好的表達式語言非常有用,以至於我們建立了 Spring 表達式語言 (SpEL) 並在 2009 年的 Spring Framework 3.0 中發布了它哇~~

我永遠不會忘記個新增功能!我當時正在 Subversion 中翻閱原始碼,我注意到 Andy Clement(我們其中一位常駐的瘋狂科學家,也是我所認識的最優秀的人之一)向 Spring 添加了一種全新的表達式語言。

當然會這麼做。

值得注意的是,該表達式是現有表達式語言(如 OGNLJBoss EL)的超集(它們都比當時的其他表達式語言提供更多功能,例如您在 Java Server Pages 或 Java Server Faces 中找到的那些)。Andy 在幾週內開發了這種新的表達式語言。所以,當我告訴你 Andy Clement(在幾週內!)簽入了一種超越 X 品牌的全新表達式語言時,這應該告訴你的是 Andy Clement 可以做任何事情,而且當機器攻擊時,我們都應該為他站在我們這邊而感到高興!

這種新的表達式語言使用 ANTLR,它是一個很棒、強大的剖析器產生器,給定語法定義,它將產生 Java 程式碼,該程式碼知道如何剖析該語法中定義的任何內容。因此,您可以使用 ANTLR 語法來教 ANTLR 如何剖析,例如,一個主題標籤 (# + A_LABEL) 或一個 ISO 8601 日期或 Java 原始碼,或一個 SQL 查詢,並且 ANTLR 將產生 Java 程式碼來剖析符合該語法的文字。當它遇到該語法的元素時,它會調用回調。它基本上是 JVM 生態系統中經典 Yacc / Lex 工具鏈的等價物,而且您毫無疑問地正在使用軟體,該軟體反過來使用 ANTLR 來提供剖析器。ANTLR 很棒。您可以編寫 Java 編譯器。SQL 剖析器。電子郵件驗證器。HTML 剖析器。天空才是極限!它被最優秀和最聰明的人使用,因此您可以確信它有效!它被用於 Groovy、Jython、Hibernate、MySQL Workbench、Apache Cassandra、Processing、Presto、Salesforce 的 Apex 以及無數其他專案。

印象深刻

這種表達式語言看起來很棒,而且 ANTLR 很棒,而且它是全面的可靠工程。

所以,自然地,Andy 移除了 ANTLR,並用他自己手寫的遞迴下降剖析器取代了它!太棒了!(誰做那件事?)

非常印象深刻!

SpEL 是老闆級的軟體,朋友們。它被廣泛用於 Spring 生態系統中,在 Spring Framework 中用於評估目的;在 Spring Security 中用於某些訪問控制規則;在 Spring Integration 中用於評估訊息的表達式;在 Spring Data 中用於將特定查詢與其他上下文(如 Spring Security)聯繫起來。這個列表還會繼續。

在過去的十年中,SpEL 變得更加神奇。它甚至有一個編譯器!這太棒了,因為該編譯步驟對使用者來說可以完全透明。您可以在 Spring 配置中和單獨作為獨立函式庫使用它來做任何您想做的事情。

讓我們看一個例子。

您需要以下依賴項。

  • 預設情況下,這包含在 Spring Initializr 上的每個 Spring Boot 專案中 - org.springframework.boot : spring-boot-starter

這是程式碼

package bootiful.el;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

@SpringBootApplication
public class BootifulApplication {

	@Bean
	Bar bar(@Value("#{ foo.name }") String name) {
		return new Bar(name);
	}

	@Bean
	Foo foo() {
		return new Foo();
	}

	@Bean
	ApplicationListener<ApplicationReadyEvent> ready() {
		return event -> {

			SpelParserConfiguration configuration = new SpelParserConfiguration(//
					SpelCompilerMode.IMMEDIATE, ClassLoader.getSystemClassLoader());
			ExpressionParser expressionParser = new SpelExpressionParser(configuration);

			double randomProperty = evaluate(expressionParser, "randomProperty", new MyContext());
			System.out.println("randomProperty: " + randomProperty);

			String uppercase = evaluate(expressionParser, "'andy clement for president'.toUpperCase()", null);
			System.out.println("uppercase: " + uppercase);
		};
	}

	@SuppressWarnings("unchecked")
	private static <T> T evaluate(ExpressionParser expressionParser, String expression, Object context) {
		Expression expression2 = expressionParser.parseExpression(expression);
		if (context != null) {
			EvaluationContext evaluationContext = new StandardEvaluationContext(context);
			return (T) expression2.getValue(evaluationContext);
		} //
		else {
			return (T) expression2.getValue();
		}
	}

	public static void main(String[] args) {
		System.setProperty("spring.profiles.active", "el");
		SpringApplication.run(BootifulApplication.class, args);
	}

}

@Data
class Foo {

	private String name = getClass().getName();

}

@Data
class Bar {

	Bar(@Value("#{ foo.name }") String name) {
		System.out.println("name: " + name);
	}

}

@Data
class MyContext {

	private final double randomProperty = Math.random();

	public int factorial(int n) {
		if (n == 0)
			return 1;
		else
			return (n * factorial(n - 1));
	}

}

這是我放入我的 application.properties 中的內容

spring.main.web-application-type=none

在此應用程式中,有兩件事需要注意:在獨立上下文中使用 SpEL,以及將 SpEL 用作 Spring 應用程式的一部分。

ApplicationListener<ApplicationReadyEvent> 中,我手動實例化一個 SpelExpressionParser 實例,我可以針對該實例評估 SpEL 表達式。我展示瞭如何配置自定義上下文(表達式可以調用方法並取消引用屬性的物件),並使用表達式語言在 String 文字上調用方法。

我也配置了兩個 bean,FooBarBar 依賴於 Foo 中的一個屬性 name,該屬性使用 SpEL 取消引用該屬性,然後引用 Spring 應用程式上下文中的其他 bean。

您喜歡這種一目瞭然的方法嗎?您學到了什麼嗎?與往常一樣,我很想收到您的來信,所以請在 Twitter 上發聲 (@starbuxman) !我將帶著另一期的YMNNALFT 回來,所以請務必不要錯過。

取得 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

檢視全部