Skip to content
/ reboot Public

ReBoot: a refactoring tool to automatically apply best practices in Java / Spring-Boot applications.

License

Notifications You must be signed in to change notification settings

thanus/reboot

Repository files navigation

ReBoot

Build Status Maven Central Codacy Badge codecov

A refactoring tool to automatically apply best practices in Java / Spring-Boot applications. ReBoot performs the following refactorings on a project:

The initial version of ReBoot was written in Rascal, see branch reboot-v1.

Refactorings

Request Mapping

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.

Refactoring diff

-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
    }
}

Explicit web annotation

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:

Refactoring diff

@RestController
@RequestMapping("/users")
public class UsersController {
    @GetMapping("/{id}")
-   public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
+   public ResponseEntity<User> getUser(@PathVariable Long id) {
        // code
    }
}

Explicit mandatory web annotation

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:

Refactoring diff

@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
    }
}

Field injection with Spring Autowired

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.

Refactoring diff

-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;
}

Field injection with Mockito

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.

Refactoring diff

-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);
}

Usage

Building from source

After cloning the project, you can build it from source with:

./mvnw clean install

Running ReBoot jar

cd reboot-core
java -jar target/reboot-core-1.1.0-SNAPSHOT-jar-with-dependencies.jar /path/to/project

Running reboot-maven-plugin

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

Excluding refactorings

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

Contributions are welcome!

Feel free to suggest and implement improvements.

About

ReBoot: a refactoring tool to automatically apply best practices in Java / Spring-Boot applications.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published