领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多欢迎来到另一期《你可能不需要另一个库》(YMNNALFT)!自从2016年以来,我花了很多时间在我的Spring Tips 视频中阐明(或者至少尝试阐明!)Spring 生态系统中一些更大的机遇。然而,今天,我怀着不同的精神来到这里,希望关注那些功能强大、有时隐藏的小巧宝石,它们可以为您节省额外的第三方依赖及其隐含的复杂性。
您的用户是否希望有一种方便、简洁的方法来定制应用程序的行为?表达式语言专为允许低接触定制应用程序行为而设计。表达式语言有很多应用。它们可以帮助您评估事物!也许它们可以运行用户配置的简单谓词逻辑。表达式语言可以取消引用环境值、将事物粘合在一起、支持模板、定制访问控制和授权谓词、支持定制消息流路由和工作流事件处理程序逻辑等等。一个好的表达式语言非常有用,因此我们在 2009 年的 Spring Framework 3.0 中构建了 Spring 表达式语言 (SpEL) 并随其一起发布!
我永远不会忘记那次添加!我当时正在 Subversion 中浏览源代码,我注意到Andy Clement(我们的一位常驻疯狂科学家,也是我认识的最棒的人之一)向 Spring 添加了一种全新的表达式语言。
当然他会这么做。
这里值得注意的是,该表达式是现有表达式语言(如OGNL和JBoss 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 配置和作为独立库。
让我们看一个例子。
您需要以下依赖项。
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,Foo
和Bar
。Bar
依赖于Foo
中的一个属性name
,它使用 SpEL 取消引用该属性,然后引用 Spring 应用程序上下文中的其他 bean。
您喜欢这种一目了然的方法吗?您学到了什么?与往常一样,我很想听到您的意见,所以请在 Twitter 上(@starbuxman) 发表您的看法!我将继续推出另一期《YMNNALFT》,所以请不要错过。