-
Notifications
You must be signed in to change notification settings - Fork 5
Setup
I’m assuming you have the following ready.
- Java 8
- Maven
- a text editor or IDE you’re comfortable with
I’m running the command-line commands in a Unix-like environment – give or take some minor adjustments this tutorial should also work on Windows.
Clone this repository and check out the initial commit. I have tagged it as
initial.
git clone https://github.com/glts/safer-spring-petclinic.git
cd safer-spring-petclinic
git checkout initial
This is our codebase. Now is a good time to check if we’re starting out all green.
Before we do, let’s upgrade to Java 8. In the POM, change java.version to
1.8. I’ve made that my first commit
e7ab16a.
- <java.version>1.7</java.version>
+ <java.version>1.8</java.version>In the project root, run the install Maven goal.
mvn install
(This compiles the production and test source, runs the tests, bundles up the WAR artefact, and installs it in the local Maven repository.) This can take quite a while the first time, because all the project’s dependencies need to be downloaded. At some point you should see the ‘BUILD SUCCESS’ message.
Then start the web server:
mvn tomcat7:run
Open a browser at http://localhost:9966/petclinic/. This is the Pet Clinic.
You can browse around a little to get familiar with the app. Hit Control-C to stop the web server when you’re done.
The annotations
@Nullable
and
@NonNull
are provided by the Checker Framework in the package
org.checkerframework.checker.nullness.qual. However, I am going to use the
equivalent annotations from the JSR 305
effort instead. Nullness annotations have been reinvented a few times. JSR 305,
‘Annotations for Software Defect Detection’, was an attempt to standardise these
annotations. Unfortunately, this effort fizzled out. Still, it is the closest
thing to a standard that we have today. (The official repository is at
https://code.google.com/p/jsr-305/.)
The JSR 305 annotations have a nicer package name (javax.annotation) than the
ones from the Checker Framework, but bear in my mind that they are less
powerful. Only the Checker Framework annotations can be applied to the use of
a type. The following, ‘a non-null List of nullable Strings’ cannot be expressed
with the JSR 305 annotations.
private @NonNull List<@Nullable String> list;Anyway, I’m adding the appropriate <dependency> in the POM in my second
commit
7d039ae.
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<!-- no version necessary, provided by io.spring.platform:platform-bom -->
</dependency>If you decide to use the Checker Framework annotations substitute the corresponding dependency from the Checker Framework:
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>1.9.8</version>
</dependency>The next step is to integrate the Checker Framework into our build, and activate the Nullness Checker.
I think it is best to keep this stuff separate from the original build, so I will create a separate Maven profile for compiling with the Checker Framework.
Keeping the Checker Framework compilation separate makes sense for a number of reasons. First, the class files that the compiler produces when an annotation processor like the Nullness Checker is attached are different from the class files output without annotation processing. This may not be an issue depending on your situation – but with annotation-heavy frameworks like Spring and Hibernate there is a risk of unwanted interaction. Second, annotation processing slows the build down.
Here is the checker profile. You can see in my third commit
71fe129
that I’ve added it at the bottom of the Maven POM, right above the final <url>
tag.
<profiles>
<profile>
<id>checker</id>
<dependencies>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker</artifactId>
<version>1.9.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>jdk8</artifactId>
<version>1.9.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessors>
<annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<arg>-Xbootclasspath/p:${org.checkerframework:jdk8:jar}</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>All this does is define a new profile checker, add two Checker Framework
dependencies, and activate the NullnessChecker annotation processor during
compilation.
The dependencies are declared with provided scope, meaning that they won’t be
bundled with the final WAR artefact. Checking will be confined to the
compilation phase.
The Checker Framework comes with an annotated JDK 8, and we’re putting it on
the classpath for the annotation processor. The annotated JDK contains the
method signatures in the JDK, annotated as appropriate with @Nullable and
@NonNull. The annotated JDK isn’t strictly necessary but it does help reduce
false positives.
With that set up, the checker profile is ready and can be activated with the
-P flag.
Kick off the build with mvn -Pchecker compile and turn the page.