搜索

12.7. 使应用程序可执行

download PDF

完成 OptaPlanner Spring Boot timetable 项目的红帽构建后,将所有内容打包成由标准 Java main() 方法驱动的单个可执行 JAR 文件。

先决条件

  • 您已完成了 OptaPlanner Spring Boot timetable 项目。

流程

  1. 使用以下内容创建 TimeTableSpringBootApp.java 类:

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class TimeTableSpringBootApp {
    
        public static void main(String[] args) {
            SpringApplication.run(TimeTableSpringBootApp.class, args);
        }
    
    }
  2. 将 Spring Initializr 创建的 src/main/java/com/example/DemoApplication.java 类替换为 TimeTableSpringBootApp.java 类。
  3. TimeTableSpringBootApp.java 类作为常规 Java 应用程序的主类运行。

12.7.1. 试用 timetable 应用程序

启动红帽构建的 OptaPlanner Spring Boot timetable 应用程序后,您可以使用您想要的任何 REST 客户端测试 REST 服务。这个示例使用 Linux curl 命令发送 POST 请求。

先决条件

  • OptaPlanner Spring Boot timetable 应用程序正在运行。

流程

使用以下命令:

$ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'

大约 5 秒后,在 application.properties 中定义的终止时间花费了时间,服务会返回类似以下示例的输出:

HTTP/1.1 200
Content-Type: application/json
...

{"timeslotList":...,"roomList":...,"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}

请注意,应用程序被分配了这四个的时间插槽之一,以及两个房间的一个。另请注意,它符合所有硬约束。例如,M. Curie 的两节课时间不同。

在服务器端,info 日志显示在 5 秒内执行什么 OptaPlanner:

... Solving started: time spent (33), best score (-8init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
... Construction Heuristic phase (0) ended: time spent (73), best score (0hard/0soft), score calculation speed (459/sec), step total (4).
... Local Search phase (1) ended: time spent (5000), best score (0hard/0soft), score calculation speed (28949/sec), step total (28398).
... Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (28524/sec), phase total (2), environment mode (REPRODUCIBLE).

12.7.2. 测试应用

良好的应用程序包括测试覆盖。这个示例测试了 OptaPlanner Spring Boot 应用程序的 Timetable Red Hat build。它使用 JUnit 测试来生成测试数据集,并将其发送到 TimeTableController 以解决。

流程

使用以下内容创建 src/test/java/com/example/solver/TimeTableControllerTest.java 类:

package com.example.solver;

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

import com.example.domain.Lesson;
import com.example.domain.Room;
import com.example.domain.TimeTable;
import com.example.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(properties = {
        "optaplanner.solver.termination.spent-limit=1h", // Effectively disable this termination in favor of the best-score-limit
        "optaplanner.solver.termination.best-score-limit=0hard/*soft"})
public class TimeTableControllerTest {

    @Autowired
    private TimeTableController timeTableController;

    @Test
    @Timeout(600_000)
    public void solve() {
        TimeTable problem = generateProblem();
        TimeTable solution = timeTableController.solve(problem);
        assertFalse(solution.getLessonList().isEmpty());
        for (Lesson lesson : solution.getLessonList()) {
            assertNotNull(lesson.getTimeslot());
            assertNotNull(lesson.getRoom());
        }
        assertTrue(solution.getScore().isFeasible());
    }

    private TimeTable generateProblem() {
        List<Timeslot> timeslotList = new ArrayList<>();
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));

        List<Room> roomList = new ArrayList<>();
        roomList.add(new Room("Room A"));
        roomList.add(new Room("Room B"));
        roomList.add(new Room("Room C"));

        List<Lesson> lessonList = new ArrayList<>();
        lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
        lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
        lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
        lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));

        lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
        lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
        lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
        lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
        return new TimeTable(timeslotList, roomList, lessonList);
    }

}

此测试会验证在解决后,所有课程都会分配给一个时间窗和房间。它还会验证它是否发现可行的解决方案(没有硬限制)。

通常,该解决方法在 200 毫秒内找到可行的解决方案。请注意,@SpringBootTest 注释 的属性 如何覆盖解决器终止,以便在找到可行的解决方案(0hard/*soft)时立即终止。这可避免硬编码解决程序时间,因为单元测试可能会在任意硬件上运行。这种方法可确保测试用时足够长来查找可行的解决方案,即使在较慢的系统上也是如此。但是,即使在快速系统中,它也无法运行时间比严格要长的时间更长。

12.7.3. 日志记录

完成 OptaPlanner Spring Boot timetable 应用程序的构建后,您可以使用日志信息来帮助微调 ConstraintProvider 中的约束。查看 info 日志文件中的分数计算速度,以评估对您的限制更改的影响。以 debug 模式运行应用程序,显示应用程序接受的每个步骤,或使用 trace 日志记录记录每一步和每次移动。

流程

  1. 运行定时应用程序的时间,例如五分钟。
  2. 查看 日志文件中的 分数计算速度,如下例所示:

    ... Solving ended: ..., score calculation speed (29455/sec), ...
  3. 更改约束,再次运行计划应用程序相同的时间,并查看 日志文件中 记录的分数计算速度。
  4. 以 debug 模式运行应用程序以记录每个步骤:

    • 要从命令行运行调试模式,请使用 -D 系统属性。
    • 要更改 application.properties 文件中的日志记录,请在该文件中添加以下行:

      logging.level.org.optaplanner=debug

      以下示例显示了在 debug 模式下 日志文件 的输出:

      ... Solving started: time spent (67), best score (-20init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
      ...     CH step (0), time spent (128), score (-18init/0hard/0soft), selected move count (15), picked move ([Math(101) {null -> Room A}, Math(101) {null -> MONDAY 08:30}]).
      ...     CH step (1), time spent (145), score (-16init/0hard/0soft), selected move count (15), picked move ([Physics(102) {null -> Room A}, Physics(102) {null -> MONDAY 09:30}]).
      ...
  5. 使用 trace logging 显示每个步骤以及每个步骤的每一个移动。
Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

© 2024 Red Hat, Inc.