面试中被问到spring aop的实现原理,说了动态代理,面试关接着问动态代理的原理是什么。。。一脸懵逼,自己还是太菜,所以借鉴了一些博客,对照部分源码,对动态代理做一个自己的理解。
针对spring的源码,说实话,看不懂。。。目前了解的是:
Spring提供了两种方式来生成代理对象: JdkProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
所以这里要做的就是对这两哥们进行介绍。
JDK动态代理
之所以使用动态代理,是因为在方法的调用中,我们不想让方法的调用者和真正的执行者有过多的接触,并且我们希望调用者调用的方法对原始的方法有着一定的增强。
本着加强自己印象的作用,这里给出了静态代理的一个示例:
interface HouseSale{
void sale();
}
class HouseOfJack implements HouseSale{
@Override
public void sale() {
System.out.println("给我100万,房子归你");
}
}
class SaleProxy implements HouseSale{
private HouseOfJack jack = new HouseOfJack();
@Override
public void sale() {
System.out.println("现有房源,欢迎选购!");
jack.sale();
System.out.println("恭喜这位爷!");
System.out.println("成功卖出,收取佣金10%,美滋滋!");
}
}
public class Market {
public static void main(String[] args) {
System.out.println("老子有钱,要买房");
SaleProxy xiaoZhang = new SaleProxy();
xiaoZhang.sale();
}
}
静态代理时,针对一个卖房主就要新建一个代理类,很不Java,而动态代理可以根据代理的对象动态的自动生成代理类,以下是demo:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class MyInvocationHandler implements InvocationHandler{
private Object houseHolder;
public MyInvocationHandler(Object object){
this.houseHolder = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("现有房源,欢迎选购!");
//jack.sale();
method.invoke(houseHolder,args);
System.out.println("恭喜这位爷!");
System.out.println("成功卖出,收取佣金10%,美滋滋!");
return null;
}
}
public class Market {
public static void main(String[] args) {
HouseOfJack houseOfJack = new HouseOfJack();
System.out.println("老子有钱,要买房");
MyInvocationHandler saleProxy = new MyInvocationHandler(houseOfJack);
HouseSale houseSale = (HouseSale) Proxy.newProxyInstance(houseOfJack.getClass().getClassLoader(), houseOfJack.getClass().getInterfaces(), saleProxy);
houseSale.sale();
}
}
JDK动态代理的使用步骤:
- 定义接口及其实现类;
- 创建*InvocationHandler类,该类实现InvocationHandler接口,重写invoke(...)方法.
- 创建需要代理的对象,执行Proxy的newProxyInstance(...)方法
- 执行接口中定义的方法。
可以看到,核心类有两个:Proxy和InvocationHandler。
Proxy类:
以下是Proxy类中包含的方法:
newProxyInstance(...)方法如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
参数介绍:
- loader:被代理对象所使用的类加载器
- interfaces:被代理类实现的接口,可以为多个
- h:被重定向时的句柄对象
其中关键的代码:
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
该方法会根据提供的类加载器和接口,创建一个代理类,创建的过程可总结为三步:
- 验证
- 缓存所创建代理类的结构,如果创建过,则直接返回
- 如果没有创建过则新建代理类
创建的代码如下:
long num;
//获得代理类数字标识
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
//获得创建新类的类名$Proxy,包名为接口包名,但需要注意的是,如果有两个接口而且不在同一个包下,也会报错
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//调用class处理文件生成类的字节码,根据接口列表创建一个新类,这个类为代理类,
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
//通过JNI接口,将Class字节码文件定义一个新类
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
最终生成的代理类通过反编译的结构如下:
public class $Proxy0 extends Proxy implements HouseSale{
...
public $Proxy0(InvocationHandler h){
super(h);
}
...
public final void sale(){
...
}
}
生成的代理类继承了Proxy类,所以JDK动态代理只能代理接口,不能代理类,(?为什么代理类一定要继承Proxy类,google了半天也没得结果,求有缘人指点)就酱~
Cglib动态代理
因为JDK不可以代理类,所以Spring AOP中引入了Cglib动态代理,所以它既可以代理接口,也可以代理类。
以下是Cglib代理的示例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class HelloConcrete{
public String sayHello(String str){
return "HelloConcrete: " + str;
}
}
class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//System.out.println("class of o : " + o.getClass());
System.out.println("here is interceptor");
return methodProxy.invokeSuper(o, objects);
}
}
public class CGLibProxy {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());
HelloConcrete helloConcrete = (HelloConcrete) enhancer.create();
System.out.println(helloConcrete.sayHello(" I love U"));
System.out.println(helloConcrete.getClass().getName());
System.out.println(helloConcrete.getClass().getSuperclass().getName());
}
}
参数的意义:
- @para1 o :代理对象本身
- @para2 method : 被拦截的方法对象
- @para3 objects:方法调用入参
- @para4 methodProxy:用于调用被拦截方法的方法代理对象
同样是两个重要的类(接口):Enhancer和MethodInterceptor:
Enhancer类:
使用的步骤:
- 创建需要代理的类或接口
- 创建MethodInterceptor()的子类,并实现interceptor(...)方法。
- 创建Enhancer对象,设置类信息和Callback信息。
通过输出的信息可以看到,代理类继承了原始类,通过反编译,代理类的结构如下:
public class HelloConcrete$$EnhancerByCGLIB$$4d552cc extends HelloConcrete implements Factory {
...
Class localClass1 = Class.forName("net.sf.cglib.test.HelloConcrete$$EnhancerByCGLIB$$4d552cc");
Class localClass2;
Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "sayHello", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.HelloConcrete")).getDeclaredMethods());
...
final void CGLIB$sayHello$0() {
super.sayHello();
}
public final void sayHello(){
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
CGLIB$BIND_CALLBACKS(this);
tmp4_1 = this.CGLIB$CALLBACK_0;
}
if (this.CGLIB$CALLBACK_0 != null) {
tmp4_1.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
}
else{
super.sayHello();
}
}
}
注:代理类名称末尾的序列为hashCode;
最终在实际方法被调用的时候,代理对象会把执行的函数重定向到interceptor(...)这里,这与JDK的动态代理是一致的,但是JDK动态代理是通过反射来对原始方法进行执行,而Cglib不然;
因为反射的效率会比较低,Cglib通过FastClass的机制来实现对被拦截方法的调用。
FastClass机制:
简单来说就是对类中的方法建立索引,然后通过索引来调用函数,有点类似HashMap。
以下是样例代码:
class FTest{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class FFTest{
public Object invoke(int index, Object o, Object[] ol){
FTest t = (FTest) o;
switch (index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
public int getIndex(String signature){
switch (signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
}
public class FastTest {
public static void main(String[] args) {
FTest test = new FTest();
FFTest ffTest = new FFTest();
int indexOff = ffTest.getIndex("f()V");
ffTest.invoke(indexOff, test, null);
int indexOfg = ffTest.getIndex("g()V");
ffTest.invoke(indexOfg, test, null);
}
}
也即是说,根据自己定义的方法的标识符预先计算好hashCode,然后根据这个hashCode直接调用原始类中的方法。这样就避免了反射机制。
在Cglib动态代理中,MethodProxy的一个内部类如下:
private static class FastClassInfo
{
FastClass f1; // net.sf.cglib.test.HelloConcrete的fastclass
FastClass f2; // HelloConcrete$$EnhancerByCGLIB$$4d552cc 的fastclass
int i1; //方法sayhello在f1中的索引
int i2; //方法CGLIB$sayHello$0在f2中的索引
}
真实调用的代码中,invokeSuper(...)的代码如下:
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
至此,两个动态代理的分析就结束了,下面给出了自己理解的面试答案:
Q:谈谈你对Spring Aop的理解?
A:AOP,即面向切面编程,是对面向对象编程的一个补充,它可以在不影响源代码的情况下对其进行增强,比如:日志,事务,权限控制等。Spring AOP是基于动态代理实现的,在不同的情景中,有两种动态代理可以选择,即JDK动态代理和Cglib动态代理,Spring Aop的默认策略是,代理接口的时候采用JDK动态代理,其他使用Cglib;JDK动态代理是根据传入的类加载器,接口和handler来构建一个新的代理类,代理类继承Proxy类,并实现传入的接口,在代理对象调用接口方法时,会被转发到handler中,然后通过反射来执行被代理类的方法;Cglib是通过继承被代理类实现的,通过构建字节码来构建代理类,在转发到interceptor方法中时,通过FastClass机制来执行被代理类的方法。
网友评论