2

动态代理及其实现原理

代理模式

​ 代理模式是一种比较好的理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

​ 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

有这样一个场景。有一个简单的手机类,只能打电话:

image

现在要改需求了,我们想要手机在打电话的时候可以开启录音:

image

但是修改源码破坏了面向对象的开闭原则(对扩展开放,对修改关闭),于是我们创建了一个子类去继承Phone:

image

我们又优化成接口的形式:

image

image

image

静态代理

​ 代理模式有静态代理和动态代理两种实现方式。

​ 所谓的静态代理就是在代码运行之前,代理类就已经存在,通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。

​ 静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

​ 从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

动态代理

​ 代理类在程序运行时创建的代理方式被成为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指令”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

​ 相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

​ 动态代理其实是一种方便运行时候动态的处理代理方法的调用机制,通过代理可以让调用者和实现者之间解耦。

​ 从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

动态代理实现方式

  • 静态代理,工程师编辑代理类代码,实现代理模式;在编译期就生成了代理类。
  • 基于 JDK 实现动态代理,通过jdk提供的工具方法Proxy.newProxyInstance动态构建全新的代理类(继承Proxy类,并持有InvocationHandler接口引用 )字节码文件并实例化对象返回。(jdk动态代理是由java内部的反射机制来实例化代理对象,并代理的调用委托类方法)
  • 基于CGlib 动态代理模式 基于继承被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可。(cglib动态代理底层是借助asm字节码技术
  • 基于 Aspectj 实现动态代理(修改目标类的字节,织入代理的字节,在程序编译的时候 插入动态代理的字节码,不会生成全新的Class )
  • 基于 instrumentation 实现动态代理(修改目标类的字节码、类装载的时候动态拦截去修改,基于javaagent) -javaagent:spring-instrument-4.3.8.RELEASE.jar (类装载的时候 插入动态代理的字节码,不会生成全新的Class )

JDK动态代理

image

image

JDK动态代理实现机制

下边来看看他的具体实现机制主要两个类:

  • InvocationHandler
  • Proxy

    根据注解描述可知,InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。

InvocationHandler接口,它就只有一个方法invoke。

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用,我们看到invoke方法一共接受三个参数,那么这三个参数分别代表什么呢?

proxy - 在其上调用方法的代理实例也就是代理的真实对象
method - 指的是我们所要调用真实对象的某个方法的Method对象
args - 指的是调用真实对象某个方法时接受的参数
public class Proxy implements java.io.Serializable {
    @CallerSensitive
    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);
        }
        // 生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        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);
        }
    }
}

其实大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类cl。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的getConstructor0方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。

进去getProxyClass0可以看到代理类从proxyClassCache缓存中获取,代码如下:

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

具体缓存生成是在ProxyClassFactory():

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

其中最重要的就是ProxyGenerator.generateProxyClass这个生成代理类字节码。(通过ProxyGenerator.generateProxyClass来手动生成一个代码类)

$Proxy0.class源码

JDK动态代理主要是基于反射,使用反射解析目标对象的属性、方法等

根据解析的内容生成proxy.class,说白了就是把要生成的class按照字符串的形式拼接,最终通过ClassLoader加载。

JDK动态代理只能代理接口:已经继承了Proxy类,可变部分只有implements

image

每个生成的动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行

代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString

image

CGLIB动态代理

​ JDK 动态代理有一个最致命的问题是只能代理实现了接口的类。

​ CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

​ CGLIB是针对类来实现代理的,他的原理是对代理的目标类生成一个子类,并覆盖其中方法实现增强,因为底层是基于创建被代理类的一个子类,所以它避免了JDK动态代理类的缺陷。

image

image

通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是CGLIB的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以CGLIB实现的代理也是可以被正常使用的。

但因为采用的是继承,所以不能对final修饰的类进行代理。final修饰的类不可继承。

image

image

  • CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.
  • 由于是继承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的
  • 做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用.
  • 提供callback 和filter设计,可以灵活地给不同的方法绑定不同的callback。编码更方便灵活。
  • CGLIB会默认代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。

AOP实现案例

​ AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。 总的来说,反射机制在生成类的过程中比较高效,执行时候通过反射调用委托类接口方法比较慢;而asm在生成类之后的相关代理类执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。 还有一点必须注意:jdk动态代理的应用前提,必须是委托类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。 由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

​ 实现AOP关键特点是定义好两个角色 切点 和 切面 。 代理模式中被代理类 委托类处于切点角色,需要添加的其他比如 校验逻辑,事务,审计逻辑 属于非功能实现逻辑通过 切面类定义的方法插入进去。

JDK动态代理 AOP 实现方式

定义切面接口,完成将通用公共方法注入到被代理类接口调用处理中

public interface IAspect {    
    /**
    * 在切点接口方法执行之前执行
    * @param args 切点参数列表
    * @return
    */
   boolean startTransaction(Object... args);    
    /**
    * 在切点接口方法执行之后执行
    */
   void endTrasaction();
}

定义切面实现类

/public class CustomAspect implements IAspect {    
    /**
    * 对参数 做判空处理
    * @param args 切点参数列表
    * @return
    */
   @Override
   public boolean startTransaction(Object... args) {
       Objects.nonNull(args);        
       boolean result = true;        
       for (Object temp :args) {            
           if (Objects.isNull(temp)){
                result =false;                 
               break;
           }
       }        
       return result;
   }    
    public void endTrasaction() {
       System.out.println("I get datasource here and end transaction");
   }
}

定义切点角色接口 因为是基于JDK实现的Aop ,所以委托类需要基于接口实现。

public interface IUserService {    
    void saveUser(String username, String password) throws Exception;
}

委托类实现

public class UserServiceImpl implements IUserService{    
    @Override
    public void saveUser(String username, String password) throws Exception {
       System.out.println("save user[username=" + username + ",password=" + password + "]");
   }
}

JDK动态代理生成器工具类

可以看到 generatorJDKProxy 方法入参只有两个参数 一个切点接口引用,一个切面接口引用;在InvocationHandler 内部类中可以完整看到切面类方法是怎么影响切点代码执行逻辑的。

public class JDKDynamicProxyGenerator {    
    /**
    * @param targetPoint 需要被代理的委托类对象
    * @param aspect 切面对象,该对象方法将在切点方法之前或之后执行
    * @return
    */
   public static Object generatorJDKProxy(IUserService targetPoint, final IAspect aspect{        return Proxy.newProxyInstance(                
                //委托类使用的类加载器
               targetPoint.getClass().getClassLoader(),                
                // 委托类实现的接口
                   targetPoint.getClass().getInterfaces(),                
                   /**
                * 生成的动态代理类关联的 执行处理器,代理我们的业务逻辑被生成的动态代理类回调
                * 具体逻辑代码执行,返回值为方法执行结果, 在aop模型中,委托类的接口方法称为切点。
                */
               new InvocationHandler() {                   
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {                        
                       // 执行切面方法,对入参进行校验
                       boolean prepareAction = aspect.startTransaction(args);    
                       if (prepareAction){                           
                           // 具体逻辑代码执行,返回值为方法执行结果
                          Object result = method.invoke(targetPoint, args);
                          aspect.endTrasaction();                           
                           return result;
                       }else {                          
                           throw  new RuntimeException("args: "+Arrays.toString(args)+"不                                    能为null ");
                       }
                   }
               });
                                                                                              }
}

测试类

public class testAopJDKProxy {    
    @Test
    public void testJDKProxy() throws Exception {
       System.out.println("无代理前 调用方法 userService.saveUser 输出......");
       IUserService userService = new UserServiceImpl();
       userService.saveUser("zby", "1234567890");       
        System.out.println("有代理后AOP 是怎么样的? Proxy......");
       IUserService proxyUserService =         (IUserService)JDKDynamicProxyGenerator.generatorJDKProxy(userService, new CustomAspect());
       proxyUserService.saveUser("zby", "1234567890");        
        /** 制造异常,两个入参都是null   */
       proxyUserService.saveUser(null, null);
   }
}

Cglib AOP 实现方式

定义切面接口

public interface IAspect {    
    /**
    * 在切点接口方法执行之前执行
    */
   void startTransaction();    
    /**
    * 在切点接口方法执行之后执行
    */
   void endTrasaction();
}

切面实现

public class CustomAspect implements IAspect {    
    @Override
       public void startTransaction() {
       System.out.println("cglib. I get datasource here and start transaction");
       }    
    public void endTrasaction() {
       System.out.println("cglib I get datasource here and end transaction");
       }
}

Cglib 是基于类实现的动态代理即业务类只需要实现类即可,不用强制必须实现某个接口为了突出这个优点这里没有实现接口

public class UserServiceImpl {    
    public void saveUser(String username, String password) {
       System.out.println("cglib save user[username=" + username + ",password=" + password + "]");
   }
}

Cglib 动态代理生成器工具类

public class CglibProxyGenerator {    
    /**
    * @param target 需要被代理的委托类对象,Cglib需要继承该类生成子类
    * @param aspect 切面对象,改对象方法将在切点方法之前或之后执行
    * @return
    */
   public static  Object generatorCglibProxy(final Object target, final IAspect aspect){        //3.1 new Enhancer
       Enhancer enhancer = new Enhancer();        //3.2 设置需要代理的父类
       enhancer.setSuperclass(target.getClass());        //3.3 设置回调
       enhancer.setCallback(new MethodInterceptor() {            
           @Override
           public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy)throws Throwable {                
               // 执行切面方法
               aspect.startTransaction();                
               // 具体逻辑代码执行,返回值为方法执行结果
               Object result = methodProxy.invokeSuper(proxy, args);                
               // 执行切面方法
               aspect.endTrasaction();                
               // 返回方法执行结果
               return result;
           }
       });        
       // 3.4 创建代理对象
       return enhancer.create();
   }}

测试类

public class testAopCglibKProxy {   
    @Test
    public void testCglibProxy() {
       System.out.println("before Proxy......");
       UserServiceImpl userService = new UserServiceImpl();
       userService.saveUser("zby", "1234567890");
       System.out.println("引入Cglib  Proxy代理库 后......");
       UserServiceImpl proxyUserService = (UserServiceImpl)                                     CglibProxyGenerator.generatorCglibProxy(userService, new CustomAspect());
       proxyUserService.saveUser("zby", "1234567890");
   }
}

花花呀
363 声望21 粉丝

学无止境 做有灵魂的程序员