Hot questions for Using AspectJ in scala

Question:

I have a library that requires me to specify a javaagent using the aspectjweaver jar. eg.

java -jar  -javaagent:/some/location/aspectjweaver-1.8.2.jar myFatJar.jar

This works fine. However, the environment that my fatjar will be running on does not have aspectjweaver-1.8.2.jar and I can't put it there (getting a more configurable environment may be a route I go down later).

Is there a way, preferably using sbt-assembly, to package up aspectjweaver into my fatjar, and run it that way? If it matters, my application is a Spray one using Scala, built using sbt.

Constraints

  • Can't have a separate jar
  • Can specify an arbitrary java command
  • Have complete control of the build process

Answer:

If you have, as you say, complete control over the build process, why don't you use compile-time weaving instead of load-time weaving? Then you just package the AspectJ runtime library aspectjrt.jar into your fat jar and are done with the problem. You even avoid the overhead of runtime weaving on application start-up.

The only reason I can think of which makes this approach problematic is that you need to weave joinpoints outside the control of your build process.

Question:

In my Java application I created methods that return Either<String, T> objects.

This is because in some places I invoke these methods as the parameter of (3rd party) methods that expect a String parameter as input. While in other places I invoke these methods as the parameter of (3rd party) methods that expect some other parameter type (T) as input.

So depending on the place where I invoke the methods that I created, the code looks like:

their.thirdPartyExpectsString(my.calculateEither().getLeft());
their.thirdPartyExpectsString(my.calculateEither() + "");

or

their.thirdPartyExpectsDouble(my.calculateEither().getRight());

(I defined Either.toString() as Either.getLeft()).

Pay attention, I cannot change the 3rd party code (anyway not without bytecode manipulation), and I would like to keep my design in which I return Either from my methods.

Is there a way to simplify my code and make it look like

their.thirdPartyExpectsString(my.calculateEither());
their.thirdPartyExpectsDouble(my.calculateEither());

I.e., not having to add the getLeft()/getRight() or + "" all the time?

Actually, it does not bother me much if I will have to do

their.thirdPartyExpectsDouble(my.calculateEither().getRight());

because I don't have to do it often. But I would like to get rid of the need to call getLeft() or + "" when my.calculateEither() returns a Left (a String).

Given an either, it's not hard to see if it represents Right or Left, simply by checking which side has a null.

But the problem is with the type conversion, i.e. the compilation error when thirdPartyExpectsString() expects a String but gets an Either.

I was able to catch the return value of my.calculateEither() by AspectJ but I could not see a way how to use something like @AfterReturning advice to make the compiler understand that I want to return my.calculateEither().getLeft(), i.e a String....

Any ideas?


Answer:

Add the following method to your implementation of the Either class:

@SuppressWarnings("unchecked")
public <T> T whichever() {
    return (T) (isLeft() ? getLeft() : getRight());
}

Note that I'm purposefully ignoring the warning about the unchecked cast, and indeed will cause a ClassCastException if you use it in a place where the external API you interface with expects a left value but you invoke it on an Either instance which contains a right value. This method will do an implicit cast based on where you use it. It will cast to a T type where you pass it to another method which expects an argument of type T or you try to assign the method return value to a variable of type T.

So the following demo code:

Either<String, Double> containsString = Either.<String, Double>left("first");
Either<String, Double> containsDouble = Either.<String, Double>right(2d);

their.expectsString(containsString.whichever());
their.expectsDouble(containsDouble.whichever());
their.expectsDouble(containsString.whichever());

will work well in the first invocation and will cause a ClassCastException in the third invocation, just an one would expect, because we consider it as an illegal use case.

In conclusion, it's nice to know that it will work in all places where the T type to which we are implicitly casting is assignable to the actual value contained by the Either object. Unfortunately, you will only find out at run time, should this not be the case.

Question:

I have a domain made in Scala and some classes made in Java. I need to make some aspects with Aspectj that I know that work because with a Java class and it works. The problem is that when the Scala class is annotated it does not work. Other annotations like hibernate's work well with my Scala class.

This is my 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 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>Group</groupId>
    <artifactId>Parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.source-target.version>1.8</java.source-target.version>
        <aspectj.version>1.8.2</aspectj.version>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.1</version>
                    <configuration>
                        <source>${java.source-target.version}</source>
                        <target>${java.source-target.version}</target>
                        <useIncrementalCompilation>false</useIncrementalCompilation>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>createClassesDir</id>
                            <phase>process-resources</phase>
                            <configuration>
                                <tasks>
                                    <mkdir dir="${project.build.directory}\unwoven-classes" />
                                    <mkdir dir="${project.build.directory}\classes" />
                                </tasks>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>aspectj-maven-plugin</artifactId>
                    <version>1.7</version>
                    <configuration>
                        <complianceLevel>1.8</complianceLevel>
                        <source>${aspectj.version>}</source>
                        <target>${aspectj.version>}</target>
                        <weaveDirectories>
                            <weaveDirectory>${project.build.directory}\unwoven-classes</weaveDirectory>
                        </weaveDirectories>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>process-classes</phase>
                            <goals>
                                <goal>compile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>scala-compile-first</id>
                            <phase>process-resources</phase>
                            <goals>
                                <goal>add-source</goal>
                                <goal>compile</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>scala-test-compile</id>
                            <phase>process-test-resources</phase>
                            <goals>
                                <goal>testCompile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.scala-lang</groupId>
                <artifactId>scala-library</artifactId>
                <version>2.12.1</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <modules>
        <module>Aspects</module>
    </modules>
</project>

I think that I have to do something with maven, because the aspects and the rest of the code works fine. Is there anyway to do that?

Thank you!


Answer:

First of all, make sure your aspects (annotation-based or native syntax) always have a .aj file extension (add them to your project via "New aspect" instead of "New class" menu in whatever IDE you use). I have removed the duplicate class from your repo in my fork and renamed the other one accordingly. I chose the native syntax one, by the way.

What was worse, though, is that you somehow expect unwoven Scala classes in a specific directory, but you did not configure the Scala plugin to actually put them there. I fixed that by adding this snippet:

<configuration>
    <outputDir>${project.build.directory}/unwoven-classes</outputDir>
</configuration>

Now the AspectJ Maven plugin finds the Scala classes there and performs binary weaving upon them. This fixes both your Java and Scala test. Both of them failed in Maven before, now at least the Java one works in IntelliJ, but not the Scala one. This is due to the fact that IDEA does not know about this strange Maven setup with the additional (intermediate) directory of yours.

So there is nothing wrong with the aspect as such or AspectJ being unable to work with Scala binaries. The project setup was wrong and in a way it still is with respect to IDE support.

So how can you fix it completely? You have several options:

  • Put the aspect code into another Maven module and there configure a weave dependency on the Java + Scala module, weaving all classes from there into the aspect module. But then you might still have issues with running the tests. But at least you could configure the IDEA project for post-compile weaving with the right dependency.
  • You could also put the Scala code in its own module instead, define it as a dependency for the Java + AspectJ module and apply binary weaving to it that way.

Other variants are possible. I do not want to over-analyse here, I just fixed your Maven setup in a quick and simple approach to get you going:

$ mvn clean verify

(...)
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Aktive Codepage: 65001.
Running aspects.AnnotationAspectTest
set(String model.JavaEntity.name)
set(String model.ScalaEntity.name)
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.055 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ aspectj-with-scala ---
[INFO] Building jar: C:\Users\Alexander\Documents\java-src\aspectj-with-scala\target\aspectj-with-scala-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
(...)

P.S.: I have also created a pull request for you to easily integrate my changes into your repo.

P.P.S.: See, an MCVE is more helpful than what you did before: Post a separate question only showing an aspect and then post this question here with only a Maven POM. I needed both plus the other classes in order to reproduce and solve the problem. After you published the GitHub project it was pretty straightforward to find and fix.

Question:

I am trying to set up Kamon on my Spray / Akka service but I am not getting very far unfortunately.

I use the sbt-assembly plugin to build a single jar file which I run with a simple java -jar service.jar.

I naively thought that I would be able to weave the service with java -javaagent:aspectj-1.8.8.jar -jar service.jar but I get :

objc[13280]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/bin/java and /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/libinstrument.dylib. One of the two will be used. Which one is undefined. Failed to find Premain-Class manifest attribute in aspectj-1.8.8.jar Error occurred during initialization of VM agent library failed to init: instrument

And of course, my service doesn't start. (it works just fine without aspectj. I also tried aspectj 1.7.4 and the paths are correct.

Any help / pointer would be very appreciated !


Answer:

The solution was because I was starting the JVM with AspectJ and not AspectJWeaver... All good now !