INTERFACES or TARGET_CLASS: Which proxyMode should

2020-02-02 06:22发布

问题:

I am looking for a way to store my object and it seems that the best approach is to use proxies. I found 2 annotation in the internet, which one should I use :

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

or

@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )

Moreover, is it true that the proxies is the best way to use than using @Component @Scope("session") or using @SessionAttributes?

回答1:

You'll need to understand what each of those annotations does to choose for yourself. See the javadoc, here. Continue for a more detailed explanation.

The first

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

creates

a JDK dynamic proxy implementing all interfaces exposed by the class of the target object

In other words, the proxy will be a subtype of the interfaces that the target object's class implements, but won't be a subclass of the target object's class itself.

Essentially Spring does the following

public class Example {
    public static void main(String[] args) throws Exception {
        Foo target = new Foo();
        InvocationHandler proxyHandler = ... // some proxy specific logic, likely referencing the `target`

        // works fine
        Printable proxy = (Printable) Proxy.newProxyInstance(Example.class.getClassLoader(),
                target.getClass().getInterfaces(), proxyHandler);

        // not possible, ClassCastException
        Foo foo = (Foo) proxy; 
    }

    public static class Foo implements Printable {
        @Override
        public void print() {
        }
    }

    public interface Printable {
        void print();
    }
}

The proxy returned won't be of type Foo and you therefore can't inject it into any targets of that type. For example, Spring will fail to inject it into a field like

@Autowired
private Foo foo;

but will successfully inject the proxy into a field like

@Autowired
private Printable printable;

All calls to the proxy will be handled by the InvocationHandler (which usually performs some use case specific logic then delegates to the target object).


The second annotation

@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )

creates

a class-based proxy (uses CGLIB).

In addition to interfaces, with CGLIB Spring will be able to create a proxy whose class is a subclass of the target's class. In essence, it does the following

Foo target = new Foo();
net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();
enhancer.setInterfaces(target.getClass().getInterfaces());
enhancer.setSuperclass(target.getClass());
net.sf.cglib.proxy.MethodInterceptor interceptor = ... // some proxy specific logic, likely referencing the `target`
enhancer.setCallback(interceptor);

// works fine
Foo proxy = (Foo) enhancer.create();

CGLIB creates a new class that is a subclass of Foo and instantiates it (invoking the constructor of Foo). All calls to the proxy will be intercepted by the provided callback (which usually performs some use case specific logic and then delegates to the target object).

Since the proxy class extends Foo, Spring can inject the proxy into a field (or constructor/method parameter) like

@Autowired
private Foo injectMe;

All this to say, if you're programming to interfaces, then ScopedProxyMode.INTERFACES will be sufficient. If you're not, then use ScopedProxyMode.TARGET_CLASS.


As for using @SessionAttributes, it is not an alternative to session scoped beans. Session attributes are just objects, they are not beans. They don't possess the full lifecycle, injection capabilities, proxying behavior that a bean may have.



回答2:

If you want to store the whole bean in the session, use @Scope, otherwise use @SessionAttributes. In the case of using @Scope, if the class implements some interfaces then use INTERFACES proxy mode, if not use TARGET_CLASS.

Usually your service implements an interface, which allows the use of JDK proxies (INTERFACES mode). But if that is not the case then use TARGET_CLASS, which creates a CGLIB proxy.

Using INTERFACES should be used if possible and TARGET as last resort if the bean does not implement interfaces.



回答3:

While going through a blog post provided in the comments above, I found a comment stating cons of interface-based proxies.

On the post, user Flemming Jønsson posted this:

Be careful with using interface-based proxies.

If you are using Spring Security or Spring Transactions you might experience oddities when using interface-based proxies.

E.g. if you have a bean T and that bean has methods a() and b() that are both annotated transactional. Calls from other beans directly to a() or b() will behave properly (as configured). However if you introduce an internal call - where a() calls b() then b's transactional metadata will have no effect. The reason is that when you are using interface-based proxies the internal call will not go through the proxy - and thus the transactional interceptor will not have a chance to start a new transaction.

The same goes for Security. If method a() only requires USER-role but calls b() that requires ADMIN-role, then the internal call from a to b will be performed for any USER with no warnings. Same reason as above, internal calls do not go through the proxy and thus the security interceptor does not have a chance to act upon the call to b() from a().

To solve issues like these use targetClass.