前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >aop:scoped-proxy

aop:scoped-proxy

作者头像
MickyInvQ
发布2021-10-13 17:48:31
4870
发布2021-10-13 17:48:31
举报
文章被收录于专栏:InvQ的专栏InvQ的专栏

文章目录

aop:scoped-proxy

此配置一般是这样使用:

代码语言:javascript
复制
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

对于ref属性,只会在userManager初始化时注入一次。这会造成什么问题呢?以session的Scope为例,因为只会注入一次,所以,userManager引用的始终是同一个userPreferences对象,即使现在可能已经过时了。此配置便可以使userManager引用的其实是一个对代理的引用,所以可以始终获取到最新的userPreferences。

其作用和注解@ScopedProxy相同。

其解析由ScopedProxyBeanDefinitionDecorator完成,类图:

在这里插入图片描述
在这里插入图片描述

解析

入口

从类图可以看出,ScopedProxyBeanDefinitionDecorator和之前的解析器都不同,它的调用入口不同以往:

DefaultBeanDefinitionDocumentReader.processBeanDefinition:

代码语言:javascript
复制
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
         // 装饰
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    }
}

BeanDefinitionParserDelegate.decorateIfRequired:

代码语言:javascript
复制
public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(node);
    if (!isDefaultNamespace(namespaceUri)) {
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver()
            .resolve(namespaceUri);
        if (handler != null) {
            return handler.
                decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
        }
    }
    return originalDef;
}

一目了然。

这么做(装饰)的原因就是此标签是用在bean内部的,从decorate的方法签名可以看出,第二个便是父(bean)BeanDefinition,所以叫做装饰。

装饰

代码语言:javascript
复制
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
    boolean proxyTargetClass = true;
    if (node instanceof Element) {
        Element ele = (Element) node;
        if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
            proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
        }
    }
    BeanDefinitionHolder holder =
            ScopedProxyUtils.
            createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
    String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());
    // 空实现
    parserContext.getReaderContext().fireComponentRegistered(
            new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));
    return holder;
}

核心便是createScopedProxy方法,其源码较长,但是这个套路之前见识过了,就是一个偷天换日: 创建一个新的BeanDefinition对象,beanName为被代理的bean的名字,被代理的bean名字为scopedTarget.原名字。被代理的bean扔将被注册到容器中。

新的BeanDefintion的beanClass为ScopedProxyFactoryBean,其类图:

在这里插入图片描述
在这里插入图片描述

代理生成

入口便是setBeanFactory方法:

代码语言:javascript
复制
@Override
public void setBeanFactory(BeanFactory beanFactory) {
    ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
    this.scopedTargetSource.setBeanFactory(beanFactory);
    ProxyFactory pf = new ProxyFactory();
    pf.copyFrom(this);
    pf.setTargetSource(this.scopedTargetSource);

    Class<?> beanType = beanFactory.getType(this.targetBeanName);
    if (!isProxyTargetClass() || beanType.isInterface() || 
        Modifier.isPrivate(beanType.getModifiers())) {
         // JDK动态代理可用的接口
        pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
    }
    // Add an introduction that implements only the methods on ScopedObject.
    ScopedObject scopedObject = new DefaultScopedObject
        (cbf, this.scopedTargetSource.getTargetBeanName());
    pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
    // Add the AopInfrastructureBean marker to indicate that the scoped proxy
    // itself is not subject to auto-proxying! Only its target bean is.
    pf.addInterface(AopInfrastructureBean.class);
    this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}

这个套路上面也见过了。

Advisor

核心的拦截逻辑是通过DelegatingIntroductionInterceptor来完成的,其类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWN9uNvt-1632905556648)(images/DelegatingIntroductionInterceptor.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWN9uNvt-1632905556648)(images/DelegatingIntroductionInterceptor.jpg)]

AdvisedSupport.addAdvice方法将其转化为Advisor:

代码语言:javascript
复制
@Override
public void addAdvice(int pos, Advice advice) throws AopConfigException {
    if (advice instanceof IntroductionInfo) {
        // We don't need an IntroductionAdvisor for this kind of introduction:
        // It's fully self-describing.
        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
    } else if (advice instanceof DynamicIntroductionAdvice) {
        // We need an IntroductionAdvisor for this kind of introduction.
    } else {
        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
    }
}

显然,DelegatingIntroductionInterceptor被包装为DefaultIntroductionAdvisor对象。

DelegatingIntroductionInterceptor到底是个什么东西呢?这其实就引出了Spring的Introduction(引入)概念。

引入

通常意义上的Spring AOP一般是在方法层面上进行逻辑的改变,而引入指的是在不修改类源码的情况下,直接为一个类添加新的功能。下面是一个引入使用的例子:

SpringAOP中的IntroductionInterceptor

例子

自定义Scope

为了便于测试,我们定义一个生存周期仅仅在于一次调用的Scope,源码:

代码语言:javascript
复制
public class OneScope implements Scope {

    private int index = 0;

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        System.out.println("get被调用");
        return new Student("skywalker-" + (index++), index);
    }
    //忽略其它方法
}

将其注册到容器中,有两种方法:

配置

此时就可以使用我们自己的Scope了:

代码语言:javascript
复制
<bean class="base.SimpleBean" id="simpleBean">
    <property name="student" ref="student" />
</bean>

<bean id="student" class="base.Student" scope="one">
    <aop:scoped-proxy />
</bean>

测试

执行以下代码:

代码语言:javascript
复制
SimpleBean simpleBean = context.getBean(SimpleBean.class);
System.out.println(simpleBean.getStudent().getName());
System.out.println(simpleBean.getStudent().getName());

可以看到以下输出:

代码语言:javascript
复制
get被调用
skywalker-0
get被调用
skywalker-1

可以得出结论: 当调用被代理的bean的方法时才会触发Scoped的语义,只是获得其对象(getStudent)没有效果

原理

doGetBean

从根本上来说在于AbstractBeanFactory.doGetBean,部分源码:

代码语言:javascript
复制
//scope非prototype和Singleton
else {
    String scopeName = mbd.getScope();
    final Scope scope = this.scopes.get(scopeName);
    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            beforePrototypeCreation(beanName);
            try {
                return createBean(beanName, mbd, args);
            }
            finally {
                afterPrototypeCreation(beanName);
            }
        }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}

scopes是BeanFactory内部的一个 LinkedHashMap<String, Scope>类型的对象。scope.get实际上调用的就是我们的OneSocpe的get方法,没有用到ObjectFactory。

所以,每调用一次getBean,就会导致一个新的Student被创建并返回

代理子类

还有一个关键的问题,从上面可以知道SimpleBean内部的student引用其实是一个CGLIB代理子类的对象,那么当调用这个代理对象的相应方法(比如getName)时,是怎样导致Student重新创建(或是getBean被调用)的?

CallbackFilter & Callback

必须首先理解下CGLIB的这两个概念。

Callback

Callback是Cglib所有自定义逻辑(增强)的共同接口

其简略类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTYZ3172-1632905556649)(images/Callback.jpg)]

CallbackFilter

在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。

jdk并不支持这么搞,只支持设置一个InvocationHandler处理(拦截)所有的方法。其类图:

在这里插入图片描述
在这里插入图片描述

Cglib的Enhancer可以指定一个Callback数组,而accept方法的返回值是一个int值,其实就是Callback数组的下标,这样便达到了指定回调逻辑的目的。

参考:

CGLIB介绍与原理

回调

一般的方法使用的是DynamicAdvisedInterceptor作为回调逻辑,其intercept关键源码:

代码语言:javascript
复制
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
    Object target = getTarget();
}

target就是被代理对象。

getTarget:

代码语言:javascript
复制
protected Object getTarget() throws Exception {
    return this.advised.getTargetSource().getTarget();
}

TargetSource前面说过了,默认是SimpleBeanTargetSource:

代码语言:javascript
复制
@Override
public Object getTarget() throws Exception {
    return getBeanFactory().getBean(getTargetBeanName());
}

至此,真相大白。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-10-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • aop:scoped-proxy
    • 解析
      • 入口
      • 装饰
    • 代理生成
      • Advisor
      • 引入
    • 例子
      • 自定义Scope
      • 配置
      • 测试
    • 原理
      • doGetBean
      • 代理子类
      • CallbackFilter & Callback
      • 回调
相关产品与服务
云顾问
云顾问(Tencent Cloud Smart Advisor)是一款提供可视化云架构IDE和多个ITOM领域垂直应用的云上治理平台,以“一个平台,多个应用”为产品理念,依托腾讯云海量运维专家经验,助您打造卓越架构,实现便捷、灵活的一站式云上治理。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档