Java高级笔记(第二章)

1. 类型信息概述

1.1 类型信息的定义

  • 自然界中的每个物体都有描述信息(如性别、年龄),而计算机中对象的信息通常用字段值表示。
  • 类型信息记录了类的名称、字段、方法、父类或接口等信息。
  • JVM通过类型信息认识和识别对象,赋予程序动态性。

1.2 类型信息存储

1.2.1 Java文件与Class文件

  • .java 文件:存储源代码。
  • .class 文件:由Java编译器生成,存储二进制字节码。
    • 每个类或接口对应一个.class文件。
    • 内部类以外部类+$+内部类名命名。
    • 并行类会生成多个.class文件。

1.2.2 Class文件结构

  • 包括魔数、访问标志、常量池、字段表、方法表、属性表等。
  • 常量池占整个类大小的60%左右。

1.2.3 JVM加载过程

  • 编译阶段:

    1. 翻译类文件(文本 -> 二进制)。
    2. 动态添加一个公有的静态常量属性.class,其为Class类实例。
  • 加载机制:

    • 预先加载:启动时加载所有类。
    • 按需加载:使用时动态加载。
  • 类加载顺序:

    1. 加载基础类(Bootstrap Loader)。
    2. 加载包含main()函数的类(AppClassLoader)。
    3. 按需加载其他类。
  • JVM三种预定义类型类加载器:

    • 引导类(Bootstrap)加载器:用原生代码实现,负责加载 <Java_Runtime_Home>/lib 下的类库。
    • 扩展类(Extension)加载器:由 SunExtClassLoader 实现,负责加载 <Java_Runtime_Home>/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库。
    • 系统类(System)加载器:由 SunAppClassLoader 实现,负责加载系统类路径(CLASSPATH)中指定的类库。
  • 父类和子类的加载顺序:当一个类具有继承关系时,加载顺序是从顶级类开始,依次加载直至加载到这个类本身。

  • 引用类的加载

    • 未初始化的静态引用:不会触发类的加载。
      public class Course {
          static {
              System.out.println("Course prepare!");
          }
      }
      
      public class Teacher {
          static {
              System.out.println("Teacher prepare!");
          }
          public static Course course;
      }
      
    • 初始化的静态引用:会触发类的加载。
      public static Course course = new Course();
      

2. 核心类

2.1 Class类

  • Class对象是类型信息的核心,用于描述类的整体信息。
  • 获取方式:
    1. .class 属性:直接通过类名获取。
    2. getClass() 方法:通过对象实例获取。
    3. Class.forName(String className) 方法:通过类的全限定名获取。

2.2 Constructor类

  • 描述构造方法,可通过以下方法获取:
    • getConstructor(Class... parameterTypes):获取指定参数类型的公有构造方法。
    • getConstructors():获取所有公有构造方法。
    • getDeclaredConstructor(Class... parameterTypes):获取任意访问权限的构造方法。
    • getDeclaredConstructors():获取所有构造方法。

2.3 Method类

  • 描述方法,可通过以下方法获取:
    • getMethod(String name, Class... parameterTypes):获取指定名称和参数类型的公有方法。

    • getMethods():获取所有公有方法。不仅包括本身类定义的方法描述对象,还包含继承自父类或接口的方法描述对。象

    • getDeclaredMethod(String name, Class... parameterTypes):获取任意访问权限的方法。

    • getDeclaredMethods():获取所有方法。

2.4 Field类

  • 描述字段,可通过以下方法获取:
    • getField(String name):获取指定公有字段。
    • getFields():获取所有公有字段。
    • getDeclaredField(String name):获取任意访问权限的字段。
    • getDeclaredFields():获取所有字段。

3. 类型信息应用——运行时类型识别

3.1 RTTI简介

  • 运行时类型识别(RTTI):在程序运行时动态识别对象和类的信息。

运行时类型识别(RTTI)是Java语言中的一个重要概念,它允许程序在运行时检查对象和类的信息。这为编写更加灵活和动态的代码提供了可能。

3.2 实现方式

RTTI主要包括以下几种方式:

  • 使用instanceof关键字。
  • 利用Class类提供的方法,比如isInstance()getClass()等。

instanceof关键字

instanceof用于判断一个对象是否是指定类型的实例或其子类的实例。如果表达式返回true,则表示该对象可以安全地转换为指定类型;如果返回false,则不能进行这种转换。

示例:

if (x instanceof Dog) {
    ((Dog)x).bark();
}

在这个例子中,首先使用instanceof检查变量x是否是Dog类的一个实例。如果是,则将x强制转换为Dog类型,并调用bark()方法。

Class.isInstance()方法

Class.isInstance()方法提供了一种更灵活的方式来检查对象是否属于某个特定类型。此方法实际上是对instanceof运算符的动态等价形式。它的参数是要检查的对象,如果对象是指定类或其子类的一个实例,则返回true

示例解释:

boolean isDog = Dog.class.isInstance(x);

这段代码的作用与instanceof关键字类似,用来判断对象x是否为Dog类的实例或者其子类的实例。如果x确实是Dog类或其派生类的一个实例,那么isDog变量将会被设置为true;否则,isDog会被设置为false

这种机制可以实现在不知道具体类名的情况下比较对象类型,增加了代码的灵活性和可重用性。对于需要处理多种类型对象的情况非常有用,特别是在设计模式如工厂模式、策略模式等中经常会被用到。此外,Class.isInstance()还可以用于反射操作中,以动态地确定对象的类型。


4. 类型信息应用——反射

4.1 反射机制

  • 在运行时动态获取类的所有属性和方法,并调用对象的成员。
  • 主要步骤:
    1. 获取操作类的Class对象。
    2. 调用getDeclaredMethods()等方法获取类定义信息。
    3. 使用反射API操作这些信息。

4.2 反射的主要功能

  1. 加载类
    • 使用Class.forName(String className)加载类。
  2. 实例化类
    • Class.newInstance()(已废弃)。
    • clazz.getDeclaredConstructor().newInstance()
  3. 执行方法
    • Method.invoke(Object obj, Object... args)
  4. 修改属性
    • Field.set(Object obj, Object value)
  5. 修改访问权限
    • AccessibleObject.setAccessible(boolean flag)

5. 代理

5.1 什么是代理模式?

代理模式(Proxy Pattern)

定义:代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。

代理通常有以下几个用途:

  • 延迟初始化(Lazy Initialization):在需要时才创建的对象。
  • 存取控制(Access Control):限制对原始对象的访问权限。
  • 日志记录(Logging):在方法调用前后记录日志。
  • 缓存(Caching):为结果添加缓存功能以提高性能。

5.2 静态代理

静态代理

静态代理意味着代理类是在编译时就已经确定好的,并且每个业务类都需要一个对应的代理类。这种方式虽然简单直接,但扩展性和维护性较差。

组成部分

  1. 接口:定义了代理和目标对象共同实现的方法。
  2. 目标对象:实现了接口并提供了实际的业务逻辑。
  3. 代理对象:同样实现了接口,但在调用接口方法时,可以添加额外的操作。

示例

public interface Speakable {
    void speak(String message);
}

public class Person implements Speakable {
    @Override
    public void speak(String message) {
        System.out.println("Speak: " + message);
    }
}

public class PersonProxy implements Speakable {
    private final Person person;

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void speak(Stringmessage) {
        System.out.println("Before speaking");
        person.speak(message);
        System.out.println("Time: " + System.currentTimeMillis());
    }
}

5.3 动态代理

5.3.1 动态代理概述

动态代理:在程序运行期间,JVM 动态生成一个代理类来代理目标对象,实现对方法调用的拦截与增强。

它常用于实现 日志记录、事务管理、权限控制等横切关注点,而无需修改原始业务逻辑代码。

5.3.2 Java 动态代理的核心组件

组件名称说明
Proxy核心类,负责创建所有代理类。生成的代理类是它的子类,并实现了目标接口。
InvocationHandler调用处理器接口,定义了 invoke() 方法,用于处理代理对象的方法调用。
ProxyHandler(如 LogHandler实现 InvocationHandler 接口,封装了增强逻辑(如日志、时间统计)。
Proxied(委托类)真实的目标对象,被代理的对象,通常通过 ProxyHandler 持有其引用。

5.3.3 动态代理使用示例

5.3.3.1. 定义接口(DataService)
public interface DataService {
    String getDataById(String id);
}

5.3.3.2 实现类(委托类 - DataServiceImpl)

import java.util.HashMap;
import java.util.Map;

public class DataServiceImpl implements DataService {
    private Map<String, String> dataMap = new HashMap<>();

    public DataServiceImpl() {
        // 初始化一些数据
        dataMap.put("1", "Data for ID 1");
        dataMap.put("2", "Data for ID 2");
        dataMap.put("3", "Data for ID 3");
    }

    @Override
    public String getDataById(String id) {
        System.out.println("从数据源获取数据...");
        return dataMap.get(id);
    }
}

5.3.3.3 调用处理器(CacheInvocationHandler)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CacheInvocationHandler implements InvocationHandler {
    private final Object target;
    private final Map<String, Object> cache = new HashMap<>();

    public CacheInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String key = method.getName() + Arrays.toString(args);

        // 检查缓存中是否存在结果
        if (cache.containsKey(key)) {
            System.out.println("从缓存中读取数据...");
            return cache.get(key);
        } else {
            // 如果缓存中不存在,则调用目标对象的方法
            Object result = method.invoke(target, args);
            // 将结果放入缓存
            cache.put(key, result);
            return result;
        }
    }
}

5.3.3.4 创建代理并调用(主程序)

import java.lang.reflect.Proxy;

public class Bootstrap {
    public static void main(String[] args) {
        // 创建目标对象
        DataService dataService = new DataServiceImpl();

        // 创建代理对象
        DataService proxy = (DataService) Proxy.newProxyInstance(
                DataService.class.getClassLoader(),
                new Class[]{DataService.class},
                new CacheInvocationHandler(dataService)
        );

        // 使用代理对象调用方法
        System.out.println(proxy.getDataById("1"));
        System.out.println(proxy.getDataById("1")); // 第二次调用,应该从缓存中获取

        System.out.println(proxy.getDataById("2"));
        System.out.println(proxy.getDataById("2")); // 同样,第二次调用应该从缓存中获取
    }
}

输出示例

当你运行上述代码时,输出可能如下:

从数据源获取数据...
Data for ID 1
从缓存中读取数据...
Data for ID 1
从数据源获取数据...
Data for ID 2
从缓存中读取数据...
Data for ID 2

5.3.4 关键 API 和方法解析

InvocationHandler 接口

  • 作用

    • 封装通用逻辑(如日志、权限检查等)。
    • 提供统一入口处理代理对象的方法调用。
  • 方法签名

Object invoke(Object proxy, Method method, Object[] args)
参数含义
proxy生成的代理对象
method被调用的方法对象
args方法参数数组

Proxy.newProxyInstance() 方法

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数含义
loader类加载器,用于加载生成的代理类
interfaces代理类需要实现的接口列表
h调用处理器,代理方法执行时会回调它的 invoke 方法

5.3.5 代理类的构造过程(底层原理)

  1. 参数校验:确保 InvocationHandler 不为空;
  2. 访问权限判断:如果是非公共类需特殊处理;
  3. 获取或生成代理类
    • 先查缓存是否存在;
    • 若无,则动态生成字节码并加载进 JVM;
  4. 创建代理实例
    • 调用代理类的构造函数;
    • 构造出最终的代理对象;

⚠️ 注意:生成的代理类继承自 Proxy,并实现你传入的接口。


5.3.6 注意事项

项目说明
必须实现接口JDK 动态代理只能代理实现了接口的类
性能开销使用反射机制,性能略低
适用于 AOP 场景如日志记录、事务控制、安全验证等
不能代理 final 类/方法因为 CGLIB 基于继承,final 类无法被继承

5.3.7 JDK 动态代理 vs CGLIB

对比项JDK 动态代理CGLIB
是否需要接口✅ 是❌ 否
实现代理方式接口实现子类继承
支持 final 类/方法❌ 不支持❌ 不支持(无法继承)
性能相对较低(反射)较高(直接调用)
应用场景Spring AOP 默认使用 JDK 代理(基于接口)无接口时使用 CGLIB,Spring Boot 自动切换

6 动态代理loader参数

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 是 JDK 动态代理的核心方法,用于动态生成代理对象。其中,loader 参数的作用非常重要。

1. ClassLoader loader 参数的作用

loader 指定了用于加载代理类的类加载器(ClassLoader)。它决定了代理类会被哪个类加载器加载到 JVM 中。

什么是类加载器?

在 Java 中,类加载器(ClassLoader)负责将 .class 文件加载到 JVM 中,并将其转换为 Class 对象。Java 的类加载机制是分层的,常见的类加载器包括:

  • Bootstrap ClassLoader:加载核心类库(如 java.lang.*)。
  • Extension ClassLoader:加载扩展类库。
  • Application ClassLoader:加载应用程序类路径下的类。
  • 自定义类加载器:用户可以定义自己的类加载器。

每个类加载器都有其特定的职责范围,并且它们之间遵循“双亲委派模型”(Parent Delegation Model),即优先委托父类加载器加载类。

2. 为什么需要指定 ClassLoader

在动态代理中,Proxy.newProxyInstance() 方法会动态生成一个代理类。这个代理类需要被加载到 JVM 中才能被使用。因此,必须明确指定一个类加载器来完成这一任务。

具体原因

  1. 动态生成的代理类需要被加载

    • 动态代理会在运行时生成一个新的代理类(例如 $Proxy0),这个类实现了目标类的所有接口。
    • 为了使这个代理类能够被 JVM 使用,必须通过类加载器将其加载到内存中。
  2. 类加载器决定了代理类的可见性

    • 在 Java 中,类的可见性和作用域是由类加载器决定的。如果代理类和目标类不在同一个类加载器下,可能会导致类加载冲突或无法访问的问题。
    • 因此,通常会使用目标类的类加载器来加载代理类,以确保代理类和目标类在同一个类加载器范围内。
  3. 支持模块化和隔离

    • 在复杂的应用程序中(如 Web 容器、插件系统),不同模块可能使用不同的类加载器。通过指定类加载器,可以确保代理类被正确加载到目标模块中。

3. loader 参数的实际意义

示例代码

Speakable proxy = (Speakable) Proxy.newProxyInstance(
    person.getClass().getClassLoader(), // 指定类加载器
    person.getClass().getInterfaces(), // 目标类实现的接口
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before method: " + method.getName());
            Object result = method.invoke(person, args);
            System.out.println("After method: " + method.getName());
            return result;
        }
    }
);

在这个例子中,person.getClass().getClassLoader() 获取了目标类 Person 的类加载器,并将其传递给 Proxy.newProxyInstance() 方法。

作用解释

  • person.getClass().getClassLoader()

    • 获取目标类 Person 的类加载器。
    • 确保代理类和目标类由同一个类加载器加载,避免类加载冲突。
  • 为什么要用目标类的类加载器?

    • 如果代理类和目标类不在同一个类加载器范围内,可能会导致以下问题:
      1. 类型不匹配:代理类和目标类被视为不同的类型。
      2. 类加载冲突:目标类的接口可能无法被代理类加载器找到。
    • 因此,使用目标类的类加载器是最安全的选择。

4. 如果不指定类加载器会发生什么?

如果不显式指定类加载器,或者指定错误的类加载器,可能会导致:

  1. 类加载失败

    • 如果代理类的类加载器无法找到目标类实现的接口,就会抛出 ClassNotFoundExceptionNoClassDefFoundError
  2. 类型不匹配

    • 即使代理类成功加载,但如果代理类和目标类由不同的类加载器加载,可能会导致类型不匹配问题。例如:
      Speakable proxy = (Speakable) Proxy.newProxyInstance(...);
      
      这里的强制类型转换可能会失败,抛出 ClassCastException
  3. 模块隔离问题

    • 在复杂的模块化系统中,不同模块可能使用不同的类加载器。如果代理类被加载到错误的模块中,可能会导致类不可见或功能异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值