1

I just tried to run an OptaPlanner project in Spring Boot, but there's only very simple text in OptaPlanner User Guide for Spring.

Actually, I think it is very easy to copy all domain objects, configuration files and drools files from an OptaPlanner project to Spring Boot project without any changes, but the only question is how to call Solver's solve method.

I made it run after Spring Boot startup with a class (named CommandLineAppStartupRunner) which implements CommandLineRunner interface, and I called solve method in its run method. Finally, I got an exception like follows:

Caused by: java.lang.IllegalArgumentException: Can not set org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore field springbootcloudbalance.domain.CloudBalance.score to springbootcloudbalance.domain.CloudBalance
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
at java.lang.reflect.Field.get(Field.java:393)
at org.optaplanner.core.impl.domain.common.accessor.ReflectionFieldMemberAccessor.executeGetter(ReflectionFieldMemberAccessor.java:54)
at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.getScore(SolutionDescriptor.java:1071)
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.cloneSolution(AbstractScoreDirector.java:212)
at org.optaplanner.core.impl.solver.scope.DefaultSolverScope.setWorkingSolutionFromBestSolution(DefaultSolverScope.java:230)
at org.optaplanner.core.impl.solver.AbstractSolver.solvingStarted(AbstractSolver.java:75)
at org.optaplanner.core.impl.solver.DefaultSolver.solvingStarted(DefaultSolver.java:210)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:190)
at springbootcloudbalance.CommandLineAppStartupRunner.run(CommandLineAppStartupRunner.java:55)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:818)
... 10 common frames omitted

I checked the code, and found the exception throws because the object from field.getDeclaringClass() is a different instance from the one from var1.getClass(). I'm afraid it due to the implementation of java reflection conflicts between OptaPlanner and Spring Boot.

The version I used is as follows:

  • OptaPlanner 7.11.0.Final
  • Spring Boot 2.0.5.RELEASE
  • JVM 1.8.0_181
Tonny Tc
  • 852
  • 1
  • 12
  • 37
  • 1
    Spring Boot and Optaplanner are perfectly capable of working together, so I guess it must be something in your code. It would help to see more of your runner class and solution class – n1ck Oct 02 '18 at 11:44
  • 1
    Hi, you can check the code from https://github.com/tonny1983/springboot-cloudbalance. Furthermore, if move the OptaPlanner-related code from `CommandLineAppStartupRunner` to `main` function, which means it is not a springboot app but a normal java app, then it works properly. – Tonny Tc Oct 02 '18 at 12:26
  • 2
    Removing the spring-boot-devtools dependency fixed it for me, so apparently that dependency does interfere with reflection somehow. I have seen this before in my own project. The explanation is probably the same as in the accepted answer of this question: https://stackoverflow.com/questions/35746370/optaplanners-drools-working-memory-is-empty. As a side note, using scanAnnotatedClasses in your XML config also makes it work (instead of defining the solution and entity class manually), when using the dev-tools dependency. I would suggest disabling it though – n1ck Oct 02 '18 at 13:23
  • An issue was submitted to OptaPlanner to provide better error messages for such cases: https://issues.jboss.org/browse/PLANNER-1586. Feel free to add any comments or suggestions. – M Talluzi Aug 23 '19 at 20:07

3 Answers3

3

Removing the spring-boot-devtools dependency fixes this error. Another SO question similar to this one explains it has something to do with different classloaders: Optaplanner's Drools working memory is empty. The accepted answer also mentions a possible fix:

To fix it, configure spring dev tools to load Drools libraries in the RestartClassLoader, together with the project's classes: using-boot-devtools-customizing-classload

n1ck
  • 259
  • 2
  • 9
  • What does boot-devtools do? OptaPlanner and it should really be made compatible. It we be interesting to know how we can allow that. – Geoffrey De Smet Oct 02 '18 at 17:48
  • I'm afraid one thing cannot be explained that why `scanAnnotatedClasses` works with `spring-boot-devtools` but `solutionClass` and `entityClass` not using the reason of `spring-boot-devtools` making project resources and libs be loaded by separated ClassLoaders. There may be some other reasons... – Tonny Tc Oct 03 '18 at 08:37
0

Nick's answer is correct. This is just to figure out what's going on.

This line means that optaplanner is extracting CloudBalance.getScore():

at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.getScore(SolutionDescriptor.java:1071)

This line means that it's using a ReflectionFieldMemberAccessor for that, which is just way to read a field through reflection (including private fields):

at org.optaplanner.core.impl.domain.common.accessor.ReflectionFieldMemberAccessor.executeGetter(ReflectionFieldMemberAccessor.java:54)

Now the error message is where it gets interesting:

Can not set ...HardMediumSoftScore field ...CloudBalance.score to ...CloudBalance

It looks like basically doing:

CloudBalance cloudBalance2 = cloudBalance.getScore();

Huh?

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120
0

Musa provided this answer, but someone deleted it, despite that the JIRA link is extremely relevant, as it points out which version of OptaPlanner will deal better with this problem:

"An issue was submitted to OptaPlanner to provide better error messages for such cases: https://issues.jboss.org/browse/PLANNER-1586. Feel free to add any comments or suggestions."

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120