Reputation: 1169
There is a lot of documentation regarding weaving into JAR files, such as the examples described in the book AspectJ in Action: Second Edition.
However, I barely see any questions regarding weaving into WAR files. Due to the popularity of WAR files and the AspectJ library, I find it hard to believe that it's not supported, and so I'm hoping for some answers.
Let's say you have a "legacy" Java project which is being packaged as a WAR file using the maven-war-plugin plugin. Keep in mind that changing it from WAR to JAR packaging is nearly impossible due to the risk involved.
Now, let's say I wanted to introduce AOP for some cross-cutting functionality. Due to the nature of our applications, my best bet is to create a separate project. So basically, the aspect classes will not be in the same project as the legacy project. It will be separate.
After this part, I'm stuck. From reading the book and the other documentation online, there seems to be two options for compile time weaving:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<weaveDependencies>
<weaveDependency>
<groupId>ajia.helloworld</groupId>
<artifactId>application-unwoven</artifactId>
</weaveDependency>
</weaveDependencies>
<aspectLibraries>
<aspectLibrary>
<groupId>ajia.helloworld</groupId>
<artifactId>profiling-aspect-library</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
</plugin>
</plugins>
</build>
The problem with the second option is that <weaveDependency>
requires the application to be a JAR file. Atleast, as far as I know.
I did come across a similar question on the AspectJ mailing list: http://aspectj.2085585.n4.nabble.com/Weaving-into-a-war-file-td2082924.html. In case the link expires, the answer to the question was:
you need to take it apart, weave the .jar and put it back together. You cannot directly weave the jar within the war
But is that even possible? I don't see any documentation for that either.
There are similar questions on SO which either don't have any responses at all or are wrong/outdated.
I would appreciate any help on this since I do not know what my next step should be.
Upvotes: 0
Views: 925
Reputation: 1902
I'll try and present two distinct solutions and then you can pick/choose or mix/match how you go about implementing your solution.
Example 1: You have been asked to dump some text to stdout pre- and post-execution of the org.apache.log4j.Logger.info(Object o)
method found in log4j
.
(This is a very contrived example, but I'm using it because I have been asked to do something not entirely dissimilar in the past!)
I'll assume you're using a Multi-Module Maven Project.
To achieve this, we can consume the log4j
jar (compiled classes), apply an Aspect to them, and spit out a new set of load-time-woven compiled classes. We do this by leveraging the weaveDependency functionality you've already called out in a module of its own. So, instead of depending on log4j
in your projects, you'll depend on ltwjar-enhanced-log4j
as described below.
With that in mind, create your project hierarchy thus:
ltwjar
/ltwjar-ltwjar-enhanced-log4j
/ltwjar-runtime
Setup your parent pom.xml to look something like this (we set a couple properties here just for convenience/consistency's sake - there are other ways to control versioning such as dependencyManagement and imported depdendencies, but that's for a different question!):
<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>ltwjar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<aspectj.version>1.8.13</aspectj.version>
<log4j.version>1.2.17</log4j.version>
</properties>
<modules>
<module>ltwjar-enhanced-log4j</module>
<module>ltwjar-runtime</module>
</modules>
</project>
Set up your ltwjar-enhanced-log4j
pom.xml like this:
<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>
<parent>
<groupId>com.example</groupId>
<artifactId>ltwjar</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>ltwjar-enhanced-log4j</artifactId>
<dependencies>
<!-- we need this because the post-weave classes may (will) depend on aspectj types -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- ATTENTION! Scope this to provided otherwise it won't work - because of transitive dependencies,
anything that depends on this module will end up getting the _actual_ log4j classes versus the ones we are enriching! -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<!-- instruct aspectj to weave the classes in the jar! -->
<weaveDependencies>
<weaveDependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</weaveDependency>
</weaveDependencies>
<Xlint>warning</Xlint>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
And now let's setup our stub "runtime" module to leverage the load-time-wove log4j ltw-enhanced-log4j
when it logs by configuring its pom.xml thus:
<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>
<parent>
<groupId>com.example</groupId>
<artifactId>ltwjar</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>ltwjar-runtime</artifactId>
<dependencies>
<!-- Note that this module doesn't care (itself) about aspectj.
It _does_ care that it depends on the ltwjar-enhanced-log4j module
and not log4j, however. So let's depend on that! -->
<dependency>
<groupId>com.example</groupId>
<artifactId>ltwjar-enhanced-log4j</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
OK that's our framework setup. Now let's create a pointless aspect to demonstrate the point!
In ltwjar-enhanced-log4j
create type com.example.aspect.LoggingAspect
like so:
package com.example.aspect;
import org.apache.log4j.Logger;
public aspect LoggingAspect {
// this pointcut will match "info(Object o)" method executions where the
// target is an instanceof Logger
pointcut logInfoWithObject(Object obj, Logger logger) :
execution(void info(Object)) && args(obj) && target(logger);
// here is our advice - simply sysout something before the execution
// proceeds and after it has finished - regardless of outcome (exception
// or not).
void around(Object obj, Logger logger) : logInfoWithObject(obj, logger) {
System.out.println("Before Logger.info(Object o)");
try {
proceed(obj, logger);
} finally {
System.out.println("After Logger.info(Object o)");
}
}
}
And finally in ltwjar-runtime
, create a driver/harness that shows everything working. Type com.example.Driver
:
package com.example;
import org.apache.log4j.Logger;
public class Driver {
private static final Logger LOG = Logger.getLogger(Driver.class);
public static void main(String[] args) {
LOG.info("In main");
}
}
Now from your parent project run mvn clean install
and then mvn -pl ltwjar-runtime exec:java -Dexec.mainClass="com.example.Driver"
.
The output should be something like this (since you don't have a log4j.xml on your classpath):
Before Logger.info(Object o)
log4j:WARN No appenders could be found for logger (com.example.Driver).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
After Logger.info(Object o)
Note the first and last lines.
What's happening here:
ltwjar-enhanced-log4j
we "enhance" log4j by wrapping an advice around Logger.info(Object o)
log4j.jar
into a new jar ltwjar-enhanced-log4j.jar
that contains the woven typeltwjar-enhanced-log4j.jar
instead of log4j
in our application code...OK so how does this fit into the warfile that was asked about originally?
Simple - don't put any code in your warfile module. If you have to put something in there, make it configuration. Instead, move all your code to a yourproject-domain
or yourproject-logic
or ... (insert module name here) and have your warfile depend on that module. This is good practice since it enables a Separation of Concerns: your application shouldn't know it's running under a warfile umbrella; it could just as easily be running as a Spring Boot tomcat uberwar etc. The packaging concern (war) is distinct from the business concern (domain).
I'll update with another example of shared aspect library this evening, unless someone beats me to it. Also, if some of this makes no sense, holler in comments!
Upvotes: 1