35

Learning the Spring Expression Language (SpEL)

 5 years ago
source link: https://www.tuicool.com/articles/hit/MZvyeme
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Overview

The Spring Expression Language (abbreviated as SpEL) is a powerful expression language. Within the Spring portfolio, it serves as the foundation for expression evaluation. It supports querying and manipulating an object graph at runtime.  It can be used with both XML-based and annotation-based Spring configurations and bean definitions. Since it is capable of assigning value dynamically at runtime, it can save us a lot of code.

Project Setup

For a Maven project, the following dependencies should be used:

<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>

The first two dependencies, spring-core and  spring-context are required for SpEL. The other two dependencies,  javax.mail and  commons-io, will be used in a practical example of SpEL.

Language Syntax and Features

SpEL supports standard mathematical operators, relational operators, logical operators, conditional operators, collections and regular expressions, etc. It can be used to inject a bean or a bean property into another bean. Method invocation of a bean is also supported. Here are some basic features and operators of SpEL:

  • The literal expression can be used in SpEL expression. For example, "Hello SpEL" is a String literal. If this literal is used as a SpEL expression, the evaluated value will also be "Hello SpEL."

  • Method invocation is supported in the SpEL expression. For example, the concat method can be called from a String literal.

  • The mathematical operators are supported in SpEL expression. All basic operators, like addition (+), subtraction (-), multiplication (*), division (/), modulus (%), exponential power (^), etc., can be used in a SpEL expression.

  • The relational operators equal (==), not equal (!=), less than (<), less than or equal (<=), greater than (>), and greater than or equal (>=) are supported in a SpEL expression, as well. To use the relational operator in the XML based configuration, the textual equivalents eq, ne, lt, le, gt, ge should be used instead.

  • The logical operators, (&&) or (||) and not (!), are supported. The textual equivalents can also be used.

  • The ternary operator is used for performing if-then-else conditional logic inside the SpEL expression. It is useful when we need to inject a value based on some condition.

  • The Elvis operator is a shortening form of the ternary operator. One common use of the ternary operator is the null checking of a variable and then returning the variable value or a default value. The Elvis operator is the shortcut way of doing the job.

  • The use of a regular expression is supported in this SpEL expression. We need to use the matches operator to check whether a string matches a given regular expression.

We will use the SpelExpressionParser  , which is an implementation of the  ExpressionParser interface to parse the SpEL expressions. Calling the  parseExpression method of the  SpelExpressionParser will return an instance of  SpelExpression , which is an implementation of the Expression interface. The evaluated result can be found by calling the  getValue   method. Here are code examples of the above features of the SpEL expression.

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);
    }
}

Now, we have evaluated the SpEL expression using the default context. SpEL expressions can be evaluated against a specific object instance, which is often mentioned as the root object. Let’s define a Bean   and use it as the context of the evaluation.

@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;
    }
}

We will create the evaluation context by creating an instance of StandardEvaluationContext . It takes the root object (  SampleBean   in our case) as a parameter in it’s constructor. One thing to remember is that the creation of the  StandardEvaluationContext   instance is expensive. So, we should cache and reuse them as much as possible. Here are some uses for evaluating the SpEL expression against the root object:

  • We can access the value of properties of a bean.

  • We can compare the value of a property of a bean with some specific value.

  • We can access the contents of the List property of a bean. The items of a List can be accessed by using the square bracket notation. The index of the item to be provided within the brackets.

  • We can access the contents of the map property of a bean. The contents of a map can also be accessed by using the square bracket notation. The key value has to be provided within the brackets.

Here are code examples of the above features.

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);
    }
}

SpEL in Bean Definition

SpEL expressions can be used in bean definitions. It can be used with both XML-based and annotation-based configuration. A SpEL expression starts with a hash (#) symbol and is wrapped with braces. Thus, it follows the form #{ <expression string>}. SpEL expressions can be used to refer a bean or properties/methods of a bean. Here is an example of annotation-based configuration:

@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;
    }
}

Practical Example

We have learned the basic features of SpEL expressions. Now, let’s apply it in an interesting practical example. Suppose we want to send an HTML email to users. Some values should be injected into the HTML template dynamically in runtime. Using SpEL expressions in the HTML template can be a good solution in this case. Here is an example of an HTML template that contains SpEL expressions:

<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>

In the above example, we have used the User bean inside the SpEL expression, which was defined earlier. We have also used the  Company bean in the SpEL expression. Here is the definition of a  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;
    }
}

How will we inject value inside  HTML template now? We have to iterate the content of the HTML template and find out all the SpEL expressions used in it. Each SpEL expression has to be parsed and evaluated. We have to replace all the SpEL expressions with the evaluated values in the content of HTML template. As the root object, we have to use the BeanExpressionContext here. Our  ApplicationContext's BeanFactory   will be used to create the instance of   BeanExpressionContext .

For sending emails, we will use the Standard JavaMail API. As the SMTP server, we will use Yahoo's SMTP (we need to create an app password in case of Yahoo). Other SMTP servers can also be used in the same way. Here is the complete example:

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("[email protected]", "xxxxxxxxxxx");
            }
        });
        mailSession.setDebug(true);
        return mailSession;
    }

    public void populateAndSendEmail(Session mailSession, String emailBody) {
        try {
            Message msg = new MimeMessage(mailSession);
            msg.setFrom(new InternetAddress("[email protected]"));
            msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("[email protected]"));
            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();
        }
    }
}

The above application will send an HTML email with the body as shown below:

iqIVfey.png!web

All the code examples in this article can be found here: https://github.com/ShahMinulAmin/spel .

Conclusion

In this article, we have learned about the basic features and syntax of the Spring expression language with some small examples. This one is a powerful feature of the Spring framework. It can be applied to various areas of any enterprise application developed by the Spring framework.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK