我正在寻找一种存储我的对象的方法,似乎最好的方法是使用代理。我在互联网上找到了2个注释,我应该使用哪个注释:
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
或
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )
此外,代理是使用@Component
@Scope("session")
还是使用@SessionAttributes
的最佳方式?
答案 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。