前言
Spring 表达式语言(缩写为 SpEL)是一种功能强大的表达式语言。在Spring产品组合中,它是评估表达式的基础。
它支持在运行时查询和操作对象图。它可以与基于 XML 和基于注解的 Spring 配置和 Bean 定义一起使用。
由于它能够在运行时动态分配值,因此可以为我们节省大量代码。
项目设置
对于 Maven 项目,应使用以下依赖项:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
前两个依赖项spring-core
spring-context
是 SpEL 所必需的。另外两个依赖项javax.mail
commons-io
,并将在 SpEL 的实际示例中使用。
语言语法和功能
SpEL 支持标准数学运算符、关系运算符、逻辑运算符、条件运算符、集合和正则表达式等。
它可用于将一个 bean 或 bean 属性注入另一个 bean。还支持对 Bean 进行方法调用。以下是 SpEL 的一些基本功能和运算符:
-
文本表达式可用于 SpEL 表达式。例如,“Hello SpEL”是一个字符串文本。如果将此文本用作 SpEL 表达式,则计算值也将为“Hello SpEL”。
-
SpEL 表达式支持方法调用。例如,可以从字符串文本调用 concat 方法。
-
SpEL 表达式支持数学运算符。所有基本运算符,如加法 (+)、减法 (-)、乘法 (*)、除法 (/)、模数 (%)、指数幂 (^) 等,都可以在 SpEL 表达式中使用。
-
SpEL 表达式还支持关系运算符等于 (==)、不等于 (!=)、小于 (<)、小于或等于 (<=)、大于 (>) 和大于或等于 (>=)。要在基于 XML 的配置中使用关系运算符,应改用文本等效项 eq、ne、lt、le、gt、ge。
-
支持逻辑运算符 (&&) 或 (||) 而不是 (!)。也可以使用文本等效项。
-
三元运算符用于在 SpEL 表达式中执行 if-then-else 条件逻辑。当我们需要根据某些条件注入值时,它很有用。
-
猫王算子是三元算子的缩写形式。三元运算符的一个常见用法是对变量进行 null 检查,然后返回变量值或默认值。猫王运算符是完成这项工作的捷径。
-
SpEL 表达式支持使用正则表达式。我们需要使用 match 运算符来检查字符串是否与给定的正则表达式匹配。
我们将使用 SpelExpressionParser
,它是接口ExpressionParser
的实现来解析 SpEL 表达式。
调用parseExpression
的方法将返回ExpressionParser
的实例,该实例是表达式接口的实现。
下面是 SpEL 表达式 SpelExpressionParser
的上述功能的代码示例。
public class ExpressionParserExample1 {
public static void main(String[] args) {
ExpressionParser expressionParser = new SpelExpressionParser();
// 1. Literal expression
Expression expression = expressionParser.parseExpression("'Hello SpEL'");
String strVal = expression.getValue(String.class);
System.out.println("1. Literal expression value:\n" + strVal);
// 2. Method invocation
expression = expressionParser.parseExpression("'Hello SpEL'.concat('!')");
strVal = expression.getValue(String.class);
System.out.println("2. Method invocation value:\n" + strVal);
// 3. Mathematical operator
expression = expressionParser.parseExpression("16 * 5");
Integer intVal = expression.getValue(Integer.class);
System.out.println("3. Mathematical operator value:\n" + intVal);
// 4. Relational operator
expression = expressionParser.parseExpression("5 < 9");
boolean boolVal = expression.getValue(Boolean.class);
System.out.println("4. Mathematical operator value:\n" + boolVal);
// 5. Logical operator
expression = expressionParser.parseExpression("400 > 200 && 200 < 500");
boolVal = expression.getValue(Boolean.class);
System.out.println("5. Logical operator value:\n" + boolVal);
// 6. Ternary operator
expression = expressionParser.parseExpression("'some value' != null ? 'some value' : 'default'");
strVal = expression.getValue(String.class);
System.out.println("6. Ternary operator value:\n" + strVal);
// 7. Elvis operator
expression = expressionParser.parseExpression("'some value' ?: 'default'");
strVal = expression.getValue(String.class);
System.out.println("7. Elvis operator value:\n" + strVal);
// 8. Regex/matches operator
expression = expressionParser.parseExpression("'UPPERCASE STRING' matches '[A-Z\\s]+'");
boolVal = expression.getValue(Boolean.class);
System.out.println("8. Regex/matches operator value:\n" + boolVal);
}
}
现在,本文已经使用默认上下文评估了 SpEL 表达式。SpEL 表达式可以针对特定对象实例(通常称为根对象)进行评估。让我们定义SampleBean
对象并将其用作评估的上下文。
@Component("sampleBean")
public class SampleBean {
private String property = "String property";
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
private HashMap<String, String> hashMap = new HashMap<String, String>();
public SampleBean() {
arrayList.add(36);
arrayList.add(45);
arrayList.add(98);
hashMap.put("key 1", "value 1");
hashMap.put("key 2", "value 2");
hashMap.put("key 3", "value 3");
}
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
public ArrayList<Integer> getArrayList() {
return arrayList;
}
public void setArrayList(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
public HashMap<String, String> getHashMap() {
return hashMap;
}
public void setHashMap(HashMap<String, String> hashMap) {
this.hashMap = hashMap;
}
}
我们将通过创建StandardEvaluationContext
的实例来创建评估上下文。它将根对象SampleBean
作为其构造函数中的参数。
要记住的一件事是,创建实例的成本很高。因此,我们应该尽可能多地缓存和重用它们。以下是针对根对象评估StandardEvaluationContext
SpEL 表达式的一些用法:
-
可以访问 bean 的属性值。
-
可以将 bean 的属性值与某个特定值进行比较。
-
可以访问 Bean 的 List 属性的内容。可以使用方括号表示法访问列表的项。要在括号内提供的项目的索引。
-
可以访问 bean 的 map 属性的内容。也可以使用方括号表示法访问地图的内容。必须在括号内提供键值。
下面是上述功能的代码示例。
public class ExpressionParserExample2 {
public static void main(String[] args) {
ExpressionParser expressionParser = new SpelExpressionParser();
// create EvaluationContext from bean
SampleBean contextBean = new SampleBean();
StandardEvaluationContext testContext = new StandardEvaluationContext(contextBean);
// 9. Property value
Expression expression = expressionParser.parseExpression("property");
String strVal = expression.getValue(testContext, String.class);
System.out.println("9. Property value:\n" + strVal);
// 10. Compare property
expression = expressionParser.parseExpression("property == 'String property'");
boolean boolVal = expression.getValue(testContext, Boolean.class);
System.out.println("10. Compare property:\n" + boolVal);
// 11. List value
expression = expressionParser.parseExpression("arrayList[0]");
strVal = expression.getValue(testContext, String.class);
System.out.println("11. List value:\n" + strVal);
// 12. Map value
expression = expressionParser.parseExpression("hashMap['key 1']");
strVal = expression.getValue(testContext, String.class);
System.out.println("12. Map value:\n" + strVal);
}
}
Bean 定义中的 SpEL
SpEL 表达式可用于 Bean 定义。它可以与基于 XML 和基于注释的配置一起使用。
SpEL 表达式以哈希 (#) 符号开头,并用大括号括起来。因此,它遵循 #{ <表达式字符串>} 的形式。
SpEL 表达式可用于引用 Bean 或 Bean 的属性/方法。下面是基于注释的配置示例:
@Component("user")
public class User {
@Value("598")
private Integer id;
@Value("John")
private String firstName;
@Value("Doe")
private String lastName;
@Value("#{user.firstName.concat(' ').concat(user.lastName)}")
private String fullName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
实际示例
我们已经了解了 SpEL 表达式的基本特征。现在,让我们在一个有趣的实际示例中应用它。
假设我们要向用户发送 HTML 电子邮件。某些值应在运行时动态注入到 HTML 模板中。在这种情况下,在 HTML 模板中使用 SpEL 表达式可能是一个很好的解决方案。下面是包含 SpEL 表达式的 HTML 模板的示例:
<html>
<head>
<title>HTML Email with SPEL expression</title>
</head>
<body>
<h4>Dear #{user.fullName},</h4>
<div style="color:blue;"><i>Thanks for registering to our system.</i></div>
<p>Best regards,
<br>#{company.getName()}
</p>
</body>
</html>
在上面的例子中,我们在前面定义的 SpEL 表达式中使用了User
bean。我们还在 SpEL 表达式中使用了Company
bean。以下是Company
bean 的定义:
@Component("company")
public class Company {
@Value("256")
private Integer id;
@Value("XYZ Inc.")
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们必须迭代 HTML 模板的内容并找出其中使用的所有 SpEL 表达式。
必须解析和计算每个 SpEL 表达式。我们必须将所有 SpEL 表达式替换为 HTML 模板内容中的评估值。
对于发送电子邮件,我们将使用标准的JavaMail API。作为SMTP服务器,我们将使用雅虎的SMTP(如果是雅虎,我们需要创建一个应用程序密码)。
其他 SMTP 服务器也可以以相同的方式使用。以下是完整的示例:
public class HTMLEmailTest {
public String parseEmailContent(StringWriter emailTemplateContent) {
// populate spel context
ConfigurableApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
StandardEvaluationContext spelContext = new StandardEvaluationContext();
spelContext.setBeanResolver(new BeanFactoryResolver(appContext.getBeanFactory()));
spelContext.addPropertyAccessor(new BeanExpressionContextAccessor());
BeanExpressionContext rootObject = new BeanExpressionContext(appContext.getBeanFactory(), null);
// create spel expression parser instance
ExpressionParser parser = new SpelExpressionParser();
// search the fileContent string and find spel expressions,
// then evaluate the expressions
Integer start = 0, braketStart = 0;
StringBuffer sb = emailTemplateContent.getBuffer();
while ((braketStart = sb.indexOf("#{", start)) > -1) {
Integer braketClose = sb.indexOf("}", start);
String expressionStr = sb.substring(braketStart + 2, braketClose);
Expression expression = parser.parseExpression(expressionStr);
String evaluatedValue = expression.getValue(spelContext, rootObject, String.class);
sb.replace(braketStart, braketClose + 1, evaluatedValue);
start = braketClose + evaluatedValue.length();
}
System.out.println(sb.toString());
return sb.toString();
}
public Properties getSmtpProperties() {
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.mail.yahoo.com");
props.put("mail.smtp.auth", "true");
props.put("mail.debug", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
return props;
}
public Session getMailSession(Properties props) {
Session mailSession = Session.getInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("XXXXXX@yahoo.com", "xxxxxxxxxxx");
}
});
mailSession.setDebug(true);
return mailSession;
}
public void populateAndSendEmail(Session mailSession, String emailBody) {
try {
Message msg = new MimeMessage(mailSession);
msg.setFrom(new InternetAddress("XXXXXX@yahoo.com"));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("YYYYYY@gmail.com"));
msg.setSentDate(new Date());
msg.setSubject("HTML Email with SPEL expression");
msg.setContent(emailBody, "text/html");
// Send the email using Transport
Transport.send(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
HTMLEmailTest htmlEmailTest = new HTMLEmailTest();
String emailTemplate = "emailTemplate.html";
ClassLoader classLoader = htmlEmailTest.getClass().getClassLoader();
File file = new File(classLoader.getResource(emailTemplate).getFile());
StringWriter emailTemplateContent = new StringWriter();
IOUtils.copy(new FileInputStream(new File(file.getAbsolutePath())), emailTemplateContent);
// replace the SPEL expressions of email template with evaluated values
String emailBody = htmlEmailTest.parseEmailContent(emailTemplateContent);
// populate SMTP server properties
Properties props = htmlEmailTest.getSmtpProperties();
// generate email session
Session mailSession = htmlEmailTest.getMailSession(props);
// generate email message and send email
htmlEmailTest.populateAndSendEmail(mailSession, emailBody);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的应用程序将发送一封带有正文的 HTML 电子邮件
结论
在本文中,我们通过一些小示例了解了 Spring 表达式语言的基本功能和语法。这是Spring框架的一个强大功能。它可以应用于由Spring框架开发的任何企业应用程序的各个领域。