I am running an annotation processor that I have wrote. It ran fine on JDK 8 and now I am experiencing a problem on JDK 12.
I have a TypeElement and I want to retrieve its binary name to pass to Class.forName.
I use javax.lang.model.util.Elements.getBinaryName(TypeElement) and it returns a garbage value <any?>$OuterClass.InnerClass instead of the expected example3.OuterClass$InnerClass.
I attempted to replace getBinaryName with TypeElement.getQualifiedName (even though it would not quite work for an inner class) but it gives me the same garbage result. I have tried searching for this issue but most search engines strip all the special characters and give me useless results.
The TypeElement was obtained by catching a MirroredTypeException like so:
try {
exampleAnnotation.value();
throw new IllegalStateException("Expected a MirroredTypeException.");
} catch (MirroredTypeException ex) {
return (TypeElement) types.asElement(ex.getTypeMirror());
}
And here is the definition of ExampleAnnotation:
package example1;
#Target(PACKAGE)
#Retention(RUNTIME)
#Documented
public #interface ExampleAnnotation {
Class<? extends Derived> value() default Derived.class;
interface Derived<A extends Annotation> extends Base<A> {
String foo();
}
}
And here is the instance of the annotation that the processor is accessing in package-info.java:
#ExampleAnnotation(OuterClass.InnerClass.class)
package example2;
import example1.ExampleAnnotation;
I have also tried the fully qualified name example3.OuterClass.InnerClass.class but that also results in garbage: <any?>$example3.OuterClass.InnerClass.
I doubt it matters but the annotation processors are still marked #SupportedSourceVersion(SourceVersion.RELEASE_8) and I am running this on Gradle 5.3.1.
I've verified the processorpath contains the jars for packages example1 and example3, including the annotation processors.
I've made no changes to account for the module system so I was thinking maybe that's somehow affecting the code.
Just tried creating a Maven project and am currently unable to reproduce the problem, so there may be an issue with my Gradle configuration, similar to what #Colin Alworth has suggested.
I had recently upgraded to a new version of Gradle and started using the "annotationProcessor" dependencies.
It appears that <any?>$ is prepended to binary/qualified class names (as it appears in the source) if the class isn't on the classpath (or if it isn't imported, or is spelled wrong). I only had the annotation's jar on the processorpath.
To alert consumers of my annotation processor of this mistake, I was able to detect it by comparing TypeElement.asType().getKind() == TypeKind.ERROR immediately after catching the MirroredTypeException.
Related
I upgraded spring from version 2.1.1 to 2.2.0 .
Since then I'm facing the following error when I start my app :
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'ParentService' available: expected single matching bean but found 2: MasterService,SlaveService .
ParentService is an interface :
public interface ParentService{
..
}
MasterService :
#Service
#MasterProfile
public class MasterService implements ParentService{
.....
}
SlaveService :
#Service
#SlaveProfile
public class SlaveService implements ParentService{
.....
}
MasterProfile annotation :
#Profile("MASTER")
public #interface MasterProfile {
}
Slave Profile :
#Profile("SLAVE")
public #interface SlaveProfile{
}
I'm passing to my app the profile with the following flag :
-Dspring.profiles.include=MASTER
According to Spring 2.2 release notes, they have done some changes and forks are enabled by default in maven. As a result the only way to pass params is with the parameter -Dspring-boot.run.jvmArguments . I used -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER but it still fails..
Passing a profile as a parameter depends on how you run your app. Be careful, the doc you mentioned is referring to the maven spring-boot plugin.
With maven plugin : mvn spring-boot:run -Dspring-boot.run.jvmArguments=-Dspring.profiles.include=MASTER
Classic java app : java -Dspring.profiles.include=MASTER -jar ./myapp.jar
In both cmd line, you can pass more than one parameter, if separated by a ,. See the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-profile-specific-properties
Since the upgrade, you now have to define your custom profile annotation like this :
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME) // Only this one is really needed
#Profile("SLAVE")
public #interface SlaveProfile {
}
Explaination:
In java, an annotation has a RetentionPolicy, which is similar to a scope. (See this: https://docs.oracle.com/javase/7/docs/api/java/lang/annotation/RetentionPolicy.html).
Without any RetentionPolicy set, the default behavior is an annotation not visible for the JVM (i.e at runtime).
When you want to run your application, you first compile it, which implies converting your .java files into .class file. Your class is only a bunch of byte code, converting your human readable file into a computer language.
Then, when Spring is loading the ApplicationContext, what it does under the hood, among many other things, is reading your .class files. During this process (see class name: org.springframework.asm.ClassReader) Spring loads the annotations that you declare. With what I've said above, during the Runtime, you end up with "two kinds" of annotations :
InvisibleAnnotation: #Retention(RetentionPolicy.COMPILE)
VisibleAnnotation: #Retention(RetentionPolicy.RUNTIME)
To conclude and understand why it was working before:
Spring-boot 2.1.0uses spring-core-5.1.2, which interprets at runtime the visible and invisible annotations, which explain why your #SlaveProfile and #MasterProfile have the expected behaviour.
Spring-boot 2.2.0uses spring-core-5.2.0, which interprets at runtime ONLY the visible annotations, which explain why your #SlaveProfile and #MasterProfile haven't the expected behaviour.
Let's say that Spring "silently" fixed a bug that was reading Invisible Annotation when they shouldn't, but didn't mention it.
Hope it helps!
Adding #Profile will not stop the bean from being instantiated. This is causing the exception. Add #Primary to any beans that the application should not default to.
Ex, add #Primary to the MasterProfile bean.
Javadoc (via Maven) is giving me the following error in one my Java JAX-RS interface method signatures:
error: element value must be a constant expression
Here is my JAX-RS interface:
public interface FooResource {
#Consumes(APPLICATION_FORM_URLENCODED_UTF_8)
public void bar();
}
Javdoc gives the error for #Consumes. Here is the definition for APPLICATION_FORM_URLENCODED_UTF_8, which appears in MyAppConstants in the same project:
public static final String APPLICATION_FORM_URLENCODED_UTF_8 =
APPLICATION_FORM_URLENCODED + ";" + CHARSET_PARAMETER + "=UTF-8";
And here is the definition of APPLICATION_FORM_URLENCODED, which appears in javax.ws.rs.core.MediaType:
public final static String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
And here is the definition of CHARSET_PARAMETER, which also appears in javax.ws.rs.core.MediaType:
public static final String CHARSET_PARAMETER = "charset";
Now I ask you: what about APPLICATION_FORM_URLENCODED_UTF_8 is not constant at compile time?
The error message didn't say that I have to provide a literal. It said I had to provide a constant. So what about this is not a constant?
(I could almost swear that this worked at one time but suddenly stopped working.)
Update: Found cause, but still don't understand.
For some reason, merely including the swagger-maven-plugin in the POM will trigger this problem! My code doesn't change at all, but as soon as I add the following dependency, suddenly I get Javadoc warnings for my existing code!!!
<dependency>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>3.1.5</version>
</dependency>
How can a single dependency make Javadoc work differently on a code file? What is swagger-maven-plugin doing?
My best guess is that this happens because swagger-maven-plugin transitively (via io.swagger:swagger-core:1.5.13) an old version of the JAX-RS specification in javax.ws.rs:jsr311-api:1.1.1. Note that the JAX-RS 2 artifact ID is javax.ws.rs-api, Maven doesn't realize that they are different versions of the same JAR, and pulls them both in as dependencies. I can only guess that javax.ws.rs:jsr311-api in fact does not use constants for the variables in question. In any case, when I threw out swagger-maven-plugin and pulled in io.swagger:swagger-annotations (which was all I needed in this project for documentation), the problem went away.
See https://github.com/kongchen/swagger-maven-plugin/issues/543.
I have a project with multiple modules in Android Studio. A module may have a dependency on another module, for example:
Module PhoneApp -> Module FeatureOne -> Module Services
I've included my annotation processing in the root module but the android-apt annotation processing occurs only at the top most level (PhoneApp) so that it should theoretically have access to all the modules at compile time. However, what I'm seeing in the generated java file is only the classes annotated in PhoneApp and none from the other modules.
PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java
In the other modules, I am finding a generated file in the intermediates directory that contains only the annotated files from that module.
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.class
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.java
My goal is to have a single generated file in PhoneApp that allows me to access the annotated files from all modules. Not entirely sure why the code generation process is running for each and failing to aggregate all annotations at PhoneApp. Any help appreciated.
Code is fairly simple and straight forward so far, checkIsValid() omitted as it works correctly:
Annotation Processor:
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GuiceModule.class)) {
if (checkIsValid(annotatedElement)) {
AnnotatedClass annotatedClass = new AnnotatedClass((TypeElement) annotatedElement);
if (!annotatedClasses.containsKey(annotatedClass.getSimpleTypeName())) {
annotatedClasses.put(annotatedClass.getSimpleTypeName(), annotatedClass);
}
}
}
if (roundEnv.processingOver()) {
generateCode();
}
} catch (ProcessingException e) {
error(e.getElement(), e.getMessage());
} catch (IOException e) {
error(null, e.getMessage());
}
return true;
}
private void generateCode() throws IOException {
PackageElement packageElement = elementUtils.getPackageElement(getClass().getPackage().getName());
String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString();
ClassName moduleClass = ClassName.get("com.google.inject", "Module");
ClassName contextClass = ClassName.get("android.content", "Context");
TypeName arrayOfModules = ArrayTypeName.of(moduleClass);
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("juice")
.addParameter(contextClass, "context")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(arrayOfModules);
methodBuilder.addStatement("$T<$T> collection = new $T<>()", List.class, moduleClass, ArrayList.class);
for (String key : annotatedClasses.keySet()) {
AnnotatedClass annotatedClass = annotatedClasses.get(key);
ClassName className = ClassName.get(annotatedClass.getElement().getEnclosingElement().toString(),
annotatedClass.getElement().getSimpleName().toString());
if (annotatedClass.isContextRequired()) {
methodBuilder.addStatement("collection.add(new $T(context))", className);
} else {
methodBuilder.addStatement("collection.add(new $T())", className);
}
}
methodBuilder.addStatement("return collection.toArray(new $T[collection.size()])", moduleClass);
TypeSpec classTypeSpec = TypeSpec.classBuilder("FreshlySqueezed")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodBuilder.build())
.build();
JavaFile.builder(packageName, classTypeSpec)
.build()
.writeTo(filer);
}
This is just for a demo of annotation processing that works with Guice, if anyone is curious.
So how can I get all the annotated classes to be included in the generated PhoneApp .java file from all modules?
It's never too late to answer a question on SO, so...
I have faced a very similar complication during one of tasks at work.
And I was able to resolve it.
Short version
All you need to know about generated classes from moduleB in moduleA is package and class name. That can be stored in some kind of MyClassesRegistrar generated class placed in known package. Use suffixes to avoid names clashing, get registrars by package. Instantiate them and use data from them.
Lond version
First of all - you will NOT be able to include your compile-time-only dependency ONLY at topmost module (lets call it "app" module as your typical android project structure does). Annotation processing just does not work that way and, as far as I could find out - nothing can be done about this.
Now to the details. My task was this:
I have human-written annotated classes. I'll name them "events". At compile time I need to generate helper-classes for those events to incorporate their structure and content (both statically-available (annotation values, consts, etc) and runtime available (I am passing event objects to those helpers when using latter). Helper class name depends on event class name with a suffix so I don't know it until code generation finished.
So after helpers are generated I create a factory and generate code to provide new helper instance based on MyEvent.class provided. Here's the problem: I only needed one factory in app module, but it should be able to provide helpers for events from library module - this can't be done straightforward.
What I did was:
skip generating factory for modules that my app module depends upon;
in non-app modules generate a so-called HelpersRegistrar implementation(s):
– they all share same package (you'll know why later);
– their names don't clash because of suffix (see below);
– differentiation between app module and library-module is done via javac "-Amylib.suffix=MyModuleName" param, that user MUST set - this is a limitation, but a minor one. No suffix must be specified for app module;
– HelpersRegistrar generated implementation can provide all I need for future factory code generating: event class name, helper class name, package (these two share package for package-visibility between helper and event) - all Strings, incorporated in POJO;
in app module I generate helpers - as usual, then I obtain HelperRegistrars by their package, instantiate them, run through their content to enrich my factory with code that provides helpers from other modules. All I needed for this was class names and a package.
Voilà! My factory can provide instances of helpers both from app module and from other modules.
The only uncertainty left is order of creating and running processor-class instances in app module and in other modules. I have not found any solid info on this, but running my example shows that compiler (and, therefore, code generation) first runs in module that we depend upon, and then - in app module (otherwise compilation of app module will be f..cked). This gives us reason to expect known order of code processor executions in different modules.
Another, slightly similar, approach is this: skip registrars, generate factories in all modules and write factory in app module to use other factories, that you get and name same way as registrars above.
Example can be seen here: https://github.com/techery/janet-analytics - this is a library where I applied this approach (the one without registrars since I have factories, but that can be not the case for you).
P. S.: suffix param can be switched to simpler "-Amylibraryname.library=true" and factories/registrars names can be autogenerated/incremented
Instead of using Filer to save generated file, use regular java file writing instead. You will need to serialize objects to temp files when processing because even static variables won't save in between modules. Configure gradle to delete the temp files before compilation.
I have created my own annotation type like this:
public #interface NewAnnotationType {}
and attached it to a class:
#NewAnnotationType
public class NewClass {
public void DoSomething() {}
}
and I tried to get the class annotation via reflection like this :
Class newClass = NewClass.class;
for (Annotation annotation : newClass.getDeclaredAnnotations()) {
System.out.println(annotation.toString());
}
but it's not printing anything. What am I doing wrong?
The default retention policy is RetentionPolicy.CLASS which means that, by default, annotation information is not retained at runtime:
Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time. This is the default behavior.
Instead, use RetentionPolicy.RUNTIME:
Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
...which you specify using the #Retention meta-annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface NewAnnotationType {
}
Having the default Retention of an annotation does not mean that you can not read it at run-time.
Since
Annotations are to be recorded in the class file by the compiler
but need not be retained by the VM at run time. This is the default behavior.
It is possible to access them reading the .class file directly
This can be accomplished by using the ASM library (handling some corner cases, of course).
Check out its excellent User guide. In particular section 4.2 Annotations.
You may want to refer to the Spring framework's handling of such annotations (it uses shaded asm dependency):
SimpleAnnotationMetadataReadingVisitor
AnnotationMetadataReadingVisitor (deprecated)
I am using Spring's declarative transactions (the #Transactional annotation) in "aspectj" mode. It works in most cases exactly like it should, but for one it doesn't. We can call it Lang (because that's what it's actually called).
I have been able to pinpoint the problem to the load time weaver. By turning on debug and verbose logging in aop.xml, it lists all classes being woven. The problematic class Lang is indeed not mentioned in the logs at all.
Then I put a breakpoint at the top of Lang, causing Eclipse to suspend the thread when the Lang class is loaded. This breakpoint is hit while the LTW weaving other classes! So I am guessing it either tries to weave Lang and fails and doesn't output that, or some other class has a reference that forces it to load Lang before it actually gets a chance to weave it.
I am unsure however how to continue to debug this, since I am not able to reproduce it in smaller scale. Any suggestions on how to go on?
Update: Other clues are also welcome. For example, how does the LTW actually work? There appears to be a lot of magic happening. Are there any options to get even more debug output from the LTW? I currently have:
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
I forgot tom mention it before: spring-agent is being used to allow LTW, i.e., the InstrumentationLoadTimeWeaver.
Based on the suggestions of Andy Clement I decided to inspect whether the AspectJ transformer is ever even passed the class. I put a breakpoint in ClassPreProcessorAgent.transform(..), and it seems that the Lang class never even reaches that method, despite it being loaded by the same class loader as other classes (an instance of Jetty's WebAppClassLoader).
I then went on to put a breakpoint in InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Not even that one is hit for Lang. And I believe that method should be invoked for all loaded classes, regardless of what class loader they are using. This is starting to look like:
A problem with my debugging. Possibly Lang is not loaded at the time when Eclipse reports it is
Java bug? Far-fetched, but I suppose it does happen.
Next clue: I turned on -verbose:class and it appears as if Lang is being loaded prematurely - probably before the transformer is added to Instrumentation. Oddly, my Eclipse breakpoint does not catch this loading.
This means that Spring is new suspect. there appears to be some processing in ConfigurationClassPostProcessor that loads classes to inspect them. This could be related to my problem.
These lines in ConfigurationClassBeanDefinitionReader causes the Lang class to be read:
else if (metadata.isAnnotated(Component.class.getName()) ||
metadata.hasAnnotatedMethods(Bean.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
return true;
}
In particular, metadata.hasAnnotatedMethods() calls getDeclaredMethods() on the class, which loads all parameter classes of all methods in that class. I am guessing that this might not be the end of the problem though, because I think the classes are supposed to be unloaded. Could the JVM be caching the class instance for unknowable reasons?
OK, I have solved the problem. Essentially, it is a Spring problem in conjunction with some custom extensions. If anyone comes across something similar, I will try to explain step by step what is happening.
First of all, we have a custom BeanDefintionParser in our project. This class had the following definition:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class<?> getBeanClass(Element element) {
try {
return Class.forName(element.getAttribute("class"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e);
}
}
// code to parse XML omitted for brevity
}
Now, the problem occurs after all bean definition have been read and BeanDefinitionRegistryPostProcessor begins to kick in. At this stage, a class called ConfigurationClassPostProcessor starts looking through all bean definitions, to search for bean classes annotated with #Configuration or that have methods with #Bean.
In the process of reading annotations for a bean, it uses the AnnotationMetadata interface. For most regular beans, a subclass called AnnotationMetadataVisitor is used. However, when parsing the bean definitions, if you have overriden the getBeanClass() method to return a class instance, like we had, instead a StandardAnnotationMetadata instance is used. When StandardAnnotationMetadata.hasAnnotatedMethods(..) is invoked, it calls Class.getDeclaredMethods(), which in turn causes the class loader to load all classes used as parameters in that class. Classes loaded this way are not correctly unloaded, and thus never weaved, since this happens before the AspectJ transformer registered.
Now, my problem was that I had a class like so:
public class Something {
private Lang lang;
public void setLang(Lang lang) {
this.lang = lang;
}
}
Then, I had a bean of class Something that was parsed using our custom ControllerBeanDefinitionParser. This triggered the wrong annotation detection procedure, which triggered unexpected class loading, which meant that AspectJ never got a chance to weave Lang.
The solution was to not override getBeanClass(..), but instead override getBeanClassName(..), which according to the documentation is preferable:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected String getBeanClassName(Element element) {
return element.getAttribute("class");
}
// code to parse XML omitted for brevity
}
Lesson of the day: Do not override getBeanClass unless you really mean it. Actually, don't try to write your own BeanDefinitionParser unless you know what you're doing.
Fin.
If your class is not mentioned in the -verbose/-debug output, that suggests to me it is not being loaded by the loader you think it is. Can you be 100% sure that 'Lang' isn't on the classpath of a classloader higher in the hierarchy? Which classloader is loading Lang at the point in time when you trigger your breakpoint?
Also, you don't mention AspectJ version - if you are on 1.6.7 that had issues with ltw for anything but a trivial aop.xml. You should be on 1.6.8 or 1.6.9.
How does ltw actually work?
Put simply, an AspectJ weaver is created for each classloader that may want to weave code. AspectJ is asked if it wants to modify the bytes for a class before it is defined to the VM. AspectJ looks at any aop.xml files it can 'see' (as resources) through the classloader in question and uses them to configure itself. Once configured it weaves the aspects as specified, taking into account all include/exclude clauses.
Andy Clement
AspectJ Project Lead
Option 1) Aspect J is open source. Crack it open and see what is going on.
Option 2) Rename your class to Bang, see if it starts working
I would not be surprised if there is hard coding to skip "lang' in there, though I can't say why.
Edit -
Seeing code like this in the source
if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
superclassname = Utility.compactClassName(superclassname, false);
} else {
superclassname = "java.lang.Object";
}
Looks like they are trying to skip weaving of java.lang.stuff.... don't see anything for just "lang" but it may be there (or a bug)