Spring AOP学习(三) -- Spring AOP实现原理

前面已经对Spring AOP做了个简单介绍,今天来分析一下Spring AOP的原理 -- JDK和Cglib代理。

Spring AOP的原理分为三部分,概述、设计模式和实现,见下图:

AOP原理

1、原理概述:织入的时机分为三种,分别是:

1)编译期(AspectJ)

2)类加载时(AspectJ 5+)

3)运行时(Spring AOP )

        运行时织入是怎么实现的?通过代理对象来实现的,代理分为动态代理和静态代理,其中基于动态代理的实现分为两种,一种是基于接口代理与基于继承代码实现,接下来会重点分析这两种实现方式。

2、设计模式

1)Spring AOP中用到了责任链模式和代理模式,Spring AOP中的责任链模式主要是应用在AOP的链式调用上。

2)代理模式

(1)代理模式定义:代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

(2)代理模式作用:为其他对象提供一种代理以便控制对这个对象的访问。

(3)代理分类:代理模式分为两种,见下图:

        静态代码缺点:要代理的方法越多,代码重复率越高。假设你的目标类有100个方法,那么你的代理类里面就要对这100个方法进行委托,但代理类里面每个方法执行的前后代码都是一致的,所以代码重复性高。

        动态代理的两类实现:基于接口代理和基于继承代理(两个的代表分别是JDK代理和Cglib代理)。

代理模式一般涉及到的角色有:

  • 抽象角色:声明真实对象和代理对象的共同接口; 
  • 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。 
  • 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

下面是代理模式的类图:

代理模式

类图说明:这个类图中有两个部分需要注意的:

(1)由于目标对象中有的方法在代理模式中都要有,所以目标类和代理类实现了同一个接口,这体现了面向对象编程的面向接口编程。

(2)代理类中引用了真实目标对象,AOP就是在代理类在调用真实目标对象之前或之后或其它时机做了些额外的工作(比如打印日志等非功能性的功能)。也就是说代理对象把真正的方法委托给目标对象去执行,而自己就去执行一些额外的逻辑,就是Aop要织入的代码,从而实现切面切入。

3、实现:

1)JDK代理的实现:

(1)实现要点:

  • 类:java.lang.reflect.Proxy  -- 通过Proxy类动态生成代理类。
  • 接口:InvocationHandler   -- 代理类要实现织入逻辑,需要实现这个接口。
  • 注意只能基于接口进行动态代理 

(2)下面通过有一个例子来说明JDK代理

①定义一个普通类:

/**
 * 普通接口 
 */
public interface Subject {
	void request();
	void hello();
}

②定义一个实现接口的类:

/**
 *	真实目标类 
 */
public class RealSubject implements Subject{

	@Override
	public void request() {
		System.out.println("RealSubject calls the method of request now ……");
	}

	@Override
	public void hello() {
		System.out.println("RealSubject calls the method of hello now ……");
	}

}

③ 自定义InvocationHandler,通过反射机制调用目标对象中的方法:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import cn.exercise.patten.proxy.RealSubject;

/**
 *	自定义InvocationHandler
 */
public class JdkProxySubject implements InvocationHandler{
	
	private RealSubject realSubject; // 目标对象
	
	public JdkProxySubject(RealSubject realSubject) {
		this.realSubject = realSubject;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("--- jdkProxy before ---");
		Object result = null;
		try {
			result = method.invoke(realSubject, args); // 利用反射调用目标对象的方法
		} catch (Exception e) {
			System.out.println("ex: " + e.getMessage());
		} finally {
			System.out.println("--- jdkProxy after ---");
			System.out.println();
		}
		return result;
	}

}

        invoke方法参数说明:

  • Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。
  • Method method:被代理的对象中被代理的方法的一个抽象。
  • Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。

④编写客户端类:

import java.lang.reflect.Proxy;

import cn.exercise.patten.proxy.RealSubject;
import cn.exercise.patten.proxy.Subject;
/**
 * 客户端类
 */
public class Client {

	public static void main(String[] args) {
		Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), // 目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM中,以便运行时需要!
				new Class[]{Subject.class}, //获取被代理类的一组口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
				new JdkProxySubject(new RealSubject()));//自定义InvocationHandler
		subject.request();
		subject.hello();
	}	

}

⑤运行结果:

(3)JDK代理源码分析

        接下来我们看看Proxy.newProxyInstance()这个方法里面的部分代码,下图是JDK源码的一个时序图,通过这个时序图的找到apply()方法,查看里面的一段重要代码:

        说明:这段代码的作用是验证传入的类加载器载入的Class和你传入的接口对应的Class是否相同,不相等则抛出异常。红框框出的部分是通过反射机制,加载每一个接口的运行时Class信息,通过接口的名称,找到类,在接着一步往下执行,生成字节码,最后生成代理类。

    通过System.setProperties()可以设置保存jdk动态代理生成的字节码文件。

    我们通过反编译,来看看通过这种方式生成的代理类是怎么样的:

//生成的代理类
public final class $Proxy0 extends Proxy implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m4;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void hello()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void request()
    throws 
  {
    try
    {
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.imooc.pattern.Subject").getMethod("hello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m4 = Class.forName("com.imooc.pattern.Subject").getMethod("request", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

    由上面的图可以看到这种方式是基于接口动态生成代理类的,通过自定义的InvocationHandler里面的invoke()方法来获取目标类方法的信息。

2)Cglib代理

        JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类(通过ASM字节码处理框架实现),并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

        JDK动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以Cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

(1)Cglib实现要点

  • Cglib是通过继承的方式时实现代理类
  • 通过Enhancer类的setCallback()方法织入代码    

(2)接下来通过一个例子来说明Cglib的使用

①创建真实目标对象类:

/**
 *	真实目标类 
 */
public class RealSubject implements Subject{

	@Override
	public void request() {
		System.out.println("RealSubject calls the method of request now ……");
	}

	@Override
	public void hello() {
		System.out.println("RealSubject calls the method of hello now ……");
	}

}

②创建织入代码类:

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 织入代码类
 */
public class DemoMethodInterceptor implements MethodInterceptor{

	@Override
	public Object intercept(Object obj, Method arg1, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("--- before ---");
		Object result = null;
		try {
			result = proxy.invokeSuper(obj, args);
		} catch (Exception e) {
			System.out.println("ex:" + e.getMessage());
		} finally {
			System.out.println("--- after ---");
		}
		return result;
	}

}

        说明:Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展 。MethodInterception是Cglib的接口,通过实现这个接口接口,在intercept中织入想要的非功能性代码。

③创建客户端类:

import cn.exercise.patten.proxy.RealSubject;
import cn.exercise.patten.proxy.Subject;
import net.sf.cglib.proxy.Enhancer;
/**
 * 客户端
 */
public class Client {
	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer(); // 传入目标对象,通过继承生成代理类
		enhancer.setSuperclass(RealSubject.class); // 织入代码
		enhancer.setCallback(new DemoMethodInterceptor());
		Subject subject = (Subject) enhancer.create();
		
		//调用方法
		subject.hello();
		subject.request();
	}
}

④运行结果

3)JDK与Cglib对比

(1)JDK只能针对有接口的类的接口方法进行动态代理;

(2)Cglib基于继承来实现代理,无法对static、final类进行代理;(单一的类是没有static修饰符的,只有静态类内部可以用static修饰;)

(3)Cglib基于继承来实现代理,无法对private、static方法进行代理。(对于static方法,它是属于类的,子类在不重写的情况下,是可以调用的,但是一旦重写了就无法调用了,普通的public方法可以通过super.method()调用,但是static方法不行。)

4)看到这里,或许你的问题就来了:既然有JDK代理,又有Cglib代理,那Spring AOP怎样判断使用JDK代理还是Cglib代理的?

        这就涉及Spring的源码了,我们通过下面的spring时序图,找到DefaultAopProxyFactory类,查看里面的代码。时序图如下:

        说明:

  • AbstractAutoProxyCreator:spring的代理创建类都是AbstractAutoProxyCreator的子类,这个抽象类同时又是InstantiationAwareBeanPostProcessor的实现类。 (1)wrapIfNecessary:包给bean如果必要,即如果它是合格的代理。 (2)createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean))
  • ProxyFactory:AOP代理程序使用的工厂,而不是通过声明式安装在一个bean工厂。这个类提供了一种简单的方式获取和用户自定义代码配置AOP代理实例。
  • ProxyCreatorSupport:代理工厂基类。提供方便的访问一个可配置的aopproxyfactory。
  • DefaultAopProxyFactory:这个类中的部分代码现实了SpringAOP是如何选择代理方式的。见下面的源码:
@Override
	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() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

        由上面的源码,总结SpringAOP是如何选择代理方式:

(1)如果目标对象实现了接口,则默认采用JDK动态代理;

(2)如果目标对象没有实现接口,则采用Cglib进行动态代理;

(3)如果目标对象实现了接口,且强制Cglib代理,则使用cglib代理;

猜你喜欢

转载自my.oschina.net/u/3696939/blog/1551039
今日推荐