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.
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'm using the com.google.code.findbugs jsr305 annotations for null analysis in eclipse and my project seems to compile and run just fine within eclipse.
However when I try to build it with maven I'm getting this error: annotation type not applicable to this kind of declaration
Here's an example:
src/test/Main.java
package test;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
method(list);
}
private static void method(List<#Nonnull String> list) {//< error here
for (String str : list) {
print(str);
}
}
private static void print(#Nonnull String str) {
System.out.println(str);
}
}
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
<version>3.1</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>test.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>JAR</outputDirectory>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<outputDirectory>bin</outputDirectory>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
</project>
eclipse settings for null analysis
.settings/org.eclipse.jdt.core.prefs
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.CheckForNull
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=error
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8
Trying to avoid having to suppress null warnings for uses of prevElem, the code around this already ensures the List elements are not null.
So eclipse seems to recognise and compile the List<#Nonnull T> x just fine but maven isn't having it. This is compiling to target 1.8 but I've tried building with 16 and that just gives me a slightly more verbose error annotation #javax.annotation.Nonnull not applicable in this type context
Is this something that just isn't allowed, if so how/why does eclipse manage to compile it?
Is there a better solution than just suppressing the null warning or redundantly checking for null in the for loop?
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}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I am looking for a way to dump the values of variables whenever it changes eg. to stdout, without manually writing the logging or println code, so that I can inspect what happens dynamically. If possible, the class name and source file line number should also be included.
For example, with the following:
public class SomeClass {
public static void main(java.lang.String[] args) {
someVariableA = 0;
someVariableB = 5;
someVariableC = 8;
someVariableD = someVariableA + someVariableB;
someVariableD = 9;
}
}
There should be some output like:
someVariableD = 5 at SomeClass.main(SomeClass.java:[lineNumber])
someVariableD = 9 at SomeClass.main(SomeClass.java:[lineNumber])
I should be able to do this without writing much code, and disable this without deleting much code. It should be able to show every change to every variable.
With logging frameworks like log4j, I still have to write the code to dump a variable?
Is this something that I can do with a library/framework, a setting in the Java compiler or JVM, or with some IDE?
That is why classes should not rely on exposing state, aka fields.
Because there is no mechanism to notify an owner when some "other" object alters such a public variable.
If you want to be notified about such things, you make state private, but offer methods to update it. Then you can use the observer pattern on top of that so that any interested object gets notified about "property changes".
For debugging purposes, you can of course set a watch on variables. Which will use some sort of instrumentation so that assignments to variables actually become method invocations.
You could use AspectJ to intercept field assignments; in this way you can avoid cluttering your logic with logging code.
SomeClass.java:
public class SomeClass {
private static int someVariableA;
private static int someVariableB;
private static int someVariableC;
private static int someVariableD;
public static void main(String[] args) {
someVariableA = 0;
someVariableB = 5;
someVariableC = 8;
someVariableD = someVariableA + someVariableB;
someVariableD = 9;
}
}
SomeAspect.java:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class SomeAspect {
#After("set(static * SomeClass.*) && args(newValue)")
public void afterFieldSet(JoinPoint joinPoint, Object newValue) {
System.out.println(joinPoint.getSignature().getName() + " = " + newValue + " at " + joinPoint.getSourceLocation());
}
}
The console output will be:
someVariableA = 0 at SomeClass.java:9
someVariableB = 5 at SomeClass.java:10
someVariableC = 8 at SomeClass.java:11
someVariableD = 5 at SomeClass.java:12
someVariableD = 9 at SomeClass.java:13
When you want to disable the logging, simply remove the aspect.
For sake of completeness, I also post here the pom.xml used for the build:
<?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.example</groupId>
<artifactId>aspectj-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<complianceLevel>${maven.compiler.source}</complianceLevel>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>SomeClass</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
</project>
To run this example, just launch mvn clean package and then java -jar target/aspectj-example.jar.
You can found more information about AspectJ on its website. I hope it helped.