Why don't I experience any exception when I lookup bean wrapped by JDK dynamic proxy by class(instead of interface)?

好久不见. 提交于 2020-01-02 10:32:44

问题


Lets consider following bean:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
    private static final AtomicLong COUNTER = new AtomicLong(0);

    private Long index;

    public MyBeanB() {
        index = COUNTER.getAndIncrement();
        System.out.println("constructor invocation:" + index);
    }

    @Transactional 
    @Override
    public long getCounter() {
        return index;
    }
}

and consider 2 different usages:

USAGE 1:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;   
    ....
}

At this case application can't be started and prints:

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'myBeanB' could not be injected as a 'my.pack.MyBeanB' because it is a JDK dynamic proxy that implements:
    my.pack.MyBeanBInterface


Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

I expected to see it because I asked spring to create JDK dynamic proxy for bean MyBeanB and that proxy is not a subtype of MyBeanB. We can easily fix it like this:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanBInterface myBeanB;   
    ....
}

USAGE 2:

MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());

Surprisingly for me it works wihtout any Runtime Exceptions but I expected to see NoSuchBeanDefinitionException at this case because int case 1 application can't start

Thanks for guy from comments - I checked the class of beanB and it is my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261 so Spring used CGLIB to create proxy but it contradicts the bean definition(@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES) and looks like a bug. )

Could you explain why it is working for case 2 not working for case 1 ?


回答1:


As I explained to you in my comments to the other question, Spring AOP can use both CGLIB and JDK proxies depending on the situation. The default are JDK proxies for classes implementing interfaces, but you can enforce CGLIB usage for them too. For classes not implementing interfaces only CGLIB remains because JDK proxies can only create dynamic proxies based on interfaces.

So looking at your case 1, you explicitly say you want interface proxies, i.e. JDK proxies:

@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)

But MyBeanA does not implement any interfaces. Consequently you get the error message you see in this case.

In case 2 however you use ApplicationContext.getBean(..) in order to create a proxy. Here you are relying on Spring to determine which proxy type to choose, you are not trying to enforce anything. Thus, proxying via CGLIB succeeds.

No surprises here.

If you want to avoid the error message in case 1, maybe you ought to use ScopedProxyMode.TARGET_CLASS.


Update: Sorry, I was irritated by your similar and nondescript class names MyBeanA and MyBeanB. It would make sense to use more descriptive, clean-code-like class names next time, ideally ones describing the roles ob the classes in your scenario like MyService, MyInterface, MyScopedBean.

Anyway, I read your question and the error message again. The error message says that according to your annotation an interface-based proxy is being generated but you are trying to inject it into a class type. You can fix that by declaring it like this:

@Autowired
private MyBeanBInterface myBeanB;

In case/usage 2 you are again explicitly declaring a class and not an interface type for your bean. So as I said, Spring tries to satisfy your requirement by the only way possible, i.e. creating a CGLIB proxy for the class. You can fix this by declaring an interface type and you will get the expected JDK proxy:

MyBeanBInterface myBeanBInterface = appContext.getBean(MyBeanBInterface.class);
System.out.println(myBeanBInterface.getCounter());
System.out.println(myBeanBInterface.getClass());

Update 2: Something I think you still do not understand according to your comments is this basic fact of OOP: If you have

  • class Base and class Sub extends Base or
  • interface Base and class Sub implements Base

you can declare Base b = new Sub() but of course not Sub s = new Base() because a Sub is also a Base, but not every Base is a Sub. For example, if you also have OtherSub extends Base, when trying to assign a Base object to a Sub variable it could be an OtherSub instance. This is why this does dot even compile without using Sub s = (Sub) myBaseObject.

So far, so good. Now look at your code again:

In usage 1 you have @Autowired private MyBeanB myBeanB; but configured MyBeanB to produce a JDK proxy, i.e. a new proxy class with parent class Proxy directly implementing MyBeanBInterface will be created. I.e. you have two different classes, both directly implementing the same interface. Those classes are assignment-incompatible to each other for the reason I explained above. With regard to the interface we have the class hierarchy

MyBeanBInterface
  MyBeanB
  MyBeanB_JDKProxy

Thus you cannot inject MyBeanB_JDKProxy into a MyBeanB field because a proxy object is not an instance of MyBeanB. Don't you understand? The problem sits in front of the computer, there is no mysterious Spring bug. You configured it to fail.

This is why I told you to change the code to @Autowired private MyBeanBInterface myBeanB; because then of course it works because the proxy implements the interface and everything is fine. I also told you that alternatively you can keep @Autowired private MyBeanB myBeanB; if you use proxyMode = ScopedProxyMode.TARGET_CLASS for your scope declaration.

In usage 2 the problem is the same: You are saying getBean(ClassB.class), i.e. you are explicitly instructing Spring to create a proxy for that class. But for a class you cannot create a JDK proxy, only a CGLIB proxy, which is what Spring does. Again, I gave you the solution by instructing you to use getBean(MyBeanBInterface.class) instead. Then you get the expected JDK proxy.

Spring is smart enough to both

  • make the JDK proxy in usage 1 find the scoped service bean MyClassB and delegate method calls to it (note: delegation, not inheritance!) and
  • make the CGLIB proxy extend MyClassB (note: inheritance here, no delegation necessary).


来源:https://stackoverflow.com/questions/58221152/why-dont-i-experience-any-exception-when-i-lookup-bean-wrapped-by-jdk-dynamic-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!