You can use AspectJ's ability to declare errors and/or warnings based on pointcuts.
Disclaimer: I have never used Spring, so I am not an expert there and just making up an example without much sense for demonstration.
Spring bean with prototype scope:
package de.scrum_master.app;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class ScopedBean {}
Spring bean with missing scope declaration:
package de.scrum_master.app;
import org.springframework.stereotype.Component;
@Component
public class UnscopedBean {}
Spring bean with using different types of auto-wiring:
This bean uses constructor and setter method wiring. If you uncomment the annotation on the field declaration you can even use another type of wiring. This does not make sense, but we want to provoke compilation errors in an aspect later.
package de.scrum_master.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
public class BeanWithAutowire {
//@Autowired
private ScopedBean scopedBean;
@Autowired
public BeanWithAutowire(ScopedBean scopedBean) {
this.scopedBean = scopedBean;
}
@Autowired
public void setScopedBean(ScopedBean scopedBean) {
this.scopedBean = scopedBean;
}
}
Aspect for static annotation consistency checking:
package de.scrum_master.aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
public aspect BeanAnnotationChecker {
declare error :
@annotation(Component) && !@annotation(Scope) :
"Spring component without scope declaration found";
declare error :
execution(@Autowired *.new(.., @Scope("prototype") *, ..)) && within(@Scope("singleton") *) :
"singleton bean auto-wired into prototype container via constructor";
declare error :
execution(@Autowired * *(.., @Scope("prototype") *, ..)) && within(@Scope("singleton") *) :
"singleton bean auto-wired into prototype container via setter method";
declare error :
set(@Autowired * *) && within(@Scope("singleton") *) :
"singleton bean auto-wired into prototype container via field assignment";
}
Maven POM using AspectJ compiler:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.scrum-master.stackoverflow</groupId>
<artifactId>aspectj-fail-build</artifactId>
<version>1.0-SNAPSHOT</version>
<name>AspectJ - fail build for wrong/missing annotations</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.source-target.version>1.7</java.source-target.version>
<aspectj.version>1.8.4</aspectj.version>
<main-class>de.scrum_master.app.ScopedBean</main-class>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<!-- IMPORTANT -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<Xlint>ignore</Xlint>
<complianceLevel>${java.source-target.version}</complianceLevel>
<encoding>UTF-8</encoding>
<verbose>true</verbose>
</configuration>
<executions>
<execution>
<!-- IMPORTANT -->
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.7.RELEASE</version>
</dependency>
</dependencies>
</project>
Console output for mvn clean package
:
(...)
[INFO] ------------------------------------------------------------------------
[INFO] Building AspectJ - fail build for wrong/missing annotations 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(...)
[ERROR] singleton bean auto-wired into prototype container via constructor
C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\BeanWithAutowire.java:14
public BeanWithAutowire(ScopedBean scopedBean) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[ERROR] singleton bean auto-wired into prototype container via setter method
C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\BeanWithAutowire.java:19
public void setScopedBean(ScopedBean scopedBean) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[ERROR] Spring component without scope declaration found
C:\Users\Alexander\Documents\java-src\SO_AJ_MavenFailBuildOnWrongAnnotation\src\main\java\de\scrum_master\app\UnscopedBean.java:6
public class UnscopedBean {}
^^^^^^^^^^^
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
(...)
I think the example is somewhat self-explanatory except for the AspectJ syntax, but you can read more about that in AspectJ manuals or tutorials. If you uncomment the @Autowired
annotation on the field declaration, you will see even more errors for explicit field assignments. AspectJ cannot match on a mere field declaration (without assignment), though. So whenever your developers rely on field annotations instead of annotated constructors or setter methods, i.e. you do not have any explicit field assignment in your code, there will be no compilation error. You can work around that by matching on getter methods or field read access in your code to indirectly match on fields. Feel free to ask how to do that if you cannot figure it out by yourself.