Pages

The Power of ClassPathScanningCandidateComponentProvider

Recently I found out about Springs ClassPathScanningCandidateComponentProvider. This class allows you to scan the classpath from a base package at runtime. This is extremely handy in all kind of situations, for instances in testing.

As an example scenario I have a secured webapplication, build with Wicket. All the WebPages need to be secured using the wicket-auth-roles. We can test this by creating a junit test using WicketTester and assert that is we want to start a secured page it will actually open the LoginPage. This will work, but it needs to be repeated for all new secure WebPages. This is of course cumbersome and it is highly likely we will forget to add a test for the next WebPage we will create.
Better is to use the power of the ClassPathScanningCandidateComponentProvider. With this class we can simply scan the classpath and look for WebPages in the admin package and check if they are annotated with the @AuthorizeInstantiation annotation.

   @Test
    public void shouldSecureAllPagesInAdminPackageExceptTheLoginPage() throws Exception {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new NonSecuredWebPagesFilter(LoginPage.class));
        Set components = provider.findCandidateComponents("nl.ns.campaigns.admin.web");
        assertEquals(0, components.size());
    }

The idea of this test is that it should not find classes that extend WebPage and that are not annotated with @AuthorizeInstantiation. If we do find one, the test will fail.
The logic match is implemented in a TypeFilter, in this case the custom NonSecuredWebPagesFilter. The code of the TypeFilter looks like this:

    private static final class NonSecuredWebPagesFilter implements TypeFilter {
        private final Class loginPage;
        public NonSecuredWebPagesFilter(Class loginPage) {
            this.loginPage = loginPage;
        }
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            boolean hasAnnotation = false;
            boolean isWebPage = false;
            boolean isLoginPage = false;
            try {
                String className = metadataReader.getClassMetadata().getClassName();
                Class clazz = Class.forName(className);
                isLoginPage = loginPage.equals(clazz);
                isWebPage = WebPage.class.isAssignableFrom(clazz);
                hasAnnotation = clazz.isAnnotationPresent(AuthorizeInstantiation.class);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e);
            }
            return !hasAnnotation && isWebPage && !isLoginPage;
        }
    }

Since the LoginPage does not have to be annotated we exclude it from our search. Only WebPages that are not annotated are returned due to this TypeFilter.

Scanning the classpath has become a piece of cake using the ClassPathScanningCandidateComponentProvider and I am sure there are many more usecases for this class.

2 comments to The Power of ClassPathScanningCandidateComponentProvider

  • matthewsteele

    Very cool! I used this pattern to print out all of my TestNG tests within a certain group. Took all of 20 minutes, and now I know how many of my tests are broken at any given time :-(

  • lvonk

    Hi Matthew,

    Thanks for replying. I have also used it to create a testsuite that has all Test classes on the classpath. This is very handy when you want to run all your tests in one go in Eclipse when you are working on a Maven multimodule build.

    Lars

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>