JDK 动态代理

Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[]

Java 9将String底层实现从char[]改成byte[]的原因主要有以下几点:

  1. 节省内存空间:在Java 9之前,String的底层实现使用char[]来存储字符数据。每个char占用2个字节(16位),这意味着一个包含n个字符的字符串需要占用2n个字节的内存空间。而在Java 9中,String的底层实现改为使用byte[]来存储字符数据。对于ASCII字符(占1个字节),这种改变可以节省一半的内存空间。对于非ASCII字符,虽然仍然需要2个字节来表示,但在某些情况下,使用byte[]仍然可以节省内存空间。

  2. 提高性能:使用byte[]作为底层实现可以提高字符串操作的性能。因为byte[]数组的大小可以根据实际字符数据的需要动态调整,而不是固定为每个字符2个字节。这样可以减少内存分配和复制的次数,从而提高性能。

  3. 支持更多的字符集:Java 9引入了一种新的字符编码标准,即UTF-8。UTF-8是一种变长编码,可以使用1到4个字节来表示一个字符。通过使用byte[]作为底层实现,String可以更好地支持UTF-8编码,从而支持更多的字符集。

  4. 简化代码实现:在Java 9之前,String类的实现涉及到许多与char[]相关的复杂操作,例如字符数组的扩容、字符数组的复制等。通过将底层实现改为byte[],可以简化这些操作的实现,使代码更加简洁和易于维护。

总之,Java 9将String底层实现从char[]改成byte[]主要是为了节省内存空间、提高性能、支持更多的字符集以及简化代码实现。

String str="aaa"与 String str=new String(“aaa”)一样吗?new String(“aaa”);创建了几个字符串对象?

String str = "aaa"String str = new String("aaa") 在功能上是相同的,它们都创建了一个内容为 “aaa” 的字符串对象。但是,它们的创建方式和内存分配有所不同。

  1. String str = "aaa": 这种方式是通过字符串字面量创建字符串对象。Java编译器会在编译时将字符串字面量放入常量池(interned strings pool)中。当执行这行代码时,JVM会检查常量池中是否已经存在内容为 “aaa” 的字符串对象,如果存在,则直接引用该对象;如果不存在,则在常量池中创建一个新的字符串对象,并将其引用赋值给变量str。

  2. String str = new String("aaa"): 这种方式是通过new关键字创建字符串对象。首先,JVM会检查常量池中是否已经存在内容为 “aaa” 的字符串对象,如果存在,则在堆内存中创建一个新的字符串对象,并将其引用赋值给变量str;如果不存在,则在常量池中创建一个新的字符串对象,并在堆内存中再创建一个新的字符串对象,将其引用赋值给变量str。

关于 new String("aaa") 创建了几个字符串对象的问题:

  • 如果常量池中已经存在内容为 “aaa” 的字符串对象,那么只会在堆内存中创建一个新的字符串对象,总共创建了1个对象。
  • 如果常量池中不存在内容为 “aaa” 的字符串对象,那么会在常量池中创建一个字符串对象,并在堆内存中再创建一个新的字符串对象,总共创建了2个对象。

常量折叠

常量折叠是一种编译器优化技术,目的是减少运行时的计算开销

在Java中,常量折叠主要指的是编译期间对常量表达式的预处理和优化。具体来说,就是在编译阶段对程序中的某些常量表达式进行求值,将计算结果直接嵌入到生成的字节码中,从而避免了在程序运行时对这些表达式的重复计算。

例如,对于代码int a = 1 + 2;,经过常量折叠后,编译器会直接将其转换为int a = 3;。同样地,对于字符串字面量的相加操作,如String s1 = "a" + "b";,在编译期也会被优化为String s1 = "ab";。这样,在运行时就不需要再进行字符串连接操作,提高了代码的执行效率。

需要注意的是,常量折叠只适用于编译时常量,也就是那些在编译时就能确定其值的表达式。这包括数字字面量、字符串字面量以及被final修饰的字段。如果一个表达式中的变量或字段的值在编译时无法确定,那么这个表达式就无法进行常量折叠。

总的来说,常量折叠是提高程序运行效率的有效手段之一,它通过在编译阶段计算和简化表达式,减少了程序运行时的计算量和资源消耗。

代理模式

代理模式是一种常用的设计模式,它通过代理类来控制对被代理类的访问。在代理模式中,代理类和被代理类通常实现相同的接口,以便客户端可以无差别地使用它们。

以下是代理模式的一些关键点:

  1. 接口实现

    • 代理类(Proxy)和被代理类(RealSubject)都实现相同的接口(Subject)。这确保了客户端可以透明地使用代理对象,就像它是真正的对象一样。
  2. 引用持有

    • 代理类包含一个对被代理类对象的引用。这个引用可以在代理类的构造函数中通过参数传递,或者在运行时动态设置。
  3. 方法调用

    • 当客户端调用代理类的方法时,代理类可以在调用被代理类的实际方法之前或之后执行一些附加操作,如访问控制、日志记录、缓存处理等。
    • 根据代理的目的,代理类可以选择在某些情况下不调用被代理类的方法。例如,远程代理可能只在需要时才连接到远程服务器。
  4. 代理目的

    • 代理模式可以有不同的目的,包括但不限于:
      • 远程代理:为远程对象提供本地代理。
      • 虚拟代理:根据需要创建开销大的对象。
      • 安全代理:在调用实际方法前进行权限检查。
      • 智能引用:额外的逻辑,如引用计数、懒加载等。
      • 日志记录代理:记录方法调用的信息。
      • 防火墙代理:保护对象不被直接访问。
  5. 结构

    • 通常,代理模式的结构包括三个角色:
      • 抽象主题(Subject):定义真实主题和代理共同遵循的接口。
      • 真实主题(RealSubject):实现抽象主题的具体类,即被代理对象。
      • 代理(Proxy):含有对真实主题的引用,并可以在调用真实主题的方法前后添加其他行为。
  6. 客户端交互

    • 客户端通过代理接口与代理对象交云,不需要知道它正在与代理还是真实对象交云。这使得可以在不影响客户端代码的情况下切换或增加代理逻辑。
  7. 实现方式

    • 代理可以是静态的或动态的。静态代理通常手动编写,每个代理类对应一个被代理类。动态代理利用反射API在运行时动态生成代理类的字节码。
  8. 优点

    • 代理模式提供了一种灵活的方式来扩展或增强对象的功能,而无需修改原有对象的代码。
    • 它可以实现懒加载、访问控制、日志记录等功能,而不会影响到真实对象的业务逻辑。
  9. 缺点

    • 由于引入了代理层,可能会有一些性能开销。
    • 对于动态代理,可能需要处理额外的异常,如反射相关的异常。

在实际应用中,代理模式广泛应用于各种场景,如框架中的ORM代理、远程服务调用的代理、Spring框架的AOP代理等。通过代理模式,可以有效地分离关注点,提高系统的灵活性和可维护性。

JDK 动态代理

JDK动态代理是Java中的一种代理机制,它允许在运行时动态地创建一个代理对象,用于拦截和增强目标对象的方法调用。以下是对JDK动态代理的详细分析:

  1. 代理类的结构

    • JDK动态代理生成的代理类默认继承自java.lang.reflect.Proxy类。
    • 代理类实现了被代理类实现的所有接口。
    • 在代理类中重写了被代理类实现的接口方法,并在这些方法内部调用了InvocationHandlerinvoke方法。
  2. 代理对象的创建

    • 要创建代理对象,需要提供一个加载器(通常是当前线程的上下文加载器)、一个或多个接口(被代理类实现的接口)以及一个InvocationHandler实例。
    • 通过调用java.lang.reflect.Proxy类的静态方法newProxyInstance来创建代理对象。
  3. 方法调用的处理

    • 当客户端通过代理对象调用方法时,代理类会将调用请求委托给InvocationHandlerinvoke方法。
    • invoke方法接收到调用请求后,可以根据需要执行一些额外的逻辑,然后决定是否调用实际的目标方法。
  4. 局限性与限制

    • 由于Java是单继承的语言,JDK动态代理只能代理接口,不能代理类。这意味着被代理的对象必须是接口类型,不能是类类型。
    • 由于代理类是通过实现接口来工作的,因此无法直接代理被代理类的私有方法。私有方法通常不会被接口定义,因此代理类无法访问它们。
  5. 与CGLIB动态代理的比较

    • CGLIB动态代理是另一种代理机制,它不依赖于接口,而是通过继承被代理类来创建代理对象。
    • CGLIB动态代理可以代理类的方法,包括私有方法,因此在某些情况下可以解决JDK动态代理的局限性。
    • CGLIB动态代理的性能可能略低于JDK动态代理,因为它需要使用字节码操作来生成代理类。

总的来说,JDK动态代理是一种基于接口的代理机制,它可以在运行时动态地创建代理对象,并允许在方法调用前后执行额外的逻辑。然而,它的局限性在于只能代理接口,并且无法直接代理私有方法。相比之下,CGLIB动态代理提供了更灵活的代理能力,但可能会引入额外的性能开销。

CGLIB动态代理

CGLIB动态代理的工作原理

CGLIB(Code Generation Library)是一个强大的、高性能、高质量的Code生成类库,它广泛的被许多AOP框架使用,如Spring AOP和dynaop,为他们提供方法的拦截。

CGLIB动态代理的工作原理是通过继承被代理类来创建新的子类,在子类中实现对方法调用的拦截和增强。CGLIB通过使用一个字节码生成器来动态生成新的子类,这个子类实现了被代理类的所有方法,并提供了一个调用处理器(MethodInterceptor),用于在方法调用前后执行自定义的逻辑。

与Java原生代理(JDK Proxy)的区别
  1. 实现方式

    • JDK Proxy是基于接口的代理,要求被代理对象必须实现一个或多个接口。
    • CGLIB是基于类的继承来实现代理,不要求被代理对象实现接口,因此可以代理普通的类。
  2. 代理限制

    • JDK Proxy只能代理接口中声明的方法。
    • CGLIB可以代理类中所有的非final方法,包括私有方法。
  3. 性能

    • JDK Proxy通常有更好的性能,因为它直接利用了Java的反射机制。
    • CGLIB由于需要生成新的类,可能会有一些性能开销,但在最新版本中已经得到了优化。
  4. 使用场景

    • JDK Proxy适用于实现接口的目标对象。
    • CGLIB适用于没有实现接口或者需要代理非public方法的目标对象。
CGLIB动态代理的使用示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLIBProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        SomeClass target = new SomeClass();

        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method call: " + method.getName());
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method call: " + method.getName());
                return result;
            }
        });

        // 创建代理对象
        SomeClass proxy = (SomeClass) enhancer.create();

        // 调用代理对象的方法
        proxy.someMethod();
    }
}

class SomeClass {
    public void someMethod() {
        System.out.println("Executing someMethod...");
    }
}
CGLIB动态代理的配置和使用场景

在Spring框架中,可以通过配置来选择使用CGLIB动态代理:

<bean id="someBean" class="com.example.SomeClass">
    <aop:scoped-proxy/>
bean>

CGLIB动态代理适用于以下场景:

  • 当目标对象没有实现任何接口时,可以使用CGLIB动态代理。
  • 当需要代理目标对象的非public方法时,可以使用CGLIB动态代理。
  • 当需要在代理对象中添加额外的行为,例如日志记录、性能监控等,可以使用CGLIB动态代理。
常见问题及解决方法
  1. 性能问题

    • 尽量使用最新版本的CGLIB库,以获得更好的性能。
    • 如果可能,尽量使用JDK Proxy,因为它通常有更好的性能。
  2. 无法代理final方法

    • CGLIB无法代理final方法,因为final方法不能被子类覆盖。如果需要代理final方法,可以考虑使用其他AOP框架,如AspectJ。
  3. 版本兼容性问题

    • 确保使用的CGLIB版本与项目中的其他库兼容。如果有冲突,可以尝试升级或降级CGLIB版本。
CGLIB适用于没有实现接口或者需要代理非public方法的目标对象 为什么 public 不行

CGLIB 能够代理没有实现接口的目标对象,以及需要代理非 public 方法(包括 private、protected 和默认包访问权限的方法)的目标对象。对于 public 方法,使用 CGLIB 进行代理也是可行的,但关键在于 CGLIB 的代理机制提供了一种能力去代理那些在 JDK 动态代理中无法代理的方法。

在 CGLIB 中,当目标类没有实现任何接口时,或者你想代理的是类的非 public 方法时,CGLIB 通过生成目标类的子类来实现代理。这种代理方式允许你拦截并增强目标类的所有非 final 方法,无论这些方法是 public、protected、还是 private。

然而,对于 public 方法,如果你的目标类实现了一个或多个接口,并且你只想代理这些接口中定义的 public 方法,那么使用 JDK 动态代理通常是更好的选择,因为它更简单、性能也通常更好一些。JDK 动态代理只能代理接口中声明的方法,而不能代理类中新增的非接口方法。

总的来说,public 方法当然可以被 CGLIB 代理,但选择 CGLIB 来代理 public 方法通常是出于以下原因:

  1. 目标类没有实现任何接口:在这种情况下,JDK 动态代理无法使用,因此 CGLIB 是一个替代方案。
  2. 需要代理非 public 方法:CGLIB 可以代理非 public 方法,而 JDK 动态代理不能。
  3. 设计选择或习惯:有些开发者或团队可能会选择 CGLIB 作为所有代理需求的解决方案,以保持一致性。
  4. 高级用例:例如,当需要代理的方法调用涉及到复杂的逻辑判断,或者需要在代理实例创建前后执行一些特定逻辑时,CGLIB 提供的 MethodInterceptorCallbackFilter 等高级特性可能更加适合。

因此,说 CGLIB 适用于代理非 public 方法,并不是指它不能代理 public 方法,而是强调其独特的能力在于代理那些通常无法通过标准的 JDK 动态代理来处理的方法。

参考文章—JDK动态代理和CGLIB动态代理

你可能感兴趣的:(Java,java,python,开发语言)