当前位置: 首页 > 知识库问答 >
问题:

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

万嘉石
2023-03-14

众所周知,Spring使用代理添加功能(例如@transactional@schedule)。有两种选择--使用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;
    }
}
@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. 要为mybeanb添加@transactional功能,Spring使用了cglib。

让我更正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. 要为mybeanb添加@transactional功能,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
 */
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. 要为mybeanb添加@transactional功能,Spring使用了JDK动态代理。

共有1个答案

袁英豪
2023-03-14

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

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

如果你举例说明,它看起来像

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

@scope代理的行为不同。文件说明:

[...]您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关作用域(如HTTP请求)检索真正的目标对象,并将方法调用委托到真正的对象上。

Spring真正做的是为代表代理的工厂类型创建一个单例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 { 

默认值为scopedproxymode.default,这通常表示除非在组件扫描指令级别配置了不同的默认值,否则不应创建作用域代理。

因此Spring没有为生成的bean创建一个有作用域的代理。您可以用

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

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

在第二个示例中,

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

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

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

Spring知道mybeanb是一个作用域代理,因此返回一个满足mybeanbAPI(即实现其所有公共方法)的代理对象,该对象内部知道如何为每个方法调用检索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之间进行选择。 但是我能够创建一个示例,说明我的假设是错误的: 情况1: 单身人士: 原型: 主要: 输出: 在这里我们可以看到两件事: 只实例化了 一次 。 为了添加的功能,

  • 问题内容: 有人可以解释什么是无作用域及其目的吗? 假设我有一个豆子 并说我没有将任何作用域bean n1注入到上述每个作用域中,然后我发现当实例化其父bean [r1 / s1 / a1]时,将为每个父bean实例化n1。 由于a1是应用程序作用域,因此a1中的作用域bean在整个a1中都不可用。直到不销毁s1并再次创建s1时,s1中的scope Bean才可用。 这是正确的吗? 以及使用它的目

  • 问题内容: 我想知道我什么时候应该在Spring中准确使用范围?我了解,如果需要Bean,则返回相同的对象实例。 那我们为什么要考虑呢? 通过示例进行解释将有助于您理解其必要性。 问题答案: 要明确简单的定义: 原型范围=每次注入/查找新对象时都会创建一个。每次都会使用new 。 单例范围=每次注入/查找相同对象时,都会返回该对象。在这里它将实例化一个实例,然后每次返回它。 原型bean是在使用时

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

  • Azure中的一些区域被称为EUAP区域,但我无法找到任何关于这意味着什么的定义。EUAP代表什么? https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.locationnames.centraluseuap?view=azure-网络 从上面的链接: Azure Cosmos DB服务中Azure美国中部EU

  • 主要内容:域名扩展每台服务或网站都有一个IP地址,域名可以理解为IP地址的一个别名,它是一种容易记住的别名。域名是在线地址的一部分,访问者将使用它来轻松找到您的网站。 例如,小牛知识库域名是:,它的IP地址是:,显然我们更容易记住,而不是IP地址: 。域名是独一无二的。 一旦您注册了,如果继续更新续费使用,其他人是不可以注册。 注册一个域名很容易,因为可以选择任何你想要的名字,但是一般选择对你的商业未来或你的博客网