Hot questions for Using AspectJ in gradle

Question:

I'd like to use AspectJ in Gradle project (it's not an Android project - just a simple Java app).

Here is how my build.gradle looks like:

apply plugin: 'java'

buildscript {
    repositories {
        maven {
            url "https://maven.eveoh.nl/content/repositories/releases"
        }
    }
    dependencies {
        classpath "nl.eveoh:gradle-aspectj:1.6"
    }
}

repositories {
    mavenCentral()
}

project.ext {
    aspectjVersion = "1.8.2"
}

apply plugin: 'aspectj' 

dependencies {
    //aspectj dependencies
    aspectpath "org.aspectj:aspectjtools:${aspectjVersion}"
    compile "org.aspectj:aspectjrt:${aspectjVersion}"
}

The code compiles, however the aspect seems to not be weaved. What could be wrong?


Answer:

I have been struggled with this for a while, so this the config I use and works well.

In your configuration do this.

configurations {
    ajc
    aspects
    aspectCompile
    compile{
        extendsFrom aspects
    }
}

In your dependencies use the following configuration. Spring dependencies are not needed if you are not using spring fwk.

dependencies {

    //Dependencies required for aspect compilation
    ajc "org.aspectj:aspectjtools:$aspectjVersion"
    aspects "org.springframework:spring-aspects:$springVersion"
    aspectCompile  "org.springframework:spring-tx:$springVersion"
    aspectCompile  "org.springframework:spring-orm:$springVersion"
    aspectCompile  "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:$hibernateJpaVersion"

}

compileJava {
    sourceCompatibility="1.7"
    targetCompatibility="1.7"
    //The following two lines are useful if you have queryDSL if not ignore
    dependsOn generateQueryDSL
    source generateQueryDSL.destinationDir

    dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, "compileJava")

    doLast{
        ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
        ant.iajc(source:"1.7", target:"1.7", destDir:sourceSets.main.output.classesDir.absolutePath, maxmem:"512m", fork:"true",
                aspectPath:configurations.aspects.asPath,
                sourceRootCopyFilter:"**/.svn/*,**/*.java",classpath:configurations.compile.asPath){
            sourceroots{
                sourceSets.main.java.srcDirs.each{
                    pathelement(location:it.absolutePath)
                }
            }
        }
    }
}

I dont use the plugin I use the ant and aspectj compiler to do that, probably there will be an easy way

Question:

I am experiencing a weird behavior with german "Umlaute" (ä, ö, ü, ß) when using Java's equality checks (either directly or indirectly. Everything works as expected when running, debugging or testing from Eclipse and input containing "Umlaute" is treated as equal or not as expected.

However when I build the application using Spring Boot and run it, these equality checks fail for words that contain "Umlaute", i.e. for words like "Nationalität".

Input is retrieved from a webpage via Jsoup and content of a table is extracted for some keywords. The encoding of the page is UTF-8 and I have handling in place for Jsoup to convert it if this is not the case. The encoding of the source files is UTF-8 as well.

    Connection connection = Jsoup.connect(url)
                .header("accept-language", "de-de, de, en")
                .userAgent("Mozilla/5.0")
                .timeout(10000)
                .method(Method.GET);
    Response response = connection.execute();
    if(logger.isDebugEnabled())
        logger.debug("Encoding of response: " +response.charset());
    Document doc;
    if(response.charset().equalsIgnoreCase("UTF-8"))
    {
        logger.debug("Response has expected charset");
        doc = Jsoup.parse(response.body(), baseURL);
    }
    else
    {
        logger.debug("Response doesn't have exepcted charset and is converted");
        doc = Jsoup.parse(new String(response.bodyAsBytes(), "UTF-8"), baseURL);
    }

    logger.debug("Encoding of document: " +doc.charset());
    if(!doc.charset().equals(Charset.forName("UTF-8")))
    {
        logger.debug("Changing encoding of document from " +doc.charset());
        doc.updateMetaCharsetElement(true);
        doc.charset(Charset.forName("UTF-8"));
        logger.debug("Changed encoding of document to: " +doc.charset());
    }
    return doc;

Example log output (from deployed app) of reading content.

Encoding of response: utf-8
Response has expected charset
Encoding of document: UTF-8

Example input:

<tr><th>Nationalität:</th>     <td> [...] </td>    </tr>

Example code that fails for words containing ä, ö, ü or ß but works fine for other words:

Element header = row.select("th").first();
String text = header.ownText();
if("Nationalität:".equals(text))
{
 // goes here in eclipse
}
else
{
 // and here in deployed spring boot app
}

Is there any difference between running from Eclipse and a built & deployed app that I am missing? Where else could this behavior come from and how I this be resolved?

As far as I can see this is not (directly) an encoding issue since the input shows "Umlaute" correctly... Since this is not reproducible when debugging, I am having a hard time figuring out what exactly goes wrong.

Edit: While input looks fine in logs (i.e. diacritics show up correctly) I realized that they don't look correct in the console: <th>Nationalität:</th>

I am currently using a Normalizer as suggested by Mirko like this: Normalizer.normalize(input, Form.NFC); (also tried it with NFD). How do (SpringBoot-) console and (logback) logoutput differ?


Answer:

Diacritics like umlauts can often be represented in two different ways in unicode: As a single-codepoint character or as a composition of two characters. This isn't a problem of the encoding, it can happen in UTF-8, UTF-16, UTF-32 etc. Java's equals method may not consider composite characters equal to single-codepoint characters, even though they look exactly the same. Try to have a look at the binary representation of the strings you are comparing, this way you should be able to track down the differences. You could also use the methods of the "Character" class to iterate through the strings and print out the properties of all the characters. Maybe this helps, too, to figure out differences.

In any case, it could help if you use java.text.Normalizer on both "sides" of the "equals", to normalize the text to, for example, Unicode Normalization Form C. This way, differences like the aforementioned should be straightened out and the strings should compare as expected.

Question:

I am using the Gradle AspectJ plugin to weave some production aspect into test Java code. I would have expected this to work out of the box with the plugin, but apparently that's not the case as demoed here: https://github.com/sedubois/gradle-aspectj-poc/tree/dc44f529831a485fcff8f4889dba8098784dddb4

The weaving of UnsupportedOperationAspect into MainSevice (both under src/main/java) works, but the weaving of this same aspect into TestService (under src/test/java) doesn't.

I am new to Groovy, Gradle and AspectJ and didn't figure out if I should add some testAspectpath configuration or similar?

EDIT1: seems unrelated, but iajc gives a warning:

... :compileTestAspect [ant:iajc] [warning] incorrect classpath: [...]\gradle-aspectj-poc\build\resources\main ...

EDIT2: I naively added this code to the Gradle dependencies:

ajInpath fileTree(dir: "src/test/java")
aspectpath fileTree(dir: "src/test/java")
testAjInpath fileTree(dir: "src/test/java")
testAspectpath fileTree(dir: "src/test/java")

It doesn't help, the first test works and the second one fails as usual, with these new messages:

... :compileAspect [ant:iajc] [warning] build config error: skipping missing, empty or corrupt aspectpath entry: [...]\gradle-aspectj-poc\src\test\java\com\hello\aop\TestService.java [ant:iajc] [warning] build config error: skipping missing, empty or corrupt inpath entry: [...]\gradle-aspectj-poc\src\test\java\com\hello\aop\TestService.java ... :compileTestAspect [ant:iajc] [warning] build config error: skipping missing, empty or corrupt aspectpath entry: [...]\gradle-aspectj-poc\src\test\java\com\hello\aop\TestService.java [ant:iajc] [warning] build config error: skipping missing, empty or corrupt inpath entry: [...]\gradle-aspectj-poc\src\test\java\com\hello\aop\TestService.java [ant:iajc] [warning] incorrect classpath: [...]\gradle-aspectj-poc\build\resources\main ...


Answer:

By default the plugin does not weave the main aspects in the test classes - we simply never made a configuration option for it. You can do this yourself using the following line:

testAspectpath sourceSets.main.output

Question:

I want to use aspects in a Java that has some resources limitations so I can't use Spring aspects due to it's big memory footprint.

So want I want to do without Spring, is to create a custom annotation and which will trigger an aspect on the annotated methods.

I have some implementation but I can't see the Apect being triggered when the method runs. This is what I have:

My custom annotation

 @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {}

Where the annotation is used

@MyCustomAnnotation
     public void someMethod() {
        System.out.println("Method runnning...");
     }

The aspect I created

@Aspect
public class MyAspect {

    @Pointcut("execution(* *(..))")
    public void callAt() {}

    @Around(("callAt()"))
    public void doSomething(ProceedingJoinPoint point)  {
      point.proceed();
      System.out.println("Aspect is runnning...");
}

The configurations I have in my gradle file

dependencies {
        classpath "gradle.plugin.aspectj:gradle-aspectj:0.1.6"
    }

apply plugin: 'aspectj-gradle'

    compile 'org.aspectj:aspectjrt:1.9.1'
    compile 'org.aspectj:aspectjweaver:1.9.1'

I have no idea why the aspect it's not being triggered, I can't see the message from that aspect in my console when the app runs. Any idea what I am missing?


Answer:

Have you tried something like:

@Around("@annotation(mycustomannotation.package.MyCustomAnnotation)")

where mycustomannotation.package.MyCustomAnnotation is the interface of the anotation you created

Question:

I am attempting to write a Gradle plugin that modifies the JARs of dependencies (by weaving with AspectJ during build). However, due to how I'm modifying them, I need to use the unmodified versions at compile time and replace those with the modified versions at run time. So, in my plugin, I created a configuration for the user to add the dependencies that need to be modified. I would like to make it so that the plugin adds those immediate dependencies to the compile-only classpath, adds the modified versions of those immediate dependencies as runtime-only classpath, and adds the unmodified transitive dependencies to both the compile and runtime classpath.

My issue is how can I add just the transitive dependencies of that configuration to the runtime classpath without adding the direct dependencies?

It seems impossible, because, to get the transitive dependencies, I need to resolve my configuration before the runtime configuration, so I can add some of the resolved dependencies of my configuration that to the runtime configuration before the runtime configuration is resolved.

As a side note, I've been relying on the ordering of the classpath so that I can include both the modified and unmodified versions at runtime and it uses the modified version. However, I think that might not be working for dynamically resolved classes.


Answer:

Take a look at the source code for my monkey patch plugin where I get the transitive dependencies of a "target" dependency without the "target" itself

Interesting snippets

            configurations {
                monkeyPatchNonTransitive { transitive = false }
                monkeyPatchTransitive
            }

And

                dependencies {
                    monkeyPatchTransitive target
                    monkeyPatchNonTransitive target
                    compileOnly(target) {
                        transitive = false
                    }
                }

                Set<Map>  depSet = [] as Set
                ResolvedDependency topDependency = configurations.monkeyPatchTransitive.resolvedConfiguration.firstLevelModuleDependencies.iterator().next()
                topDependency.children.each { ResolvedDependency child ->
                    child.allModuleArtifacts.each { ResolvedArtifact artifact ->
                        ModuleVersionIdentifier mvi = artifact.moduleVersion.id
                        def dependency = [
                                group  : mvi.group,
                                name   : mvi.name,
                                version: mvi.version,
                                ext    : artifact.extension
                        ]
                        if (artifact.classifier) {
                            dependency['classifier'] = artifact.classifier
                        }
                        depSet << dependency
                    }
                }

Question:

I am trying to add ajc compiler to my gradle project as gradle plugin. Unfortunately, during compilation it shows me massive amount of errors due to Lombok.

build.gradle:

group 'com.kmb.bank'
version '0.0.1-SNAPSHOT'

project.ext {
    aspectjVersion = '1.9.2'
}

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "gradle.plugin.aspectj:gradle-aspectj:0.1.6"
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: "aspectj.gradle"

sourceCompatibility = 11
targetCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.7'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'commons-codec', name: 'commons-codec', version: '1.11'

    implementation('org.springframework.boot:spring-boot-starter-amqp')
    implementation('org.springframework.boot:spring-boot-starter-web')
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile ('org.springframework.boot:spring-boot-starter-security')
    compile("org.springframework.boot:spring-boot-starter-data-mongodb")
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-   aop', version: '2.1.1.RELEASE'
    }

It shows me errors that there are no getter, setters for every model.


Answer:

This is working for me:

plugins {
    id 'java'
    id "io.freefair.aspectj.post-compile-weaving" version "4.1.4"
}

group 'com.amdocs'
version '1.0.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'org.projectlombok', name: 'lombok', version: '1.18.10'
    annotationProcessor "org.projectlombok:lombok:1.18.10"
}

Question:

I use spring 5, Java 10 and Gradle 4.8

I face the following issue:

java.lang.ClassNotFoundException: org.aspectj.util.PartialOrder$PartialComparable

I read on the stack overflow to add:

compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1'

However when I do this, I get 100 errors like:

error: module java.persistence reads package org.aspectj.internal.lang.reflect from both aspectjrt and org.aspectj.weaver

How to fix the issue?

Dependencies:

compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'org.springframework', name: 'spring-core', version: '5.0.7.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '5.0.7.RELEASE'
compile group: 'org.springframework', name: 'spring-context-support', version: '5.0.7.RELEASE'
compile group: 'org.postgresql', name: 'postgresql', version: '42.2.1'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.8.RELEASE'
compile group: 'io.projectreactor', name: 'reactor-core', version: '3.1.8.RELEASE'
compile group: 'org.springframework.security', name: 'spring-security-core', version: '5.0.6.RELEASE'
compile group: 'org.springframework.security', name: 'spring-security-config', version: '5.0.6.RELEASE'

compile group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.1.Final'
compile group: 'org.hibernate', name: 'hibernate-hikaricp', version: '5.3.1.Final'
compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.10.Final'
compile group: 'org.glassfish', name: 'javax.el', version: '3.0.1-b08'
compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.6.2'

compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.1'
compile group: 'org.freemarker', name: 'freemarker', version: '2.3.28'
compile group: 'javax.mail', name: 'javax.mail-api', version: '1.6.2'
compile group: 'javax.activation', name: 'activation', version: '1.1.1'

Answer:

The problem was that spring-data-jpa has dependency of aspectjrt version 1.8.x which (as I suppose) is not ready for java9 modularity. I excluded it from dependencies and included proper versions of both aspectjrt and weaver and it works now.