我有一个用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;
我试图使用注释处理来用message
属性丰富每个JSR-303约束,该属性的值将等于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部分中说明要实现什么。到目前为止我已经咨询了...
因此,我选择了基于Eclipse JDT编写一个实用程序。
我花了一段时间才找到所有依赖的libs来完成这项工作。对于其他感兴趣的人来说,下面是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>
我写了四个类,一个是主束,另一个是门面和utils。
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;
}
}
使用JSR-303注释在上下文启动时验证Springbean是否有一种简单的方法?我继承了一些将配置表示为JSR-303注释POJO的类。这些都是在对象层次结构中传递的,所以我一直在使用它们。 我可以想象定义bean来设置这样一个类的属性,然后在上下文启动时运行JSR-303验证器,以确保正确设置所有值。 请注意,我不是在问SpringMVC中绑定表单提交的验证问题。
问题内容: 我刚刚开始测试JSR-303 Bean验证,并且想知道是否有可能。我们的应用程序中有一个用于所有String字段的默认正则表达式。如果我想使用bean验证来应用此功能,我想我需要在表单对象中注释每个字段。 一击是否可以将@Pattern应用于所有字符串(或某些字段)?我们正在WebLogic 10.3.4上使用Hibernate实现,并以JSF2.0为前端。验证应该独立于视图,因为我可
请看一个例子: 注释字段的示例: 当字段违反@Pattern时,我从@Pattern而不是@CustomAnnotation获得错误消息。这是只显示@CustomAnnotation消息的方式吗?
我们正在开发REST服务,并希望使用JSR303进行输入数据验证,但这里的问题是所有模型对象都是从groovy DSL生成的,并将作为jars导入。因此,在对象字段之上编写JSR-303注释没有灵活性。 那么,有没有其他方法可以不用注释使用JSR-303,可以通过XML配置吗?或者在这种情况下,请提供任何验证建议。 谢啦
Spring还支持基于JSR-250的注释,包括@ PostConstruct,@ PreDestroy和@Resource注释。 虽然这些注释并不是真的需要,因为你已经有了其他的替代品,但让我们对它们进行简要的了解。 @PostConstruct和@PreDestroy注释 要定义bean的设置和拆卸,我们只需使用init-method和/或destroy-method参数声明<bean>。 i
问题内容: 我有如下测试: 具有以下输出: 但是,我希望注释可以被覆盖,并且不会出现警告。 有没有办法做到这一点? 编辑:我发现一个错误似乎与此有关,但我最终运行4.2.0,仍然得到上述行为。 问题答案: 实际上,JSR-303不支持覆盖验证注释。相反,子类中重写方法的注释将 累积 应用:从规范的3.3节开始: