`
agapple
  • 浏览: 1595542 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

spring的auto-proxy自动代理(融合机制实现)

    博客分类:
  • java
阅读更多

背景

最近在实施并行加载,遇到一个问题: 重复代理,或者说是两次cglib代理。

主要是并行加载技术本身是采用了cglib+拦截的技术进行控制,所以势必会要求进行一次代理配置那

1. 如果需要代理的原始对象已经是一个cglib代理后的对象,比如性能监控,日志记录等等。

2. 其他同事在做的自动路由,按需加载都会要求进行一次cglib代理

 

如何平衡多次代理的问题,就冒出来了。

思路

接近于spring的autoProxyCretor的一套机制,利用了BeanPostProcessor,就是在bean的生命周期上做点文章。

 

spring默认提供的几种auto-proxy: 

 

  • BeanNameAutoProxyCreator   :  可以配置需要被进行auto-proxy的bean names列表,它控制的是需要代理的bean列表
  • InfrastructureAdvisorAutoProxyCreator  
  • DefaultAdvisorAutoProxyCreator :  将对应匹配的advisor,自动添加到spring的bean。它控制的是advisor的匹配,所有的bean都会被自动代理

再思考一下我自己的需求:
1.  允许和BeanNameAutoProxyCreator指定对应的bean names和inteceptorNames,而不是自动代理所有的bean。

 

2.  如果原始对象是proxyFactoryBean,配置的并行加载拦截器是基于同一个proxyFactoryBean
3.  多次的融合机制,是可以进行合并处理。 因为不同的框架会自定义配置一份代理拦截,需要将两份拦截器进行合并处理。

针对该需求的解决方案:
1. 继续引用auto-proxy的那套机制,基于BeanPostProcessor在bean的生命周期初始化的最后一环节进行切入。
切入代码:
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        // 不做处理
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            return wrapIfNecessary(bean, beanName, cacheKey);
        }

        return bean;
    }
2.  识别bean的对象类型
*  如果原先的bean是proxyFactoryBean,则将原先定义的inteceptorNames和融合的配置进行合并
*  其他的bean进行代理对象构造。
3.  多次融合的拦截器合并,并不是真正的配置项的合并(那样成本太大,需要在spring解析时插入点代码)。换种思路就是将多次融合做为一个chian的处理,前一个融合会创建一个ProxyFactoryBean,后一个融合识别出是一个ProxyFactoryBean就会在原先的基础上在做融合,这样就解决了多次融合的合并问题。

对应的核心代码:(也就没几行)
if (ProxyFactoryBean.class.isAssignableFrom(bean.getClass())) {
    ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
    String[] orignInterceptorNames = getInterceptorFromProxyFactoryBean(proxyFactoryBean);
    String[] newInterceptorNames = new String[orignInterceptorNames.length
      + interceptorNames.length];
    if (applyCommonInterceptorsFirst) {// 如果是true,则将Auto-proxy的拦截器定义到最前面
        // 构造新的的拦截器列表
        System.arraycopy(interceptorNames, 0, newInterceptorNames, 0, interceptorNames.length);
        System.arraycopy(orignInterceptorNames, 0, newInterceptorNames, interceptorNames.length,
     orignInterceptorNames.length);
    } else {
        System.arraycopy(orignInterceptorNames, 0, newInterceptorNames, 0,
     orignInterceptorNames.length);
        System.arraycopy(interceptorNames, 0, newInterceptorNames, orignInterceptorNames.length,
     interceptorNames.length);
    }
    // 重新设置新的inteceptorNames
    proxyFactoryBean.setInterceptorNames(newInterceptorNames);
    return proxyFactoryBean;
} else {
    // 如果是单例,对应的代理bean对象为同一个
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setBeanFactory(beanFactory);
    proxyFactoryBean.setBeanClassLoader(proxyClassLoader);
    proxyFactoryBean.setInterceptorNames(interceptorNames);
    proxyFactoryBean.copyFrom(this); // 拷贝对应的一些Proxy config
    proxyFactoryBean.setTarget(bean);
    return proxyFactoryBean.getObject();
}
 
对应的配置文件示例:
     <!-- 多例测试 -->
     <bean id="asyncLoadTestServiceForCompsitePrototype" class="com.agapple.asyncload.domain.AsyncLoadTestServiceImpl" scope="prototype" />
     <!-- 单例测试 -->
     <bean id="asyncLoadTestServiceForCompsiteSingleton" class="com.agapple.asyncload.domain.AsyncLoadTestServiceImpl" scope="singleton" />
     <!-- 原本是ProxyFactoryBean -->
     <bean id="logInteceptor" class="com.agapple.asyncload.spring.LogInteceptor" />
     <bean id="asyncLoadTestServiceForCompsiteFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
    	<property name="interceptorNames">
    		<list>
    			<value>logInteceptor</value>
    		</list>
    	</property>
    	<property name="targetName" value="asyncLoadTestService" />
    	<property name="proxyTargetClass" value="true" /> <!-- 强制申明为cglib代理 -->
     </bean>
     
     <!-- 第一次融合代理 -->
     <bean class="com.agapple.asyncload.impl.spring.CompositeAutoProxyCreator">
        <property name="beanNames">
        	<list>
        		<value>asyncLoadTestServiceForCompsitePrototype</value>
        		<value>asyncLoadTestServiceForCompsiteSingleton</value>
        		<value>asyncLoadTestServiceForCompsiteFactoryBean</value> <!-- 代理的对象原本已经是一个proxyFactoryBean的final cglib-->
        	</list>
        </property>
        <property name="interceptorNames">
        	<list>
	            <value>asyncLoadInterceptor</value>
        	</list>
        </property>
     </bean>
     <!-- 多次的代理会只作用于一个代理对象 -->
     <bean class="com.agapple.asyncload.impl.spring.CompositeAutoProxyCreator">
     	<property name="applyCommonInterceptorsFirst" value="true"/>
        <property name="beanNames">
        	<list>
        		<value>asyncLoadTestServiceForCompsitePrototype</value>
        		<value>asyncLoadTestServiceForCompsiteSingleton</value>
        		<value>asyncLoadTestServiceForCompsiteFactoryBean</value>  <!-- 代理的对象原本已经是一个proxyFactoryBean的final cglib-->
        	</list>
        </property>
        <property name="interceptorNames">
        	<list>
    			<value>logInteceptor</value>
    		</list>
        </property>
     </bean>

代码

配置文件:https://code.google.com/p/asyncload/source/browse/trunk/src/test/resources/asyncload/applicationContext.xml,查找对应的Compsite

 

最后

在查找具体的解决方案是一个比较曲折的过程,基本上把ProxyFactoryBean机制,auto-proxy机制代码实现都看了一遍。

过程1 : 原先是寄希望于spring可以提供类似的一个global advisor的概念,每个ProxyFatoryBean除了自己配置的inteceptorNames的拦截器之外,还回从一些global的定义中获取一些大家公用的advisor。找了一圈发现没有,曾经的GlobalAdvisorAdapterRegistry给了我一些希望,最后发现是一个绝望的内容。 

 

过程2:在看AutoProxyCreator那套机制时,其实它预留了一些扩展点,主要是根据bean name获取对应的auto-proxy信息。bean name到具体的bean的处理,因为是在BeanPostProcessor,处于getBean()生命周期的最后一步,如果此时进行this.beanFactory.getBean()就是一个死循环,此路不通,所以原先的auto-proxy相关的扩展点基本走不同。

 

过程3: 在2走不通后,一直在琢磨是否可以通过直接处理生成后的object,获取原始object对应的ProxyFactoryBean.getAdvsisor()方法。后来看了半天代码,发现也是条思路,因为proxyFactoryBean生成的proxy对象完全和ProxyFactoryBean无关,也就不会有getAdvisor()方法,因为生成的cglib代理或者jdk代理,cglib代理到还有对advised的引用持有,有办法通过反射获取。而jdk那个压根没则,所以有是一条思路。

 

过程4: 2,3都走不通了,今天在debug另一个问题时,发现BeanPostProcessor的相应callback方法中传递的bean object居然是原生的ProxyFactoryBean,也就是说未进行getObject()调用之前的原始对象。大喜,总于找到突破口,最后就是自己实现了一套auto-proxy机制,因为原先的扩展点不支持传递bean object对象。

 

最后只能说自己对spring的一些机制还不够了解,需要持续加强。发现去扩展spring的一些点,是学习spring最快的一种方式,找扩展点的过程是对spring的一个总体把握的过程


悟了3天的问题,总于有了解决方案,仅以此文几年一下我那3天死去的脑细胞!!

分享到:
评论

相关推荐

    Node.js-http-proxy-middleware用于把请求代理转发到其他服务器的中间件

    4. **错误处理**:当代理过程中发生错误时,`http-proxy-middleware` 提供了错误处理机制,可以捕获并处理这些错误,避免应用程序崩溃。 5. **浏览器同步**:对于前端开发环境,`http-proxy-middleware` 可以与 ...

    influx-proxy-2.5.7-linux-amd64.tar.gz

    本文将详细探讨InfluxDB的核心特性和Influx-proxy的功能,并结合"Influx-proxy-2.5.7-linux-amd64.tar.gz"这个压缩包文件,解析其在Linux AMD64平台上可能的部署与使用。 首先,InfluxDB以其高效的存储和查询机制而...

    sharding-proxy实现分表

    Sharding-Proxy 是一个数据库中间件,它扮演着数据库代理的角色,位于应用程序与真实数据库之间。其主要功能是实现数据分片逻辑,使得应用程序无需感知复杂的分片规则,即可透明地访问分片后的数据库。Sharding-...

    Petalinux dma-proxy.已测可用,注意参数一致

    `dma-proxy.c`、`dma-proxy-test.c`、`dma-proxy.h` 这些文件名暗示了它们在实现或测试dma-proxy的功能中起到的作用: 1. `dma-proxy.c`: 这通常是一个C语言源代码文件,其中包含了dma-proxy的主要实现逻辑。它可能...

    node-https-proxy-agent, HTTPS端点的HTTP代理 `http.Agent` 实现.zip

    node-https-proxy-agent, HTTPS端点的HTTP代理 `http.Agent` 实现 https-proxy-agent HTTPS的HTTP代理 http.Agent 实现 这个模块为连接到指定的HTTP或者HTTPS代理服务器提供了 http.Agent 实现,并且可以与内置的...

    ios-webkit-debug-proxy-1.9.0-win64-bin

    ios-webkit-debug-proxy-1.9.0-win64-bin

    docker-letsencrypt-nginx-proxy-companion-examples, 结合 Docker gen和 letsencrypt Nginx 代理伙伴的示例.zip

    docker-letsencrypt-nginx-proxy-companion-examples, 结合 Docker gen和 letsencrypt Nginx 代理伙伴的示例 docker-letsencrypt-nginx-proxy-companion-examples这个库是使用 nginx代理插件, docker gen和 docker-...

    kube-proxy-temp-1.16.8.yaml

    aws-kube-proxy1.16.8版本示例文件

    kubernetes 使用cilium 网络插件 替换kube-proxy

    在Kubernetes环境中,传统的服务发现和网络代理方案是通过kube-proxy来实现的,它提供了iptables、ipvs等不同的工作模式。然而,随着云原生应用的发展,对网络性能、安全性和可观测性的需求日益增强,这催生了Cilium...

    使用mysql-proxy实现mysql读写分离

    其中,`--proxy-backend-addresses` 指定的是主数据库的地址和端口,而 `--proxy-read-only-backend-addresses` 指定的是从数据库的信息。 5. **启动mysql-proxy**:通过上述命令启动mysql-proxy后,它将会根据...

    smiley-http-proxy-servlet-1.7.jar

    java运行依赖jar包

    browsermob-proxy-2.1.4.zip

    browsermob-proxy-2.1.4,与selenium一起进行爬虫,获取network中的链接资源

    Midjourney-Proxy-Plus-3.8.6免授权版

    MJ-PROXY-PLUS-3.8.6免授权,功能和商用版本相同,最新版本,由于MJ官网更新,老版本已无法使用MJ-PROXY-PLUS,安装方法和老版本一样,详细可参考MJ-PROXY-PLUS配置教程。 使用自己的MJ账号,支持GPT3.5自动翻译等。...

    mysql-proxy 安装配置

    - `--proxy-address`: 设置代理服务监听的IP和端口,默认为本地的4040端口。 - `--proxy-read-only-backend-addresses`: 设置只读数据库服务器的地址,用于实现读写分离。 - `--proxy-backend-addresses`: 主...

    mysql-proxy-0.8.5-windows-x86-32bit

    `mysql-proxy-0.8.5-windows-x86-34bit` 是 MySQL Proxy 的一个特定版本,适用于32位Windows操作系统。 MySQL Proxy 的主要功能和优势包括: 1. **透明代理**:MySQL Proxy 可以悄无声息地插入到客户端和服务器...

    前端开源库-superagent-proxy

    `superagent-proxy`是基于流行的`superagent`库的一个扩展,它为`superagent`提供了代理支持,从而帮助开发者绕过跨域问题,实现更加灵活的数据请求。 ### 1. 超级代理superagent-proxy简介 `superagent-proxy`是`...

    charles-proxy-4.5.6-win64

    charles 解压就能用 charles-proxy-4.5.6-win64

    Spring Boot 使用 ProxyServlet 代理并统一响应

    通过这种方式,我们可以利用Spring Boot和`ProxyServlet`实现反向代理,使得客户端无需关心后端微服务的具体部署情况,同时还能对所有服务的请求和响应进行集中管理,提高系统的可维护性和扩展性。在实际项目中,还...

    php-http-proxy, 在基于workerman的PHP中,HTTP代理.zip

    php-http-proxy, 在基于workerman的PHP中,HTTP代理 php-http-proxy基于workerman的PHP编写的HTTP代理。启动。php start.php 启动 -d停止停止。php start.php 停止状态。php start.php 状态其他链接https

Global site tag (gtag.js) - Google Analytics