I am trying to build a POC project using AspectJ without using Spring AOP. I am using an annotation based approach where I want to run the aspect #Around the method which has been annotated with an annotation. For some reason my aspects don't get triggered. Below is my code:
pom.xml
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
resources/META-INF/aop.xml
<aspectj>
<aspects>
<aspect name="com.aditya.personal.aspects.MetricsAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="com.carrot.personal.aspects.*"/>
</weaver>
</aspects>
</aspectj>
My Aspect:
#Aspect
public class DataTrackAspect {
#Around("#annotation(com.carrot.personal.aspects.DataTrackEnabled)")
public Object performAspect(ProceedingJoinPoint joinPoint, DataTrackEnabled dataTrackEnabled) throws Throwable {
Object result = joinPoint.proceed();
DataTrackHelper dataTrackHelper = new DataTrackHelper();
dataTrackHelper.recordInstanceCount(dataTrackEnabled.dataClass(), dataTrackEnabled.dataName(), dataTrackEnabled.instance());
return result;
}
}
The annotated method
#DataTrackEnabled(dataClass = "Hey", dataName = "There", instance = "How are you?")
public Map<String, String> fetchUser() {
Map<String, String> returnable = new HashMap<>();
returnable.put("firstName", "carrot");
returnable.put("lastName", "radish");
return returnable;
}
I can't seem to figure it out as to what am I missing. I have uploaded the sample code on GitHub here.
You are using AspectJ Maven Plugin, i.e. you are going to use compile-time weaving. Therefore, you do not need aop.xml because that one is used for load-time weaving. So you can delete it.
During compilation you should get a compile error:
Unbound pointcut parameter 'dataTrackEnabled'
That tells you that your advice method has a parameter dataTrackEnabled which does not occur anywhere in the pointcut as it should. So just change the pointcut from
#Around("#annotation(com.carrot.personal.aspects.DataTrackEnabled)")
to
#Around("#annotation(dataTrackEnabled)")
As you see, I am referring to the method parameter name from the pointcut. If you would not bind the annotation to a parameter, then you would need the fully qualified class name as you used it before.
Also, your Maven POM should cause this error:
diamond operator is not supported in -source 1.5
This is because using AspectJ Maven does not automatically deactivate or replace Maven Compiler Plugin and the latter complains that you did not set source and target versions for it. So you have two options:
Deactivate Maven Compiler, let AspectJ Maven compile your Java and aspect classes.
Set the compiler level to 1.8 (or simply 8) for Maven Compiler and use the same settings for AspectJ Maven.
I will choose option no. 2 in this case. Maven knows properties named maven.compiler.source and maven.compiler.target. Let us define and use them:
<!-- (...) -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- (...) -->
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>${maven.compiler.target}</complianceLevel>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<!-- (...) -->
Now there are a few more warnings:
Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
bad version number found in C:\Users\alexa\.m2\repository\org\aspectj\aspectjrt\1.8.9\aspectjrt-1.8.9.jar expected 1.8.2 found 1.8.9
The first two are just because you forgot to set <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>. While you are at it, you can set the encoding for generated reports, too: <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>.
The last one is aspect-related and due to the fact that AspectJ Maven 1.7 depends on AspectJ 1.8.2, not 1.8.9. But be careful! It depends on aspectjtools (for using the compiler), not on aspectjweaver (for load-time weaving). You can either upgrade the version or override the dependency inside the plugin. I am going to show you both at the same time, just in case.
Last but not least, the latest version of AspectJ 1.8 is 1.8.13. But you can just use the very latest version 1.9.6, it supports up to Java 14 (the next version for Java 15+16 is almost ready) and can still produce byte code for Java 8.
How about this POM?
<?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>com.carrot</groupId>
<artifactId>SO_AJ_MavenProjectNotWorking_66734262</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<aspectj.version>1.9.6</aspectj.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>${maven.compiler.target}</complianceLevel>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
Now when you run the program, you should see something like:
Recording: dataClass = Hey, dataName = There, instance = How are you?
Recording: dataClass = Hey, dataName = There, instance = How are you?
You may ask why the advice is triggered twice. Well, just because #annotation(dataTrackEnabled) captures both the method call() and the method execution() pointcut. So let us limit the match to just executions.
The full solution looks like this (do not forget RUNTIME retention for your annotation):
package com.carrot.personal.aspects;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface DataTrackEnabled {
String dataClass();
String dataName();
String instance();
}
package com.carrot.personal.app;
public class DataTrackHelper {
public void recordInstanceCount(String dataClass, String dataName, String instance) {
System.out.println("Recording: dataClass = " + dataClass + ", dataName = " + dataName + ", instance = " + instance);
}
}
package com.carrot.personal.app;
import com.carrot.personal.aspects.DataTrackEnabled;
import java.util.HashMap;
import java.util.Map;
public class Application {
public static void main(String[] args) {
System.out.println(new Application().fetchUser());
}
#DataTrackEnabled(dataClass = "Hey", dataName = "There", instance = "How are you?")
public Map<String, String> fetchUser() {
Map<String, String> returnable = new HashMap<>();
returnable.put("firstName", "carrot");
returnable.put("lastName", "radish");
return returnable;
}
}
package com.carrot.personal.aspects;
import com.carrot.personal.app.DataTrackHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class DataTrackAspect {
#Around("#annotation(dataTrackEnabled) && execution(* *(..))")
public Object performAspect(ProceedingJoinPoint joinPoint, DataTrackEnabled dataTrackEnabled) throws Throwable {
System.out.println(joinPoint);
Object result = joinPoint.proceed();
DataTrackHelper dataTrackHelper = new DataTrackHelper();
dataTrackHelper.recordInstanceCount(dataTrackEnabled.dataClass(), dataTrackEnabled.dataName(), dataTrackEnabled.instance());
return result;
}
}
I added some more log output, so let us run the application again:
execution(Map com.carrot.personal.app.Application.fetchUser())
Recording: dataClass = Hey, dataName = There, instance = How are you?
{firstName=carrot, lastName=radish}
Related
I am writing a custom maven-plugin for my project. Following the instructions mentioned here
https://maven.apache.org/guides/plugin/guide-java-plugin-development.html#using-setters I added a #Parameter using setters as shown below.
#Parameter(property = "destinationDirectory", defaultValue = "${project.build.directory}/generated-resources")
private String _destinationDirectory;
private Path dstDirRoot;
public void setDestinationDirectory(String destinationDirectory) {
Path dstDir = Paths.get(destinationDirectory);
if (dstDir.isAbsolute()) {
this._destinationDirectory = dstDir.toString();
} else {
this._destinationDirectory = Paths.get(baseDir, dstDir.toString()).toString();
}
dstDirRoot = Paths.get(this._destinationDirectory);
}
Pom.xml entries on the usage side
<plugin>
<groupId>com.me.maven</groupId>
<artifactId>my-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<goals>
<goal>run</goal>
</goals>
<phase>generate-resources</phase>
</execution>
</executions>
<configuration>
<destinationDirectory>${project.build.directory}/myDir</destinationDirectory>
</configuration>
</plugin>
Now, I was expecting that during the plugin execution, it would call setDestinationDirectory method. But it doesn't. #Parameter(property="...") doesn't seem to have any impact.
Is this a bug? Or am I missing something?
From maven-plugin-plugin version 3.7.0 you can simply add #Parameter annotation on public setter methods.
You code can looks like:
#Parameter(...)
public void setDestinationDirectory(String destinationDirectory) {
...
}
You also need to define version of maven-plugin-plugin and maven-plugin-annotations dependency in your pom.xml - both should have the same version.
<project>
<properties>
<maven-plugin-tools.version>3.7.1</maven-plugin-tools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<scope>provided</scope>
<version>${maven-plugin-tools.version</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>${maven-plugin-tools.version}</version>
<executions>
<execution>
<id>help-mojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</pluginManagement>
</build>
</project>
If I remember correctly, when the annotation has property = destinationDirectory, it will read a system property from system properties (e.g. -D) or pom properties, unless a configuration section is specified in the XML.
mvn generate-resources -DdestinationDirectory=/path/to/dir
If a configuration is specified in the XML, which is the case in your example, the name of the configuration will match either the name of the variable or the specified alias, if any. You can try the following options and check if it solves the issue:
Setting an alias:
#Parameter(alias = "destinationDirectory", defaultValue = "${project.build.directory}/generated-resources")
private String _destinationDirectory;
Renaming the variable:
#Parameter(defaultValue = "${project.build.directory}/generated-resources")
private String destinationDirectory;
It's usually a good practice to keep the name of the configuration and the variables consistent, for easier maintenance.
I wanted to use AOP-styled annotations for #Around advice in a non-Spring project. I was able to make up some code, but it's been giving me trouble - instead of seeing the console output as coded in the Advice I can only see the SampleClass method's printout.
Providing a minimal, reproductible example of my code. Hoping to get some hint on how to get it working. Thanks!
Main.java
package pl.bart;
public class Main {
public static void main(String[] args) {
System.out.println(new SampleClass().a());
}
}
SampleClass.java
package pl.bart;
public class SampleClass {
#Annotation
public String a() {
return "Hello from sample class";
}
}
Annotation.java
package pl.bart;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Annotation {
}
Advice.java
package pl.bart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class Advice {
#Pointcut("#annotation(Annotation)")
public void callAt() {
}
#Around("callAt()")
public Object advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Hello from advice");
return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}
}
META-INF/aop.xml
<aspectj>
<aspects>
<aspect name="pl.bart.Advice"/>
<weaver options="-verbose -showWeaveInfo">
<include within="pl.bart.*"/>
</weaver>
</aspects>
</aspectj>
pom.xml
<?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>pl.bart</groupId>
<artifactId>aspectj-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/aspectj/
aspectjweaver/1.8.9/
aspectjweaver-1.8.9.jar
</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The are three mistakes in you project.
1.
#Pointcut("#annotation(Annotation)")
public void callAt() {
}
use fully qualified names of classes, i.e. pl.bart.Annotation instead of Annotation - it costs you nothing but saves a lot of time, in particular aspectj 1.5.4 "does not support" simple names.
2.
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
aspectj does some magic with bytecode, and it is too naive to expect the library released 12 years ago supports java 17, switch to the recent aspectj 1.9.9, also note you need to add --add-opens java.base/java.lang=ALL-UNNAMED to JVM arguments.
3.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
if you are going to use load time weaving and you don't use native aspectJ (.aj) syntax, you do not need aspectj-maven-plugin - it compiles .aj files and performs compile time weaving.
I am bored of adding manual logs for debugging each and every method that I write.
I came to know about #Loggable annotation of jcabi but am not successful in implementing that and your help is highly appreciated.
Below is the code that I have tried.
import com.jcabi.aspects.Loggable;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class Jcabi {
#Loggable
private static String checkJcabi(String stringToPrint) {
log.info("Print Successfull");
return stringToPrint;
}
public static void main(String[] args) {
checkJcabi("Hello World!");
}
}
IDE console prints this:
[main] INFO com.amazon.optimus.cpamutil.utils.Jcabi - Print Successfull
This is the log for the log.info() I have added in the method and there is no log for the #Loggable annotation something like this (below) as mentioned in this post
[INFO] com.example.Foo #power(2, 10): 1024 in 12μs
[INFO] com.example.Foo #power(3, 3): 27 in 4μs
Below are the dependencies packages that I use:
JCabiAspects = 1.0;
AspectJ = 6.0;
Slf4j = 1.7;
Slf4j_Simple = 1.7;
Let me know if you need more details. Thanks.
You need to do weaving of your binaries, as explained here: http://aspects.jcabi.com/example-weaving.html
You need to do the binary weaving. In the post that you linked, it says so and it also says that you can use the jcabi-maven-plugin to do the weaving for you, given that you are using maven.
Just add a few dependencies to your classpath and configure jcabi-maven-plugin for aspect weaving (get their latest versions in Maven Central)
<project>
<dependencies>
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>ajc</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
If you are using gradle, you can use this gradle-aspectj-binary plugin
I have one aspect with using aspectJ as below:
public aspect TestAspect {
pointcut classicPointcut(PersistenceManagerImpl object) : execution(manager.PersistenceManagerImpl.new(..)) && target(object);
after(PersistenceManagerImpl object) : classicPointcut(object){
System.err.println(object.getClass().getSimpleName());
}
}
this aspect is in module aspect. this module is packaking as jar. PersistenceManagerImpl is in other module but i need use it in module aspect. For dependency management i use maven. But here is of course problem with cyclic reference. Exists some way how can a resolve this problem ?
----------EDIT----------
I get only this error:
java.lang.NoSuchMethodError:TestAspect.ajc$after$TestAspect$1$cc149106(Ljava/lang/Object;)V
When i move my aspect to same module, when is PersistenceManagerImpl i obtain correct solution(of course). But this is not, what i wanted.
Could you put the error result of the compiling code? You could try to put another module as dependency first then later put the dependency on the aspectj maven plugin at weaveDependency in pom.xml as follow:
....
<dependency>
<groupId>com.maventest</groupId>
<artifactId>mytest</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
....
....
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<showWeaveInfo>true</showWeaveInfo>
<complianceLevel>${maven.compiler.target}</complianceLevel>
<encoding>${project.build.sourceEncoding}</encoding>
<weaveDependencies>
<weaveDependency>
<groupId>com.maventest</groupId>
<artifactId>mytest</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
ps: You could see my question post asking the same thing here
Your aspect seems to be specific for the module in which PersistenceManagerImpl is located, so that module should be a dependency of the aspect module. On the other hand, that module depends on the aspect module because it needs it as an <aspectLibrary> in the AspectJ Maven configuration. Indeed a circular dependency, but an unnecessary one. You have two options:
Move the aspect to the application module which it is specific for because IMO it belongs there if it explicitly uses specific classes from there. Only aspects which implement cross-cutting concerns in a way applicable to multiple modules should be in their own aspect library.
Following the previous thought, you could make your aspect more general, e.g. do something like this:
public aspect TestAspect {
pointcut classicPointcut(Object object) :
execution(*..PersistenceManagerImpl.new(..)) &&
target(object);
after(Object object) : classicPointcut(object){
System.err.println(object.getClass().getSimpleName());
}
}
I have the simple code below for testing the FindBugs #NonNull annotation with Maven. I execute
mvn clean install
And it correctly fails to build because print(null) violates the non-null condition.
You can set NonNull as default for all method parameters inside a class using the class annotation
#DefaultAnnotation(NonNull.class)
How can I set NonNull as default for all method parameters inside all classes under a given package (and sub-packages)?
src/main/java/test/Hello.java
package test;
import edu.umd.cs.findbugs.annotations.NonNull;
public class Hello {
static public void print(#NonNull Object value) {
System.out.println("value: " + value.toString());
}
static public void main(String[] args) {
if (args.length > 0) {
print(args[0]);
} else {
print(null);
}
}
}
pom.xml
<?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>hello</groupId>
<artifactId>hello</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>net.sourceforge.findbugs</groupId>
<artifactId>annotations</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>net.sourceforge.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<includeTests>true</includeTests>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
<execution>
<id>findbugs-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
You can do this for individual packages, but I haven't found a way to have it propagate to subpackages. For method parameters use the built-in package annotation #ParametersAreNonnullByDefault. Apply the annotation to the package in its package-info.java file inside the package's directory.
Note that I'm using the javax.annotation annotations from JSR-305 which FindBugs honors.
com/example/foo/package-info.java
/**
* Package that doesn't allow null values as method parameters.
*/
#ParametersAreNonnullByDefault
package com.example.foo;
import javax.annotation.ParametersAreNonnullByDefault;
For fields and method return values you'll need to create your own annotations. I did this by copying the source for ParametersAreNonnullByDefault and changing the ElementType enum.
com/example/util/FieldsAreNonnullByDefault.java
package com.example.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
/**
* Applies the {#link Nonnull} annotation to every class field unless overridden.
*/
#Documented
#Nonnull
#TypeQualifierDefault(ElementType.FIELD) // <-- use METHOD for return values
#Retention(RetentionPolicy.RUNTIME)
public #interface FieldsAreNonnullByDefault
{
// nothing to add
}
I began rewriting a fairly complex system from scratch a couple months ago, and every package has these three annotations applied (fields, parameters, and return values). One benefit that's come out of the incentive to avoid null values is using the Null Object pattern where appropriate. That combined with favoring final fields as much as possible and small classes that do one thing only has really kept the code clean.
You can do this to parameters, fileds and method return value simultaneously by putting these lines in your package-info.java:
#DefaultAnnotation(NonNull.class)
package com.my.package;
When findbugs runs on the code in that package, all methods and fields are assumed to be non-null unless you annotate them with #CheckForNull.
I also do not know of a way to make this apply to sub-packages. I do this for each package.