• 学习AOP之深入一点Spring Aop


    上一篇《学习AOP之认识一下SpringAOP》中大体的了解了代理、动态代理及SpringAop的知识。因为写的篇幅长了点所以还是再写一篇吧。接下来开始深入一点Spring aop的一些实现机制。

    上篇中最后有那段代码使用了一个ProxyFactory类来完成代理的工作,从而实现了Aop的Around Advice,代码如下:

    package aop.demo;
    
    import org.springframework.aop.framework.ProxyFactory;
    
    public class ClientCode {
    
    	public static void main(String[] args) {
    		ProxyFactory proxyFactory = new ProxyFactory();     // 创建代理工厂
            proxyFactory.setTarget(new SayImpl());         // 射入目标类对象
            proxyFactory.addAdvice(new SayImplAroundAdvice());
            ISay say = (ISay) proxyFactory.getProxy();
            say.say();
    	}
    
    }
    

    那么接下来就聊聊ProxyFactory吧,看看它都干了些啥。

    1、ProxyFactory的奥秘

    继续看上面的代码只用了5行,这里面意思也非常明确,只有在第4行的时候有一个getProxy的方法并转换为ISay接口。看来代理对象的来源可以从它入手了。

    public Object getProxy() {
    	return createAopProxy().getProxy();
    }
    

    只不过代码只有一行,调用的是一个createAopProxy()的方法返回了AopProxy类型的对象,再通过AopProxy的getProxy来获得了代理对象。

    那么只好再看一下createAopProxy()是啥样子咯:

    protected final synchronized AopProxy createAopProxy() {
    	if (!this.active) {
    		activate();
    	}
    	return getAopProxyFactory().createAopProxy(this);
    }
    

    这个方法在org.springframework.aop.framework.ProxyCreatorSupport这个类里面,ProxyFactory是继承了它的。这个类字面意思就是一个代理创建的支持类。

    但是看了createAopProxy方法后又郁闷了,还有一个getAopProxyFactory(),真是一层套一层啊。当然这里还是需要从类的层次结构来看会清楚一些,只是我主要是看它是怎么生成代理对象的,设计上的事情回头再看。

    //这个方法访问了一个内部成员
    public AopProxyFactory getAopProxyFactory() {
    	return this.aopProxyFactory;
    }
    
    //再看aopProxyFactory其实是在构造函数里创建的
    public ProxyCreatorSupport() {
    	this.aopProxyFactory = new DefaultAopProxyFactory();
    }
    

    这里看到了DefaultAopProxyFactory这个工厂类,好了,也就是它才是创建代理的真正人物。那么这里接着createAopProxy直接看代码:

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    	if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    		Class targetClass = config.getTargetClass();
    		if (targetClass == null) {
    			throw new AopConfigException("TargetSource cannot determine target class: " +
    					"Either an interface or a target is required for proxy creation.");
    		}
    		if (targetClass.isInterface()) {
    			return new JdkDynamicAopProxy(config);
    		}
    		return CglibProxyFactory.createCglibProxy(config);
    	}
    	else {
    		return new JdkDynamicAopProxy(config);
    	}
    }
    

    从这里可以看到有两种AopProxy的代理:Cglib和Jdk。它们俩的区别:

    • Cglib创建代理慢但执行快而且可以代理类
    • Jdk创建代理快但执行慢,只可以代理接口

    顺着ClientCode这个代码肯定是用JdkDynmaicAopProxy,最终proxyFactory.getProxy()调用的是JdkDynmaicAopProxy的实例。那好就看一下JdkDynmaicAopProxy中getProxy都做了啥吧:

    public Object getProxy() {
    	return getProxy(ClassUtils.getDefaultClassLoader());
    }
    
    public Object getProxy(ClassLoader classLoader) {
    	if (logger.isDebugEnabled()) {
    		logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    	}
    	Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
    
    

    好了,这里看到了熟悉的代码,即通过Proxy.newProxyInstance生成代理对象交给调用者。Spring通过抽象工厂模式设计了两种代理方法。

    2、再看看ProxyFactroyBean

    但是在xml配置的时候用的并不是ProxyFactory,而是ProxyFactroyBean。有点奇怪,为什么会有两个类呢?先来看看ProxyFactoryBean:

    public class ProxyFactoryBean extends ProxyCreatorSupport
    		implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
    
    

    哦哟,原来这家伙继承了FactoryBean,好了,原来它借且FactoryBean提供了一个中间层。因为Spring如果发现Ioc创建的对象带有FactoryBean时会调用FactoryBean的getObject方法来获得对象。有了这个它在Ioc容器里获得的便是getObject返回的代理对象,而不是返回ProxyFactoryBean本身,这样才能注入嘛。所以ProxyFactoryBean主要是使用在Ioc容器里的。

    具体getObject里实现的原理和ProxyFactory类似,主要还是和ProxyCreatorSupport有关,ProxyCreatorSupport封装了这部分逻辑,所以可以复用。

    3、进入切面的小世界

    写了这么多发现我还是停留在“代理”的层面,但是AOP难道仅仅止于此吗?当然不是,比如ISay接口增加一个noaop方法,这个方法我就不希望被代理,那怎么做呢?

    先调整一下例子代码,增加noaop方法。

    public interface ISay {
    	void say();
    	void noaop();
    }
    
    public class SayImpl implements ISay{
    
    	public void say() {
    		System.out.print("我是5207.");
    	}
    
    	public void noaop() {
    		System.out.println("别aop我");
    	}
    
    }
    
    

    好了,然后增加一个切面,让这个切面去做分辨,以xml配置为例,下面对spring.xml做一下修改:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 声明被代理的目标对象 -->
        <bean id="sayImpl" class="aop.demo.SayImpl"></bean>
        <!-- 声明用于增强的拦截器对象 -->
        <bean id="sayImplAroundAdvice" class="aop.demo.SayImplAroundAdvice"></bean>
    
        <!-- 配置一个切面 -->
        <bean id="sayAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice" ref="sayImplAroundAdvice"/>            <!-- 增强 -->
            <property name="pattern" value="aop.demo.SayImpl.s.*"/> <!-- 切点(正则表达式) -->
        </bean>   
        
        <!-- 声明代理对象 -->
        <bean id="sayProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="interfaces" value="aop.demo.ISay"/>            <!-- 这个就是被代理的接口 -->
            <property name="target" ref="sayImpl"/>                        <!-- 这个就是被代理的对象 -->
            <property name="interceptorNames" value="sayAdvisor"/>         <!-- 这个就是代理的增强器 --> 
        </bean>
    </beans>
    

    上面xml中新增了一个切面sayAdvisor,它的作用是以正则表达式的规则来选择是否aop。比如本例子的意思是只代理SyaImpl的s开头的方法。那么noaop方法应该是不会被代理啦。

    client代码也修改一下:

    public class Client {
    
    	@SuppressWarnings("resource")
    	public static void main(String[] args) {
    		ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
    		ISay say = (ISay)context.getBean("sayProxy");
    		say.say();
    		say.noaop();//增加noaop的调用,看看会不会被代理
    	}
    
    }
    
    

    执行的结果如下:

    大家好:我是5207.希望大家多多点赞.
    别aop我

    这就发现advisor已经有效果啦。

    4、自动完成对切面的代理

    之前的各种代码都带有一个问题,就是client最终调用的时候都是获得的代理对象,如下面的代码:

    ISay say = (ISay)context.getBean("sayProxy");
    

    那在做Aop增强的时候改老的代码,这样就失败了Aop的意义了。所以没有办法可以自动就完成这个操作,只要配置好就可以透明的完成这个代理过程呢?

    spring提供了自动代理的实现,对spring.xml做一下调整:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 声明被代理的目标对象 -->
        <bean id="sayImpl" class="aop.demo.SayImpl"></bean>
        <!-- 声明用于增强的拦截器对象 -->
        <bean id="sayImplAroundAdvice" class="aop.demo.SayImplAroundAdvice"></bean>
    
        <!-- 配置一个切面 -->
        <bean id="sayAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice" ref="sayImplAroundAdvice"/>            <!-- 增强 -->
            <property name="pattern" value="aop.demo.SayImpl.s.*"/> <!-- 切点(正则表达式) -->
        </bean>   
       
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
            <property name="optimize" value="true"/>
        </bean>
    </beans>
    

    在此增加一个DefaultAdvisorAutoProxyCreator,原先的代理就不需要啦。然后再看一下客户端调用直接改成调用SayImpl,看看能不能实现代理:

    package aop.demo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Client {
    
    	@SuppressWarnings("resource")
    	public static void main(String[] args) {
    		ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
    		ISay say = (ISay)context.getBean("sayImpl");
    		say.say();
    		say.noaop();
    	}
    }
    

    输出:

    大家好:我是5207.希望大家多多点赞.
    别aop我

    效果达成,只不过这里的关键是DefaultAdvisorAutoProxyCreater是怎么做的呢?看了看代码发现其主要是借助Ioc容器在初始化对象时完成的代理的自动生成的。在BeanPostProccer的postProcessAfterInitialization过程中完成了对代理的生成。具体的原理可以参考引用中的文章,太累了不写了。

    参考及引用
    死磕Spring AOP系列2:剖析Bean处理器之BeanNameAutoProxyCreator

    注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
    若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!
    http://www.cnblogs.com/5207

  • 相关阅读:
    Python入门-函数进阶
    Python入门-初始函数
    Leetcode300. Longest Increasing Subsequence最长上升子序列
    Leetcode139. Word Break单词拆分
    Leetcode279. Perfect Squares完全平方数
    Leetcode319. Bulb Switcher灯泡开关
    Leetcode322. Coin Change零钱兑换
    二叉树三种遍历两种方法(递归和迭代)
    Leetcode145. Binary Tree Postorder Traversal二叉树的后序遍历
    Leetcode515. Find Largest Value in Each Tree Row在每个树行中找最大值
  • 原文地址:https://www.cnblogs.com/5207/p/6055152.html
  • Copyright © 2020-2023  润新知