zoukankan      html  css  js  c++  java
  • 【Spring注解驱动开发】困扰了我很久的AOP嵌套调用终于解决了!

    写在前面

    最近在分析Spring源码时,在同一个类中写了嵌套的AOP方法,测试时出现:Spring AOP在同一个类里自身方法相互调用时无法拦截。哎,怎么办?还能怎么办呢?继续分析Spring源码,解决问题呗。于是乎,有了本文。

    项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    问题阐述

    Spring AOP在同一个类里自身方法相互调用时无法拦截。比如下面的代码:

    public class SomeServiceImpl implements SomeService  {  
        public void someMethod()  {  
            someInnerMethod();  
        }  
        public void someInnerMethod(){  
        }  
    } 
    

    两个方法经过AOP代理,执行时都实现系统日志记录。单独使用someInnerMethod时,没有任何问题。但someMethod就有问题了。someMethod里调用的someInnerMethod方法是原始的,未经过AOP增强的。我们期望调用一次someMethod会记录下两条系统日志,分别是someInnerMethod和someMethod的,但实际上只能记录下someMethod的日志,也就是只有一条。在配置事务时也可能会出现问题,比如someMethod方法是REQUIRED,someInnerMethod方法是REQUIRES_NEW,someInnerMethod的配置将不起作用,与someMethod方法会使用同一个事务,不会按照所配置的打开新事务。

    问题分析

    由于java这个静态类型语言限制,最后想到个曲线救国的办法,出现这种特殊情况时,不要直接调用自身方法,而通过AOP代理后的对象。在实现里保留一个AOP代理对象的引用,调用时通过这个代理即可。例如下面的代码。

    //从beanFactory取得AOP代理后的对象  
    SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService");   
    
    //把AOP代理后的对象设置进去  
    someServiceProxy.setSelf(someServiceProxy);   
    
    //在someMethod里面调用self的someInnerMethod,这样就正确了  
    someServiceProxy.someMethod();  
    

    但这个代理对象还要我们手动set进来。有没有更好的方式解决呢?

    问题解决

    幸好SpringBeanFactory有BeanPostProcessor扩展,在bean初始化前后会统一传递给BeanPostProcess处理,繁琐的事情就可以交给程序了,代码如下,首先定义一个BeanSelfAware接口,实现了此接口的程序表明需要注入代理后的对象到自身。

    public class SomeServiceImpl implements SomeService,BeanSelfAware{  
    	//AOP增强后的代理对象  
        private SomeService self;
        //实现BeanSelfAware接口  
        public void setSelf(Object proxyBean){  
            this.self = (SomeService)proxyBean  
        }  
        public void someMethod(){  
            //注意这句,通过self这个对象,而不是直接调用的  
            someInnerMethod();
        }  
        public void someInnerMethod(){  
        }  
    }  
    

    再定义一个BeanPostProcessor,beanFactory中的每个Bean初始化完毕后,调用所有BeanSelfAware的setSelf方法,把自身的代理对象注入自身。

    public class InjectBeanSelfProcessor implements BeanPostProcessor  {     
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{ 
            if(bean instanceof BeanSelfAware){  
                System.out.println("inject proxy:" + bean.getClass());  
                BeanSelfAware myBean = (BeanSelfAware)bean;  
                myBean.setSelf(bean);  
                return myBean;  
            }  
            return bean;  
        }  
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{  
            return bean;  
        }  
    }
    

    最后,在BeanFactory配置中组合起来,只需要把BeanPostProcesser加进去就可以了,比平常多一行配置而已。

    <!-- 注入代理后的bean到bean自身的BeanPostProcessor... -->  
    <bean class=" org.mypackage.InjectBeanSelfProcessor"></bean>  
    
    <bean id="someServiceTarget" class="org.mypackage.SomeServiceImpl" />   
    
    <bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">  
        <property name="target">  
            <ref local="someServiceTarget" />  
        </property>  
        <property name="interceptorNames">  
            <list>  
                <value>someAdvisor</value>  
            </list>  
        </property>  
    </bean>  
    <!-- 调用spring的DebugInterceptor记录日志,以确定方法是否被AOP增强 -->  
    <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor" />  
    
    <bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
        <property name="advice">  
            <ref local="debugInterceptor" />  
        </property>  
        <property name="patterns">  
            <list>  
                <value>.*someMethod</value>  
                <value>.*someInnerMethod</value>  
            </list>  
        </property>  
    </bean>  
    

    这里的someService#someInnerMethod就表现出预期的行为了,无论怎样,它都是经过AOP代理的,执行时都会输出日志信息。

    注意事项

    用XmlBeanFactory进行测试需要注意,所有的BeanPostProcessor并不会自动生效,需要执行以下代码:

    XmlBeanFactory factory = new XmlBeanFactory(...);  
    InjectBeanSelfProcessor postProcessor = new InjectBeanSelfProcessor();  
    factory.addBeanPostProcessor(postProcessor);  
    

    好了,咱们今天就聊到这儿吧!别忘了给个在看和转发,让更多的人看到,一起学习一起进步!!

    项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    写在最后

    如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Spring注解驱动开发。公众号回复“spring注解”关键字,领取Spring注解驱动开发核心知识图,让Spring注解驱动开发不再迷茫。

    参考:iteye.com/blog/fyting-109236

  • 相关阅读:
    发布 Rafy .NET Standard 版本 Nuget 包
    使用 MarkDown & DocFX 升级 Rafy 帮助文档
    apache2服务器支持cgi功能
    百兆网口与千兆网口速率协商不成功
    ubuntu etho0 up cron
    linux 后台进程
    MySQL的事务性
    linux下visual studio code配置c++调试环境实例
    linux下visual studio code中gdb调试文件launch.json解析
    Zookeeper安装后,编译C client时报错"syntax error near unexpected token `1.10.2"
  • 原文地址:https://www.cnblogs.com/binghe001/p/13205873.html
Copyright © 2011-2022 走看看