In this case(spring 5.0.x version):
public class AppConfig {
@Bean
public ProxyFactoryBean proxyFactoryBean(TargetService targetService) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setProxyTargetClass(true);
proxyFactoryBean.setTarget(targetService);
proxyFactoryBean.setInterceptorNames("myBeforeAdvice");
return proxyFactoryBean;
}
}
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before invoke method [ " + method.getName() + " ], aop before logic invoked");
}
}
@Priority(12)
public class TargetService {
public void testAopApi() {
System.out.println("testAopApi has invoked");
}
}
public class Entry {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TargetService.class,
MyBeforeAdvice.class,
AppConfig.class);
context.getBean(TargetService.class).testAopApi();
}
}
I need to get the proxy bean of TargetService type by "getBean(Class
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.eugene.sumarry.resourcecodestudy.newissue.TargetService' available: Multiple beans found with the same priority ('12') among candidates: [targetService, proxyFactoryBean]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.determineHighestPriorityCandidate(DefaultListableBeanFactory.java:1421)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1027)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:338)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:333)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
at com.eugene.sumarry.resourcecodestudy.newissue.Entry.main(Entry.java:13)
As we know spring has two solution to deal with above situation that is by @Primary and @Priority annotation。
// org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveNamedBean(java.lang.Class<T>, java.lang.Object...)
String candidateName = determinePrimaryCandidate(candidates, requiredType);
if (candidateName == null) {
candidateName = determineHighestPriorityCandidate(candidates, requiredType);
}
When I replace the @Priority(12)
with the @Primary
annotation, this problem can be solved perfectly.
But, when I try to use the @Priority
annotation to deal with the problem, I don't know how to solve it, because I cannot add @Priority
annotation to proxy object.
After a lot of testing, I found that this is indeed a problem. When the ProxyFactoryBean
proxies the bean in the Spring container, we will get an above exception if we use the getBean(Class<T> requiredType)
API to get the bean.
Comment From: quaff
You should try latest 5.2.x
Comment From: sbrannen
Please note that the example works if you switch to the following lookup.
context.getBeansOfType(TargetService.class).get("proxyFactoryBean").testAopApi();
Does that suit your needs?
Comment From: AvengerEug
Please note that the example works if you switch to the following lookup.
java context.getBeansOfType(TargetService.class).get("proxyFactoryBean").testAopApi();
Does that suit your needs?
Yeah, this is a solution such as official documents aop-api-proxying-intf chapter. I just found this problem when I learning spring aop api. I think this is indeed a problem. If we try to get bean by "getBean(Class requiredType)" api, we will eventually delegate to “getBean(String name)” api. Can Spring-Team provide a way such as PostProcessor to add @Priority annotation to proxy objects to solve this problem? If the cost of fixing this problem is too high, I think we can add some remarks about this problem in the official documents
Comment From: AvengerEug
Please note that the example works if you switch to the following lookup.
java context.getBeansOfType(TargetService.class).get("proxyFactoryBean").testAopApi();
Does that suit your needs?
It also doesn't work in spring 5.2.x version.
Comment From: quaff
When I replace the @Priority(12) with the @Primary annotation, this problem can be solved perfectly.
Which bean(original or proxied) do you get and want?
Comment From: AvengerEug
When I replace the @priority(12) with the @primary annotation, this problem can be solved perfectly.
Which bean(original or proxied) do you get and want?
It depends on whether I put @primary on Targetservice or ProxyFactoryBean. Spring-Framework will return bean that owns @Primary annotation. In case A, will return targetService that is original object. In case B, will return targetService proxy object
// case A
@Primary
public class TargetService {
public void testAopApi() {
System.out.println("testAopApi has invoked");
}
}
@Bean
public ProxyFactoryBean proxyFactoryBean(TargetService targetService) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setProxyTargetClass(true);
proxyFactoryBean.setTarget(targetService);
proxyFactoryBean.setInterceptorNames("myBeforeAdvice");
return proxyFactoryBean;
}
// --------------------------------------------------------------------------------------
// case B
public class TargetService {
public void testAopApi() {
System.out.println("testAopApi has invoked");
}
}
@Primary
@Bean
public ProxyFactoryBean proxyFactoryBean(TargetService targetService) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setProxyTargetClass(true);
proxyFactoryBean.setTarget(targetService);
proxyFactoryBean.setInterceptorNames("myBeforeAdvice");
return proxyFactoryBean;
}
There is solution to this problem such as "getBean(String name)" api and @Primary.
There are two solutions in spring source code when we get multiple beanName by "getBean(Class requiredType)" api. source code:
@Nullable
private <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
// Get bean names by type
String[] candidateNames = getBeanNamesForType(requiredType);
// .............................
if (candidateNames.length > 1) {
Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
for (String beanName : candidateNames) {
if (containsSingleton(beanName) && args == null) {
Object beanInstance = getBean(beanName);
candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
}
else {
candidates.put(beanName, getType(beanName));
}
// @Primary annotation strategy
String candidateName = determinePrimaryCandidate(candidates, requiredType);
if (candidateName == null) {
// @Priority annotation strategy
candidateName = determineHighestPriorityCandidate(candidates, requiredType);
}
}
.........
}
}
I can't solve the problem by @Priority annotation.
Comment From: quaff
Because priority of both beans are equals, what do you think which one should be first?
Have you tried promote priority by annotating @Order
?
Comment From: quaff
It seems @Order
doesn't affects priority, is this by design or we could treat @Order
as negated priority? @sbrannen
Comment From: mdeinum
Chancing the @Order
won't help. There is the real object and a class-based proxy of that object. Both are annotated with the same @Ordered
(as both are the class). So chancing the @Order
value won't help.
Although I would suspect this being a corner-case, it will only happen in the case of one creating your own class based proxies for a bean that has an @Order
without one being the @Primary
. The solution would be to annotate the proxy with @Primary
or don't use the ProxyFactoryBean
but use @Aspect
to define aspects (as this will replace the bean with a proxy instead of adding a second bean).
Comment From: sbrannen
Thanks for chiming in, @mdeinum.
Although I would suspect this being a corner-case, it will only happen in the case of one creating your own class based proxies for a bean that has an
@Order
without one being the@Primary
. The solution would be to annotate the proxy with@Primary
or don't use theProxyFactoryBean
but use@Aspect
to define aspects (as this will replace the bean with a proxy instead of adding a second bean).
After all of the discussion here, I was going to make a similar point to your last point.
@AvengerEug, let's look at this from a different perspective.
What exactly are you trying to achieve?
Do you have a concrete need for the existence of both the proxy and the target object as top-level beans in the ApplicationContext
?
Phrased differently, what guided you to create the ProxyFactoryBean
via an @Bean
method?
As @mdeinum hinted at, implementing an @Before
advice method in an @Aspect
class using AspectJ would be a more elegant solution if your ultimate goal is to have only the proxied/advised TargetService
bean in the ApplicationContext
.
Comment From: AvengerEug
Chancing the
@Order
won't help. There is the real object and a class-based proxy of that object. Both are annotated with the same@Ordered
(as both are the class). So chancing the@Order
value won't help.Although I would suspect this being a corner-case, it will only happen in the case of one creating your own class based proxies for a bean that has an
@Order
without one being the@Primary
. The solution would be to annotate the proxy with@Primary
or don't use theProxyFactoryBean
but use@Aspect
to define aspects (as this will replace the bean with a proxy instead of adding a second bean).
Thank you for your advice.
Comment From: AvengerEug
Thanks for chiming in, @mdeinum.
Although I would suspect this being a corner-case, it will only happen in the case of one creating your own class based proxies for a bean that has an
@Order
without one being the@Primary
. The solution would be to annotate the proxy with@Primary
or don't use theProxyFactoryBean
but use@Aspect
to define aspects (as this will replace the bean with a proxy instead of adding a second bean).After all of the discussion here, I was going to make a similar point to your last point.
@AvengerEug, let's look at this from a different perspective.
What exactly are you trying to achieve?
Do you have a concrete need for the existence of both the proxy and the target object as top-level beans in the
ApplicationContext
?Phrased differently, what guided you to create the
ProxyFactoryBean
via an@Bean
method?As @mdeinum hinted at, implementing an
@Before
advice method in an@Aspect
class using AspectJ would be a more elegant solution if your ultimate goal is to have only the proxied/advisedTargetService
bean in theApplicationContext
.
Yean, I agree with @mdeinum. I just found this bug when I learning aop api from Official documents: "Using the ProxyFactoryBean to Create AOP Proxies" chapter. I tried to solve it, but there was nothing I could do. So I created this issue.
Comment From: sbrannen
Thanks for the feedback, @AvengerEug.
In light of that, I am closing this issue as "works as designed".
To avoid the presence of both the proxy and the target object as top-level beans in the ApplicationContext
, please either use an auto-proxy facility or Spring's @AspectJ
support.