必知必会的强大SPEL工具

前言

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作为其构造函数中的参数。

要记住的一件事是,创建实例的成本很高。因此,我们应该尽可能多地缓存和重用它们。以下是针对根对象评估StandardEvaluationContextSpEL 表达式的一些用法:

  • 可以访问 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 表达式中使用了Userbean。我们还在 SpEL 表达式中使用了Companybean。以下是Companybean 的定义:

@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框架开发的任何企业应用程序的各个领域。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全粘架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值