gst*_*low 19 java proxy singleton spring dynamic-proxy
正如我们所知道Spring使用代理来增加功能(@Transactional和@Scheduled举例)。有两种选择-使用JDK动态代理(该类必须实现非空接口),或使用CGLIB代码生成器生成子类。我一直认为proxyMode允许我在JDK动态代理和CGLIB之间进行选择。
但是我能够创建一个示例,说明我的假设是错误的:
单身人士:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Run Code Online (Sandbox Code Playgroud)
原型:
@Service
@Scope(value = "prototype")
public class MyBeanB {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional // just to force Spring to create a proxy
public long getCounter() {
return index;
}
}
Run Code Online (Sandbox Code Playgroud)
主要:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Run Code Online (Sandbox Code Playgroud)
输出:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到两件事:
MyBeanB只实例化了一次。@Transactional功能MyBeanB,Spring使用了CGLIB。让我更正MyBeanB定义:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
Run Code Online (Sandbox Code Playgroud)
在这种情况下,输出为:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到两件事:
MyBeanB被实例化3次。@Transactional功能MyBeanB,Spring使用了CGLIB。你能解释发生了什么吗?代理模式如何真正起作用?
我已经阅读了文档:
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
Run Code Online (Sandbox Code Playgroud)
但我不清楚。
我研究了另一种情况,其中从中提取了接口MyBeanB:
public interface MyBeanBInterface {
long getCounter();
}
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
Run Code Online (Sandbox Code Playgroud)
在这种情况下,输出为:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到两件事:
MyBeanB被实例化3次。@Transactional功能MyBeanB,Spring使用了JDK动态代理。为@Transactional行为生成的代理的作用与范围代理不同。
@Transactional代理是包装特定bean来添加会话管理行为的代理。所有方法调用将在委派给实际bean之前和之后执行事务管理。
如果您进行说明,它看起来像
main -> getCounter -> (cglib-proxy -> MyBeanB)
Run Code Online (Sandbox Code Playgroud)
就我们的目的而言,您基本上可以忽略其行为(删除@Transactional,您应该会看到相同的行为,除非您没有cglib代理)。
该@Scope代理行为有所不同。该文档指出:
[...]您需要注入一个代理对象,该对象公开与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索真实的目标对象,并将方法调用委托给真实的对象。
Spring真正在做的是为代表代理的工厂类型创建一个singleton bean定义。但是,相应的代理对象会在每次调用时在上下文中查询实际的bean。
如果您进行说明,它看起来像
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
Run Code Online (Sandbox Code Playgroud)
由于MyBeanB是原型Bean,因此上下文将始终返回新实例。
就此答案而言,假设您MyBeanB直接使用
MyBeanB beanB = context.getBean(MyBeanB.class);
Run Code Online (Sandbox Code Playgroud)
实际上,这是Spring为满足@Autowired注射目标所做的工作。
在第一个示例中,
main -> getCounter -> (cglib-proxy -> MyBeanB)
Run Code Online (Sandbox Code Playgroud)
您声明原型bean定义(通过注释)。@Scope有一个proxyMode元素
指定是否应将组件配置为作用域代理,如果是,则指定代理是基于接口还是基于子类。
默认值为
ScopedProxyMode.DEFAULT,这通常表示除非在组件扫描指令级别配置了其他默认值,否则不应创建任何作用域代理。
因此,Spring不会为生成的bean创建作用域代理。您使用
MyBeanB beanB = context.getBean(MyBeanB.class);
Run Code Online (Sandbox Code Playgroud)
现在,您具有对MyBeanB由Spring创建的新对象的引用。就像任何其他Java对象一样,方法调用将直接转到引用的实例。
如果getBean(MyBeanB.class)再次使用,Spring将返回一个新实例,因为bean定义是针对原型bean的。您没有这样做,因此所有方法调用都转到同一个对象。
在第二个示例中
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
Run Code Online (Sandbox Code Playgroud)
您声明通过cglib实现的作用域代理。当从Spring请求这种类型的bean时
MyBeanB beanB = context.getBean(MyBeanB.class);
Run Code Online (Sandbox Code Playgroud)
Spring知道这MyBeanB是一个作用域代理,因此返回一个满足API MyBeanB(即实现其所有公共方法)的代理对象,该API 内部知道如何MyBeanB为每个方法调用检索类型的实际bean 。
尝试跑步
@Service
@Scope(value = "prototype")
public class MyBeanB {
Run Code Online (Sandbox Code Playgroud)
这将true暗示Spring正在返回单例代理对象(而不是原型Bean)。
在代理实现内部的方法调用上,Spring将使用一个特殊的getBean版本,该版本知道如何区分代理定义和实际的MyBeanBbean定义。这将返回一个新MyBeanB实例(因为它是原型),并且Spring将通过反射(经典Method.invoke)将方法调用委托给它。
您的第三个示例与您的第二个示例基本相同。