注意:
这可能不是确切的解决方案。但我想讨论
解决该问题的可能方法。我已经提到了
可能有助于工作的替代方法。请加入
讨论。
我想尝试在JDK-5 中引入并在JDK-6 在JSR-269 规范下标准化的Java 注释处理器。
需要注释文件 (java @interfaces),这些文件应该使用自定义注释根据规则进行验证。如果无法注释每个文件,则必须注释包含要验证的类的包(也可以遍历内部包)。以下示例将演示如何使用带注释的类和注释处理器来验证类。我已将示例项目上传到 github 存储库中。请查看github上的项目仓库https://github.com/hjchanna/compile_validation_java
步骤 01:创建注释接口 (@interface)
package com.mac.compile_validation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author hjchanna
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface CompileValidation {
}
STEP 02:创建Processor类,向编译器引入编译验证规则
处理器类应该extend 来自javax.annotation.processing.AbstractProcessor,并且应该使用@SupportedAnnotationTypes 和@SupportedSourceVersion 注释进行注释。请根据具体要求或验证规则修改CompileValidationProcessor类。
package com.mac.compile_validation;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
/**
*
* @author hjchanna
*/
@SupportedAnnotationTypes("com.mac.compile_validation.CompileValidation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CompileValidationProcessor extends AbstractProcessor {
/**
* Processes a set of annotation types on type elements originating from the
* prior round and returns whether or not these annotation types are claimed
* by this processor. If {@code
* true} is returned, the annotation types are claimed and subsequent
* processors will not be asked to process them; if {@code false} is
* returned, the annotation types are unclaimed and subsequent processors
* may be asked to process them. A processor may always return the same
* boolean value or may vary the result based on chosen criteria.
*
* The input set will be empty if the processor supports {@code
* "*"} and the root elements have no annotations. A {@code
* Processor} must gracefully handle an empty set of annotations.
*
* @param annotations the annotation types requested to be processed
* @param roundEnv environment for information about the current and prior
* round
* @return whether or not the set of annotation types are claimed by this
* processor
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Iterate through compiling files which annotated with @CompileValidation
for (Element elem : roundEnv.getElementsAnnotatedWith(CompileValidation.class)) {
//find type element for element
TypeElement typeElement = findEnclosingTypeElement(elem);
//required parameter types
TypeElement stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String");
TypeElement integerType = processingEnv.getElementUtils().getTypeElement("java.lang.Integer");
//find construtors according to your scenario
ExecutableElement conA = findConstructor(typeElement, stringType.asType(), integerType.asType());
ExecutableElement conB = findConstructor(typeElement, stringType.asType(), stringType.asType());
//check availability of constructors, if not available it should show a warning message in compile time
if (conA == null || conB == null) {
String message = "Type " + typeElement + " has malformed constructors.";
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
}
}
return true; // no further processing of this annotation type
}
/**
* Returns a constructor which have two parameters and parameter types equal
* to paramA and paramB. Return null if required constructor is not
* available.
*
* @param typeElement like the class which may constructors encapsulated
* @param paramA first parameter of required constructor
* @param paramB second parameter of required constructor
* @return constructor which have required parameters
*/
private static ExecutableElement findConstructor(TypeElement typeElement, TypeMirror paramA, TypeMirror paramB) {
List<ExecutableElement> executableElements = ElementFilter.constructorsIn(typeElement.getEnclosedElements());
for (ExecutableElement executableElement : executableElements) {
List<VariableElement> variableElements = (List<VariableElement>) executableElement.getParameters();
//match constructor params and length
if (variableElements.size() == 2
&& variableElements.get(0).asType().equals(paramA)
&& variableElements.get(1).asType().equals(paramB)) {
return executableElement;
}
}
return null;
}
/**
* Returns the TypeElement of element e.
*
* @param e Element which contain TypeElement
* @return Type element
*/
public static TypeElement findEnclosingTypeElement(Element e) {
while (e != null && !(e instanceof TypeElement)) {
e = e.getEnclosingElement();
}
return TypeElement.class.cast(e);
}
}
步骤03:创建处理服务链接文件
然后需要在项目的资源路径(/src/main/resources/META-INF/services)中添加一个名为javax.annotation.processing.Processor的类。该文件仅包含Processor 的类名。按照上例配置文件内容如下。
com.mac.compile_validation.CompileValidationProcessor
之前的方法适用于maven项目。如果需要,可以将配置文件手动注入到输出.jar 文件的META-INF/services 文件夹中。
步骤 04:禁用当前项目的验证
禁用当前项目的注释处理。如果启用,它将无法构建当前项目,因为编译器会尝试定位 Processor 类进行验证。但它仍然没有编译。所以它会因为自己而无法构建项目。将以下代码添加到pom.xml(在<build> -> <plugin> 内)。
<compilerArgument>-proc:none</compilerArgument>
现在差不多完成了。唯一需要做的就是将构建输出 .jar 文件依赖添加到原始项目中。
是时候测试项目了。使用之前创建的自定义注释来注释所需的类 (CompileValidation)。如果无法验证带注释的类,它将显示警告。我的输出如下。
替代解决方案
- 可以使用
PMD,这是一个 java 源代码扫描器。它提供了使用 xml 配置定义规则的方法。
- 尝试在启动时使用 java 反射验证类。 (这不是你问的问题。但在开始像 spring、hibernate 和其他知名框架一样工作之前验证内容是一个好习惯。)
- 尝试
Java Instrumentation API,但如果应用程序违反规则,唯一可能的反应就是使应用程序崩溃。这不是一个好习惯。