A refactoring tool to automatically apply best practices in Java / Spring-Boot applications. ReBoot performs the following refactorings on a project:
- Request Mapping
- Explicit web annotation value (PathVariable, RequestParam, RequestHeader, etc)
- Explicit mandatory web annotation (PathVariable, RequestParam, RequestHeader, etc)
- Field injection with Spring Autowired
- Field injection with Mockito
The initial version of ReBoot was written in Rascal, see branch reboot-v1
.
Spring provides HTTP method specific shortcut variants
for @RequestMapping
. These custom annotations (@GetMapping
, @PostMapping
, etc) are less verbose and more
expressive than @RequestMapping
. This refactoring is also applied to projects using Spring Cloud OpenFeign,
as they also reuse Spring annotations.
-import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.GetMapping;
@RestController
@RequestMapping("/users")
public class UsersController {
- @RequestMapping(method = RequestMethod.GET)
+ @GetMapping
public ResponseEntity<List<User>> getUsers() {
// code
}
- @RequestMapping(path = "/{id}", method = RequestMethod.GET)
+ @GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
// code
}
}
URI Variables can be named explicitly,
like @PathVariable("id") Long id
, but this is redundant. This detail can be left out if the names are the same. This
refactoring is also applied to projects using Spring Cloud OpenFeign,
as they also reuse Spring annotations. This refactoring is not only applicable for PathVariable
but also RequestParam,
RequestHeader,
RequestAttribute,
CookieValue,
ModelAttribute,
SessionAttribute.
For example, for @PathVariable
:
@RestController
@RequestMapping("/users")
public class UsersController {
@GetMapping("/{id}")
- public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
+ public ResponseEntity<User> getUser(@PathVariable Long id) {
// code
}
}
The attribute required
set to true on annotations like @PathVariable
is not necessary as this is default already.
This refactoring is also applied to projects using Spring Cloud OpenFeign,
as they also reuse Spring annotations. This refactoring is not only applicable for PathVariable
but also RequestParam,
RequestHeader,
RequestAttribute,
CookieValue,
ModelAttribute,
SessionAttribute.
For example, for @PathVariable
:
@RestController
@RequestMapping("/users")
public class UsersController {
@GetMapping("/{id}")
- public ResponseEntity<User> getUser(@PathVariable(required = true) Long id) {
+ public ResponseEntity<User> getUser(@PathVariable Long id) {
// code
}
}
Dependency injection with field injection is not recommended. Instead, constructor injection should be used, leading to safer code and easier to test. This is explained in more detail in article why-field-injection-is-evil.
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
+@RequiredArgsConstructor
@RestController
@RequestMapping("/users")
public class UsersController {
- @Autowired
- private UsersService usersService;
- @Autowired
- private UsernameService usernameService;
+ private final UsersService usersService;
+ private final UsernameService usernameService;
}
Just like the above refactoring, it is not recommended to do field injection with Mockito for the same reasons. This is explained in more detail in article Mockito: Why You Should Not Use InjectMocks Annotation to Autowire Fields.
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
+import org.mockito.Mockito;
@ExtendWith(value = MockitoExtension.class)
class UsersControllerTest {
- @Mock
- private UsersService usersService;
- @Mock
- private UsernameService usernameService;
- @InjectMocks
- private UsersController usersController;
+ private UsersService usersService = Mockito.mock(UsersService.class);
+ private UsernameService usernameService = Mockito.mock(UsernameService.class);
+ private UsersController usersController = new UsersController(usersService, usernameService);
}
After cloning the project, you can build it from source with:
./mvnw clean install
cd reboot-core
java -jar target/reboot-core-1.1.0-SNAPSHOT-jar-with-dependencies.jar /path/to/project
Add reboot-maven-plugin to your POM:
<build>
<plugins>
<plugin>
<groupId>nl.thanus</groupId>
<artifactId>reboot-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
</plugins>
</build>
By default, reboot-maven-plugin uses ${project.basedir}
as path to refactor. If you want to specify another path, you
can use the property directory
:
<build>
<plugins>
<plugin>
<groupId>nl.thanus</groupId>
<artifactId>reboot-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<directory>/path/to/project</directory>
</configuration>
</plugin>
</plugins>
</build>
Run plugin:
mvn nl.thanus:reboot-maven-plugin:1.0.0:reboot
Refactorings can be excluded from ReBoot.
To exclude when you are running the ReBoot jar add the flag -e or --excluded with refactoring you want to exclude. For example:
java -jar target/reboot-core-1.1.0-SNAPSHOT-jar-with-dependencies.jar /path/to/project -e request-mappings -e autowired-field-injection
To exclude refactorings from the maven plugin, add them to the configuration tag like this:
<excluded>
<refactoring>request-mappings</refactoring>
<refactoring>autowired-field-injection</refactoring>
</excluded>
The excluded refactorings should be named as:
- request-mappings
- web-annotations
- autowired-field-injection
- mockito-field-injection
Feel free to suggest and implement improvements.