Post-compile weaving aspects into a project using Gradle - java

Background
Performing post-compile weaving of projects using:
AspectJ 1.9.4
io.freefair.aspectj.post-compile-weaving 4.1.1
Java 11.0.3
Gradle 5.6.2 (Groovy 2.5.4, Kotlin 1.3.41)
This project does not use Maven or Spring.
Layout
The projects include:
app.aspects - Contains a single LogAspect class annotated with #Aspect.
app.aspects.weaver - No source files, only dependencies to declare aspects and project to weave.
app.common - Defines #Log annotation referenced by pointcuts described in LogAspect.
app.program.main - Files to be woven with jointpoints described in LogAspect.
Gradle
Build files that relate to aspects are defined here. The idea is that weaving is independent from the application so neither the application's common classes nor the main program need know about weaving. Rather, the main program need only reference #Log from the common package and AJC will take care of the weaving.
app.aspects
apply plugin: "io.freefair.aspectj.post-compile-weaving"
dependencies {
// For the #Log annotation
compileOnly project(':app.common')
// The LogAspect's joinpoint references the Main Program
compileOnly project(':app.program.main')
// Logging dependency is also compiled, but not shown here
}
app.aspects.weaver
apply plugin: "io.freefair.aspectj.post-compile-weaving"
dependencies {
compileOnly "org.aspectj:aspectjrt:1.9.4"
// This should set the -aspectpath ?
aspect project(":app.aspects")
// This should set the -inpath ?
inpath(project(":app.program.main")) {
// Only weave within the project
transitive = false
}
}
Classes
Log
The Log annotation is straightforward:
package com.app.common.aspects;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR })
public #interface Log {
boolean secure() default false;
}
Main Program
The main program resembles:
package com.app.program.main;
import com.app.common.aspects.Log;
#Log
public class Program {
/** This is the method to weave. */
protected void run() throws InterruptedException, TimeoutException {
}
}
Logging Aspect
The logging aspect resembles (see the code from a related question):
#Aspect
public class LogAspect {
// In the future this will target points annotated with #Log
#Pointcut("execution(* com.app.program.main.Program.run(..))")
public void loggedClass() {
}
#Around("loggedClass()")
public Object log(final ProceedingJoinPoint joinPoint) throws Throwable {
return log(joinPoint, false);
}
private Object log(final ProceedingJoinPoint joinPoint, boolean secure) throws Throwable {
// See last year's code for the full listing
log.info("\u21B7| {}{}#{}({})", indent, className, memberName, params);
}
}
Problem
It appears weaving is taking place, but the advice cannot be found:
.../app.aspects/build/classes/java/main!com/app/aspects/LogAspect.class [warning] advice defined in com.app.aspects.LogAspect has not been applied [Xlint:adviceDidNotMatch]
Question
What needs to change so that weaving of the LogAspect into Program's run() method works using Gradle?
Options File
The ajc.options file shows:
-inpath
.../app.aspects/build/classes/java/main
-classpath
.../.gradle/caches/modules-2/files-2.1/org.aspectj/...
-d
.../app.aspects/build/classes/java/main
-target
11
-source
11
It is disconcerting that -aspectpath isn't shown and -inpath is listing app.aspects instead of app.program.main.

Merging apps.aspects and apps.aspects.weaver into the same project has produced:
Join point 'method-execution(void com.app.program.main.Program.run())' in Type 'com.app.program.main.Program' (Program.java:396) advised by around advice from 'com.app.aspects.LogAspect' (LogAspect.class(from LogAspect.java))
While this solves the problem, I don't understand why LogAspect needs to be in the same project that performs the weaving. The Gradle file becomes:
apply plugin: "io.freefair.aspectj.post-compile-weaving"
dependencies {
compileOnly "org.aspectj:aspectjrt:1.9.4"
compileOnly project(':app.common')
compileOnly project(':app.program.main')
compileOnly org_apache_logging_log4j__log4j_api
inpath(project(":app.program.main")) {
transitive = false
}
}
compileJava.ajc.options.compilerArgs += "-showWeaveInfo"
compileJava.ajc.options.compilerArgs += "-verbose"

Related

can't figure out the java annotation processor

I'm trying to deal with annotation processors. I followed tutorials. Here is my code:
#ExampleAnnotation
private void someMethod(){
System.out.println("hi");
}
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.METHOD)
public #interface ExampleAnnotation {
}
#SupportedAnnotationTypes("org.example.ExampleAnnotation")
public class Processor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> anots, RoundEnvironment roundEnvironment) {
anots.forEach(System.out::println);
return true;
}
}
I created META-INF/SERVICES/javax.annotation.processing.Processor
and registered my processor: org.example.Processor. It seems like everything is OK, but block of code in the processor just dont start. I have no idea what is wrong. P.S.: I use Gradle and Java 11.
i fixed my issue and decided to make a little step-by-step tutorial to create simple JAP:
Java-Annotation-Processor-guide
This is guide for annotation processing using gradle
I spent 3 days trying to deal with it, but with this little tutorial you will made JAP in 5 minutes.
sorry for bad english :)
So, first you should do is create gradle subproject for your project:
create project itself with gradle init or intellij
add to your main project's settings.gradle file this line include "*your subproject name, lets say:*annotation-processor"
Congrats, now let go to the processor itself
here is so much tutorials on the web about this part, so you can read something like this https://www.baeldung.com/java-annotation-processing-builde,
or this (Если ты русский): https://habr.com/ru/company/e-legion/blog/206208/
Final step is very easy - you will add your annotation processor to main project.
!!! instead of annotation-processor you should use your subproject name !!!
kotlin (or java + kotlin) -> {
add this plugin to your plugins: id "org.jetbrains.kotlin.kapt" version "1.7.21"
add this to your dependencies:
kapt project('annotation-processor')
compileOnly project('annotation-processor')
}
java -> {
add this to your dependencies:
annotationProcessor project('annotation-processor')
compileOnly project('annotation-processor')
}
(or you can read this on github: https://github.com/Blu3cr0ss/Java-Annotation-Processor-guide)

Micrometer TimedAspect doesn't intercept calls to methods annotated with #Timed

I am trying to use Micrometer to record execution time in my Java application. This is related to my other question about used #Timed annotation.
I have a class CountedObject that has the following 2 methods:
#Measured
#Timed(value = "timer1")
public void measuredFunction() {
try {
int sleepTime = new Random().nextInt(3) + 1;
Thread.sleep(sleepTime * 1000L);
} catch (InterruptedException e) {}
}
#Timed(value = "timer2")
public void timedFunction() {
try {
int sleepTime = new Random().nextInt(3) + 1;
Thread.sleep(sleepTime * 1000L);
} catch (InterruptedException e) {}
}
I have defined a custom annotation #Measured
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Measured {
}
And a MeasuredAspect to intercept calls to methods annotated with my #Measured annotation:
#Aspect
public class MeasuredAspect {
#Around("execution(* *(..)) && #annotation(Measured)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
return AppMetrics.getInstance().handle(pjp);
}
}
In my AppMetrics class I initialize an instance of micrometer's TimedAspect and in the handle(ProceedingJoinPoint pjp) method pass the ProceedingJoinPoint pjp to the TimedAspect instance.
public class AppMetrics {
private static final AppMetrics instance = new AppMetrics();
private MeterRegistry registry;
private TimedAspect timedAspect;
public static AppMetrics getInstance() {
return instance;
}
private AppMetrics() {
this.registry = new SimpleMeterRegistry();
this.timedAspect = new TimedAspect(registry);
}
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
return timedAspect.timedMethod(pjp);
}
}
In my application main, I create an object of CountedObject and invoke measuredFunction() and timedFunction() then I check my registry.getMeters(); only timer1 used by the measuredFunction() [which is annotated by both #Measured and #Timed] is found, while the timer2 that should be used by timedFunction() [annotated only by #Timed] doesn't exist.
I am using eclipse with AspectJ Development Tools Plugin and my project is a Gradle project with AspectJ capability. I am using id "io.freefair.aspectj" version "5.1.1" plugin in my Gradle plugins. This is a basic java application not a Spring app.
What configurations needs to be done or what code changes are required so that micrometer TimedAspect can intercept my method calls directly [i.e timedFunction() should be timed and timer2 should be found in the registry] without the need of my custom annotation?
I created an example project for you:
https://github.com/kriegaex/SO_AJ_MicrometerTimed_67803726
Quoting the read-me (sorry, but answers only containing links are frowned upon on StackOverflow):
In https://github.com/micrometer-metrics/micrometer/issues/1149 and on StackOverflow, an FAQ about Micrometer's #Timed annotation is,
why it works with Spring AOP, but not when using Micrometer as an aspect library for native AspectJ in the context of compile-time weaving (CTW),
e.g. with AspectJ Maven Plugin. It can be made to work with load-time weaving (LTW) when providing an aop.xml pointing to TimedAspect,
but in a CTW the aspect never kicks in.
The reason is that the aspect has been compiled with Javac, not with the AspectJ compiler (AJC), which is necessary to "finish" the Java class,
i.e. to enhance its byte code in order to be a full AspectJ aspect. The LTW agent does that on the fly during class-loading, but in a CTW context
you need to explicitly tell AJC to do post-compile weaving (a.k.a. binary weaving) on the Micrometer library, producing newly woven class files.
This is done by putting Micrometer on AJC's inpath in order to make sure that its class files are being transformed and written to the target
directory. The inpath in AspectJ Maven is configured via <weaveDependencies>. There are at least two ways to do this:
You can either create your own woven version of the library in a separate Maven module and then use that module instead of Micrometer.
In that case, you need to exclude the original Micrometer library in the consuming module, in order to make sure that the unwoven
class files are not on the classpath anymore and accidentally used.
The way shown here in this example project is a single-module approach, building an executable uber JAR with Maven Shade. The Micrometer class
files are not a re-usable library like in the first approach, but it is nice for demonstration purposes, because we can just run the sample
application and check its output:
$ mvn clean package
...
[INFO] --- aspectj-maven-plugin:1.12.6:compile (default) # SO_AJ_MicrometerTimed_67803726 ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void de.scrum_master.app.Application.doSomething())' in Type 'de.scrum_master.app.Application' (Application.java:23) advised by around advice from 'io.micrometer.core.aop.TimedAspect' (micrometer-core-1.7.0.jar!TimedAspect.class(from TimedAspect.java))
...
[INFO] --- maven-shade-plugin:3.2.4:shade (default) # SO_AJ_MicrometerTimed_67803726 ---
[INFO] Including org.hdrhistogram:HdrHistogram:jar:2.1.12 in the shaded jar.
[INFO] Including org.latencyutils:LatencyUtils:jar:2.0.3 in the shaded jar.
[INFO] Including org.aspectj:aspectjrt:jar:1.9.6 in the shaded jar.
[INFO] Excluding io.micrometer:micrometer-core:jar:1.7.0 from the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT.jar with C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT-shaded.jar
[INFO] Dependency-reduced POM written at: C:\Users\me\java-src\SO_AJ_MicrometerTimed_67803726\target\dependency-reduced-pom.xml
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
$ java -jar target/SO_AJ_MicrometerTimed_67803726-1.0-SNAPSHOT.jar
Juni 05, 2021 1:12:27 PM io.micrometer.core.instrument.push.PushMeterRegistry start
INFO: publishing metrics for LoggingMeterRegistry every 1m
Juni 05, 2021 1:13:00 PM io.micrometer.core.instrument.logging.LoggingMeterRegistry lambda$publish$5
INFO: method.timed{class=de.scrum_master.app.Application,exception=none,method=doSomething} throughput=0.166667/s mean=0.11842469s max=0.2146482s
Please specifically note those log lines (line breaks inserted for better readability):
Join point 'method-execution(void de.scrum_master.app.Application.doSomething())'
in Type 'de.scrum_master.app.Application' (Application.java:23)
advised by around advice from 'io.micrometer.core.aop.TimedAspect'
(micrometer-core-1.7.0.jar!TimedAspect.class(from TimedAspect.java))
The above is proof that the #Timed annotation actually causes Micrometer's TimedAspect to be woven into our application code. And here are
the measurements created by the aspect for the sample application:
method.timed
{class=de.scrum_master.app.Application,exception=none,method=doSomething}
throughput=0.166667/s mean=0.11842469s max=0.2146482s
I'm not sure what do you expect from this:
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
return timedAspect.timedMethod(pjp);
}
If I understand this correctly, it does nothing.
There are guides that you can follow to set-up AspectJ properly for your project. After it is done TimedAspect should work, you don't need MeasuredAspect or #Measured just set up AspectJ.
Inspired by #kriegaex's Maven solution https://github.com/kriegaex/SO_AJ_MicrometerTimed_67803726, this is what I came up with for Gradle.
IMPORTANT once you have produced your jar, it replaces micrometer-core, so remember to exclude the original micrometer-core
from your dependencies. If you don't, it will be the luck of the draw
which TimedAspect and CountedAspect classes are chosen by your
runtime.
The goal is to produce a replacement jar for io.micrometer:micrometer-core. This involves compiling with ajc the original micrometer-core jar together with its transitive dependencies. The original micrometer-core contains only two #Aspects, TimedAspect and CountedAspect, so only those classes will changed by ajc.
build.gradle
plugins {
id 'java-library'
id 'io.freefair.aspectj.post-compile-weaving' version '6.6'
}
dependencies {
implementation 'org.aspectj:aspectjrt:1.9.9.1'
inpath 'io.micrometer:micrometer-core:1.10.2'
}
jar {
exclude 'dummy'
}
sourcesJar {
exclude 'dummy'
}
// Since I'm only compiling micrometer's library code, I don't need linting
compileJava.ajc.options.compilerArgs += "-Xlint:ignore"
Gradle won't allow me to compile without at least one java class to compile. To work around this, I created the following class. Yes, this does get compiled, but does not get included in the jar because it is filtered out by the jar's exclude path.
dummy.Dummy.java
package dummy;
public class Dummy {
public static void main(String[] args) {}
}

How to include my custom annotation processor jar file to my other gradle project?

I am writing a simple java application wherein some classes if I annotate with my custom #Log annotation should print the message "found #Log annotation at <class_name>". I am trying to experiment with custom annotation processors. For that I wrote a second project which will contain my custom annotation & its processor as follows:
#SupportedAnnotationTypes ("Log")
#AutoService({Processor.class})
public class LogProcessor extends AbstractProcessor {
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "found #Log annotation at " + element);
}
}
return true;
}
}
And my custom #Log annotation code is as follows:
#Target (ElementType.TYPE)
public #interface Log {}
I am using #AutoService for registration of the annotation processor. How do I include the .jar file generated on running a gradle build? I tried the following way:
repositories {
mavenCentral()
flatDir {
dirs 'libs'
}
}
dependencies {
annotationProcessor name: 'AnnotationSupplier-1.0-SNAPSHOT'
}
or
dependencies {
annotationProcessor files('/libs/AnnotationSupplier-1.0-SNAPSHOT.jar')
}
But that is not working, and I am not sure how else to add a annotation processor jar. My expectation is that upon running the project with my AnnotationSupplier-1.0-SNAPSHOT added, I should be able to see "found #Log annotation at <class_name>" in the console, which I am not.
You can try this as a workaround:
implementation files('/path/to/custom-processor.jar')
annotationProcessor files('/path/to/custom-processor.jar')
For me the problem was solved by adding the annotation processor module under the parent project (by creating a new module) and adding the following in the parent build.gradle:
dependencies {
annotationProcessor project(':AnnotationProcessor')
compile project(':AnnotationProcessor')
}
Note 1: AnnotationProcessor is the name of my annotation processor module which I created under the parent project.
Note 2: I am doing this in IntelliJ IDE.
Note 3: Make sure to activate annotation processing for the project

Lombok getters/setters are not visible from my annotation processor

I did custom implementation of javax.annotation.processing.AbstractProcessor and it work.
But my processor do not found getters, setters and constructors which generated by Lombok.
Here my propceesor(do I need to create minimal example?):
https://github.com/hohserg1/ElegantNetworking/blob/1.12.2-annotation-processor/src/main/java/hohserg/elegant/networking/annotation/processor/ElegantPacketProcessor.java#L62
Example class:
ElegantPacket //my
#Value //lombok
public class Test implements ClientToServerPacket {
int some; //it visible
//int getSome() //generated by Lombok, it invisible
//public Test(int some) //generated by Lombok, it invisible
}
If you wish to run both Lombok and another annotation processor, then you should delombok your code and run your annotation processor on the result.
This is what the Checker Framework Gradle Plugin does, as explained in the Checker Framework Manual.
Explanation:
Most annotation processors either produce output (say, issue warnings) or generate new classes. Lombok is an annotation processor that modifies existing code. It does so by accessing internal APIs of the javac compiler (it also supports eclipsec). These manipulations cause javac to emit bytecode that contains Lombok's changes to your classes. However, those changes are invisible to earlier phases of the compiler, notably your annotation processor. Another way of saying all this is that Lombok does not play well with other annotation processors.
Ok, I solve this by using annotationProcessor of gradle dependency configuration:
dependencies {
//gradle 4.6+
annotationProcessor 'org.projectlombok:lombok:1.18.8', "io.gitlab.hohserg.elegant.networking:annotation-processor:2.8"
...
}
Also not all lombok changes visible from my annotation processor still. Changes of fields access modifiers is not visible, but it can be determine from lombok annotations. As example, #Value makes package-private fields to private.
Also plugin apt may be used on gradle less that 4.6
buildscript {
repositories {
...
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
...
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.9'
}
}
dependencies {
apt 'org.projectlombok:lombok:1.18.8', "io.gitlab.hohserg.elegant.networking:annotation-processor:2.7"
...
}

Groovy AST transformation - How to compile java annotated class with Gradle and Intellij IDEA

I want to develop a Groovy AST transformation to add some methods on certain classes. So I write an annotation class and corresponding transformation class. Then I annotate a java class with my Groovy AST annotation.
When I compile the java annotated class with embedded groovy compiler (for example by this snippet: Class enhancedClass = new GroovyClassLoader().parseClass(new File("..."));), the transformation is performed and methods are added to the compiled class which is called enhancedClass in the snippet.
But I cann't compile the java class with Gradle groovy plugin and Intellij IDEA correctly.
QUESTION: Can everyone help me to working with Groovy AST transformation in Gradle and Intellij IDEA?
NOTE 1: I use Intellij IDEA 14 ultimate edition.
NOTE 2:
My Groovy AST classes and the java annotated class and my "build.gradle" file are somethings like the followings:
Annotation class:
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.TYPE)
#GroovyASTTransformationClass(classes = {MyASTTransformation.class})
public #interface MyAST {
}
and Transformation class:
#CompileStatic
#GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
public class MyASTTransformation implements ASTTransformation {
#Override
public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
...
}
}
The java annotated class:
#MyAST
public class A {
...
}
The "build.gradle" file:
apply plugin: 'groovy'
sourceSets {
main {
groovy {
srcDirs = ['src/main/groovy', 'src/main/java']
}
java {
srcDirs = []
}
}
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.4'
}
I created my first custom AST transformation and I also ran into similar issues so sharing my limited experience. I'm using Groovy 2.4.7 and IntelliJ IDEA 2016.3.2. I was only able to use the custom transform annotation if:
It was created as a separate artifact (project) and another project that was using the annotation referenced it.
Ran a Groovy test script in the same project as the custom AST transformation code that ran a GroovyShell with Groovy code that used the custom annotation.
I had first attempted doing it in the same project but that didn't work. I believe it's because the AST transformation occurs at compile time. Here's my example:
AST transformation project
Annotation
#Retention (RetentionPolicy.SOURCE)
#Target ([ElementType.TYPE, ElementType.METHOD])
#GroovyASTTransformationClass (classes = [SqlAssistTransform])
#interface SqlAssist {
}
Transformation
#GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class SqlAssistTransform extends AbstractASTTransformation {
...
}
settings.gradle
rootProject.name = 'groovy-sql-transform'
build.gradle
...
group = 'com.company.groovy.transform'
version = '1.0-groovy-2.4'
description = 'Groovy AST transformation for SQL syntax'
...
Project using AST annotation
Annotated class
#SqlAssist
class Something {
...
}
Dependency example #1 - dependency on AST artifact from repository
build.gradle
...
dependencies {
...
// As long as your local repository (or remote repository) has the AST transformation project installed. i.e. Run '.\gradlew install' in AST project to install to local repository
compile group:'com.company.groovy.transform', name:'groovy-sql-transform', version: '1.0.0-groovy-2-4'
...
}
...
Dependency example #2 - dependency on AST project
build.gradle
...
dependencies {
...
compile project(':groovy-sql-transform')
...
}
...
settings.gradle
rootProject.name = 'project-name'
// Assumption that AST project lives at the same level as this project
include "groovy-sql-transform"
project(":groovy-sql-transform").projectDir = new File("../groovy-sql-transform")
Testing
I didn't create a proper unit test, but I did use the following to test and debugged in IntelliJ so I could look at the AST in different places in my AST transformation code. Example test Groovy script that I had within the AST transformation project:
new GroovyShell(getClass().classLoader).evaluate '''
import com.company.groovy.transform.SqlAssist
#SqlAssist
class Testing {
...
}
'''

Categories

Resources