Spring Boot @Autowired - checkstyle / pmd / spotbugs rule to warn on non-interface usage

629 views Asked by At

Having recently been bit by Spring CGLib vs JDK Dynamic proxying issues in a Spring Boot application (that when run on a Mac seems to use CGLib where as the same app when run on Linux is using JDK dynamic proxying).

I'm looking for a linter tool config / plugin (PMD, Checkstyle, Spotbugs etc) that can identify where an @Autowired annotated field / parameter is being used and the Class being autowired is defined with a class instance rather than an interface.

For example:

public interface MyService {
}


public class MyServiceImpl implements MyService {
}


@RestController
public class MyController {
  @Autowired
  private MyServiceImpl serviceImpl;   // this should raise a warning in a linter as
                                       // class is being used for injection type

  @Autowired
  private MyService service;           // this should not raise a warning, as interface
                                       // is used for type
}
2

There are 2 answers

0
VIAGC On BEST ANSWER

I don't think this will be possible to check with PMD, Checkstyle, Spotbugs, etc. But there is a solution to check this. ArchUnit offers writing custom rules, a custom rule can be written for this. Assuming you are using JUnit5 or JUnit4 for testing, you could do something like-

@ArchTest                                         
public static final ArchRule archRule = noFields()
    .that()                                        
    .areAnnotatedWith(Autowired.class)             
    .should()                                      
    .haveNameEndingWith("Impl");                  

This can be incorporated within your tests, you would have to read the documentation of arch unit and set up in your work environment, it super easy to integrate. You can create similar rules for setters or methods-

@ArchTest                                                         
public static final ArchRule archRule1 = noMethods()               
     .that()                                                       
     .areAnnotatedWith(Autowired.class)                       
     .should()                                                     
     .haveRawParameterTypes(MyServiceImpl.class, MyDaoImpl.class);

As you integrate this into your code base, you can create custom rules for yourself. Using ArchUnit for spring projects is generally a great idea, they also have pre configured templates that you can use in your project. Hope this helps.

0
Chris White On

In addition to @VyomYdv's answer above, I stumbled on this github issue for spring: https://github.com/spring-projects/spring-boot/issues/8277

Essentially a bean that is annotated with @Validated and @ConfigurationProperties and also implements an interface will be a candidate for being a JDK proxy instance at runtime, and you can't autowire the class instance, you have to autowire as the interface.

So amend YyomYdv's rule as follows:

public static final ArchRule AUTOWIRE_PROXY_RULE = noFields()
    .that()
    .areAnnotatedWith(Autowired.class)
    .should()
    .haveRawType(new DescribedPredicate<>("that does not autowire an interface") {
        @Override
        public boolean apply(final JavaClass input) {
            return !input.isInterface();
        }
    })
    .andShould().haveRawType(new DescribedPredicate<>("class is not a JDK proxy candidate") {
        @Override
        public boolean apply(final JavaClass input) {
            return !input.getAllRawInterfaces().isEmpty()
                && input.getPackageName().startsWith("com.mypackage")
                && input.isAnnotatedWith(Validated.class)
                && input.isAnnotatedWith(ConfigurationProperties.class);
        }
    });