Search code examples
javaspringannotationsaopfactory

Spring: Delegate to custom proxy wrapper for interface injection


In a very large legacy application I have interfaces and classes that do not implement those interfaces.

The interfaces are generated based on the class so the signatures are the same (except the interface adds another exception on top) and the names are similar (so it's easy to find the class name from the interface name).

To get an implementation of the interface we do a bunch of processing and logging calls but basically use java.lang.reflect.Proxy to delegate to the class. Simplified it looks like this:

// This will create a proxy and invoke handler that calls HelloWorld.doSomething
HelloWorldInterface i = MyProxyUtil.getInstance(HelloWorldInterface.class);
i.doSomething();

public interface HelloWorldInterface {
    public void doSomething() throws Exception;  
}

public class HelloWorld {
    public void doSomething() {
     //something
    }
}

Is it possible with Spring annotation processing, to generically @Autowire all fields of type *Interface and have spring use MyProxyUtil.getInstance(*Interface.class) to inject the implementation?

Such that

@Autowire HelloWorldInterface a;

HelloWorldInterface b = MyProxyUtil.getInstance(HelloWorldInterface.class);

@Autowire AnotherInterface c;

AnotherInterface d = MyProxyUtil.getInstance(AnotherInterface.class);


a == b
c == d

Solution

  • Yes, you need to implement a AutowireCandidateResolver.

    For example:

    public class ProxyAutowiredCandidateResolver extends SimpleAutowireCandidateResolver {
    
        @Override
        public Object getSuggestedValue(DependencyDescriptor descriptor) {
            String dependencyClassName = descriptor.getDependencyType().getSimpleName();
            if (dependencyClassName.endsWith("Interface")) {
                return MyProxyUtil.getInstance(descriptor.getDependencyType());
            }
    
            return super.getSuggestedValue(descriptor);
        }
    
    }
    

    You could use a BeanFactoryPostProcessor to configure it in the application context:

    public class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
    
        private AutowireCandidateResolver autowireCandidateResolver;
    
        public void postProcessBeanFactory(
                ConfigurableListableBeanFactory beanFactory) throws BeansException {
            DefaultListableBeanFactory  bf = (DefaultListableBeanFactory) beanFactory;
            bf.setAutowireCandidateResolver(autowireCandidateResolver);
    
    
        }
    
        public AutowireCandidateResolver getAutowireCandidateResolver() {
            return autowireCandidateResolver;
        }
    
        public void setAutowireCandidateResolver(
    
                AutowireCandidateResolver autowireCandidateResolver) {
            this.autowireCandidateResolver = autowireCandidateResolver;
        }
    
    }
    
    <bean id="autowireCandidateResolverConfigurer" class="AutowireCandidateResolverConfigurer">
            <property name="autowireCandidateResolver">
                <bean class="ProxyAutowiredCandidateResolver" />
            </property>
    </bean>