Como sabemos, Spring usa proxies para agregar funcionalidad ( @Transactionaly @Scheduledpor ejemplo). Hay dos opciones: usar un proxy dinámico JDK (la clase tiene que implementar interfaces no vacías) o generar una clase secundaria usando el generador de código CGLIB. Siempre pensé que proxyMode me permite elegir entre un proxy dinámico JDK y CGLIB.
Pero pude crear un ejemplo que muestra que mi suposición es incorrecta:
Caso 1:
Único:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototipo:
@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;
}
}
Principal:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Salida:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Aquí podemos ver dos cosas:
MyBeanBfue instanciado solo una vez .- Para agregar la
@Transactionalfuncionalidad paraMyBeanB, Spring usó CGLIB.
Caso 2:
Déjame corregir la MyBeanBdefinición:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
En este caso, la salida es:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Aquí podemos ver dos cosas:
MyBeanBfue instanciado 3 veces.- Para agregar la
@Transactionalfuncionalidad paraMyBeanB, Spring usó CGLIB.
¿Podría explicar lo que está pasando? ¿Cómo funciona realmente el modo proxy?
PD
He leído la documentación:
/**
* 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
*/
Pero no está claro para mí.
Actualizar
Caso 3:
Investigué un caso más, en el que extraje la interfaz de 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 {
y en este caso el resultado es:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Aquí podemos ver dos cosas:
MyBeanBfue instanciado 3 veces.- Para agregar la
@TransactionalfuncionalidadMyBeanB, Spring usó un proxy dinámico JDK.

MyBeanBclase no extiende ninguna interfaz, por lo que no es sorprendente que el registro de la consola muestre instancias de proxy CGLIB. En el caso 3, introduce e implementa una interfaz, por lo tanto, obtiene un proxy JDK. Incluso lo describe en su texto introductorio.<aop:config proxy-target-class="true">o@EnableAspectJAutoProxy(proxyTargetClass = true), respectivamente.Respuestas:
El proxy generado para el
@Transactionalcomportamiento tiene un propósito diferente que los proxies con ámbito.El
@Transactionalproxy es uno que envuelve el bean específico para agregar un comportamiento de administración de sesión. Todas las invocaciones de métodos realizarán la gestión de transacciones antes y después de delegar en el bean real.Si lo ilustras, se vería como
Para nuestros propósitos, esencialmente puede ignorar su comportamiento (eliminar
@Transactionaly debería ver el mismo comportamiento, excepto que no tendrá el proxy cglib).El
@Scopeproxy se comporta de manera diferente. La documentación dice:Lo que Spring realmente está haciendo es crear una definición de bean singleton para un tipo de fábrica que representa el proxy. Sin embargo, el objeto proxy correspondiente consulta el contexto del bean real para cada invocación.
Si lo ilustras, se vería como
Como
MyBeanBes un bean prototipo, el contexto siempre devolverá una nueva instancia.A los fines de esta respuesta, suponga que recuperó el
MyBeanBdirectamente conque es esencialmente lo que hace Spring para satisfacer un
@Autowiredobjetivo de inyección.En tu primer ejemplo,
Usted declara una definición de bean prototipo (a través de las anotaciones).
@Scopetiene unproxyModeelemento queEntonces Spring no está creando un proxy con alcance para el bean resultante. Recuperas ese frijol con
Ahora tiene una referencia a un nuevo
MyBeanBobjeto creado por Spring. Esto es como cualquier otro objeto Java, las invocaciones de métodos irán directamente a la instancia referenciada.Si lo usó
getBean(MyBeanB.class)nuevamente, Spring devolvería una nueva instancia, ya que la definición de bean es para un bean prototipo . No está haciendo eso, por lo que todas las invocaciones de métodos van al mismo objeto.En tu segundo ejemplo,
declara un proxy con ámbito que se implementa a través de cglib. Al solicitar un bean de este tipo a Spring con
Spring sabe que
MyBeanBes un proxy con alcance y, por lo tanto, devuelve un objeto proxy que satisface la API deMyBeanB(es decir, implementa todos sus métodos públicos) que internamente sabe cómo recuperar un bean de tipo realMyBeanBpara cada invocación de método.Intenta correr
Esto devolverá una
truepista al hecho de que Spring está devolviendo un objeto proxy singleton (no un bean prototipo).En una invocación de método, dentro de la implementación de proxy, Spring usará una
getBeanversión especial que sepa cómo distinguir entre la definición de proxy y laMyBeanBdefinición de bean real . Eso devolverá una nuevaMyBeanBinstancia (ya que es un prototipo) y Spring le delegará la invocación del método a través de la reflexión (clásicoMethod.invoke).Su tercer ejemplo es esencialmente el mismo que su segundo.
fuente
context.getBean(MyBeanB.class), en realidad no estás obteniendo el proxy, estás obteniendo el bean real.@Autowiredobtiene el proxy (de hecho, fallará si inyecta enMyBeanBlugar del tipo de interfaz). No sé por qué Spring te permite hacergetBean(MyBeanB.class)con INTERFACES.@Transactional. Con@Autowired MyBeanBInterfaceproxies y con ámbito, Spring inyectará el objeto proxy.getBean(MyBeanB.class)Sin embargo, si solo lo hace , Spring no devolverá el proxy, devolverá el bean de destino.