This article has been accepted by Github github.com/silently952…

Wechat official account: Beta learning Java

preface

Work recently received a demand, need docking third-party companies API interface, because every company to provide a different data format and the late may butt more company, we give the technical way is to design a data parsing module, the data structure of different parsed into our internal definition of general data object, If interfaces of third-party companies are added later, data parsing rules need to be configured in the background to complete the interconnection

We investigated expression languages OGNL and SpEL (there are more than two) and chose SpEL because our project itself relies on Spring

SpEL introduction and features overview

SpEL is a powerful expression language provided by Spring. It is a cornerstone module of Spring and is used in many spring modules. Although SpEL is a cornerstone of Spring, it is used completely independently of Spring. SpEL provides the following key features:

  • Literal expression
  • Boolean and relational operators
  • Regular expression
  • Such expressions
  • Access properties, Arrays, Lists, Maps
  • The method call
  • Relational operator
  • parameter
  • Calling the constructor
  • Bean reference
  • Constructing an Array
  • Inline lists, inline maps
  • Ternary operator
  • variable
  • User-defined functions
  • Set the projection
  • A collection of screening
  • Template expression

SpEL represents the initial experience of language

The following code uses the SpEL API to parse the text string expression Hello World.

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
Copy the code

The interface ExpressionParser parses expression strings and uses single quotes to represent strings in expressions

SpEL supports many features, such as calling methods, accessing properties, and calling constructors.

ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'.concat('! ') "); String message = (String) exp.getValue();Copy the code

SpEL also supports the use of the standard “. Symbol, i.e., the set of nested properties prop1.prop2.prop3 and property values

ExpressionParser parser = new SpelExpressionParser();

// invokes getBytes().length
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
Copy the code

You can also use public

T getValue(Class

desiredResultType) to get a result that returns the specified type. Without casting, an EvaluationException will be thrown if the actual result cannot be cast to the specified type

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
Copy the code

A more common use of SpEL is to provide a parsed expression string for a particular object instance, called a root object. This approach is also used in our project, setting json data from different sources to root Object and specifying string expressions to parse each standard field

GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");

EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
Copy the code

EvaluationContext interface

Interface EvaluationContext a StandardEvaluationContext out-of-the-box implementation; Use reflection to manipulate objects and cache java.lang.Reflect’s Method, Field, and Constructor instances to improve performance when evaluating expressions that resolve properties, methods, and help perform type conversions

StandardEvaluationContext can setRootObject () or transfer the root object to the constructor to set the root object, can also use setVariable () to set a variable, RegisterFunction () to register methods; We can also extend the functionality of SpEL by customizing ConstructorResolvers, MethodResolvers, PropertyAccessor.

Parser configuration

By using org. Springframework. Expression. Spel. SpelParserConfiguration to fine configuration parser functionality, for example: if the specified array or collection in the expression of index location value is null, let it automatically creates the element; If the index exceeds the current size of the array, it is automatically expanded

class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);
Copy the code

Language reference

Literal expression

The types supported for literal expression are string, date, numeric value (int, real, hexadecimal), Boolean, and null. Strings are separated by single quotes. A single quote itself is represented by two single quote characters in a string. The following list shows simple uses of text. Numbers support the use of minus signs, exponents and decimal points

ExpressionParser parser = new SpelExpressionParser(); // evals to "Hello World" String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); Double avogadrosNumber = (double) parser. ParseExpression ("6.0221415E+23").getvalue (); // evals to 2147483647 int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); Object nullValue = parser.parseExpression("null").getValue();Copy the code

Properties, Arrays, Lists, Maps

Using attribute references is simple: just use one. Represents nested attribute values.

// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
Copy the code

Arrays and lists use square brackets to get their contents

ExpressionParser parser = new SpelExpressionParser();

// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        teslaContext, String.class);

// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        societyContext, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        societyContext, String.class);
Copy the code

The content of maps is obtained by specifying the key value in square brackets.

// Officer's Dictionary

Inventor pupin = parser.parseExpression("Officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
        societyContext, "Croatia");
Copy the code

A method is called

Method calls are still supported in expressions

// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Copy the code

The isMember method used here is the root object method. This method will report an error if the root object does not exist

The operator

Relational operators: equal, not equal, less than, less than or equal, greater than, and greater than or equal are used in the same way as normal Java

// evaluates to true boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); // evaluates to false Boolean falseValue = parseExpression("2 < -5.0").getValue(Boolean. Class); // evaluates to false Boolean falseValue = parseExpression("2 < -5.0").Copy the code

In addition to the standard relational operator SpEL, the instanceof and matches operations on regular expressions are supported.

boolean falseValue = parser.parseExpression( "'xyz' instanceof T(int)").getValue(Boolean.class); // evaluates to true Boolean trueValue = parser. ParseExpression ("'5.00' matches '^-? \\d+(\\.\\d{2})? $'").getValue(Boolean.class);Copy the code

Logical operators: Supported logical operators and, OR, not.

/ -- AND -- // evaluates to false boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);  // evaluates to true String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- OR -- // evaluates to true boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); // evaluates to true String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); // -- NOT -- // evaluates to false boolean falseValue = parser.parseExpression("! true").getValue(Boolean.class); // -- AND and NOT -- String expression = "isMember('Nikola Tesla') and ! isMember('Mihajlo Pupin')"; boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);Copy the code

#this and #root variables

The variable #this always defines and points to the current execution object. The variable #root points to the root context object.

// create an array of integers List<Integer> primes = new ArrayList<Integer>(); Primes. AddAll (Arrays. AsList (2,3,5,7,11,13,17)); // create parser and set variable primes as the array of integers ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ? {... }) // evaluates to [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( "#primes.? [#this>10]").getValue(context);Copy the code

function

Sometimes you may need to customize some utility functions used in the expression, can use first StandardEvaluationContext. RegisterFunction methods registered function, and then call a function in the expression

ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.registerFunction("reverseString", StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class })); ReverseString helloWorldReversed = parser. ParseExpression ("#reverseString('hello')"). String.class);Copy the code

Bean reference

If the parsing context is configured, the bean parser can look up bean classes from expressions using the @ symbol and FactoryBean objects using &

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
Copy the code

SpEL already provides BeanFactoryResolver by default, so you don’t need to implement it yourself

Collection selection

Selection is a powerful expression language feature that can be used to set filter criteria and return a subset that meets the criteria, similar to filter in Stream. Choose to use syntax? [selectionExpression].

Filtering examples of lists

ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(Arrays.asList(1, 7, 0, 3, 4, 9)); Object subList = parser.parseExpression("#root.? [#this>5]").getValue(context); / / [7, 9]Copy the code

Example of map filtering

ExpressionParser parser = new SpelExpressionParser(); Map<String,Integer> root = new HashMap<>(); root.put("3",3); root.put("6",6); root.put("8",8); StandardEvaluationContext context = new StandardEvaluationContext(root); Object subMap = parser.parseExpression("#root.? [#this.value>5]").getValue(context); / / {6 = 6, 8 = 8}Copy the code

In addition to returning all selected elements, it can also be used to get the first or last value. The syntax for the selection that gets the first entry matched is ^[…] The last matching selection syntax is $[…].

Set the projection

A collection projection is also a powerful feature that can take a field of an object in a list or map and return it as a collection, similar to the map operation in Stream

ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(Arrays.asList( new User("Silently9521"), new User("Silently9522"), new User("Silently9527"))); Object nameList = parser.parseExpression("#root.! [#this.name]").getValue(context); //[Silently9521, Silently9522, Silently9527]Copy the code

IO /spring-fram…


Write to the end (pay attention, don’t get lost)

There may be more or less deficiencies and mistakes in the article, suggestions or opinions are welcome to comment and exchange.

Finally, white piao is not good, creation is not easy, I hope friends can like the comments pay attention to three even, because these are all the power source I share 🙏


I have handwritten a simple version of SpringMVC from scratch, as well as the preparation of a detailed description of the document, I hope to help partners in-depth understanding of the core principle of SpringMVC, friends in need welcome to pay attention to the public number: Beta learning JAVA, reply to the source code