Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I dynamically generate and reference a class in a source file in the same Maven project?

In a Maven Build, I am dynamically generating some Java types using a byte code generation library (Byte Buddy). Naturally, these class files don't have corresponding source files. Only a few classes would be generated this way. The majority of the code for this project is going to be Java source. Ideally, the Java source would reference the generated types in a static way, rather than using reflection or runtime code generation, which means the classes need to be on the the compile class path for javac. Can I get the generated classes on the compile class path for the same Maven project i.e. without having a separate Maven project and artifact to hold the generated byte code referenced by the Maven project containing the source code?

UPDATE: I have already tried putting the generated classes directly into target/classes i.e. project.build.outputDirectory, early in the Maven Build Lifecycle, but it seems that is not on the class path. The generated types couldn't be resolved by the Maven Compiler Plugin or the IDE. I also tried using the Build Helper Maven Plugin to add an extra resources directory under target containing the generated classes, which were then automatically copied into target/classes. This configuration exhibited the same problem.

UPDATE: I have created a complete public repo on GitHub: https://github.com/mches/so-42376851

Here is the Maven POM for the project I want to have static classes that referenced byte code enhanced classes:

<?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>

    <parent>
        <groupId>demo</groupId>
        <artifactId>demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>demo-enhanced</artifactId>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>demo</groupId>
                                    <artifactId>demo-original</artifactId>
                                    <version>${project.version}</version>
                                    <overWrite>true</overWrite>
                                    <outputDirectory>${project.build.outputDirectory}</outputDirectory>
                                    <includes>**/*.class</includes>
                                    <excludes>META-INF/</excludes>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>transform</goal>
                        </goals>
                        <configuration>
                            <initialization>
                                <entryPoint>net.bytebuddy.test.SimpleEntryPoint</entryPoint>
                                <groupId>demo</groupId>
                                <artifactId>demo-transformer</artifactId>
                            </initialization>
                            <transformations>
                                <transformation>
                                    <plugin>net.bytebuddy.test.SimplePlugin</plugin>
                                    <groupId>demo</groupId>
                                    <artifactId>demo-transformer</artifactId>
                                </transformation>
                            </transformations>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

and here is the Parent POM:

<?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>demo</groupId>
    <artifactId>demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>demo-original</module>
        <module>demo-transformer</module>
        <module>demo-enhanced</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <byte-buddy.version>1.6.9</byte-buddy.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy</artifactId>
                <version>${byte-buddy.version}</version>
            </dependency>
            <dependency>
                <groupId>demo</groupId>
                <artifactId>demo-original</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>demo</groupId>
                <artifactId>demo-transformer</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>demo</groupId>
                <artifactId>demo-enhanced</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>net.bytebuddy</groupId>
                    <artifactId>byte-buddy-maven-plugin</artifactId>
                    <version>${byte-buddy.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

foo/Bar.java (original source):

package foo;

public class Bar {
}

net/bytebuddy/test/SimplePlugin.java (byte code enhancer):

package net.bytebuddy.test;

import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.modifier.Visibility;


import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;

public class SimplePlugin implements Plugin {
    @Override
    public boolean matches(TypeDescription target) {
        return target.getName().equals("foo.Bar");
    }

    @Override
    public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
        if (typeDescription.getTypeName().equals("foo.Bar")) {
            builder = builder.defineField("qux", String.class, Visibility.PRIVATE)
                    .defineMethod("getQux", String.class, Visibility.PUBLIC)
                    .intercept(FieldAccessor.ofField("qux"))
                    .defineMethod("setQux", void.class, Visibility.PUBLIC)
                    .withParameter(String.class)
                    .intercept(FieldAccessor.ofField("qux"));
        }
        return builder;
    }
}

foo/Baz.java (static source file referencing dynamic type):

package foo;

public class Baz {
    private Bar bar = new Bar();

    public String getQuux() {
        return bar.getQux();
    }

    public void setQuux(String quux) {
        bar.setQux(quux);
    }
}

UPDATE: Maven seems to understand a structure involving a consolidated module with the enhanced byte code and static class source code, as well as separate modules for each, but the IDEs, IntelliJ and Eclipse, fail to understand the class path for either structure the way Maven does.

like image 225
Mark Chesney Avatar asked Feb 21 '17 20:02

Mark Chesney


People also ask

What does generate sources do in Maven?

The “maven-source” plugin is used to pack your source code and deploy along with your project. This is extremely useful, for developers who use your deployed project and also want to attach your source code for debugging.

In which folder artifacts build using Maven are created?

The target folder is the maven default output folder. When a project is build or packaged, all the content of the sources, resources and web files will be put inside of it, it will be used for construct the artifacts and for run tests.

What is Maven source?

The Source Plugin creates a jar archive of the source files of the current project. The jar file is, by default, created in the project's target directory.

How to add more source directories to a Maven project?

With Maven, we can use the Builder Helper plugin to add more source directories. This plugin lets us customize the build lifecycle in different ways. One of its goals is the add-sources, which is intended to add more src directories to the project during the generate-sources phase.

How do I add a class to a Maven project?

Assuming we have a Maven project already created, let's add a new source directory called another-src in the src/main folder. After that, let's create a simple Java class inside this folder: Let's now create another class in our src/main/java directory that uses the Foo class we've just created:

How to build submodules in Maven?

Now, the submodules are regular Maven projects, and they can be built separately or through the aggregator POM. By building the project through the aggregator POM, each project that has packaging type different than pom will result in a built archive file.

How to get the source code of a new dynamic class?

The source code of the new dynamic class is created via the ToString method of the instance dt of the utility class I created for this demo, DynamicType.


1 Answers

With Byte Buddy, you are generating class files and not source files. Unfortunately, Maven only knows about generated sources, but not about generated class files. The easiest is therefore to create a specific module.

If this is not possible, you can however copy them to any source folder of your Maven project such as resources. Some IDEs find those classes and treat them just if you had them in your java folder but without attempting to compile them as the files end with .class.

like image 199
Rafael Winterhalter Avatar answered Oct 19 '22 23:10

Rafael Winterhalter