Java Proxy和CGLIB动态代理原理

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。同时代理对象可以调用被代理对象的方法,并对其进行增强。可以总结为代理对象 = 增强代码 + 目标对象(原对象)

静态代理的代理关系在编译时就确定,而动态代理的代理关系是在运行期确定的。

用Java动态代理

  需要用到Proxy类的两个静态方法

  • getProxyClass
    • 这个方法, 会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的
  • newProxyInstance(一般直接用这个)
    • 封装了得到代理类Class对象、构造函数等细节,直接返回了代理对象
public interface Delivery {

    /**
     * 进行打包
     */
    void pack();

    /**
     * 发货
     */
    void deliver();

    /**
     * 签收
     */
    void sign();
}

  EMS进行发货

public class Ems implements Delivery {
    @Override
    public void pack() {
        System.out.println("邮政快递员打好包");
    }

    @Override
    public void deliver() {
        System.out.println("邮政快递运输");
    }

    @Override
    public void sign() {
        System.out.println("收件人签收");
    }
}

      测试用例

public class DeliveryPackage {

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Ems ems = new Ems();
        Delivery delivery = (Delivery) getProxy(ems);
        delivery.pack();

        delivery.deliver();

        delivery.sign();
    }


    public static Object getProxy(Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //target为被代理对象,获取代理类的Class
        Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());

        //获取代理类的构造器
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);

        //获取代理对象
        Object invokeObj = constructor.newInstance((InvocationHandler) (proxy, method, args) -> {
            Object invoke = method.invoke(target, args);
            return invoke;
        });
        return invokeObj;
    }

}

         使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,方法会根据指定的参数动态创建代理对象。三个参数的意义如下:

  1. loader,指定代理对象的类加载器;
  2. interfaces,代理对象需要实现的接口,可以同时指定多个接口;
  3. handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里
public static Object getProxyByProxyMethod(Object target) {
   // 直接返回代理对象
   return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         Object invoke = method.invoke(target, args);
         System.out.println("代理对象增强方法");
         return invoke;
      }
   });
}

  newProxyInstance()返回一个实现指定接口的代理对象,对对象的所有方法调用会转发给InvocationHandler.invoke()方法。动态代理神奇的地方就是:

  1. 代理对象是在程序运行时产生的,而不是编译期
  2. 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;

        注意1:对于从Object中继承的方法,JDK Proxy会把hashCode()equals()toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    
 
    Objects.requireNonNull(h);

    // 下面这一段其实就是getProxyClass方法的内部实现
    // --------------------------------------------------------------
    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对象
    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);
    }
}

  为什么必须实现接口?

 System.out.println(delivery.getClass().getSuperclass());
//class java.lang.reflect.Proxy

  无论是什么代理类,都会继承自Proxy,而Java又是单继承的,所以想要被代理对象与代理对象产生联系,就只能通过接口来实现了

CGLIB动态代理

       Code Generation Library是一个基于ASM的字节码生成库,允许在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。

  CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑

  字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法在覆盖该方法时,自然是可以插入自己的代码的。因为需要Override被代理对象的方法,所以自然用CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖

  a.使用CGLIB动态代理不要求必须有接口生成的代理对象是目标对象的子类对象所以需要代理的方法不能是private或者final或者static的
  b.使用CGLIB动态代理需要有对cglib的jar包依赖(导入asm.jar和cglib-nodep-2.1_3.jar)

  来看示例,假设我们有一个没有实现任何接口的类HelloConcrete

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

  因为没有实现接口该类无法使用JDK代理,通过CGLIB代理实现如下:

  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法
  2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象
// CGLIB动态代理
// 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
  /**
     拦截方法:在代理实例上拦截并处理目标方法的调用,返回结果
     * obj:目标对象代理的实例;
     * method:目标对象调用父类方法的method实例;
     * args:调用父类方法传递参数;
     * proxy:代理的方法去调用目标方法
*/
    @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象
Enhancer enhancer = new Enhancer();               //增强器,动态代码生成器
enhancer.setSuperclass(HelloConcrete.class);      //设置父类,就是被代理的目标类
enhancer.setCallback(new MyMethodInterceptor());  //设置回调

HelloConcrete hello = (HelloConcrete)enhancer.create();  //通过字节码动态技术创建子类
System.out.println(hello.sayHello("I love you!"));

  运行上述代码输出结果:

日志信息: You said: [I love you!]
         HelloConcrete: I love you!

  通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()方法,将调用转发给原始对象

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理。

  如果对CGLIB代理之后的对象类型进行深挖,可以看到如下信息:

# HelloConcrete代理对象的类型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces: 
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

  看到使用CGLIB代理之后的对象类型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,这是CGLIB动态生成的类型;父类是HelloConcrete,印证CGLIB是通过继承实现代理;同时实现了net.sf.cglib.proxy.Factory接口,这个接口是CGLIB自己加入的,包含一些工具方法。

注意:
  (1).CGLIB是通过实现目标类的子类来实现代理,不需要定义接口
  (2).生成代理对象使用最多的是通过Enhancer和继承Callback接口的MethodInterceptor接口来生成代理对象,设置callback对象的作用是当调用代理对象方法的时候会交给callback对象的来处理。
  (3).创建子类对象是通过使用Enhancer类的对象,通过设置enhancer.setSuperClass(Class class)和enhancer.setCallback(Callback callback)来创建代理对象。 

参考:

https://www.cnblogs.com/CarpenterLee/p/8241042.html

http://rejoy.iteye.com/blog/1627405

http://www.cnblogs.com/shijiaqi1066/p/3429691.html(推荐)

posted on 2018-09-27 10:02  溪水静幽  阅读(204)  评论(0)    收藏  举报