Язык выражений Spring — это мощный функционал, которым обеспечиваются динамическое построение запросов и манипуляции с объектами во время выполнения. Применим язык выражений Spring в приложении Spring Boot 3 для определения и оценки бизнес-правил из записей, сохраняемых в базе данных. Проиллюстрируем на примере, связанном с правилами скидок для приложения электронной коммерции.
Язык выражений Spring
Благодаря языку выражений Spring разработчики выполняют выражения в объектах. Особенно кстати он приходится при создании гибких бизнес-правил, которые изменяются в зависимости от данных в базе данных.
Пример использования
Возьмем приложение электронной коммерции, в котором применяются скидки на основе критериев вроде возраста клиента и статуса его членства. Эти критерии будут храниться в базе данных и динамически оцениваться языком выражений Spring для определения применимых скидок.
Настройка проекта Spring Boot 3
1. Создание проекта Spring Boot
Настраиваем новый проект Spring Boot, включаем такие зависимости:
- Spring Web;
- Spring Data JPA;
- ради простоты — база данных H2.
2. Добавление зависимостей в pom.xml
Вот пример pom.xml для Spring Boot 3:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>discount-rules</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>discount-rules</name>
<description>Demo project for Spring Boot 3</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- выполняется поиск родительского в репозитории -->
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. Определение сущности базы данных
Создаем класс сущности для правил скидок:
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class DiscountRule {
@Id
@GeneratedValue
private Long id;
private String condition; // Условие языка выражений Spring
private double discountPercentage;
// Конструкторы
public DiscountRule() {}
public DiscountRule(String condition, double discountPercentage) {
this.condition = condition;
this.discountPercentage = discountPercentage;
}
// Геттеры и сеттеры
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public double getDiscountPercentage() {
return discountPercentage;
}
public void setDiscountPercentage(double discountPercentage) {
this.discountPercentage = discountPercentage;
}
}
4. Создание репозитория
Теперь для управления сущностями DiscountRule создаем репозиторий Spring Data JPA:
import org.springframework.data.jpa.repository.JpaRepository;
public interface DiscountRuleRepository extends JpaRepository<DiscountRule, Long> {
}
5. Образец данных в базе данных
Вот примеры правил для скидок, сохраняемые в базе данных:

6. Заполнение базы данных
Чтобы при запуске приложения база данных заполнилась этими правилами, воспользуемся CommandLineRunner:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class DataInitializer implements CommandLineRunner {
private final DiscountRuleRepository repository;
public DataInitializer(DiscountRuleRepository repository) {
this.repository = repository;
}
@Override
public void run(String... args) throws Exception {
repository.saveAll(Arrays.asList(
new DiscountRule("customerAge > 60", 10),
new DiscountRule("membershipLevel == 'GOLD'", 15),
new DiscountRule("membershipLevel == 'SILVER'", 5),
new DiscountRule("totalAmount > 100", 20)
));
}
}
7. Оценка правил языком выражений Spring
Создаем службу для извлечения правил скидок и их оценки по текущему заказу:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DiscountService {
@Autowired
private DiscountRuleRepository discountRuleRepository;
public double calculateDiscount(Order order) {
List<DiscountRule> rules = discountRuleRepository.findAll();
double totalDiscount = 0;
ExpressionParser parser = new SpelExpressionParser();
for (DiscountRule rule : rules) {
StandardEvaluationContext context = new StandardEvaluationContext(order);
boolean isMatch = parser.parseExpression(rule.getCondition()).getValue(context, Boolean.class);
if (isMatch) {
totalDiscount += order.getTotalAmount() * (rule.getDiscountPercentage() / 100);
}
}
return totalDiscount;
}
}
8. Пример класса заказа
Вот простой класс Order, в приложении он является заказом:
public class Order {
private double totalAmount;
private int customerAge;
private String membershipLevel;
// Геттеры и сеттеры
public double getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(double totalAmount) {
this.totalAmount = totalAmount;
}
public int getCustomerAge() {
return customerAge;
}
public void setCustomerAge(int customerAge) {
this.customerAge = customerAge;
}
public String getMembershipLevel() {
return membershipLevel;
}
public void setMembershipLevel(String membershipLevel) {
this.membershipLevel = membershipLevel;
}
}
9. Создание контроллера REST
Чтобы предоставить конечную точку для расчета скидок на основе заказа, создаем контроллер REST.
10. Пример запроса
Протестируем приложение инструментами вроде Postman или curl, вот пример отправки запроса:
POST /api/discount
Content-Type: application/json
{
"totalAmount": 150,
"customerAge": 65,
"membershipLevel": "GOLD"
}
11. Ожидаемый ответ
Учитывая правила в базе данных, вот ожидаемый ответ:
- Скидка для лиц старше 60: 10 % от 150 = 15,00.
- Скидка для статуса членства GOLD: 15 % от 150 = 22,50.
- Скидка при общей сумме totalAmount больше 100: 20 % от 150 = 30,00.
Общая скидка = 15,00 + 22,50 + 30,00 = 67,50.
Заключение
Языком выражений Spring в приложении Spring Boot 3 создаются динамичные и гибкие бизнес-правила, оцениваемые во время выполнения на основе данных, которые хранятся в базе данных. Таким подходом обеспечивается большая адаптивность бизнес-логики, упрощается реализация сложных правил без их жесткого задания в приложении.
Благодаря такой настройке в приложении электронной коммерции динамически применяются скидки на основе меняющихся бизнес-правил. А значит, такое приложение остается адаптивным к потребностям клиентов.
Читайте также:
- Реализация распределенной трассировки с OpenTelemetry и Spring Boot 3
- Java Spring Boot против Golang
- Ключевые вопросы для собеседования по Spring Boot в 2023 году. Часть 2
Читайте нас в Telegram, VK и Дзен
Перевод статьи kiarash shamaii: Using Spring Expression Language (SpEL) to Create a Simple Rule Engine





