Maven, Netbeans Run and the use of a Javaagent

Published: 05/03/11 02:13 PM


Here’s my setup for a maven 3 multi-module project, using a java-agent, that can be run either from the Netbeans-IDE or with a handcrafted windows-batch-file.

My system is: Windows7 64bit, Netbeans7, JDK7, Maven 3

The Multimodule Project

First I created a new Multimodule Project (”File”->”New Project”->”Maven”->”POM Project”).
I set up my multimodule-pom for my needs (utf-8, Java 1.7, Checkstyle, etc., see pom.xml(multi) below (entries under modules will be added by Netbeans automatically when creating submodules).

For Checkstyle-config, I used the netbeans Config-Dialog “Project-Properties”->”Formatting”->”CheckStyleFormatting”. This automatically creates configs in the pom and creates a file called nb-configuration.xml with IDE-specific configs. The pom <configLocation> suggested by Netbeans is just fine for my case (config/sun_checks.xml) as this is a predefined config. If you wish to use custom checkstyle-config you should follow this very good article about multimodule-projects and checkstyle.

The agent / first submodule

Then I created the first submodule “agent”. This is as simple as creating a regular Maven Java App with Netbeans. The project-location is important though.
So after “File”->”New Project”->”Maven”->”Java Application”, you specfiy your project to have the location directly below the multimodule-project. That is, if your multimodule-project is located in Z:\\myprojects\\mymultimoduleproject\\ (this is where the pom.xml is located), then your submodule should have exactly this path as project-location. Netbeans will add a folder according to the project-name directly below the multimodule-folder and it will add this submodule to the modules-section of the parent-pom, as well as adapting the pom of our submodule to refer to the parent pom.

The agent shown here is only a skeleton for demonstration purposes. As I decided to use javassist, I added the following dependency

<dependency>
	<groupId>org.javassist</groupId>
	<artifactId>javassist</artifactId>
	<version>3.14.0-GA</version>
</dependency>

Here’s a good article about Bytecode-Engineering Libs. (German only!)

As it will be important later on to have a jar-file with a certain manifest entry, I added this section to <build>-<plugins> as well:

<plugin>
	<artifactId>maven-jar-plugin</artifactId>
	<version>2.3.1</version>
	<configuration>
		<archive>
			<index>true</index>
			<manifestEntries>
				<Premain-Class>de.macrominds.test.agent.Agent</Premain-Class>
			</manifestEntries>
		</archive>
	</configuration>
</plugin>

The full pom is shown at the end of the article.

And here’s the agent’s javacode:

package de.macrominds.test.agent;
 
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.log4j.Logger;
 
/**
 * Example Agent, that doesn't do anything. It's just here for 
 * demonstration-purposes.
 * @author Thomas
 */
public class Agent {
    private static final Logger LOG = Logger.getLogger(Agent.class);
    public static void premain(final String arg, Instrumentation instr){
        System.out.println("premain called");
        instr.addTransformer(new ClassFileTransformer() {
            private boolean consideredWorthACheck(String className){
                // TODO check Classname against ruleset to quickly check if 
                // costly analysis shall be performed.
                return true;
            }
            public byte[] transform(ClassLoader loader, String className, 
                                    Class<?> classBeingRedefined, 
                                    ProtectionDomain protectionDomain, 
                                    byte[] classfileBuffer) 
                                        throws IllegalClassFormatException {
                if(consideredWorthACheck(className)){
                    ClassPool cp = ClassPool.getDefault();
                    try {
                        CtClass cc = cp.get(className);
                        // TODO perform analysis and transformation strategies here
                        LOG.info("didn't perform transformation on "+className+". Not implemented yet.");
                    } catch (NotFoundException ex) {
                        LOG.error("could not load class "+className
                                +" for introspection though the ruleset says is"
                                + " worth an introspection. Falling back. That "
                                + "means, the unmodified Bytecode is returned");
                    }
                }
                return classfileBuffer;
            }
        });
    }
}

The runnable App / second submodule

What I wanted to have at the end is an Application, that can be distributed easily (though I decided to use a handcrafted batch-file for the moment). That means, I wanted dependent libs to be in a dedicated lib-folder.

I created the second submodule-project in the same way, I did before with the agent-module: “File”->”New Project”->”Maven”->”Java Application” and made sure, that the project-location is right below the multimodule-project’s folder (where the pom is located).

In order to get the dependencies in place (lib-folder) and to have the according classpath-entries added to the manifest of the jar-file, I added the plugins maven-dependency-plugin and maven-jar-plugin to the pom and configured the dependency-plugin to store the dependencies in ${project.build.directory}/lib and the jar-plugin to add the classpath and to prefix the classpath with lib/. In order to have the agent in place, I added the agent as dependency.

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>testmulti</artifactId>
        <groupId>de.macrominds.test</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>de.macrominds.test</groupId>
    <artifactId>app</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>app</name>
    <url>http://www.test.nourl/app</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                               <!-- output of dependent libs  see ClasspathPrefix-configuration below -->                                
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <index>true</index>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <!-- prefix the classpath for the distribution -->
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>${main.class}</mainClass>
                        </manifest>
                        <manifestEntries>
                            <url>${project.url}</url>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>agent</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</project>

I left the autogenerated main-class as it was, it’s just testing now. (All you need is a Java-Class with a main-method. Just make sure to have the entry

<main.class>de.macrominds.test.app.App</main.class>

of the multimodule pom correctly set to your package and classname.)

Running the Application from the console / out of the explorer

As I really didn’t want to fiddle around with the several tools out there for the moment, I decided to create a handcrafted Windows-batch-file for now.

However: If you’re interested in this stuff, here are some links:

Back to the batch file: I placed the following batch-file directly in the multimodule-project-location. You can see, that the commandline-argument -javaagent is passed to the jvm in order to get the agent working. It resides under the lib-folder as soon as you build the project, because we specified that in the above pom.

rem switch to appliation distribution directory
cd .\app\target\
rem launch nodez using the javaagent
java.exe -javaagent:lib/agent-1.0-SNAPSHOT.jar -jar app-1.0-SNAPSHOT.jar  
rem return to directory, this script has been started from
cd %~dp0

Running the Application from the Netbeans IDE

This part was a bit tricky.

I fiddled around with maven-exec, but then decided to use Netbeans-Run-Configuration under “Project-Properties”->”Run”. There I specified the Main-class (de.macrominds.test.app.App), the Working Directory (app-module-base-path app-module-target-path), e.g. “Z:\\myprojects\\mymultimoduleproject\\app” “Z:\\myprojects\\mymultimoduleproject\\app\\target” and the VM-Options (java-agent).

The first tests didn’t succeed. I tried “lib/agent-1.0-SNAPSHOT.jar”, “./lib/agent-1.0-SNAPSHOT.jar”, “agent-1.0-SNAPSHOT.jar”, but nothing would work. Well, as you can see, I made a mistake, when I used the app-path for working directory. It certainly has to be the target-path, so the vm-options I tried before might have worked with the target path.

The solution is to use your maven repositories path in VM Options: “-javaagent:${settings.localRepository}/de/macrominds/test/agent/1.0-SNAPSHOT/agent-1.0-SNAPSHOT.jar”.

That’s it. You can now run the application from netbeans (right-click on the app-module->”run”), or build it using maven or netbeans and then start it with the batch-file. For distribution, I’d modify the paths in the batch file and put target/app-1.0-SNAPSHOT.jar and target/lib together with the batch-file into a zip file.

My needs for distribution are currently only to show progress to the customer, so I guess there will be another solution for release, that is easier to maintain and suits other OS as well.


pom.xml(multi)

<?xml version="1.0" encoding="UTF-8"?>
<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>de.macrominds.test</groupId>
    <artifactId>testmulti</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>testmulti</name>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- global main class for execution, needed for executable app-jar -->
        <main.class>de.macrominds.test.app.App</main.class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <source>1.7</source>
                    <target>1.7</target>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <configLocation>config/sun_checks.xml</configLocation>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>
    <modules>
        <module>agent</module>
        <module>app</module>
  </modules>
</project>

pom.xml(agent)

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>testmulti</artifactId>
        <groupId>de.macrominds.test</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>de.macrominds.test</groupId>
    <artifactId>agent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>agent</name>
    <url>http://www.test.nourl/javaagent</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <agent.class>de.macrominds.test.agent.Agent</agent.class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <index>true</index>
                        <manifestEntries>
                            <Premain-Class>${agent.class}</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.14.0-GA</version>
        </dependency>
    </dependencies>
</project>

Kommentieren Sie diesen Artikel

JWebPane reloaded / Thoughts on a Swing-based Browser-component

Instrumentation (Javaagent) and Javaassist, className Issue with getClassFile2