当前位置: 首页 > 面试题库 >

Spring中的作用域代理是什么?

薛望
2023-03-14
问题内容

正如我们所知道Spring使用代理来增加功能(@Transactional@Scheduled举例)。有两种选择-
使用JDK动态代理(该类必须实现非空接口),或使用CGLIB代码生成器生成子类。我一直认为proxyMode允许我在JDK动态代理和CGLIB之间进行选择。

但是我能够创建一个示例,说明我的假设是错误的:

情况1:

单身人士:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;

    public void foo() {
        System.out.println(myBeanB.getCounter());
    }

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

原型

@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;
    }
}

主要:

MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

输出:

constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e

在这里我们可以看到两件事:

  1. MyBeanB只实例化了 一次
  2. 为了添加的@Transactional功能MyBeanB,Spring使用了CGLIB。

情况2:

让我更正MyBeanB定义:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

在这种情况下,输出为:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2

在这里我们可以看到两件事:

  1. MyBeanB被实例化 3 次。
  2. 为了添加的@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
 */

但是我不清楚。

更新资料

情况3:

我研究了另一种情况,其中从中提取了接口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 {

在这种情况下,输出为:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92

在这里我们可以看到两件事:

  1. MyBeanB被实例化 3 次。
  2. 为了添加的@Transactional功能MyBeanB,Spring使用了JDK动态代理。

问题答案:

@Transactional行为生成的代理的作用与作用域代理不同。

@Transactional代理是包装特定bean来添加会话管理行为的代理。所有方法调用将在委派给实际bean之前和之后执行事务管理。

如果您进行说明,它看起来像

main -> getCounter -> (cglib-proxy -> MyBeanB)

就我们的目的而言,您基本上可以忽略其行为(删除@Transactional该行为,除了没有cglib代理外,您应该会看到相同的行为)。

@Scope代理行为有所不同。该文档指出:

[…]您需要注入一个代理对象,该对象公开与范围对象相同的公共接口, 但也可以从相关范围 (例如HTTP请求)中 检索实际目标对象
,并将方法调用委托给实际对象。

Spring真正在做的是为代表代理的工厂类型创建一个singleton bean定义。但是,相应的代理对象会在每次调用时在上下文中查询实际的bean。

如果您进行说明,它看起来像

main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)

由于MyBeanB是原型Bean,因此上下文将始终返回新实例。

就此答案而言,假设您MyBeanB直接使用

MyBeanB beanB = context.getBean(MyBeanB.class);

实际上,这是Spring为满足@Autowired注射目标所做的工作。

在第一个示例中

@Service
@Scope(value = "prototype")
public class MyBeanB {

您声明原型Bean定义(通过注释)。@Scope有一个proxyMode元素

指定是否应将组件配置为作用域代理,如果是,则指定代理是基于接口还是基于子类。

默认值为ScopedProxyMode.DEFAULT,这通常表示除非在组件扫描指令级别配置了其他默认值,否则
不应创建任何作用域代理

因此,Spring不会为生成的bean创建作用域代理。您使用

MyBeanB beanB = context.getBean(MyBeanB.class);

现在,您具有对MyBeanB由Spring创建的新对象的引用。就像任何其他Java对象一样,方法调用将直接转到引用的实例。

如果getBean(MyBeanB.class)再次使用,Spring将返回一个新实例,因为bean定义是针对原型bean的。您没有这样做,因此所有方法调用都转到同一个对象。

在第二个示例中

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

您声明通过cglib实现的作用域代理。当从Spring请求这种类型的bean时

MyBeanB beanB = context.getBean(MyBeanB.class);

Spring知道这MyBeanB是一个作用域代理,因此返回一个满足API MyBeanB(即实现其所有公共方法)的代理对象,该API
内部知道如何MyBeanB为每个方法调用检索类型的实际bean 。

尝试跑步

System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));

这将返回true暗示Spring正在返回单例代理对象(而不是原型Bean)的事实。

在代理实现内部的方法调用上,Spring将使用一个特殊的getBean版本,该版本知道如何区分代理定义和实际的MyBeanBbean定义。这将返回一个新MyBeanB实例(因为它是原型),Spring会通过反射(经典Method.invoke)将方法调用委托给它。

您的第三个示例与您的第二个示例基本相同。



 类似资料:
  • 众所周知,Spring使用代理添加功能(例如和)。有两种选择--使用JDK动态代理(类必须实现非空接口),或者使用CGLIB代码生成器生成子类。我一直认为proxyMode允许我在JDK动态代理和CGLIB之间进行选择。 但我能创造一个例子,说明我的假设是错误的: 单身: 这里我们可以看到两点: 只实例化了一次。 要为添加功能,Spring使用了cglib。 让我更正定义: 实例化了3次。 要为添

  • 我在spring中尝试使用会话范围的bean时出错,错误是: 我的配置是:网络。xml: 豆子: 我一直在寻找这个问题,我所找到的是添加以下行web.xml: 这解决了问题,但副作用是,我所有的@Scheduled注释方法都运行了两次,我认为这是因为spring创建了两个上下文。 阅读Spring文档它说: DispatcherServlet、RequestContextListener和Requ

  • 最近,我在用户代理中看到了许多来自带有gzip(gfe)的浏览器的请求。哪个浏览器在用户代理中使用它们。它的实际功能。以下示例: Mozilla/5.0(iPhone;CPU iPhone OS 12_3_1,如Mac OS X)AppleWebKit/605.1.15(KHTML,如Gecko)版本/12.1.1 Mobile/15E148 Safari/604.1,gzip(gfe)

  • 在Netty代理示例的中,方法的代码如下: 我的问题是的目的是什么?通道激活后需要发送一个空的TCP消息帧吗?

  • 主要内容:动态代理的区分,spring作用域,spring单例的线程安全问题,接口幂等性,AspectJ的使用,Ribbon负载均衡策略,AOP的使用场景,1.String StringBuffer StringBuilder的区别面试总结: 1.动态代理 2.spring作用域 动态代理的区分 JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对

  • 本文向大家介绍js跨域的原理是什么?相关面试题,主要包含被问及js跨域的原理是什么?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。