动态代理与静态代理

  • 前言
  • 代理模式
  • 静态代理
  • 动态代理
    • JDK代理
    • 动态生成代理对象原理
      • 生成class数据源码
      • 动态代理类真身
  • 总结

前言

近期在研究Hook技术,需要用到动态代理,说到动态代理就会聊到它的兄弟静态代理,那它们到底是怎么一回事呢?实现方式有哪些呢?一起来看下

代理模式

代理在我们生活中随处可见,比如我们生活中的各种中介公司,以买一辆二手车为例:如果我买车,我可以自己去网上找车源,然后做质量检测,车辆过户等一系列的流程,但是这太费时间和精力了,我就是想花钱买个车而已,最后我居然吭哧吭哧的干了这么多活;于是我可以通过中介买车,我只用给钱,选车就行了,其它活都交给中介干,这就是代理的一种实现;还有网络代理(不管是正向还是反向代理),房屋租赁公司等都是代理的具体实现
在Java编程里就有一种设计模式,即代理模式,提供了一种对目标对象的访问方式,即通过代理对象访问目标对象,代理对象是指具有与被代理对象相同的接口的类,客户端必须通过代理对象与被代理的目标类进行交互

为啥使用代理模式:

  • 中间隔离:某些情况下,客户端不想或者不能直接引用一个目标对象,而代理类可以在客户端和目标类之前起到中介作用
  • 开闭原则,扩展功能:代理类除了是客户类和目标类的中介,还可以通过给代理类增加额外的功能来扩展目标类的功能,这样我们只需要修改代理类而不需要再修改目标类,
    符合代码设计的开闭原则(对扩展开放,对修改关闭)。代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后对返回结果的处理等。
    代理类本身并不真正实现服务,而是同过调用目标类的相关方法,来提供特定的服务。真正的业务功能还是由目标类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的目标类。

代理实现方式:
如果按照代理创建的时期来进行分类的话, 可以分为静态代理、动态代理

  • 静态代理是由程序员创建或特定工具自动生成代理类,再对其编译,在程序运行之前,代理类.class文件就已经被创建了
  • 动态代理是在程序运行时通过反射机制动态创建代理对象

如图:

开源框架应用:
Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的,一个是IOC,一个是AOP

  • IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对;依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现

  • AOP是Aspect Oriented Programming的缩写,即面向切面编程;与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。

  • IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?就是动态代理技术,目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

Retrofit框架应该是时下Android端最流行的网络访问框架了,而动态代理技术在里面就运用的非常多,对接口进行解析,获取到接口里的注解信息,参数等,然后封装成Http请求,最后再由底层网络请求框架OKHttp执行

静态代理

静态代理是代理模式实现方式之一,比较简单,主要分为三个角色:客户端,代理类,目标类;而代理类需要与目标类实现同一个接口,并在内部维护目标类的引用,进而执行目标类的接口方法,并实现在不改变目标类的情况下前拦截,后拦截等所需的业务功能。

假设现在项目经理给你提了一个需求:在项目所有类的有关用户操作的方法前后打印日志;这种情况下就可以通过静态代理实现

做法就是:为每个相关类都编写一个代理类(业务重合的可以共用一个代理类),并且让它们实现相同的接口

定义接口:

public interface ILogin {void userLogin();
}

定义目标类:

public class UserLogin implements ILogin {@Overridepublic void userLogin() {System.out.print("用户登录");}
}

定义代理类:

public class UserLoginProxy implements ILogin {private UserLogin mLogin;public UserLoginProxy() {mLogin = new UserLogin();}@Overridepublic void userLogin() {System.out.print("登录前。。。");mLogin.userLogin();System.out.print("登录后。。。");}
}

客户端:

public class Test {public static void main(String[] args){ILogin loginProxy = new UserLoginProxy();loginProxy.userLogin();}
}

这样我们就在尽量不修改现有类的基础上实现了这个需求,同时客户端只用跟代理类打交道,完全不用了解代理类与目标类的实现细节,也不需要知道目标类是谁;当然你如果想指定目标类,将在外部创建目标类,传给代理类
这里只是在同步情况下的一种实现,如果是异步操作,就需要进行一些改动

静态代理总结:

  • 优点:在符合开闭原则的情况下,对目标对象功能进行扩展和拦截
  • 缺点:需要为每个目标类创建代理类和接口,导致类的数量大大增加,工作量大;接口功能一旦修改,代理类和目标类也得相应修改,不易维护

动态代理

从静态代理的实现过程可以知道工作量太大,如果是在项目早期同步做还好,要是在接手老项目或者项目晚期再做,你可能要为成百上千个类创建对应的代理对象,那真的挺让人崩溃的;所以我们就需要想有没有别的方法:如何少写或者不写代理类却能完成这些功能

做Java的都知道一个.java文件会先被编译成.class文件,然后被类加载器加载到JVM的方法区,存在形式是一个Class对象(所谓的Class对象就是Class文件在内存中的实例);我们构造出的任何对象实例是保存在Heap中,而实例是由其Class对象创建的(可以通过任意实例的getClass方法获取对应的Class对象),比如平时使用new关键字加构造方法Person p = new Person()就是将这个Class对象在Heap中创建一个实例;可以看出要创建一个实例,最关键的是得到对应的Class对象,而得到Class对象追根溯源需要一个Java文件;那么我们要做的就是不写Java文件,而是直接得到代理Class对象,然后根据它通过反射创建代理实例

Class对象里面包含了一个类的所有信息,比如构造器,方法,字段等;那我们怎么在不写代理类的前提下获取到这些信息呢?从静态代理的实现知道目标类和代理类实现了同一个接口,这是为了尽可能保证代理对象的内部结构和目标对象一致,这样代理对象只需要专注于代码增强部分的编写,所以接口拥有代理对象和目标对象共同的类信息,那么我们就可以从接口获取本来应该由代理类提供的信息,但是要命的是接口是不能实例化的啊

这就要讲到动态代理了:在动态代理中,不需要我们再手动创建代理类,只需要编写一个动态处理器及指定要代理的目标对象实现的接口,真正的代理对象由JDK在运行时为我们创建;JDK提供了java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy来实现动态代理

JDK代理

Proxy.getProxyClass(ClassLoader, interfaces)方法只需要接收一个类加载器和一组接口就可以返回一个代理Class对象,然后就可以通过反射创建代理实例;其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在我们的角度就是通过接口Class对象创建代理类Class对象

这里通过一个很骚的比喻来说明下:一个东厂太监(接口Class对象)有一家子财产,但是为了侍奉皇帝这一伟大事业,毅然决然的割了DD(没有构造方法),虽然实现了自己的理想,但是不能繁育下一代(不能构造器创建对象),也就没有后人继承自己的家业;但是好在华佗在世,江湖上有一个高人(Proxy),发明了一个克隆大法(getProxyClass),不仅克隆出了几乎和太监一样的下一代(新的Class),还拥有自己的小DD(构造方法),这样这个下一代就能继承太监的家产(类结构信息,其实是实现了该接口),同时还能娶妻生子,传给下一代(创建实例)

用一副图展示动态代理和静态代理实现思路:

那这样就很简单了

    public static Object loadProxy(Object target) throws Exception {//通过接口Class对象创建代理Class对象Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());//拿到代理Class对象的有参构造方法Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class);//反射创建代理实例Object proxy = constructors.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("执行前日志..."+"\n");//执行目标类的方法Object result = method.invoke(target, args);System.out.println("执行后日志..."+"\n");return result;}});return proxy;}public static void main(String[] args) throws Exception {ILogin proxy = (ILogin) loadProxy(new UserLogin());proxy.userLogin();}

看看打印结果

这样无论系统有多少目标类,通过传进来的目标类都可以获取到对应的代理对象,就达到我们在执行目标类前后加日志的效果了,同时还不需要编写代理类

Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,如下

    public static Object loadProxy(Object object) {return Proxy.newProxyInstance(object.getClass().getClassLoader(), //和目标对象的类加载器保持一致object.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成代理对象new InvocationHandler() { //事件处理器,即对目标对象方法的执行@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("执行前日志...");Object result = method.invoke(object, args);System.out.println("执行后日志...");return result;}});}

JDK动态代理总结:

  • 优点:相对于静态代理,极大的减少类的数量,降低工作量,减少对业务接口的依赖,降低耦合,便于后期维护;同时在某些情况下是最大的优势,即可以统一修改代理类的方法逻辑,而不需要像静态代理需要修改每个代理类
  • 缺点:因为使用的是反射,所以在运行时会消耗一定的性能;同时JDK代理只支持interface的动态代理,如果你再继续深究源码,会发现,所有动态生成的代理对象都有一个共同的父类,即都继承于Proxy;Java的单继承机制决定了无法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,无法像静态代理中的代理类可以在内部扩展更多的功能

动态生成代理对象原理

在动态代理的过程中,我们不能清晰的看到代理类的实际样子,而且被代理对象和代理对象是通过InvocationHandler来完成的代理过程,其中代理对象是如何生成的,具体什么样,为什么代理对象执行的方法都会走到InvocationHandler中的invoke方法,要想了解这些就需要对Java是如何动态生成代理对象源码进行分析,继续往下看


你们有没有好奇getProxyClass这个方法是怎么通过【目标类实现的接口】生成代理Class对象的呢?

如果你在第一个方法输入如下代码

System.out.println("执行日志..."+(proxy instanceof Proxy)+"\n");
System.out.println("执行日志..."+(proxyClass.getName())+"\n");
System.out.println("执行日志..."+(proxyClass.getSuperclass().getName())+"\n");
System.out.println("执行日志..."+(proxyClass.getSuperclass().getSimpleName())+"\n");
System.out.println("执行日志..."+(proxyClass.getInterfaces()[0].getSimpleName())+"\n");

得到的结果是

  • 第一个结果为true,说明代理对象属于Proxy类型
  • 第二个结果是代理对象的全限定类名
  • 第三个结果是代理对象的父类的全限定名,第四个结果是父类名
  • 第四个结果是代理对象实现的接口名

从这四点是不是能知道点啥了:动态生成的代理对象的父类是Proxy,实现了ILogin接口;这也就是为什么能将代理对象强转成ILogin,从而调用其接口方法;也说明了为什么只能支持动态生成接口代理,不能动态生成class代理,因为最终生成的代理对象肯定会继承Proxy类,如果我们提供的类已经继承了其它类,那就不能继承Proxy类了,动态代理也就无从谈起了

接下来再从源码分析下其原理,鉴于篇幅原因,这里只分析重点代码

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());/*** 如果存在实现给定接口的给定加载器定义的代理类,则只返回缓存副本; 否则,它将通过ProxyClassFactory创建代理类*/private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");    }return proxyClassCache.get(loader, interfaces);}private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>{// 所有代理类名称的前缀private static final String proxyClassNamePrefix = "$Proxy";// 用于生成唯一代理类名称的下一个数字private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {//通过传入的接口clone后的接口......String proxyPkg = null;     // 定义代理class的包名int accessFlags = Modifier.PUBLIC | Modifier.FINAL;for (Class<?> intf : interfaces) {int flags = intf.getModifiers();//获取接口修饰符if (!Modifier.isPublic(flags)) {//我们定义的接口修饰符都是public,所以这里不会进去accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// 如果没有非public代理接口,请使用com.sun.proxy包proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** 生成代理class名称* 比如com.sun.proxy.$Proxy0*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** 通过接口和class名称生成代理class数据* 如果继续看generateProxyClass,会发现里面是生成class数据,包括写入Object的三个初始方法、写入实现的接口、写入继承Proxy类等到ByteArrayOutputStream中,然后转换成byte数组返回,至于是否会将Byte数据写到class文件保存在本地,视情况而定(默认不保存到硬盘)   */byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {//将动态生成的class数据加载到内存中 生成一个代理class对象return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}}}

生成class数据源码

ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析:


public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);final byte[] var4 = var3.generateClassFile();//是否将生成的class数据保存到硬盘,默认不保存if (saveGeneratedFiles) {......}return var4;}private byte[] generateClassFile() {//添加hashCode方法this.addProxyMethod(hashCodeMethod, Object.class);//添加equals方法this.addProxyMethod(equalsMethod, Object.class);//添加toString方法this.addProxyMethod(toStringMethod, Object.class);Class[] var1 = this.interfaces;int var2 = var1.length;int var3;Class var4;//遍历接口数组for(var3 = 0; var3 < var2; ++var3) {var4 = var1[var3];Method[] var5 = var4.getMethods();int var6 = var5.length;//添加接口里的方法,此时方法体还为空for(int var7 = 0; var7 < var6; ++var7) {Method var8 = var5[var7];this.addProxyMethod(var8, var4);}}Iterator var11 = this.proxyMethods.values().iterator();List var12;while(var11.hasNext()) {var12 = (List)var11.next();checkReturnTypes(var12);}Iterator var15;try {//添加一个带有InvocationHandler的构造方法this.methods.add(this.generateConstructor());var11 = this.proxyMethods.values().iterator();//生成方法体代码while(var11.hasNext()) {var12 = (List)var11.next();var15 = var12.iterator();while(var15.hasNext()) {ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));//方法体里生成调用InvocationHandler的invoke方法代码this.methods.add(var16.generateMethod());}}this.methods.add(this.generateStaticInitializer());} catch (IOException var10) {throw new InternalError("unexpected I/O Exception", var10);}if (this.methods.size() > 65535) {throw new IllegalArgumentException("method limit exceeded");} else if (this.fields.size() > 65535) {throw new IllegalArgumentException("field limit exceeded");} else {this.cp.getClass(dotToSlash(this.className));this.cp.getClass("java/lang/reflect/Proxy");var1 = this.interfaces;var2 = var1.length;//生成实现接口,继承Proxy类代码for(var3 = 0; var3 < var2; ++var3) {var4 = var1[var3];this.cp.getClass(dotToSlash(var4.getName()));}this.cp.setReadOnly();ByteArrayOutputStream var13 = new ByteArrayOutputStream();DataOutputStream var14 = new DataOutputStream(var13);try {var14.writeInt(-889275714);var14.writeShort(0);var14.writeShort(49);this.cp.write(var14);var14.writeShort(this.accessFlags);var14.writeShort(this.cp.getClass(dotToSlash(this.className)));var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));var14.writeShort(this.interfaces.length);Class[] var17 = this.interfaces;int var18 = var17.length;for(int var19 = 0; var19 < var18; ++var19) {Class var22 = var17[var19];var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));}var14.writeShort(this.fields.size());var15 = this.fields.iterator();while(var15.hasNext()) {ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();var20.write(var14);}var14.writeShort(this.methods.size());var15 = this.methods.iterator();while(var15.hasNext()) {ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();var21.write(var14);}var14.writeShort(0);return var13.toByteArray();} catch (IOException var9) {throw new InternalError("unexpected I/O Exception", var9);}}}

从源码可以得出:

  • JDK帮我们生成了这样一个class数据,它继承了Proxy类,添加了一个带InvocationHandler参数的构造方法,这样也就明白了为什么使用构造方法反射创建代理对象的时候传入了一个InvocationHandler参数,因为默认会调用到Proxy类的构造方法,其参数正好是InvocationHandler,赋值给内部的成员变量h

    protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;
    }
    
  • 实现了我们指定的接口,并实现了接口里的方法,同时接口中的方法调用了InvocationHandler的invoke方法
  • 当代理对象执行方法的时候,方法里面都会执行InvocationHandler的invoke方法,我们就可以在这里执行目标类方法

这样可以说动态生成代理class对象其实是动态生成代理class数据

动态代理类真身

使用如下代码将动态生成的class数据保存到硬盘

    public static void write(){byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", UserLogin.class.getInterfaces());String path = "D:/Workspaces/com/sun/proxy/LoginProxy.class";try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}}

接下来将这个class文件反编译成Java文件看看:如果你没有jad工具,可以通过JAD Java Decompiler下载,使用如下命令

/**
* -d :用户指定输出文件保存目录
* d:\  :具体目录目录
* -sjava :输出文件扩展名 这里保存成Java文件
* D:\Workspaces\com\sun\proxy\LoginProxy.class :class文件
*/
jad -d d:\ -sjava D:\Workspaces\com\sun\proxy\LoginProxy.class
/**
* 上面的命令没有指定保存的Java文件名,下面的命令可以指定保存文件名
* -p 输出详细文件信息
*/
jad -p D:\Workspaces\com\sun\proxy\LoginProxy.class > D:\Workspaces\com\sun\proxy\LoginProxy.java

代理类:

public final class $Proxy0 extends Proxy implements ILogin
{public $Proxy0(InvocationHandler invocationhandler){super(invocationhandler);}public final boolean equals(Object obj){try{return ((Boolean)super.h.invoke(this, m1, new Object[] {obj})).booleanValue();}catch(Error _ex) { }catch(Throwable throwable){throw new UndeclaredThrowableException(throwable);}}public final String toString(){try{return (String)super.h.invoke(this, m2, null);}catch(Error _ex) { }catch(Throwable throwable){throw new UndeclaredThrowableException(throwable);}}public final void userLogin(){try{super.h.invoke(this, m3, null);return;}catch(Error _ex) { }catch(Throwable throwable){throw new UndeclaredThrowableException(throwable);}}public final int hashCode(){try{return ((Integer)super.h.invoke(this, m0, null)).intValue();}catch(Error _ex) { }catch(Throwable throwable){throw new UndeclaredThrowableException(throwable);}}private static Method m1;private static Method m2;private static Method m3;private static Method m0;static {try{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]);m3 = Class.forName("com.mango.demo.proxy.ILogin").getMethod("userLogin", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);}catch(NoSuchMethodException nosuchmethodexception){throw new NoSuchMethodError(nosuchmethodexception.getMessage());}catch(ClassNotFoundException classnotfoundexception){throw new NoClassDefFoundError(classnotfoundexception.getMessage());}}
}
  • JDK为我们生成了一个$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类
  • 代理类实现了ILogin接口,继承了Proxy类,并有一个带InvocationHandler参数的构造方法,使用super调用Proxy类的构造方法,证实了上面的分析
  • 实现了接口的userLogin方法,并在其内部调用InvocationHandler的invoke方法,其h正是Proxy类定义的成员变量
  • 最下面是通过反射拿到类中的几个方法,作为参数传递到InvocationHandler.invoke方法中,即调用动态代理对象的任何方法,最终都是走到InvocationHandler.invoke方法中(所以在invoke方法中写日志需要判断下,是否是调用代理对象指定的方法走到这里)

至此就解答了这一节【动态生成代理对象原理】刚开始提出的几个疑问了

总结

JDK动态代理是实现AOP编程的一种途径,可以在执行指定方法前后贴入自己的逻辑,像Spring、Struts2就有用到该技术(拦截器设计就是基于AOP的思想);只不过JDK动态代理只能实现基于接口的动态代理,也算是一个遗憾,还有这种实现方式也不能避免类数量增加,因为你必须要为每个业务类编写业务接口;那么有没有不用写代理类、也不用写业务接口的代理方法呢?

方法是有的,需要用到CGLib了:

CGLIB(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口
CGLIB比JDK的代理更加强大,不只可以实现接口,还可以扩展类,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理
CGLIB底层封装了ASM,通过对字节码的操作来生成类,具有更高的性能,但是CGLIB创建代理对象时所花费的时间却比JDK多;ASM是一套JAVA字节码生成架构,能够动态生成.class文件并在加载进内存之前进行修改
使用CGLIB需要引用jar包cglib-nodep-3.2.5.jar(如果引入cglib.jar,还需要引入asm的jar包)

像Spring框架都支持这两种动态代理方式,但Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

至于在我们自己的业务里需要选择哪种方式实现AOP,还是需要视情况而定,两种方式组合使用或许能支持更多的业务需求

Android开发如何理解Java静态代理 动态代理及动态生成代理对象原理 看这篇就够了相关推荐

  1. 【java面试】6万字最全Java知识体系梳理,还在找面试题?看这篇就够啦

    文章目录 一.基础 1.1JVM 1.JVM五大内存区域 2.新生代和老年代 3.加载类的过程 4.OOM 5.JVM调优 1.2 GC 1.可达性分析 2.java中的引用 3.GC回收算法 4.G ...

  2. Android开发实践:Java层与Jni层的数组传递

    Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni ...

  3. Android原生TabLayout使用全解析,看这篇就够了

    前言 为什么会有这篇文章呢,是因为之前关于TabLayout的使用陆陆续续也写了好几篇了,感觉比较分散,且不成体系,写这篇文章的目的就是希望能把各种效果的实现一次性讲齐,所以也有了标题的「看这篇就够了 ...

  4. NDK撩妹三部曲(四)—NDK 开发如何优雅的定位 Native 异常,看这篇就够了

    NDK 开发如何优雅的定位 Native 异常,看这篇就够了 从何说起? 摘要 案例实操 aaddr2line objdump ndk-stack 1.假设我们已经通过 adb logcat 拿到了程 ...

  5. Android画中画模式-看这篇就够啦

    最近做做播放器,有个浮窗播放的需求,两种实现方式,一种是申请浮窗权限,创建浮窗参考 flowWindow,一种是采用画中画模式(8.0以上) 关于画中画 Android 8.0 Oreo(API Le ...

  6. 99. 中高级开发面试必问的Redis,看这篇就够了

    中高级开发面试必问的Redis,看这篇就够了! 一.概述 二.数据类型 STRING LIST SET HASH ZSET 三.数据结构 字典 跳跃表 四.使用场景 计数器 缓存 查找表 消息队列 会 ...

  7. Java应用系统监控看这篇就够了

    Java应用系统监控看这篇就够了 文章目录 业务背景 系统监控发展历程 技术方案 日志监控技术方案 Grafana+阿里云SLS日志服务 分布式链路追踪技术方案 阿里云jaeger方案 开源框架sky ...

  8. python java混合编程_详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...

  9. Java String,看这篇就够了

    String,是Java中最重要的类.这句肯定的推断不是Java之父詹姆斯·高斯林说的,而是沉默王二说的,因此你不必怀疑它的准确性. 关于字符串,有很多的面试题,但我总觉得理论知识绕来绕去没多大意思. ...

最新文章

  1. 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
  2. linux netlink 编程示例(二)应用层
  3. 论文翻译 基于R-FCN的物体检测
  4. EAS BOS 发布
  5. 开始折腾iphone cdma 恢复,降级,刷机,越狱,手编,写号
  6. JAVA之编译期和运行期区别
  7. Android_(控件)使用自定义控件在屏幕中绘制一条虚线
  8. ARM裸机工作笔记0001---ARM那些事
  9. 如何安装、管理多个 NodeJS 版本?
  10. unity scence灯光不显示_Unity基础教程系列(四)——多场景(Loading Levels)
  11. JVM和GC知识点整理
  12. ffmpeg之PCM转AAC
  13. oracle减法函数mius_Oracle常用函数及其用法
  14. Transparent Tribe行动
  15. 010项目沟通管理和干系人管理
  16. phaser.sprite.body overlap collide seperate
  17. 出现 leaked ServiceConnection 解决办法
  18. 企业如何通过APS系统进行产能规划?
  19. thinkPHP基于php的枣院二手图书交易系统--php-计算机毕业设计
  20. 关于MFC程序中隐藏任务栏图标的问题

热门文章

  1. 2019亚洲北京新零售展览会-展会新闻资讯
  2. java初级面试题(三)
  3. 【Java】JDK彻底卸载
  4. bsm公式的matlab代码_1000份MATLAB源代码,拿走不谢!
  5. 新时代下如何构建TDSQL-C数据库产品
  6. Caesar I (Crypto, Training) 的多种简单解法
  7. 基于DeepFaceLab的AI换脸
  8. 将前台数据成批插入后台数据库
  9. 2个用到数学归纳法的有意思的证明
  10. 实在智能RPA微观:电商应该如何告别单身