0

我有一个使用 JSR-303 约束注释的数据传输对象,例如...

public class AssetOwnedDailyLocatableId implements Serializable, AssetOwned, HasOperatingDay, Locatable {

private static final long serialVersionUID = 1L;

@NotEmpty
@Size(min = 1, max = 30)
private String locationName;

@NotEmpty
private String operatingDay;

@NotEmpty
@Size(min = 1, max = 30)
private String assetOwner;

我正在尝试使用注释处理来丰富每个 JSR-303 约束,message其值将等于constraint-name.class-name.member-name.

例如,使用上面的方法,locationName字段注释的最终生成输出看起来像......

@NotEmpty(message="{NotEmpty.AssetOwnedDailyLocatableId.locationName}")
@Size(min = 1, max = 30, message="{Size.AssetOwnedDailyLocatableId.locationName}")
private String locationName;

为什么?因为我想完全控制自定义验证消息。我有数百个数据传输对象,我想用类似的东西来处理......

/**
 * ViolationConstraint message processor. During compile time it scans all DTO
 * classes that have <code>javax.validation.constrants.*</code> or 
 * <code>org.hibernate.validator.constraints.*</code>annotated
 * fields, then enriches the annotation with a <code>message</code> attribute
 * where its value will be <code>constraint-name.class-name.field-name</code>.
 * 
 * @param <T>
 *            any JSR-303 annotation type
 * 
 */
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes(value = { "javax.validation.constraints.*", "org.hibernate.validator.constraints.*" })
public class ValidationMessagesProcessor<T extends Annotation> extends AbstractProcessor {

private static final String JAVAX_PATH = "javax.validation.constraints.*";
private static final String HIBERNATE_PATH = "org.hibernate.validator.constraints/*";

private PackageUtil<T> util;

public ValidationMessagesProcessor() {
    super();
    util = new PackageUtil<T>();
}

/* (non-Javadoc)
 * @see javax.annotation.processing.AbstractProcessor#process(java.util.Set, javax.annotation.processing.RoundEnvironment)
 */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    if (!roundEnvironment.processingOver()) {
        String message;
        message = ValidationMessagesProcessor.class.getName() + " will begin processing now...";
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
        try {
            final List<Class<T>> annotationTypes = new ArrayList<Class<T>>();
            final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
            final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
            annotationTypes.addAll(jxTypes);
            annotationTypes.addAll(hibTypes);

            for (final Element e : roundEnvironment.getRootElements()) {

                // TODO Do the real work!

                /*message = "... JSR-303 annotation '" + a.annotationType().getClass().getName() + "' found in "
                        + e.getSimpleName();
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message); */
            }
        } catch (final IOException ioe) {
            message = "Failed to locate javax.validation.constraints or org.hibernate.validator.constraints classes on classpath!";
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
        }
    }
    return true; // no further processing of this annotation type
}

}

我想知道上述方法是否可行,或者我是否应该尝试其他方法(这可能更简单)。此外,如果可行,请说明在上述处理器的 //TODO 部分中实现什么的方向。目前咨询过...

4

2 回答 2

1

所以我选择创作一个基于 Eclipse JDT 的实用程序。

我花了一段时间来寻找所有依赖库来完成这项工作。对于其他感兴趣的人,这里是 Maven 依赖项:

    <!-- Validation API and Impl -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>${validation-api.version}</version>
    </dependency>
    <!-- Hibernate validator impl -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>

    <!-- Required to power classpath scanning for JSR-303 classes within JAR packages -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <!-- Required to employ all Eclipse JDT capabilities -->
    <!-- This specific collection of artifact versions is known to work together -->
    <!-- Take caution when upgrading versions! -->
    <dependency>
        <groupId>org.eclipse.tycho</groupId>
        <artifactId>org.eclipse.jdt.core</artifactId>
        <version>3.8.1.v20120502-0834</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.tycho</groupId>
        <artifactId>org.eclipse.osgi</artifactId>
        <version>3.8.0.v20120430-1750</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.core.resources</artifactId>
        <version>3.7.100.v20110510-0712</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.jdt.core</artifactId>
        <version>3.7.0.v_B61</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.core.runtime</artifactId>
        <version>3.7.0.v20110110</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.equinox.common</artifactId>
        <version>3.6.0.v20110523</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.text</artifactId>
        <version>3.5.100.v20110505-0800</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.core.jobs</artifactId>
        <version>3.5.100.v20110404</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.core.contenttype</artifactId>
        <version>3.4.100.v20110423-0524</version>
    </dependency>
    <dependency>
        <groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
        <artifactId>org.eclipse.equinox.preferences</artifactId>
        <version>3.4.0.v20110502</version>
    </dependency>

我编写了四个类,一个带有主线束,另一个是外观和实用程序。

线束:_

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.jdt.JDTFacade;
import org.spp.im.mui.commons.util.FileUtil;
import org.spp.im.mui.commons.util.PackageUtil;
import org.springframework.util.CollectionUtils;

/**
 * A utility that scans all DTO classes that have
 * <code>javax.validation.constrants.*</code> or
 * <code>org.hibernate.validation.constraints.*</code> annotated fields, then
 * enriches the annotation with a <code>message</code> attribute where its value
 * will be <code>constraint-name.class-name.field-name</code>.
 * 
 * @author cphillipson
 * @param <T>
 *            any JSR-303 annotation type
 * 
 */
 public class ConstraintMessageUtil<T extends Annotation> {

private static Logger log = LoggerFactory.getLogger(ConstraintMessageUtil.class);

private static final String JAVAX_PATH = "/javax/validation/constraints/*";
private static final String HIBERNATE_PATH = "/org/hibernate/validator/constraints/*";

private PackageUtil<T> util;
private JDTFacade<T> facade;

public ConstraintMessageUtil() {
    util = new PackageUtil<T>();
    facade = new JDTFacade<T>();
}

public void process(String sourcePath) throws Exception {

    // step #1: build a set of JSR-303 constraint classes
    final Set<Class<T>> annotationTypes = new HashSet<Class<T>>();
    try {
        final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
        final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
        annotationTypes.addAll(jxTypes);
        annotationTypes.addAll(hibTypes);
        // remove @Valid from the mix
        annotationTypes.remove(Valid.class);
        Assert.isTrue(!annotationTypes.contains(Valid.class));
    } catch (final IOException ioe) {

    }

    // step #2: get all files recursively from source path
    final Collection<File> allJavaSourceInDirectory = FileUtil.getAllJavaSourceInDirectory(new File(sourcePath),
            true);

    // step #3: filter files to just the ones that contain annotations
    final List<File> annotatedSources = new ArrayList<File>();
    if (!CollectionUtils.isEmpty(allJavaSourceInDirectory)) {
        boolean containsJsr303Annotation;
        String typeName;
        for (final File f : allJavaSourceInDirectory) {
            for (final Class<T> annotationType : annotationTypes) {
                typeName = annotationType.getName();
                containsJsr303Annotation = FileUtil.isContentInFile(f, typeName);
                if (containsJsr303Annotation) {
                    annotatedSources.add(f);
                    break; // at least one annotation found, move along
                }
            }
        }
    }

    // step #4: for each annotated source file parse and rewrite with
    // enriched message for each JSR-303 annotation
    enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(annotatedSources, annotationTypes);

}

// note: probably could have implemented an ASTVisitor, but...
protected void enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(
        List<File> annotatedSources, Set<Class<T>> constraintTypes) throws IOException, MalformedTreeException,
        BadLocationException {
    if (!CollectionUtils.isEmpty(annotatedSources)) {
        // reusable local variables... a veritable cornucopia
        Set<FieldDeclaration> fieldCandidates;
        Document document;
        String contents;
        String constraintName;
        String className;
        String fieldName;
        StringBuilder sb;
        AbstractTypeDeclaration td;
        IExtendedModifier[] modifiers;
        CompilationUnit unit;
        AST ast;
        MemberValuePair mvp;
        Expression exp;
        NormalAnnotation na;

        // iterate over all java source containing jsr-303 annotated fields
        for (final File source : annotatedSources) {
            unit = facade.generateCompilationUnitForFile(source);
            ast = unit.getAST();
            // get the set of fields which are annotated
            fieldCandidates = facade.obtainAnnotatedFieldsFromClassInCompilationUnit(unit, constraintTypes);
            log.info(source.getName() + " contains " + fieldCandidates.size()
                    + " fields with constraint annotations.");
            // iterate over each annotated field
            for (final FieldDeclaration fd : fieldCandidates) {
                modifiers = (IExtendedModifier[]) fd.modifiers().toArray(
                        new IExtendedModifier[fd.modifiers().size()]);
                int i = 0;
                // iterate over modifiers for the field
                for (final IExtendedModifier modifier : modifiers) {
                    // interested in Eclipse JDT's DOM form of Annotation
                    if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) {
                        // construct the key-value pair
                        sb = new StringBuilder();
                        constraintName = ((org.eclipse.jdt.core.dom.Annotation) modifier).getTypeName().toString();
                        // Ignore @Valid annotations
                        if (!constraintName.equals(Valid.class.getSimpleName())) {
                            td = (AbstractTypeDeclaration) fd.getParent();
                            className = td.getName().toString();
                            fieldName = fd.fragments().get(0).toString();
                            // field may have an assignment, so strip it
                            if (fieldName.contains("=")) {
                                final int end = fieldName.indexOf("=");
                                fieldName = fieldName.substring(0, end).trim();
                            }
                            sb.append("{");
                            sb.append(constraintName);
                            sb.append(".");
                            sb.append(className);
                            sb.append(".");
                            sb.append(fieldName);
                            sb.append("}");
                            // construct new properties, and instead of
                            // updating
                            // the existing annotation, replace it
                            mvp = ast.newMemberValuePair();
                            mvp.setName(ast.newSimpleName("message"));
                            exp = ast.newStringLiteral();
                            ((StringLiteral) exp).setLiteralValue(sb.toString());
                            mvp.setValue(exp);
                            na = ast.newNormalAnnotation();
                            na.setTypeName(ast.newSimpleName(constraintName));
                            na.values().add(mvp);
                            // don't forget to add the original annotation's
                            // member-value pairs to the new annotation
                            if (modifier instanceof NormalAnnotation) {
                                final NormalAnnotation ona = (NormalAnnotation) modifier;
                                final List<?> values = ona.values();
                                for (int j = 0; j < values.size(); j++) {
                                    final MemberValuePair omvp = (MemberValuePair) values.get(j);
                                    mvp = ast.newMemberValuePair();
                                    mvp.setName(ast.newSimpleName(omvp.getName().toString()));
                                    // a value can be a String, Number or
                                    // reference to a constant
                                    switch (omvp.getValue().getNodeType()) {
                                        case ASTNode.NUMBER_LITERAL:
                                            mvp.setValue(ast.newNumberLiteral(omvp.getValue().toString()));
                                            break;
                                        case ASTNode.STRING_LITERAL:
                                            exp = ast.newStringLiteral();
                                            ((StringLiteral) exp).setLiteralValue(omvp.getValue().toString());
                                            mvp.setValue(exp);
                                            break;
                                        case ASTNode.QUALIFIED_NAME:
                                            final QualifiedName oqn = (QualifiedName) omvp.getValue();
                                            exp = ast.newQualifiedName(ast.newName(oqn.getQualifier().toString()),
                                                    ast.newSimpleName(oqn.getName().toString()));
                                            mvp.setValue(exp);
                                            break;
                                    }
                                    na.values().add(mvp);
                                }
                            }
                            fd.modifiers().remove(i);
                            fd.modifiers().add(i, na);
                            log.info("@" + constraintName + " on " + fieldName + " in " + className
                                    + " has been enriched with a 'message' attribute whose value is now '"
                                    + sb.toString() + "'.");
                        }
                        i++;
                    }
                }
            }
            contents = FileUtil.toString(source);
            document = new Document(contents);
            facade.saveUpdatesToFile(unit, document, source);
        }
    }
}

public static void main(String args[]) {
    final ConstraintMessageUtil util = new ConstraintMessageUtil();
    try {
        // e.g., on Windows,
        // "D:\\workspaces\\alstom-grid\\SPP-MUI\\spp-im-mui-dto\\src\\main\\java\\org\\spp\\im\\mui\\dto"
        util.process(args[0]);
    } catch (final Exception e) {
        e.printStackTrace();
    }
}
}

实用程序

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.AntPathMatcher;

/**
 * Package utility. Provides handy methods for finding classes (of a particular
 * type) within a package on the classpath.
 * 
 * @author cphillipson
 * 
 * @param <T>
 *            types of classes to be found in package
 */
class PackageUtil<T> {

public List<Class<T>> listMatchingClasses(String matchPattern) throws IOException {
    final List<Class<T>> classes = new LinkedList<Class<T>>();
    final PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    scanner.setPathMatcher(new AntPathMatcher());
    final Resource[] resources = scanner.getResources("classpath:" + matchPattern);
    for (final Resource resource : resources) {
        final Class<T> clazz = getClassFromResource(resource);
        classes.add(clazz);
    }
    return classes;
}

public Class<T> getClassFromResource(Resource resource) {
    Class<T> result = null;
    try {
        String resourceUri = resource.getURI().toString();
        resourceUri = resourceUri.substring(0, resourceUri.indexOf(".class")).replace("/", ".");
        if (resourceUri.contains("!")) { // class was found in an archive
            resourceUri = resourceUri.substring(resourceUri.indexOf("!") + 2);
        }
        // try printing the resourceUri before calling forName, to see if it
        // is OK.
        result = (Class<T>) Class.forName(resourceUri);
    } catch (final Exception ex) {
        ex.printStackTrace();
    }
    return result;
}
}


/**
* A collection of special-purposed methods for working with files and
* directories. Wraps Apache Commons I/O.
* 
* @author cphillipson
* 
*/
public class FileUtil {

public static Collection<File> getAllJavaSourceInDirectory(File directory, boolean recursive) {
    // scans directory (and sub-directories if recursive flag is true) for
    // .java files, returns a collection of files
    return FileUtils.listFiles(directory, new String[] { "java" }, recursive);
}

public static boolean isContentInFile(File file, String fragment) throws IOException {
    boolean result = false;
    final String contents = toString(file);
    if (contents.contains(fragment)) { // does file contain fragment?
        result = true;
    }
    return result;
}

public static String toString(File file) throws IOException {
    final String result = FileUtils.readFileToString(file, "utf8");
    return result;
}

public static void toFile(File file, String content) throws IOException {
    FileUtils.writeStringToFile(file, content, "utf8");
}
}

门面:_

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.util.FileUtil;

/**
 * Abstract syntax tree facade. Backed by Eclipse JDT, this facade provides a
 * number of conveniences, like the ability to:
 * <ul>
 * <li>generate an {@link CompilationUnit} from a source {@File}</li>
 * <li>save updates in a {@link Document} managed by {@link CompilationUnit} to
 * a {@link File}</li>
 * </ul>
 * and much more. Credit goes to <a href=
 * "http://svn.apache.org/repos/asf/openejb/branches/eclipse-plugins-1.0.0.alpha/plugins/org.apache.openejb.devtools.core/src/main/java/org/apache/openejb/devtools/core/JDTFacade.java"
 * >Apache OpenEJB DevTools JDTFacade source</a> for providing much of the
 * inspiration for this implementation.
 * 
 * @author cphillipson
 * @param <T>
 *            any annotation type
 * 
 */
 public class JDTFacade<T extends java.lang.annotation.Annotation> {

private static Logger log = LoggerFactory.getLogger(JDTFacade.class);

public CompilationUnit generateCompilationUnitForFile(File file) throws IOException {
    final String source = FileUtil.toString(file);
    final Document document = new Document(source);
    final ASTParser parser = ASTParser.newParser(AST.JLS4);
    parser.setSource(document.get().toCharArray());
    final CompilationUnit unit = (CompilationUnit) parser.createAST(null /* no ProgressMonitor */);
    unit.recordModifications();
    return unit;
}

public void saveUpdatesToFile(CompilationUnit unit, Document document, File file) throws MalformedTreeException,
        IOException, BadLocationException {
    final TextEdit edits = unit.rewrite(document, null /* no options */);
    edits.apply(document);
    boolean writeable = true; // should always be able to write to file...
    if (!file.canWrite()) { // .. but just in case we cannot...
        writeable = file.setWritable(true);
    }
    if (writeable) {
        FileUtil.toFile(file, document.get());
        log.info("Successfully wrote updates to " + file.getName());
    } else {
        log.warn("Unable to write to " + file.getName());
    }
}

public Set<FieldDeclaration> obtainAnnotatedFieldsFromClassInCompilationUnit(CompilationUnit unit,
        Set<Class<T>> annotationTypes) {
    final Set<FieldDeclaration> fields = new HashSet<FieldDeclaration>();
    final List<AbstractTypeDeclaration> types = unit.types();
    IExtendedModifier[] modifiers;
    for (final AbstractTypeDeclaration type : types) {
        if (type.getNodeType() == ASTNode.TYPE_DECLARATION) {
            // Class def found
            final List<BodyDeclaration> bodies = type.bodyDeclarations();
            for (final BodyDeclaration body : bodies) {
                if (body.getNodeType() == ASTNode.FIELD_DECLARATION) {
                    final FieldDeclaration field = (FieldDeclaration) body;
                    modifiers = (IExtendedModifier[]) field.modifiers().toArray(new IExtendedModifier[0]);
                    for (final IExtendedModifier modifier : modifiers) {
                        if (!(modifier instanceof Annotation)) {
                            continue;
                        }
                        final Annotation annotationModifer = (Annotation) modifier;
                        for (final Class<T> clazz : annotationTypes) {
                            if (annotationModifer.getTypeName().toString().equals(clazz.getCanonicalName())
                                    || annotationModifer.getTypeName().toString().equals(clazz.getSimpleName())) {
                                fields.add(field);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
    return fields;
}

}
于 2012-08-16T03:40:02.670 回答
0

您不能使用注释处理来修改您的代码。但是,您可以创建新类,这些新类可以对您拥有的类进行子类化,并且它们可以包含其他注释。

如果你想修改你的代码,你需要一个在编译时或加载时修改你的代码的库(例如作为一个特殊的类加载器)。

我不知道哪个库最适合您的情况,但BCEL似乎能够胜任这项任务。

也可以看看:

于 2012-08-15T17:03:55.190 回答