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:
- In
ltwjar-enhanced-log4j
we "enhance" log4j by wrapping an advice around Logger.info(Object o)
- We repackage
log4j.jar
into a new jar ltwjar-enhanced-log4j.jar
that contains the woven type
- We depend on
ltwjar-enhanced-log4j.jar
instead of log4j
in our application code...
- ...And in doing so, the advice sticks
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!