Field#getGenericType()抛出java.lang.TypeNotPresentException

时间:2018-03-09 00:43:20

标签: java maven reflection annotations

我正在编写一个Maven插件,我需要知道List<Person>的类型,其中Person是依赖项中定义的对象。此插件在process-classes阶段运行,以根据在宿主项目中的注释类中找到的内容生成一些文件。在这种情况下,宿主项目包含对其他项目中的库的引用,这些库作为Maven依赖项包含在内。

Person类:

package com.dependency.models;    

public class Person {
    // Irrelevent
}

在我正在执行查询的类中使用:

package com.project.wrappers;

import com.dependency.Person;    

@MyAnnotation
public class Wrapper {
     List<Person> people;
     // Other irrelevant stuff
}

注释:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE})
public @interface MyAnnotation {}

在Maven插件中使用上下文我通过注释加载所有Wrapper个对象,然后进行处理。为此,我需要通过Reflections库使用自定义类加载器:

public static Set<Class<?>> findAnnotatedClasses(MavenProject mavenProject, Class<? extends Annotation> input) throws MojoExecutionException {
    List<String> classpathElements = null;

    try {
        classpathElements = mavenProject.getCompileClasspathElements();
        List<URL> projectClasspathList = new ArrayList<URL>();
        for (String element : classpathElements) {
            Application.getLogger().debug("Considering compile classpath element (via MavenProject): " + element);
            try {
                projectClasspathList.add(new File(element).toURI().toURL());
            } catch (MalformedURLException e) {
                throw new MojoExecutionException(element + " is an invalid classpath element", e);
            }
        }

        // Retain annotations
        JavassistAdapter javassistAdapter = new JavassistAdapter();
        javassistAdapter.includeInvisibleTag = false;

        URLClassLoader urlClassLoader = new URLClassLoader(projectClasspathList.toArray(new URL[]{}),
                Thread.currentThread().getContextClassLoader());

        Reflections reflections = new Reflections(
                new ConfigurationBuilder().setUrls(
                        ClasspathHelper.forClassLoader(urlClassLoader)
                ).addClassLoader(urlClassLoader).setScanners(new TypeAnnotationsScanner(), new TypeElementsScanner(),
                        new FieldAnnotationsScanner(), new TypeAnnotationsScanner(), new SubTypesScanner(false)
                ).setMetadataAdapter(javassistAdapter)
        );

        return findAnnotatedClasses(reflections, input);

    } catch (DependencyResolutionRequiredException e) {
        throw new MojoExecutionException("Dependency resolution failed", e);
    }
}

到这里的一切都很棒。但如果我在我的解析器中尝试以下操作,则会失败:

Field field = Wrapper.class.getDeclaredField("people");
ParameterizedType listType = (ParameterizedType) field.getGenericType();
Class<?> listTypeClass = (Class<?>) listType.getActualTypeArguments()[0];

抛出以下异常:

Caused by: java.lang.TypeNotPresentException: Type com.dependency.models.Person not present
at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117)
at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125)
at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at sun.reflect.generics.repository.FieldRepository.getGenericType(FieldRepository.java:85)
at java.lang.reflect.Field.getGenericType(Field.java:247)

如果进一步追随,那么:

Caused by: java.lang.ClassNotFoundException: com.dependency.models.Person
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114)

这意味着我的类加载适用于最后一步之前的所有内容。我想我需要一种方法来覆盖sun.reflect库中使用的类加载器,但这可能是不可能的。有关更好方法的任何建议吗?

更新

我已将依赖关系jar添加到URLClassLoader并尝试覆盖上面CoreReflectionFactory使用的当前类加载器无效。

1 个答案:

答案 0 :(得分:0)

解决方案不仅是将依赖项添加到类加载器,而且还要实际解析jar路径,然后将这些jar的路径添加到classpath中,以便反射库将其包含在其扫描中。

因此,从Maven获得所有工件,然后在构建该工件时进行解析并将每条路径添加到Reflections。像这样:

public static URL[] buildProjectClasspathList(ArtifactReference artifactReference)  throws ArtifactResolutionException, MalformedURLException, DependencyResolutionRequiredException {

    List<URL> projectClasspathList = new ArrayList<URL>();

    // Load build class path
    if(classpathElementsCache == null) {
        Application.getLogger().debug("Compile Classpath Elements Cache was null; fetching update");
        classpathElementsCache = artifactReference.getMavenProject().getCompileClasspathElements();
    }

    for (String element : classpathElementsCache) {
        Application.getLogger().debug("Looking at compile classpath element (via MavenProject): " + element);
        Application.getLogger().debug("  Adding: " + element);
        projectClasspathList.add(new File(element).toURI().toURL());
    }

    // Load artifact(s) jars using resolver
    if(dependencyArtifactsCache == null) {
        Application.getLogger().debug("Dependency Artifacts Cache was null; fetching update");
        dependencyArtifactsCache =  artifactReference.getMavenProject().getDependencyArtifacts();
    }

    Application.getLogger().debug("Number of artifacts to resolve: "
            + dependencyArtifactsCache.size());

    for (Artifact unresolvedArtifact : dependencyArtifactsCache) {
        String artifactId = unresolvedArtifact.getArtifactId();

        if (!isArtifactResolutionRequired(unresolvedArtifact, artifactReference)) {
            Application.getLogger().debug("  Skipping: " + unresolvedArtifact.toString());
            continue;
        }

        org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(
                unresolvedArtifact.getGroupId(),
                unresolvedArtifact.getArtifactId(),
                unresolvedArtifact.getClassifier(),
                unresolvedArtifact.getType(),
                unresolvedArtifact.getVersion());

        ArtifactRequest artifactRequest = new ArtifactRequest()
                .setRepositories(artifactReference.getRepositories())
                .setArtifact(aetherArtifact);

        // This takes time; minimizing what needs to be resolved is the goal of the specified dependency code block
        ArtifactResult resolutionResult = artifactReference.getRepoSystem()
                .resolveArtifact(artifactReference.getRepoSession(), artifactRequest);

        // The file should exist, but we never know.
        File file = resolutionResult.getArtifact().getFile();
        if (file == null || !file.exists()) {
            Application.getLogger().warn("Artifact " + artifactId +
                    " has no attached file. Its content will not be copied in the target model directory.");
            continue;
        }

        String jarPath = "jar:file:" + file.getAbsolutePath() + "!/";
        Application.getLogger().debug("Adding resolved artifact: " + file.getAbsolutePath());
        projectClasspathList.add(new URL(jarPath));
    }

    return projectClasspathList.toArray(new URL[]{});
}

ArtifactReference包含MavenProject和Repo对象的地方:

public class ArtifactReference {
    private MavenProject mavenProject;
    private RepositorySystem repoSystem;
    private RepositorySystemSession repoSession;
    private List<RemoteRepository> repositories;
    private List<String> specifiedDependencies;
}