Issue
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.
Add
@DependsOn("checkedGuitar")
toGuitarSinger
:@Service @Lazy @DependsOn("checkedGuitar") public class GuitarSinger implements Singer {
Add one more line
appContext.getBean("checkedGuitar")
tomain
.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, ...
Answered By - kriegaex
Answer Checked By - Terry (JavaFixing Volunteer)