Spring Expression Language ("SpEL" para abreviar) es un poderoso lenguaje de expresión que admite la consulta y manipulación de un gráfico de objetos en tiempo de ejecución. La sintaxis del lenguaje es similar a Unified EL, pero ofrece características adicionales, en particular la invocación de métodos y la funcionalidad básica de creación de plantillas de cadenas.
Aunque existen varios otros lenguajes de expresión Java: OGNL, MVEL, JBoss EL y otros. - el lenguaje de expresión Spring Expression Language se creó para brindar a la comunidad Spring un lenguaje de expresión único y con buen soporte que se pueda usar en todos los productos del portafolio Spring. Las características del lenguaje están determinadas por los requisitos de los proyectos en el portafolio de Spring, incluidos los requisitos de herramientas para admitir la finalización de código en Spring Tools for Eclipse. . Sin embargo, SpEL se basa en una API independiente de la tecnología que permite la integración de otras implementaciones de lenguajes de expresión si fuera necesario.
Aunque SpEL es el producto de evaluación de expresiones fundamental en el portafolio de Spring, no está directamente relacionado. a Spring y se puede utilizar de forma independiente. Para demostrar la autocontención, muchos de los ejemplos de este capítulo utilizan SpEL como si fuera un lenguaje de expresión independiente. Esto requiere la creación de varias clases de infraestructura de arranque, como un analizador. La mayoría de los usuarios de Spring no necesitan lidiar con este marco, por lo que pueden crear simplemente cadenas de expresiones para evaluar. Un ejemplo de un uso tan típico es la integración de SpEL en la creación de definiciones de beans basadas en XML o en anotaciones.
Este capítulo analiza las características
del lenguaje de expresión, su API y la sintaxis del lenguaje. . En algunos lugares, las clases Inventor
y Society
se utilizan como objetivos para evaluar expresiones. Las declaraciones de estas clases y los
datos utilizados para completarlas se enumeran al final del capítulo.
El lenguaje de expresión admite la siguiente funcionalidad:
Literal expresiones (literales);
Expresiones literales (literales);
Expresiones literales (literales);
Operadores booleanos y relacionales;
Expresiones regulares;
Expresiones de clase;
Acceso a propiedades, arrays, listas y arrays asociativos;
Acceso a métodos;
Operadores de relación;
Asignación;
Llamar a constructores;
Enlaces a contenedores;
Construcción de una matriz;
Listas incrustadas;
Matrices asociativas incrustadas;
Operador ternario;
Variables;
Funciones definidas por el usuario;
Proyección de colecciones;
Obtención de colecciones;
Expresiones de plantilla.
Computación
Esta sección presenta el uso simple de SpEL y su lenguaje de expresión.
El
siguiente código utiliza la API SpEL para evaluar la expresión de cadena literal Hello
World
.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
- El valor de la variable de mensaje es
'Hello World'
.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'")
val message = exp.value as String
- El valor de la variable de mensaje es
'Hello World'
.
Las clases e interfaces SpEL que es más probable que utilice se encuentran en el paquete org.springframework.expression
y sus subpaquetes, como spel.support
.
La interfaz ExpressionParser
es responsable
de analizar la cadena de expresión. En el ejemplo anterior, la cadena de expresión es una cadena
literal, indicada
por las comillas simples que la rodean. La interfaz Expression
es responsable de
evaluar una cadena de
expresión previamente definida. Se pueden generar dos excepciones ParseException
y
EvaluationException
al llamar a parser.parseExpression
y exp.getValue
,
respectivamente.
SpEL admite una amplia gama de funciones, como la invocación de métodos, el acceso a propiedades y la invocación de constructores.
En el siguiente ejemplo de invocación de métodos, llamamos al método concat
en
un literal de cadena:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
- El valor de
message
ahora es '¡Hola mundo!'.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')")
val message = exp.value as String
- Valor
message
ahora es '¡Hola mundo!'.
El siguiente ejemplo de llamada a la propiedad JavaBean llama a la propiedad String
Bytes
:
ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
- Esta línea convierte un literal en una matriz de bytes.
val parser = SpelExpressionParser()
// accesses 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes")
val bytes = exp.value as ByteArray
- Esta línea convierte un literal en una matriz de bytes.
SpEL también admite propiedades anidadas usando notación de puntos estándar (por ejemplo,
prop1.prop2.prop3
), así como también establece los valores de propiedad en
consecuencia. También es
posible acceder a campos públicos.
El siguiente ejemplo muestra cómo utilizar la notación de puntos para obtener la longitud de un literal:
ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
'Hello World'.bytes.length
especifica la longitud del literal.
val parser = SpelExpressionParser()
// accesses 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length")
val length = exp.value as Int
'Hello World'.bytes.length
especifica la longitud del literal.
Se puede llamar al constructor String en lugar de usar un literal de cadena, como se muestra en el siguiente ejemplo:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
- Crea una nueva
String
a partir de un literal y conviértela a mayúsculas.
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")
val message = exp.getValue(String::class.java)
- Cree una nueva
String
a partir de un literal y conviértala a mayúsculas.
Tenga en cuenta el uso del método genérico: public <T> T getValue(Clase<T> desiredResultType)
. El uso de este método elimina la necesidad de convertir el valor de la expresión al
tipo de
resultado deseado. Si el valor no se puede convertir al tipo T
o convertir utilizando
un conversor
de tipos registrado, entonces se genera una EvaluationException
.
Un uso más común de SpEL es proporcionar una cadena de expresión que se evalúa contra una instancia
de objeto
específica (llamada objeto raíz). El siguiente ejemplo muestra cómo obtener la propiedad
name
de
una instancia de la clase Inventor
o crear una condición booleana:
// Create and configure the calendar
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");
// Parse the name as an expression String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla" exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and configure the calendar
val c = GregorianCalendar() c .set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name")
// Parse the name as an expression
val name = exp.getValue( tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
Información básica sobre EvaluationContext
Se utiliza la interfaz EvaluationContext
en expresiones de evaluación para resolver propiedades, métodos o campos, y para realizar
conversiones de tipos.
Spring proporciona dos implementaciones.
SimpleEvaluationContext
: expone un subconjunto de las características principales del lenguaje SpEL y opciones de configuración para categorías de expresiones que no requieren la sintaxis SpEL completa. y debe ser son significativamente limitados. Los ejemplos incluyen, entre otros, expresiones de enlace de datos y filtros basados en propiedades.StandardEvaluationContext
: abre la gama completa de capacidades del lenguaje SpEL y opciones de configuración. Puede usarlo para especificar el objeto raíz predeterminado y configurar todas las estrategias relacionadas con la evaluación disponibles.
SimpleEvaluationContext
está diseñado para proporcionar soporte de sintaxis parcial.
sólo lenguaje
SpEL. Excluye referencias a tipos, constructores y referencias de beans de Java. También requiere
una elección
explícita del nivel de soporte para propiedades y métodos en expresiones. De forma predeterminada,
el método de
fábrica estático create()
permite acceso de solo lectura a las propiedades. También
puede hacer que un
constructor ajuste el nivel de soporte requerido, centrándose en un parámetro específico o alguna
combinación de los
siguientes parámetros:
Solo un
PropertyAccessor
especial (sin reflexión)Propiedades de enlace de datos para acceso de solo lectura
Propiedades de enlace de datos para lectura y escritura acceso
Conversión de tipos
De forma predeterminada, SpEL utiliza el servicio de conversión disponible en
Spring core (org.springframework.core.convert.ConversionService
). Este servicio
de conversión
viene con muchos convertidores integrados para realizar conversiones generales, pero también es
completamente
extensible para permitir que se agreguen conversiones personalizadas entre tipos. Además, puede
funcionar con
genéricos. Esto significa que si trabaja con tipos escritos en expresiones, SpEL intenta realizar
conversiones para
preservar la corrección del tipo de cualquier objeto que encuentre.
¿Qué significa esto realmente? Supongamos
que se utiliza una asignación con setValue()
para establecer la propiedad
List
. El tipo de
propiedad es en realidad List<Boolean>
. SpEL reconoce que los elementos de la
lista deben
convertirse a Boolean
antes de colocarse en ella. El siguiente ejemplo muestra cómo
hacer esto:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed here as a String. SpEL and the conversion service
// recognizes that it should be Boolean and converts it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b - false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed as a String here. SpEL and the conversion service
// recognizes that it should be Boolean and converts it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b - false
val b = simple.booleanList[0]
Configuración del analizador
Puede configurar el analizador de expresiones SpEL utilizando el objeto
de configuración del analizador
(org.springframework.expression.spel.SpelParserConfiguration
). El
objeto de configuración controla la lógica operativa de algunos de los componentes de la expresión.
Por ejemplo, si
está indexando una matriz o colección y el elemento en el índice especificado es null
,
SpEL puede crear
automáticamente ese elemento. Esto tiene sentido cuando se utilizan expresiones que constan de una
cadena de
referencias de propiedades. Si indexa una matriz o lista y especifica un índice que está más allá
del tamaño actual
de la matriz o lista, SpEL puede hacer crecer automáticamente la matriz o lista para acomodar ese
índice. Para
agregar un elemento en el índice especificado, SpEL intentará crear el elemento utilizando el
constructor de tipo de
elemento predeterminado antes de establecer el valor especificado. Si el tipo de elemento no tiene
un constructor
predeterminado, se agregará null
a la matriz o lista. Si no hay un solucionador
integrado o especial
que sepa cómo establecer el valor, null
permanecerá en la matriz o lista en el índice
especificado. El
siguiente ejemplo demuestra la expansión automática de la lista:
class Demo {
public List<String> list;
}
// Enable:
// - automatic initialization of an empty (null) link
// - the autocollection is expanded
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);
// demo.list will now be an actual collection of 4 entries
// Each entry is a new empty line
class Demo {
var list: List<String>? = null
}
// Enable:
// - automatic initialization of an empty (null) link
// - the autocollection is expanded
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3] ")
val demo = Demo() val o = expression.getValue(demo)
// demo.list will now be an actual collection of 4 entries
// Each entry is a new empty string
Compilar con SpEL
Spring Framework 4.1 incluye un compilador de expresiones básico. Normalmente, las expresiones se interpretan, lo que proporciona una mayor flexibilidad dinámica en la evaluación, pero no proporciona un rendimiento óptimo. Para el uso ocasional de expresiones, esto está bien, pero cuando se utilizan otros componentes como Spring Integration, el rendimiento puede ser un factor y no hay una necesidad real de dinamismo.
El compilador SpEL está diseñado para satisfacer esta necesidad. En el momento de la evaluación, el compilador genera una clase Java que incorpora la lógica de ejecución de la expresión en tiempo de ejecución y utiliza esta clase para realizar una evaluación de la expresión mucho más rápida. Como no es necesario escribir expresiones, el compilador compila utilizando la información recopilada durante las evaluaciones interpretadas de la expresión. Por ejemplo, no reconoce el tipo de una referencia de propiedad únicamente a partir de la expresión, pero durante el primer cálculo interpretado comienza a comprender de qué se trata. Naturalmente, compilar a partir de dicha información derivada puede generar problemas en el futuro si los tipos de los distintos elementos de la expresión cambian con el tiempo. Por este motivo, la compilación es mejor para expresiones cuya información de tipo no cambiará cuando se evalúen repetidamente.
Considere la siguiente expresión básica:
someArray[0].someProperty.someOtherProperty < 0.1
Debido a que la expresión anterior implica acceso a matrices, algunas desreferenciaciones de propiedades y operaciones numéricas, el beneficio de rendimiento puede ser muy notable. En la prueba de microbenchmark de ejemplo de 50.000 iteraciones, tomó 75 ms evaluar usando el intérprete, pero solo 3 ms usando la versión compilada de la expresión.
Configuración del compilador
El compilador no está habilitado de forma predeterminada, pero puede activarlo de dos maneras diferentes. Puede habilitarlo mediante el procedimiento de configuración del analizador o mediante una propiedad Spring si el uso de SpEL está integrado en otro bean. Esta sección analiza ambas opciones.
El compilador puede funcionar en uno de tres modos, que se reflejan en el
tipo de enumeración org.springframework.expression.spel.SpelCompilerMode
. Los modos son
los siguientes:
OFF
(predeterminado): el compilador está apagado.IMMEDIATE
: En modo inmediato, las expresiones se compilan lo más rápido posible. Normalmente esto ocurre después del primer cálculo interpretado. Si la expresión compilada falla (generalmente debido a un cambio de tipo, como se describió anteriormente), la persona que llama al código de evaluación de la expresión recibe una excepción.MIXED
: En modo mixto, las expresiones cambian sin problemas entre el modo interpretado y compilado a lo largo del tiempo. Después de un cierto número de ejecuciones interpretadas, se cambian al formato compilado, pero si algo sale mal en el formulario compilado (por ejemplo, el tipo cambia, como se describió anteriormente), la expresión vuelve automáticamente al formato interpretado. Algún tiempo después, la expresión puede generar otra forma compilada y cambiar a esa. Básicamente, una excepción que el usuario recibe en el modoIMMEDIATE
se maneja internamente.
El modo IMMEDIATE
existe porque que el modo MIXED
puede causar problemas
con
expresiones que tienen efectos secundarios. Si una expresión compilada produce un error catastrófico
después de
tener un éxito parcial, es posible que ya haya realizado alguna acción que afectó el estado del
sistema. Si esto
sucede, es posible que el código de llamada no desee que se vuelva a ejecutar silenciosamente en
modo interpretado,
ya que parte de la expresión podría ejecutarse dos veces.
Después de elegir un modo, use SpelParserConfiguration
para configurar el analizador. El siguiente
ejemplo muestra cómo hacer esto:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
Si especifica un modo de compilador, también puede especificar un cargador de clases (se permite pasar nulo). Las expresiones compiladas se definen en un cargador de clases secundario creado bajo cualquier clase especificada. Es importante asegurarse de que, si se especifica un cargador de clases, pueda reconocer todos los tipos involucrados en el proceso de evaluación de expresiones. Si no especifica un cargador de clases, se utilizará el cargador de clases predeterminado (normalmente el cargador de clases de contexto para el hilo que se ejecuta mientras se evalúan las expresiones).
El segundo método de configuración del compilador está pensado para
para ser utilizado si SpEL está implementado en cualquier - otro componente, y no es posible
configurarlo a través
de un objeto de configuración. En estos casos, puede establecer la propiedad
spring.expression.compiler.mode
a través de una propiedad del sistema desde la JVM (o
mediante el
mecanismo SpringProperties
) en uno de los tipos de enumeración. valores
SpelCompilerMode(off
, inmediato
o mixed
).
Limitaciones del compilador
Inicio con Spring Framework 4.1, se utiliza un marco de compilación básico. Sin embargo, el marco aún no admite la compilación de todo tipo de expresiones. La atención inicial se centró en expresiones comunes que probablemente se utilicen en contextos críticos para el rendimiento. Los siguientes tipos de expresiones no se pueden compilar actualmente:
Expresiones relacionadas con tareas;
Expresiones que utilizan el servicio de conversión;
Expresiones que utilizan reconocedores o descriptores de acceso especiales;
Expresiones que utilizan selección o proyección.
Se podrán compilar más tipos de expresiones en el futuro.
Expresiones en definiciones de beans
Las expresiones SpEL se pueden usar para definir instancias de BeanDefinition
con
metadatos de
configuración basados en XML o en anotaciones. En ambos casos, la sintaxis para definir una
expresión es
#{ <expression string> }
.
4.2.1. Configuración XML
El valor de una propiedad o argumento de constructor se puede especificar mediante expresiones, como se muestra en el siguiente ejemplo:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
Todos los beans en el contexto de la aplicación están disponibles como variables predefinidas con su
nombre de
bean normal. Esto incluye beans de contexto estándar como environment
(como org.springframework.core.env.Environment
),
así como systemProperties
y systemEnvironment
(escriba Map<String,
Object>
) para obtener acceso al entorno de ejecución.
El siguiente ejemplo demuestra el acceso
a systemProperties
bean como para la variable SpEL:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
Tenga en cuenta que en este caso no es necesario anteponer a la variable predefinida el símbolo
#
.
También puede hacer referencia a otras propiedades del bean por su nombre, como se muestra en el siguiente ejemplo:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
Configuración de anotación
Para establecer un valor predeterminado, puede colocar la anotación
@Value
a campos, métodos, parámetros de métodos o parámetros de constructor.
En el siguiente ejemplo, se establece un valor predeterminado para un campo:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
El siguiente ejemplo muestra el equivalente, pero para un definidor de propiedades:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
Los métodos y constructores descubiertos y asociados automáticamente también pueden usar
@Value
anotación, como se muestra en los siguientes ejemplos:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
private lateinit var defaultLocale: String
@Autowired
fun configure(movieFinder: MovieFinder,
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
this.movieFinder = movieFinder
this.defaultLocale = defaultLocale
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
// ...
}