Guia de idiomas do Spring Expression
1. Visão geral
O Spring Expression Language (SpEL) é uma linguagem de expressão poderosa que oferece suporte à consulta e manipulação de um gráfico de objetos em tempo de execução. Pode ser usado com configurações XML ou Spring baseadas em anotações.
Existem vários operadores disponíveis no idioma:
Type | *Operators * |
---|---|
Arithmetic |
+, -,* ,/, %, ^, div, mod |
Relational |
<, >, ==, !=, ⇐, >=, lt, gt, eq, ne, le, ge |
Logical |
and, or, not, &&, |
, ! |
|
Conditional |
?: |
Regex |
matches |
*2. Operadores *
Para esses exemplos, usaremos a configuração baseada em anotação. Mais detalhes sobre a configuração XML podem ser encontrados nas seções posteriores deste artigo.
As expressões SpEL começam com o símbolo _ # _ e são colocadas entre chaves: _ # \ {expression} _. As propriedades podem ser referenciadas de maneira semelhante, começando com um símbolo _ $ _ e envoltas em chaves: _ $ \ {property.name} _. Os espaços reservados de propriedade não podem conter expressões SpEL, mas as expressões podem conter referências de propriedade:
#{${someProperty} + 2}
No exemplo acima, suponha que someProperty tenha o valor 2, portanto a expressão resultante seria 2 + 2, que seria avaliada como 4.
====* 2.1 Operadores aritméticos *
Todos os operadores aritméticos básicos são suportados.
@Value("#{19 + 1}")//20
private double add;
@Value("#{'String1 ' + 'string2'}")//"String1 string2"
private String addString;
@Value("#{20 - 1}")//19
private double subtract;
@Value("#{10* 2}")//20
private double multiply;
@Value("#{36/2}")//19
private double divide;
@Value("#{36 div 2}")//18, the same as for/operator
private double divideAlphabetic;
@Value("#{37 % 10}")//7
private double modulo;
@Value("#{37 mod 10}")//7, the same as for % operator
private double moduloAlphabetic;
@Value("#{2 ^ 9}")//512
private double powerOf;
@Value("#{(2 + 2) *2 + 9}")//17
private double brackets;
As operações de divisão e módulo possuem aliases alfabéticos, div para / e mod para _% _. O operador _ + _ também pode ser usado para concatenar seqüências de caracteres.
====* 2.2 Operadores relacionais e lógicos *
Todas as operações relacionais e lógicas básicas também são suportadas.
@Value("#{1 == 1}")//true
private boolean equal;
@Value("#{1 eq 1}")//true
private boolean equalAlphabetic;
@Value("#{1 != 1}")//false
private boolean notEqual;
@Value("#{1 ne 1}")//false
private boolean notEqualAlphabetic;
@Value("#{1 < 1}")//false
private boolean lessThan;
@Value("#{1 lt 1}")//false
private boolean lessThanAlphabetic;
@Value("#{1 <= 1}")//true
private boolean lessThanOrEqual;
@Value("#{1 le 1}")//true
private boolean lessThanOrEqualAlphabetic;
@Value("#{1 > 1}")//false
private boolean greaterThan;
@Value("#{1 gt 1}")//false
private boolean greaterThanAlphabetic;
@Value("#{1 >= 1}")//true
private boolean greaterThanOrEqual;
@Value("#{1 ge 1}")//true
private boolean greaterThanOrEqualAlphabetic;
Todos os operadores relacionais também têm aliases alfabéticos. Por exemplo, nas configurações baseadas em XML, não podemos usar operadores que contêm colchetes angulares (_ <_, _ ⇐, _ > _, _> = _). Em vez disso, podemos usar _lt (menor que), le (menor que ou igual), gt (maior que) ou ge (maior que ou igual).
====* 2.3 Operadores lógicos *
O SpEL suporta todas as operações lógicas básicas:
@Value("#{250 > 200 && 200 < 4000}")//true
private boolean and;
@Value("#{250 > 200 and 200 < 4000}")//true
private boolean andAlphabetic;
@Value("#{400 > 300 || 150 < 100}")//true
private boolean or;
@Value("#{400 > 300 or 150 < 100}")//true
private boolean orAlphabetic;
@Value("#{!true}")//false
private boolean not;
@Value("#{not true}")//false
private boolean notAlphabetic;
Assim como os operadores aritméticos e relacionais, todos os operadores lógicos também possuem clones alfabéticos.
====* 2.4. Operadores condicionais *
Operadores condicionais são usados para injetar valores diferentes, dependendo de alguma condição:
@Value("#{2 > 1 ? 'a' : 'b'}")//"a"
private String ternary;
O operador ternário é usado para executar lógica condicional compacta se-então-outro dentro da expressão. Neste exemplo, estamos tentando verificar se houve true ou não.
Outro uso comum para o operador ternário é verificar se alguma variável é null e retornar o valor da variável ou um padrão:
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;
O operador Elvis é uma maneira de reduzir a sintaxe do operador ternário para o caso acima usado na linguagem Groovy. Também está disponível no SpEL. O código abaixo é equivalente ao código acima:
@Value("#{someBean.someProperty ?: 'default'}")//Will inject provided string if someProperty is null
private String elvis;
====* 2.5 Usando Regex no SpEL *
O operador matches pode ser usado para verificar se uma sequência corresponde ou não a uma determinada expressão regular.
@Value("#{'100' matches '\\d+' }")//true
private boolean validNumericStringResult;
@Value("#{'100fghdjf' matches '\\d+' }")//false
private boolean invalidNumericStringResult;
@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }")//true
private boolean validAlphabeticStringResult;
@Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }")//false
private boolean invalidAlphabeticStringResult;
@Value("#{someBean.someValue matches '\d+'}")//true if someValue contains only digits
private boolean validNumericValue;
====* 2.6 Acessando objetos List e Map *
Com a ajuda do SpEL, podemos acessar o conteúdo de qualquer Map ou List no contexto. Criaremos um novo workersHolder de bean que armazenará informações sobre alguns trabalhadores e seus salários em um List e um Map:
@Component("workersHolder")
public class WorkersHolder {
private List<String> workers = new LinkedList<>();
private Map<String, Integer> salaryByWorkers = new HashMap<>();
public WorkersHolder() {
workers.add("John");
workers.add("Susie");
workers.add("Alex");
workers.add("George");
salaryByWorkers.put("John", 35000);
salaryByWorkers.put("Susie", 47000);
salaryByWorkers.put("Alex", 12000);
salaryByWorkers.put("George", 14000);
}
//Getters and setters
}
Agora podemos acessar os valores das coleções usando o SpEL:
@Value("#{workersHolder.salaryByWorkers['John']}")//35000
private Integer johnSalary;
@Value("#{workersHolder.salaryByWorkers['George']}")//14000
private Integer georgeSalary;
@Value("#{workersHolder.salaryByWorkers['Susie']}")//47000
private Integer susieSalary;
@Value("#{workersHolder.workers[0]}")//John
private String firstWorker;
@Value("#{workersHolder.workers[3]}")//George
private String lastWorker;
@Value("#{workersHolder.workers.size()}")//4
private Integer numberOfWorkers;
===* 3. Use na configuração do Spring *
====* 3.1 Referenciando um Bean *
Neste exemplo, veremos como usar o SpEL na configuração baseada em XML. Expressões podem ser usadas para referenciar beans ou campos/métodos de bean. Por exemplo, suponha que tenhamos as seguintes classes:
public class Engine {
private int capacity;
private int horsePower;
private int numberOfCylinders;
//Getters and setters
}
public class Car {
private String make;
private int model;
private Engine engine;
private int horsePower;
//Getters and setters
}
Agora criamos um contexto de aplicativo no qual expressões são usadas para injetar valores:
<bean id="engine" class="com..spring.spel.Engine">
<property name="capacity" value="3200"/>
<property name="horsePower" value="250"/>
<property name="numberOfCylinders" value="6"/>
</bean>
<bean id="someCar" class="com..spring.spel.Car">
<property name="make" value="Some make"/>
<property name="model" value="Some model"/>
<property name="engine" value="#{engine}"/>
<property name="horsePower" value="#{engine.horsePower}"/>
</bean>
Dê uma olhada no bean someCar. Os campos engine e horsePower de someCar usam expressões que são referências de bean aos campos engine bean e horsePower respectivamente.
Para fazer o mesmo com configurações baseadas em anotações, use a anotação _ @ Value (“# \ {expression}”) _.
====* 3.2 Usando operadores na configuração *
Cada operador da primeira seção deste artigo pode ser usado em configurações baseadas em XML e em anotações. No entanto, lembre-se de que na configuração baseada em XML, não podemos usar o operador de colchete angular “<“. Em vez disso, devemos usar os aliases alfabéticos, como lt (menor que) ou le (menor ou igual a). Para configurações baseadas em anotações, não existem essas restrições.
public class SpelOperators {
private boolean equal;
private boolean notEqual;
private boolean greaterThanOrEqual;
private boolean and;
private boolean or;
private String addString;
//Getters and setters
@Override
public String toString() {
//toString which include all fields
}
Agora vamos adicionar um bean spelOperators ao contexto do aplicativo:
<bean id="spelOperators" class="com..spring.spel.SpelOperators">
<property name="equal" value="#{1 == 1}"/>
<property name="notEqual" value="#{1 lt 1}"/>
<property name="greaterThanOrEqual" value="#{someCar.engine.numberOfCylinders >= 6}"/>
<property name="and" value="#{someCar.horsePower == 250 and someCar.engine.capacity lt 4000}"/>
<property name="or" value="#{someCar.horsePower > 300 or someCar.engine.capacity > 3000}"/>
<property name="addString" value="#{someCar.model + ' manufactured by ' + someCar.make}"/>
</bean>
Recuperando esse bean do contexto, podemos verificar se os valores foram injetados corretamente:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");
Aqui podemos ver a saída do método toString do bean spelOperators:
[equal=true, notEqual=false, greaterThanOrEqual=true, and=true,
or=true, addString=Some model manufactured by Some make]
===* 4. Analisando expressões programaticamente *
Às vezes, podemos querer analisar expressões fora do contexto da configuração. Felizmente, isso é possível usando SpelExpressionParser. Podemos usar todos os operadores que vimos nos exemplos anteriores, mas devemos usá-los sem chaves e símbolo de hash. Ou seja, se queremos usar uma expressão com o operador _ + _ quando usado na configuração do Spring, a sintaxe é _ # \ {1 + 1}; _ quando usada fora da configuração, a sintaxe é simplesmente 1 + 1.
Nos exemplos a seguir, usaremos os beans Car e Engine definidos na seção anterior.
====* 4.1 Usando ExpressionParser *
Vejamos um exemplo simples:
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();
ExpressionParser é responsável por analisar as cadeias de expressão. Neste exemplo, o analisador SpEL simplesmente avaliará a string ‘Any String' como uma expressão. Sem surpresa, o resultado será ‘Qualquer String'.
Assim como o uso do SpEL na configuração, podemos usá-lo para chamar métodos, acessar propriedades ou chamar construtores.
Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();
Além disso, em vez de operar diretamente no literal, poderíamos chamar o construtor:
Expression expression = expressionParser.parseExpression("new String('Any string').length()");
Também podemos acessar a propriedade bytes da classe String, da mesma maneira, resultando na representação de bytes [] da string:
Expression expression = expressionParser.parseExpression("'Any string'.bytes");
byte[] result = (byte[]) expression.getValue();
Podemos encadear chamadas de método, assim como no código Java normal:
Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
Integer result = (Integer) expression.getValue();
Nesse caso, o resultado será 9, porque substituímos o espaço em branco pela string vazia. Se não desejamos converter o resultado da expressão, podemos usar o método genérico T getValue (Classe <T> allowedResultType) _, no qual podemos fornecer o tipo de classe desejado que queremos que seja retornado. Observe que _EvaluationException será lançada se o valor retornado não puder ser convertido em desiredResultType:
Integer result = expression.getValue(Integer.class);
O uso mais comum é fornecer uma sequência de expressões avaliada em uma instância de objeto específica:
Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("model");
EvaluationContext context = new StandardEvaluationContext(car);
String result = (String) expression.getValue(context);
Nesse caso, o resultado será igual ao valor do campo model do objeto car, “Model 3“. A classe StandardEvaluationContext especifica em qual objeto a expressão será avaliada.
Não pode ser alterado após a criação do objeto de contexto. StandardEvaluationContext é caro de construir e, durante o uso repetido, cria um estado em cache que permite que avaliações de expressão subsequentes sejam executadas mais rapidamente. Devido ao armazenamento em cache, é uma boa prática reutilizar StandardEvaluationContext onde for possível, se o objeto raiz não for alterado.
No entanto, se o objeto raiz for alterado repetidamente, podemos usar o mecanismo mostrado no exemplo abaixo:
Expression expression = expressionParser.parseExpression("model");
String result = (String) expression.getValue(car);
Aqui, chamamos o método getValue com um argumento que representa o objeto ao qual queremos aplicar uma expressão SpEL. Também podemos usar o método getValue genérico, assim como antes:
Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
boolean result = expression.getValue(car, Boolean.class);
====* 4.2 Usando ExpressionParser para definir um valor *
Usando o método setValue no objeto Expression retornado pela análise de uma expressão, podemos definir valores nos objetos. O SpEL cuidará da conversão de tipos. Por padrão, o SpEL usa org.springframework.core.convert.ConversionService. Podemos criar nosso próprio conversor personalizado entre tipos. ConversionService reconhece genéricos, portanto pode ser usado com genéricos. Vamos dar uma olhada em como podemos usá-lo na prática:
Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);
CarPark carPark = new CarPark();
carPark.getCars().add(car);
StandardEvaluationContext context = new StandardEvaluationContext(carPark);
ExpressionParser expressionParser = new SpelExpressionParser();
expressionParser.parseExpression("cars[0].model").setValue(context, "Other model");
O objeto carro resultante terá model "Outro modelo", que foi alterado de "Model 3".
====* 4.3 Configuração do Analisador *
No exemplo a seguir, usaremos a seguinte classe:
public class CarPark {
private List<Car> cars = new ArrayList<>();
//Getter and setter
}
É possível configurar ExpressionParser chamando o construtor com um objeto SpelParserConfiguration . Por exemplo, se tentarmos adicionar o objeto car ao array cars da classe CarPark sem configurar o analisador, obteremos um erro como este:
EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid
Podemos alterar o comportamento do analisador, para permitir a criação automática de elementos se o índice especificado for nulo (autoGrowNullReferences, _ o primeiro parâmetro do construtor) ou aumentar automaticamente uma matriz ou lista para acomodar elementos além do tamanho inicial ( _autoGrowCollections, o segundo parâmetro).
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
StandardEvaluationContext context = new StandardEvaluationContext(carPark);
ExpressionParser expressionParser = new SpelExpressionParser(config);
expressionParser.parseExpression("cars[0]").setValue(context, car);
Car result = carPark.getCars().get(0);
O objeto car resultante será igual ao objeto car que foi definido como o primeiro elemento da matriz cars do objeto carPark do exemplo anterior.
===* 5. Conclusão*
O SpEL é uma linguagem de expressão poderosa e bem suportada que pode ser usada em todos os produtos do portfólio Spring. Ele pode ser usado para configurar aplicativos Spring ou gravar analisadores para executar tarefas mais gerais em qualquer aplicativo.
Os exemplos de código deste artigo estão disponíveis no linked GitHub repository.