SpringAOP原理之---Java动态代理

前言:

前两天在学习Spring的AOP时,看到Spring默认使用JDK动态代理来实现AOP,于是对Java的动态代理原理产生了疑惑和兴趣,便查找了一些资料来解惑。现将理解记录如下。

参考资料
1. Java 动态代理机制分析及扩展,第 1 部分
2. 使用ProxyGenerator类生成字节码
3. 说说 cglib 动态代理
4. ProxyGenerator.java源码

一、代理的概念

1. 什么是代理模式

代理模式是常用的设计模式之一,按照代理的创建时期可以分为两种:
- 静态代理:编译前代码就已创建或生成,编译后代理类的.class文件就生成了。
- 动态代理:在程序运行时,代理类的.class文件由JVM利用反射机制生成或由ASM(字节码生成框架)动态创建。在Java中,动态代理的实现方式有两种:JDK动态代理,使用反射机制生成代理类的字节码文件,限于实现了接口的委托类;CGLIB动态代理,利用ASM框架生成代理类的字节码文件,限于没有使用final修饰的类。限制的具体原因看完后面的内容后即可明白。

2. 为什么要用代理

解决在直接访问对象时带来的问题,如:要访问的对象不在本机器上;直接访问造成系统开销过大等

二、静态代理

静态代理,直接编码实现代理类。假如有委托类B,其实现了接口A的doSomething()方法,那么代理类C同样要实现接口A,不过要持有委托类B的实例引用,以便在doSomething()方法中做分派转发,在B.doSomething()前后可以织入一些其他的动作。

1. 实现
/**
*委托类和代理类的接口
*/
public interface Count {
    public void count();
}
/**
*委托类,这里做实际的业务逻辑
*/
public class CountImpl implements Count {

    @Override
    public void count() {
        System.out.println("CountImpl do something...");
    }
}
public static void main(String[] args) throws Exception {
        CountImpl countImpl = new CountImpl();
        Count count = new CountProxy(countImpl);
        count.count();
}

输出:

before count…
CountImpl do something…
after count…

2. 优点

编码简洁明了

3. 缺点和解决方案

对于多个代理来说,代码重用率低,灵活性不足。使用动态代理来解决。

三、JDK动态代理

1. 分析

我想在实现之前,先浅析一下JDK动态代理的基本原理,了解原理之后再去看实现,会清晰明白许多。
在JDK动态代理中,使用到了两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler是个十分简单的接口,只有一个方法:

/**
*该方法会在JDK利用反射生成代理类字节码文件时,在接口的实现方法(如接口A的doSomething()方法)中调用,因为生成的动态代理类中有一个InvocationHandler的引用。
*该接口的用法是,我们实现该接口,并在实现类内部持有一个委托类的引用,在invoke方法中围绕着委托类的方法做一些前置、后置操作。
*Object proxy 生成的动态代理类的实例
*Method method 委托类的方法对象
*Object[] args 委托类的方法所需要用到的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

Proxy是个比较复杂的类,方法众多,不过我们在利用其实现动态代理时往往只需要关注一个方法即可:

/**
*该静态方法用来动态生成代理类对象,该对象继承于Proxy类并实现了委托类的所有接口,
*内部持有我们自定义的InvocationHandler对象的引用。
*ClassLoader loader 类加载器,一般使用委托类的类加载器,不过基本上用户类都是AppClassLoader加载的
*Class<?>[] interfaces 委托类所实现的所有接口,这样代理类的实例就可以由委托类的某个接口来持有了
*InvocationHandler h InvocationHandler实现类对象,代理类在调用方法时实际上都分派转发给了h.invoke
*/
 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
     //InvocationHandler不能为空
     Objects.requireNonNull(h);
     final Class<?>[] intfs = interfaces.clone();
     final SecurityManager sm = System.getSecurityManager();
     if (sm != null) {
         //检测类加载器和要实现的接口的合法性
         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
     }
      // 生成动态代理类的Class对象
     Class<?> cl = getProxyClass0(loader, intfs);
     try {
         if (sm != null) {
             checkNewProxyPermission(Reflection.getCallerClass(), cl);
         }
         //获取动态代理类的构造函数,构造函数的的唯一参数即InvocationHandler
         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块省略
 }

最后我们来看一下JDK利用反射生成的代理类的代码:

/**
*异常处理的代码都省略了
*/
public final class $Proxy extends Proxy implements Count{
  private static Method m3;
  private static Method m1;
  private static Method m2;

  public $Proxy(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }

  public final int count() {
      return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
  }
  public final boolean equals(Object paramObject){
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  public final String toString(){
      return (String)this.h.invoke(this, m2, null);
  }

  static{
      m3 = Class.forName("com.rambo.proxy.Count").getMethod("count");
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
}

看到这,是否对JDK的动态代理瞬间理清楚了呢? 其实cglib的动态代理和这个类似,只不过cglib是用asm框架生成的字节码文件,而且生成的动态代理类是委托类的子类,如果委托类是final的,cglib就无能为力了。

2. 实现
public class CountIH implements InvocationHandler {
    private Count count;
    /**1
     * 内部持有实现类的引用
     */
    public CountIH(Count countImpl) {
        this.count = countImpl;
    }
    //将调用分派转发到委托类的方法上,可在方法执行前后做一些前置、后置操作。如记录日志、安全控制、事务控制等等
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before count...");
        Object reslut = method.invoke(count, args);
        System.out.println("after count...");
        return reslut;
    }
}

public static void main(String[] args) throws Exception {
        CountImpl countImpl = new CountImpl();
        InvocationHandler handler = new CountIH(countImpl);
        //对于Spring中如何强转类型,还有疑问,继续看SpringAOP时再去寻找答案吧
        Count count = (Count) Proxy.newProxyInstance(Count.class.getClassLoader(), countImpl.getClass().getInterfaces(), handler);
        count.count();
}
3. 优点

解决静态代理的问题

4. 缺点和解决方案

如果委托类没有实现接口,则无法使用JDK动态代理,这时候就要用到cglib了;而cglib对于final类无能为力,所以这两个动态代理方法是相辅相成的。

四、cglib动态代理

/**
*利用增强器直接生成动态代理类对象,我推测this作为参数实际上和JDK动态代理中的newProxyInstance
*方法中的InvocationHandler h参数作用类似,都是为了代理类进行分派转发。
*JDK动态代理中是为了调用h.invoke方法,cglib中是为了调用this.intercept方法。
*/
public class CountProxyCglib implements MethodInterceptor {
    public Object getProxyInstance(Object target){
        return Enhancer.create(target.getClass(), this);
    }

    @Override
    public Object intercept(Object target, Method method, Object[] params, MethodProxy proxy) throws Throwable {
        System.out.println("before count...");
        //调用父类的方法
        Object result = proxy.invokeSuper(target, params);
        System.out.println("after count...");
        return result;
    }
}
/**
*net.sf.cglib.proxy.Enhancer类的静态方法
*/
public static Object create(Class type, Callback callback) {
        Enhancer e = new Enhancer();
        e.setSuperclass(type);
        e.setCallback(callback);
        return e.create();
}

五、SpringAOP和动态代理

Spring使用JDK动态代理或CGLIB代理来实现,Spring缺省使用JDK动态代理来实现,从而任何接口都可别代理,如果被代理的对象实现不是接口将默认使用CGLIB代理,不过CGLIB代理当然也可应用到接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值