EL表达式,全称Expression Language,是一种表达式语言,它借鉴了JavaScript和XPath的表达式语言,并设计用来简化在Java Web应用程序中的表达式。在JSP 2.0及以后的版本中,EL表达式被引入,允许开发者在JSP页面上更方便地访问和操作数据。
在Spring框架中,Spring EL(Spring Expression Language)被引入,以提供一种更强大、更简洁的方式来装配Bean,处理运行时数据,并执行方法。Spring EL允许开发者通过表达式将数据装配到属性或构造函数中,调用JDK中提供的静态常量,获取外部Properties文件中的配置,甚至可以对不同Bean的字段进行计算再进行赋值。
总的来说,Spring EL是Spring框架中一个重要的工具,它简化了数据访问和操作,提高了应用程序的灵活性和可维护性。它是一种基于Java的表达式语言,它可以在运行时对Spring管理的对象进行动态访问和操作。
Spring EL的基本语法结构如下:
#{expression}
其中,expression
是一个符合Spring EL语法的表达式。
当然,下面我将详细介绍Spring EL表达式的语法。
Spring EL的语法非常直观且易于学习,它允许你通过简单的表达式来访问和操作Java对象。以下是Spring EL表达式的一些基本语法元素:
在Spring EL中,你可以使用.
来访问对象的属性或方法。例如:
// 访问属性
#{user.name}
// 调用方法(无参数)
#{user.getName()}
// 调用方法(有参数)
#{user.getFullName('John', 'Doe')}
如果属性名或方法与Java关键字冲突,你可以使用['']
语法来访问它们:
#{user['class']} // 访问名为class的属性
Spring EL支持各种字面量,包括字符串、数字、布尔值等:
#{100} // 数字字面量
#{'Hello'} // 字符串字面量
#{true} // 布尔字面量
你可以使用标准的算术运算符来执行计算:
#{10 + 20} // 加法
#{30 - 10} // 减法
#{10 * 5} // 乘法
#{100 / 2} // 除法
#{15 % 4} // 取模
Spring EL支持各种比较运算符,用于比较值:
#{10 == 10} // 等于
#{10 != 20} // 不等于
#{5 < 10} // 小于
#{15 <= 10} // 小于等于
#{10 > 5} // 大于
#{10 >= 5} // 大于等于
你可以使用逻辑运算符来组合或修改布尔表达式:
#{true && false} // 逻辑与
#{true || false} // 逻辑或
#{!true} // 逻辑非
Spring EL支持三元运算符,它允许你根据条件选择值:
#{10 > 5 ? 'Greater' : 'Lesser or Equal'} // 如果10大于5,则结果为'Greater',否则为'Lesser or Equal'
你可以使用Spring EL来访问和操作集合(如列表、集合)和数组:
// 访问列表元素
#{myList[0]} // 访问列表的第一个元素
#{myList[1]} // 访问列表的第二个元素
// 访问数组元素
#{myArray[0]} // 访问数组的第一个元素
// 访问Map元素
#{myMap['key']} // 访问Map中键为'key'的值
#{myMap.key} // 如果键是合法的标识符,也可以这样访问
对于集合,你可以使用.
和?[]
来进行投影(选择集合中每个元素的某个属性)和选择(基于某个条件过滤集合):
// 投影 - 选择每个用户的名字
#{users.![name]}
// 选择 - 选择年龄大于18的用户
#{users.?[age > 18]}
Spring EL允许你调用静态方法,但通常这需要在Spring配置中明确允许:
// 调用静态方法(需要配置)
#{T(java.lang.Math).random()}
在这里,T()
函数用于获取类类型,然后你可以调用其静态方法。但请注意,出于安全考虑,默认情况下Spring EL不允许调用静态方法,你需要显式地在Spring配置中启用它。
Spring EL还支持正则表达式匹配:
#{user.email matches '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'} // 检查email是否匹配正则表达式
在Spring EL中,你可以调用带有参数的方法。这些参数可以是字面量、变量表达式或其他EL表达式。方法参数按照它们在EL表达式中出现的顺序进行传递。
// 调用带有参数的方法
#{user.getFullName('John', 'Doe')} // 假设getFullName接收两个字符串参数
Spring EL允许你注册自定义函数,这些函数可以在EL表达式中调用。自定义函数通过实现特定的接口或使用Spring的@Value
和MethodInvokingFactoryBean
来定义。
一旦注册了自定义函数,你就可以在EL表达式中像调用内置函数一样调用它们。
例如,假设你注册了一个名为concat
的自定义函数,你可以这样使用它:
#{concat('Hello', ' World')} // 调用自定义的concat函数
要实现自定义函数,你需要创建一个Java类,实现Spring的org.springframework.expression.ExpressionParser
接口,或者使用Spring提供的StandardEvaluationContext
来注册你的函数。
Spring EL支持内联列表,允许你在表达式中直接定义列表。这对于临时需要列表的场景非常有用。
// 定义内联列表
#{[1, 2, 3, 4, 5]} // 创建一个包含整数的列表
#{['apple', 'banana', 'cherry']} // 创建一个包含字符串的列表
你还可以在内联列表中混合使用不同类型的元素。
与内联列表类似,Spring EL也支持内联映射(有时也称为字典或哈希表)。你可以使用{key1: value1, key2: value2, ...}
的语法来定义它们。
// 定义内联映射
#{{'key1': 'value1', 'key2': 'value2'}} // 创建一个映射,其中key1对应value1,key2对应value2
内联映射在需要快速定义键值对集合时非常有用。
在Spring EL中,你可以定义和使用变量。这些变量可以根据它们的作用域(如方法作用域、请求作用域、会话作用域等)进行存储和访问。
// 设置变量(通常在Spring配置中完成)
#set($var = 'someValue')
// 使用变量
#{$var}
请注意,上面的#set
指令不是Spring EL标准语法的一部分,但某些Spring EL的扩展或模板引擎(如Thymeleaf)可能支持这种语法来设置变量。在纯Spring EL表达式中,变量的设置通常是通过Spring的上下文管理来完成的。
使用T()
运算符,你可以引用Java类型,并在必要时执行类型转换。这对于访问静态方法或执行类型转换特别有用。
// 引用Java类型并调用静态方法(需要配置支持)
#{T(java.lang.Math).random()} // 调用Math类的random静态方法
// 类型转换
#{T(java.lang.Integer).valueOf('42')} // 将字符串'42'转换为Integer类型
在某些情况下,你可能希望在EL表达式中使用模板文字,这些模板文字允许你插入表达式的值。虽然这不是Spring EL核心功能的一部分,但某些与Spring集成的模板引擎(如Thymeleaf或FreeMarker)提供了这种功能。
由于Spring EL非常强大,因此在使用时需要注意安全性。默认情况下,Spring会限制EL表达式的某些功能(如访问Java类、调用静态方法等),以防止潜在的安全风险。你可以通过配置来放宽这些限制,但这需要谨慎考虑。
总的来说,Spring EL是一个功能丰富的表达式语言,它提供了许多高级特性和功能来满足复杂的应用程序需求。通过合理地使用这些特性,你可以编写出更简洁、更灵活的代码。
Spring EL在Spring框架中有广泛的应用场景,以下是一些常见的例子:
业务场景
在电商系统中,我们通常需要处理用户的购物车、订单以及订单中的商品项。本案例将模拟一个用户结算购物车的流程,并使用Spring EL来处理订单数据的计算和验证。
实体类
首先定义User
、Cart
、CartItem
、Product
和Order
等实体类。
public class User {
private String username;
// ... 其他属性 ...
// 省略getter和setter方法
}
public class Cart {
private List<CartItem> items = new ArrayList<>();
public BigDecimal getTotalPrice() {
return items.stream()
.map(CartItem::getTotalPrice)
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
}
// 省略getter和setter方法
}
public class CartItem {
private Product product;
private int quantity;
public BigDecimal getTotalPrice() {
return product.getPrice().multiply(BigDecimal.valueOf(quantity));
}
// 省略getter和setter方法
}
public class Product {
private String name;
private BigDecimal price;
// 省略getter和setter方法
}
public class Order {
private User user;
private List<OrderItem> orderItems = new ArrayList<>();
private BigDecimal totalAmount;
// 根据订单项计算订单总额
public void calculateTotalAmount() {
this.totalAmount = orderItems.stream()
.map(OrderItem::getTotalPrice)
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
}
// 省略getter和setter方法
}
public class OrderItem {
private Product product;
private int quantity;
public BigDecimal getTotalPrice() {
return product.getPrice().multiply(BigDecimal.valueOf(quantity));
}
// 省略getter和setter方法
}
服务类
接下来,我们创建一个服务类来模拟购物车结算流程,并使用Spring EL来处理业务逻辑。
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public Order createOrderFromCart(Cart cart, User user) {
Order order = new Order();
order.setUser(user);
ExpressionParser parser = new SpelExpressionParser();
// 遍历购物车中的商品项,转换为订单项
for (CartItem cartItem : cart.getItems()) {
OrderItem orderItem = new OrderItem();
orderItem.setProduct(cartItem.getProduct());
orderItem.setQuantity(cartItem.getQuantity());
// 使用Spring EL计算订单项的总价(虽然这里可以直接调用方法,但为了展示EL,我们依然使用它)
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("cartItem", cartItem);
BigDecimal totalPrice = parser.parseExpression("#cartItem.product.price * #cartItem.quantity")
.getValue(context, BigDecimal.class);
// 这里只是为了演示EL,实际上可以直接使用orderItem.getTotalPrice()
System.out.println("Order item total price calculated by Spring EL: " + totalPrice);
order.getOrderItems().add(orderItem);
}
// 计算订单总额(同样可以使用EL,但这里我们调用方法以保持清晰)
order.calculateTotalAmount();
return order;
}
}
注意:在实际应用中,我们通常不会使用Spring EL来计算订单项的总价或订单总额,因为直接调用方法会更加直观和高效。Spring EL更适合用于动态表达式求值,如配置文件中的条件判断、动态方法调用等场景。
配置类
为了简化配置,我们可以使用Java配置类来创建和配置ApplicationContext
。但在这个案例中,我们实际上不需要特殊的配置,因为服务类可以自动装配ApplicationContext
。不过,为了完整性,这里还是提供一个简单的配置类。
@Configuration
public class AppConfig {
// 可以在这里配置其他beans,如数据源、服务类等
// 但在这个案例中,我们不需要额外的配置
}
运行和测试
最后,我们可以编写一个简单的测试类来运行和测试我们的服务。
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testCreateOrderFromCart() {
User user = new User();
user.setUsername("testUser");
Product product1 = new Product();
product1.setName("Product 1");
product1.setPrice(new BigDecimal("10.00"));
Product product2 = new Product();
product2.setName("Product 2");
product2.setPrice(new BigDecimal("20.00"));
Cart cart = new Cart();
cart.getItems().add(new CartItem(product1, 2));
cart.getItems().add(new CartItem(product2, 1));
Order order = orderService.createOrderFromCart(cart, user);
assertNotNull(order);
assertEquals(2, order.getOrderItems().size());
assertEquals(new BigDecimal("40.00"), order.getTotalAmount());
}
}
这个测试类使用Spring Boot的测试功能来运行,并自动装配了OrderService
。它创建了一个购物车和用户,然后调用createOrderFromCart
方法来创建订单,并验证订单的内容是否正确。
注意:虽然这个案例包含了Spring EL的使用,但如前所述,这里使用Spring EL并不是最佳实践。在实际项目中,应该根据具体需求来决定是否使用Spring EL以及如何使用它来最大化其价值和灵活性。
与Spring EL相似的其他表达式语言包括JSP表达式语言(JSP EL)、OGNL(Object-Graph Navigation Language)和MVEL(MVFLEX Expression Language)等。以下是对这些技术的简要比较:
综上所述,Spring EL作为一种功能强大且易于使用的表达式语言,在Spring框架中发挥着重要作用。虽然它具有一定的学习成本和性能开销,但通过与Spring框架的无缝集成和丰富的功能支持,使得开发者能够更高效地处理动态数据和表达式计算任务。
术因分享而日新,每获新知,喜溢心扉。 诚邀关注公众号 『
码到三十五
』 ,获取更多技术资料。