What a shame I haven’t published anything for a long time. To make up for this long pause, I’m going to discuss how Spring can support with analyzing class metadata. These days everybody is crazy about using annotations in their frameworks. Occasionally you need to know what classes/methods are annotated. A good example might be to build a list of class names for all JPA entities (those classes annotated with javax.persistence.Entity and the like).

A simple use-case showing how to detect classes annotated as components

This use-case is using a few Spring APIs and some home grown code that controls resource matching (ClassResourceResolver and AnnotationMetadataMatcher).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.IOException;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.util.ClassUtils;

public class ClassResourceResolverTest {
  @Test
  public void testSelect() throws IOException {
      // given
      ClassResourceResolver scanner = new ClassResourceResolver(
              ClassUtils.convertClassNameToResourcePath(AComponent.class.getPackage().getName()));
      final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
              new PathMatchingResourcePatternResolver());

      // when
      String[] classNames = scanner.select(new AnnotationMetadataMatcher(metadataReaderFactory, false,
              Component.class));
      // then
      Assert.assertArrayEquals(new String[] { AComponent.class.getName() }, classNames);
      Assert.assertTrue(metadataReaderFactory.getMetadataReader(classNames[0]).getAnnotationMetadata()
              .isAnnotated(Component.class.getName()));
  }
}

@Component
class AComponent {
}

@Repository
class ARepository {
}

ClassResourceResolver is doing the resource matching all from a set of resource paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 public String[] select(ClassResourceMetadataMatcher matcher) throws IOException {
      List<String> result = new ArrayList<String>();

      for (String path : resourcePath) {
          String resourcePattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path + "/**/*.class";
          Resource[] resources = this.resourcePatternResolver.getResources(resourcePattern);
          for (Resource resource : resources) {
              if (resource.isReadable()) {
                  MetadataReader reader = matcher.match(resource, resourcePatternResolver);
                  if (reader != null) {
                      result.add(reader.getClassMetadata().getClassName());
                  }
              }
          }
      }
      return result.toArray(new String[result.size()]);
  }

A ClassResourceMetadataMatcher is looking at the annotations present on a class resource

1
2
3
4
5
6
7
8
9
10
11
 @Override
  public MetadataReader match(Resource resource, ResourceLoader resolver) throws IOException {
      for (Class<? extends Annotation> annotationType : annotationTypes) {
          MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
          if (new AnnotationTypeFilter(annotationType, considerMetaAnnotations).match(metadataReader,
                  metadataReaderFactory)) {
              return metadataReader;
          }
      }
      return null;
  }

Under the hood Spring is using the ASM bytecode library for looking into the class files. Note that no class examined by the code above is actually loading any of the class objects so it is fairly lightweight and not filling up your perm gen space. Still you need to make up useful search paths so you don’t end up scanning through the full classpath.

The full code just is available as a gist:

Comments