INTERFACES或TARGET_CLASS:我应该选择哪个proxyMode?

时间:2014-02-13 16:19:22

标签: spring spring-mvc

我正在寻找一种存储我的对象的方法,似乎最好的方法是使用代理。我在互联网上找到了2个注释,我应该使用哪个注释:

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

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

此外,代理是使用@Component @Scope("session")还是使用@SessionAttributes的最佳方式?

3 个答案:

答案 0 :(得分:25)

如果要将整个bean存储在会话中,请使用@Scope,否则使用@SessionAttributes。在使用@Scope的情况下,如果该类实现了某些接口,则使用INTERFACES代理模式,如果不使用TARGET_CLASS。

通常,您的服务实现一个接口,允许使用JDK代理(INTERFACES模式)。但如果情况并非如此,则使用TARGET_CLASS,这将创建一个CGLIB代理。

如果可能的话,应该使用INTERFACES,如果bean没有实现接口,则使用TARGET作为最后的手段。

答案 1 :(得分:22)

您需要了解每个注释的用途。请参阅javadoc,here。继续进行更详细的解释。

第一个

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

创建

  

实现目标对象类公开的所有接口的JDK动态代理

换句话说,代理将是目标对象的类实现的接口的子类型,但不会是目标对象的类本身的子类。

基本上Spring会执行以下操作

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();
    }
}

返回的代理不是Foo类型,因此您无法将其注入该类型的任何目标。例如,Spring将无法将其注入像

这样的字段中
@Autowired
private Foo foo;

但会成功将代理注入

之类的字段
@Autowired
private Printable printable;

所有对代理的调用都将由InvocationHandler处理(通常执行一些特定于用例的逻辑,然后委托给目标对象)。


第二个注释

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

创建

  

基于类的代理(使用CGLIB)。

除了接口之外,使用CGLIB Spring还可以创建一个代理,其类是目标类的子类。实质上,它执行以下操作

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创建一个新类,它是Foo的子类并实例化它(调用Foo的构造函数)。所有对代理的调用都将被提供的回调拦截(通常执行一些特定于用例的逻辑,然后委托给目标对象)。

由于代理类扩展Foo,Spring可以将代理注入字段(或构造函数/方法参数),如

@Autowired
private Foo injectMe;

所有这一切,如果你是programming to interfaces,那么ScopedProxyMode.INTERFACES就足够了。如果不是,请使用ScopedProxyMode.TARGET_CLASS


至于使用@SessionAttributes,它不能替代会话范围的bean。会话属性只是对象,它们不是bean。它们不具备bean可能具有的完整生命周期,注入功能和代理行为。

答案 2 :(得分:6)

在浏览上面评论中提供的blog post时,我发现了一条注释,说明了基于接口的代理的缺点。

在帖子上,用户FlemmingJønsson发布了这个:

  

小心使用基于接口的代理。

     

如果您使用的是Spring Security或Spring Transactions,则在使用基于接口的代理时可能会遇到奇怪的现象。

     

E.g。如果你有一个bean T,那个bean的方法a()和b()都是带注释的事务。从其他bean直接调用a()或b()将正常运行(如配置)。但是,如果引入内部调用 - 其中a()调用b(),则b的事务元数据将不起作用。原因是当您使用基于接口的代理时,内部调用将不会通过代理 - 因此事务拦截器将无法启动新事务。

     

安全性也是如此。如果方法a()只需要USER-role但需要调用需要ADMIN-role的b(),那么将对任何没有警告的USER执行从a到b的内部调用。与上述相同的原因是,内部调用不通过代理,因此安全拦截器没有机会对来自()的b()调用进行操作。

     

要解决这些问题,请使用targetClass。