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.

Java

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();
  1. El valor de la variable de mensaje es 'Hello World'.
Kotlin
 
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") 
val message = exp.value as String
  1. 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:

Java

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 
String message = (String) exp.getValue();
  1. El valor de message ahora es '¡Hola mundo!'.
Kotlin

val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") 
val message = exp.value as String
  1. Valor message ahora es '¡Hola mundo!'.

El siguiente ejemplo de llamada a la propiedad JavaBean llama a la propiedad String Bytes:

Java

ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); 
byte[] bytes = (byte[]) exp.getValue();
  1. Esta línea convierte un literal en una matriz de bytes.
Kotlin

val parser = SpelExpressionParser()
// accesses 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") 
val bytes = exp.value as ByteArray
  1. 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:

Java

ExpressionParser parser = new SpelExpressionParser();
// accesses 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 
int length = (Integer) exp.getValue();
  1. 'Hello World'.bytes.length especifica la longitud del literal.
Kotlin

val parser = SpelExpressionParser()
// accesses 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") 
val length = exp.value as Int
  1. '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:

Java

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 
String message = exp.getValue(String.class);
  1. Crea una nueva String a partir de un literal y conviértela a mayúsculas.
Kotlin

val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") 
val message = exp.getValue(String::class.java)
  1. 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:

Java

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

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

Java

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

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:

Java

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
Kotlin

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 modo IMMEDIATE 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:

Java

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

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:

Java

public class FieldValueTestBean {
    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }
    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin

class FieldValueTestBean {
    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}

El siguiente ejemplo muestra el equivalente, pero para un definidor de propiedades:

Java

public class PropertyValueTestBean {
    private String defaultLocale;
    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }
    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin

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:

Java

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;
    }
    // ...
}
Kotlin

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
    }
    // ...
}
Java

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;
    }
    // ...
}
Kotlin

class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
            @Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
    // ...
}