Search code examples
springaopspring-aop

Define a missing method through AOP?


I'm in a situation where the implementation of a library we are using is newer than the implementation one of our dependencies was coded against. E.g. Dependency uses MyLibrary-1.0 and we're using MyLibrary-2.0.

In the newer implementation a deprecated method has been removed, which causes run-time errors for us.

I'm trying to use AOP (Spring-AOP to be specific) to intercept calls made to the missing method, and proxy them into an existing method... but I can't seem to get the Aspect right.

It feels like Java is raising the 'java.lang.NoSuchMethodError' exception before my Aspect has an opportunity to intercept. Is there some trick I'm missing, or is this just not feasible (e.g. the method must exist in order to aspect it)?

@Before("execution(* com.mylibrary.SomeClass.*(..))") 
     Fails with java.lang.NoSuchMethodError

@Around("target(com.mylibrary.SomeClass) && execution(* missingMethod(..))")
     Fails with java.lang.NoSuchMethodError

Solution

  • Assuming that your are talking about a 3rd party library which is independent of Spring, you cannot use Spring AOP with its proxy-based "AOP lite" approach which only works for public, non-static methods of Spring components. Please use the more powerful AspectJ instead. The Spring manual explains how to integrate full AspectJ with load-time weaving (LTW) into Spring applications. If your application is not based on Spring so far and you just wanted to use the framework because of Spring AOP, you can skip the whole Spring stuff altogether and use plain AspectJ.

    The feature you want to use is an inter-type declaration (ITD), more specifically AspectJ's ability to declare methods for existing classes. Here is some sample code:

    3rd party library:

    package org.library;
    
    public class Utility {
        public String toHex(int number) {
            return Integer.toHexString(number);
        }
    
        // Let us assume that this method was removed from the new library version
        /*
        @Deprecated
        public String toOct(int number) {
            return Integer.toOctalString(number);
        }
        */
    }
    

    Let us assume that the method I commented out was just removed from the latest version your own project depends on, but you know how to re-implement it.

    Project dependency depending on old version of 3rd party library:

    package com.dependency;
    
    import org.library.Utility;
    
    public class MyDependency {
        public void doSomethingWith(int number) {
            System.out.println(number + " in octal = " + new Utility().toOct(number));
        }
    }
    

    Because the previously deprecated method Utility.toOct does not exist anymore in the version used by your own project, you will get NoSuchMethodError during runtime when calling MyDependency.doSomethingWith.

    Your own application:

    package de.scrum_master.app;
    
    import org.library.Utility;
    
    import com.dependency.MyDependency;
    
    public class Application {
        public static void main(String[] args) {
            System.out.println("3333 in hexadecimal = " + new Utility().toHex(3333));
            new MyDependency().doSomethingWith(987);
        }
    }
    

    As you can see, the application also uses the same library, but a different method which still exists in the current version. Unfortunately, it also uses the dependency which relies on the existence of the removed method. So how should we repair this?

    Aspect using ITD:

    AspectJ to the rescue! We just add the missing method to the 3rd party library.

    package de.scrum_master.aspect;
    
    import org.library.Utility;
    
    public aspect DeprecatedMethodProvider {
        public String Utility.toOct(int number) {
            return Integer.toOctalString(number);
        }
    }
    

    If you compile this project with the AspectJ compiler Ajc, it just works. In your real life scenario, compile the aspect into its own aspect library, put the weaving agent aspectjweaver.jar on the JVM command line in order to activate LTW and enjoy how it weaves the method into the library class via byte code instrumentation during class-loading.

    Log output:

    3333 in hexadecimal = d05
    987 in octal = 1733
    

    Et voilà! Enjoy. :-)