Search code examples
javaspringdependency-injectionproxyspring-aop

Why does ProxyFactoryBean throw "No qualifying bean of type" with @Autowired? And doesn't throw with pre-getBean()?


I've tried to use ProxyFactoryBean with @Autowired, but it throws an exception at the setInstrument method.

The question - why does it work with previously called getBean method or @DependsOn? I'm trying to understand what Spring performs for these additional steps.

Thank you in advance!

I have two interfaces - Singer and Instrument. And two implementations for them - GuitarSinger.class and Guitar.class.

public interface Singer {
    void singSong();
    Instrument getInstrument();
}
@Service
@Lazy
public class GuitarSinger implements Singer {
    private Instrument guitar;

    @Autowired
    public void setInstrument(Instrument instrument) {
        this.guitar = instrument;
    }

    @Override
    public Instrument getInstrument() {
        return guitar;
    }

    @Override
    public void singSong() {
        System.out.println("I'm singing a song");
        guitar.play();
    }
}
public interface Instrument {
    void play();
}
public class Guitar implements Instrument {
    @Override
    public void play() {
        System.out.println("I'm a guitar!");
    }
}

This interface is meant for for proxy introduction:

public interface GuitarChecker {
    boolean isGuitarOk();
}

This mix-in class implements the interface we want to introduce to the proxy:

public class GuitarCheckerMixin extends DelegatingIntroductionInterceptor implements GuitarChecker {
    @Override
    public boolean isGuitarOk() {
        System.out.println("I don't know how to check the guitar");
        return true;
    }
}

And eventually, the configuration class with main method:

@Configuration
@ComponentScan("com.annotation.test")
public class Config {
    @Bean
    public ProxyFactoryBean checkedGuitar() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        Instrument guitar = new Guitar();
        proxyFactoryBean.setTarget(guitar);
        proxyFactoryBean.addAdvisor(new DefaultIntroductionAdvisor(new GuitarCheckerMixin()));
        proxyFactoryBean.setProxyTargetClass(true);
        return proxyFactoryBean;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext appContext
                = new AnnotationConfigApplicationContext(Config.class);
        Singer singer = appContext.getBean(Singer.class);
        singer.singSong();
        Instrument instrument = singer.getInstrument();
        GuitarChecker guitarChecker = (GuitarChecker) instrument;
        guitarChecker.isGuitarOk();
    }
}

If I run main, I will get:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.annotation.test.Instrument' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:759)
    ... 17 more

But if I add one of these two steps (works ok with only one of them), it will work correctly and print result.

  1. Add @DependsOn("checkedGuitar") to GuitarSinger:

     @Service
     @Lazy
     @DependsOn("checkedGuitar")
     public class GuitarSinger implements Singer {
    
  2. Add one more line appContext.getBean("checkedGuitar") to main.

     public static void main(String[] args) {
         AnnotationConfigApplicationContext appContext
             = new AnnotationConfigApplicationContext(Config.class);
         appContext.getBean("checkedGuitar");
         Singer singer = appContext.getBean(Singer.class);
         singer.singSong();
         Instrument instrument = singer.getInstrument();
         GuitarChecker guitarChecker = (GuitarChecker) instrument;
         guitarChecker.isGuitarOk();
     }
    

With one of these two updates, the application will print the following lines:

I'm singing a song
I'm a guitar!
I don't know how to check the guitar

Could you please help me with understanding of the issue? Why does it work with @DependsOn or getBean()?


Solution

  • I am not a Spring user, so I do not know how proxy bean factories and the other stuff you use are meant to be used canonically. But how about his?

    package de.scrum_master.spring.q70623926;
    
    import org.springframework.aop.framework.ProxyFactoryBean;
    import org.springframework.aop.support.DefaultIntroductionAdvisor;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("de.scrum_master.spring.q70623926")
    public class Config {
      @Bean
      public ProxyFactoryBean checkedGuitar() throws ClassNotFoundException {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(new Guitar());
        proxyFactoryBean.setProxyTargetClass(true);
        // Make CGLIB proxy implement the same interfaces as the target class
        proxyFactoryBean.setProxyInterfaces(Guitar.class.getInterfaces());
        proxyFactoryBean.addAdvisor(new DefaultIntroductionAdvisor(new GuitarCheckerMixin()));
        return proxyFactoryBean;
      }
    
      public static void main(String[] args) {
        try (AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext(Config.class)) {
          Singer singer = appContext.getBean(GuitarSinger.class);
          singer.singSong();
          ((GuitarChecker) singer.getInstrument()).isGuitarOk();
        }
      }
    }
    

    This yields:

    12:39:49.887 ...ProxyFactoryBean - Advice has changed; re-caching singleton instance
    12:39:49.887 ...ProxyFactoryBean - Advice has changed; re-caching singleton instance
    12:39:49.887 ...ProxyFactoryBean - Advice has changed; re-caching singleton instance
    12:39:49.887 ...ProxyFactoryBean - Advice has changed; re-caching singleton instance
    12:39:49.887 ...ProxyFactoryBean - Advice has changed; re-caching singleton instance
    12:39:49.979 ...DefaultListableBeanFactory - Creating shared instance of singleton bean 'guitarSinger'
    I'm singing a song
    I'm a guitar!
    I don't know how to check the guitar
    12:39:50.023 ...AnnotationConfigApplicationContext - Closing ...AnnotationConfigApplicationContext@61baa894, ...