Saturday, August 30, 2014

Using Maven to Detect Duplicate Classes

Using Maven to Detect Duplicate Classes

As the number of dependencies from some our corporate project grew, we ran into a couple cases where we would be transitively pulling into different versions of the same classes. For a long time, we were lucky, as this did not cause any issues, until someday a developer new to our team introduced trying to run our application using a different operating system. The JVM on his machine loaded up the classes in a different order, and his application instance used an older version of a class that resulted in errors running the application.

This developer thankfully pointed out we can detect when we were pulling in different versions of classes using the Maven Enforcer plugin with some help of some extra rules. We added something like this to our parent POM:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>1.3.1</version>
        <executions>
            <execution>
                <id>enforce</id>
                <phase>validate</phase>
                <configuration>
                    <rules>
                        <banDuplicateClasses>
                            <ignoreClasses>
                                <ignoreClass>javax.*</ignoreClass>
                                <ignoreClass>org.junit.*</ignoreClass>
                                <ignoreClass>org.hamcrest.*</ignoreClass>
                                <ignoreClass>org.slf4j.*</ignoreClass>
                            </ignoreClasses>
                            <findAllDuplicates>true</findAllDuplicates>
                        </banDuplicateClasses>
                    </rules>
                    <fail>false</fail>
                </configuration>
                <goals>
                    <goal>enforce</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>extra-enforcer-rules</artifactId>
                <version>1.0-beta-2</version>
            </dependency>
        </dependencies>
    </plugin>

Example output from the validate phase of a build:

    [INFO] --- maven-enforcer-plugin:1.1:enforce (enforce) @ cataloging-api ---
    [INFO] Adding ignore: javax.*
    [INFO] Adding ignore: org.junit.*
    [INFO] Adding ignore: org.hamcrest.*
    [INFO] Adding ignore: org.slf4j.*
    [WARNING] Rule 0: org.apache.maven.plugins.enforcer.BanDuplicateClasses failed with message:
    Duplicate classes found:

      Found in:
        org.company.id.api:id-api:jar:1.28.0:compile
        org.compnay.legacy.id-api:id-common-api:jar:2.8.0:compile
      Duplicate classes:
        org/company/service/id/SAMLEntityId.class
        org/company/service/id/crypto/ServiceKey.class

We were surprised at the amount of duplicate classes we were bringing in. We thought our dependency management was in pretty good shape, but adding the new rules showed we still had some work to do.

It is also worth noting, while our project is large, adding the plugin execution to the build added an insignifcat amount of time to the build overall, so there should be no performance concerns if you were considering adding something similar to your project.

Saturday, August 9, 2014

Spring AOP: Excluding Methods in Pointcuts

We use Spring AOP to make use of aspects in our applications for the purpose of recording statistics about requests they receive. This approach has worked out well because it helps separate the concern of tracking statistics from our business logic. Previous iterations of our applications mixed the two, leaing to code that was generally harder to maintain.

Below is an example of how we might configure an aspect to target all entry points into our business application with the same aspect:

    <aop:config>
        <aop:pointcut id="requestPointcut"
                      expression="execution(public * org.company.service.*.*(..))"/>
        <aop:aspect id="requestAspect" ref="requestStatAspect">
            <aop:around method="around" pointcut-ref="requestPointcut"/>
        </aop:aspect>
    </aop:config>

    <bean id="requestAspect" class="org.company.service.aop.RequestIdentifierAspect" lazy-init="true"/>

With the above, we are saying we want request identifiers managed for every request to our interfaces. With the wildcards in the expression, we are saying target every class in the org.mycompany.myapp.service package and every method in those classes.

This approach worked greatly until we added an interface that provided health reporting for system monitoring purposes. This method lived in a new interface. Let's suppose added the interface method is defined as:

  • public boolean org.company.HealthService.isHealthy()
    

Since the method signature is different (it takes no arguments), running the advice in the aspect would lead to errors as the aspect evaluated the method arguments. We could change the code in the aspect to avoid processing if the arguments contained within the join point do not match what we are expecting. This approach is not that attractive because it one, involves changing code for special cases, and two, could lead to the aspect siltently not invoking its main logic at times we might not expect. If we target a method with the aspect unintentionally, we want to be informed by errors in our log.

An alternative we found was to exclude the new method from the pointcut expression:

  • execution(public * org.company.service.*.*(..)) &amp;&amp; !execution(* org.company.service.HealthService.*(..))
    

Just with adding special case logic to an aspect or code in general to avoid exception cases, we have to be careful that the exceptional cases we avoid targeting with the aspect does not get out of hand as we are tempted to add more in the future.