JavaSE——反射(类加载器,双亲委派)

本文深入讲解Java反射机制,包括反射的基本概念、如何使用反射取得类信息、实例化对象,以及反射与类操作、构造方法、普通方法的关系。此外,还探讨了反射在代理设计模式中的应用,以及动态代理设计模式的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.初识反射

1.1.认知反射

反射指的是对象的反向处理操作,根据对象倒推类的组成,默认情况下,必须要先导入一个包,而后才能产生类的实例化对象,如下:

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date() ;
    }
}

以上是我们正常的关于对象的处理流程:根据包名.类名找到类,而反射的"反"指的是根据对象来取得对象的来源信息,而这个"反"的操作核心的处理就在于Object类的一个方法:取得Class对象:

public final native Class<?> getClass();

该方法返回的是一个Class类对象,这个Class描述的就是类,调用getClass()方法示例如下:

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date() ;
        System.out.println(date.getClass());
    }
}

此时通过对象取得了对象的来源,这就是"反"的本质,在反射的世界里面,看重的不再是一个对象,而是对象身后的组成(类、构造、普通、成员等),Class类描述接口与类的组成,Class对象由JVM在第一次加载类时产生,并且全局唯一

1.2.取得任意类Class对象的方法

Class类是描述整个类的概念,也是整个反射的操作源头,在使用Class类的时候需要关注的是这个类对象,而这个类对象的产生模式一共有三种:

  • 调用对象的getClass()取得Class对象,该方法在Object类中
  • 类名称.class取得class对象
  • Class.forName(类全名称)取得Class对象

Class.forName方法示例:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.util.Date") ;
        System.out.println(cls.getName());
    }
}

1.3.反射取得实例化对象

public T newInstance():通过反射实例化类对象

public class Test {
    public static void main(String[] args) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException {
        Class<?> cls = Class.forName("java.util.Date") ;
        Object obj = cls.newInstance() ; // 实例化对象,等价于 new java.util.Date() ;
    }
}

取得了Class对象就意味着取得了一个指定类的操作权,除了关键字new之外,对于对象的实例化模式有了第二种做法:反射

1.4.反射vs工厂模式

工厂设计模式原则:如果是自己编写的接口,要想取得本接口的实例化对象,最好使用工厂类来设计

传统工厂类:

interface IFruit {
    public void eat() ;
}
class Apple implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Apple] 吃苹果 ");
    }
}
class FruitFactory {
    private FruitFactory() {}
    public static IFruit getInstance(String className) {
        if ("apple".equals(className)) {
            return new Apple() ;
        }
        return null ;
    }
}

public class Test {
    public static void main(String[] args) {
        IFruit fruit = FruitFactory.getInstance("apple") ;
        fruit.eat() ;
    }
}

以上传统工厂有一个很大的问题:每增加一个接口的子类就需要修改工厂类去new对应的对象,如下:

interface IFruit {
    public void eat() ;
}
class Apple implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Apple] 吃苹果 ");
    }
}

class Orange implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Orange] 吃橘子 ");
    }
}
class FruitFactory {
    private FruitFactory() {}
    public static IFruit getInstance(String className) {
        if ("apple".equals(className)) {
            return new Apple() ;
        }else if ("orange".equals(className)) {
            return new Orange() ;
        }
        return null ;
    }
}

想解决关键字new带来的问题,最好的做法就是通过反射来完成处理,因为Class类可以使用newInstance()实例化对象,同时Class.forName()能够接收类名称:

interface IFruit {
    public void eat() ;
}
class Apple implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Apple] 吃苹果 ");
    }
}

class Orange implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Orange] 吃橘子 ");
    }
}

class FruitFactory {
    private FruitFactory() {}
    public static IFruit getInstance(String className) {
        IFruit fruit = null ;
        try {
            fruit = (IFruit) Class.forName(className).newInstance() ;
        } catch (InstantiationException | IllegalAccessException |
                ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return fruit ;
    }
}

public class Test {
    public static void main(String[] args) {
        IFruit fruit = FruitFactory.getInstance("xpu.edu.Orange") ;
        fruit.eat() ;
    }
}
//[Orange] 吃橘子

引入反射后,每当新增接口子类,无需去修改工厂类代码就可以很方便的进行接口子类扩容。以上这种工厂类代码我们称之为简单工厂模式,反射是简单工厂的一种实现方式

2.反射与类操作

利用反射可以做出一个对象具备的所有操作行为,最为关键的是这一切的操作都可以基于Object进行

2.1.取得父类(父接口)信息

在java中任何的程序类都一定会有父类,在Class类中就可以通过如下方法来取得父类或者实现的父接口:

  • 取得类的包名称::public Package getPackage()
  • 取得父类的Class对象:public native Class<? super T> getSuperclass()
  • 取得实现的父接口:public Class<?>[] getInterfaces()

示例如下:

interface IFruit {}
interface IMessage{}
class CLS implements IFruit,IMessage{}
public class Test {
    public static void main(String[] args) {
        // 取得类对象
        Class<?> cls = CLS.class ;
        // 取得Package名称
        System.out.println(cls.getPackage().getName());
        // 取得父类名称
        System.out.println(cls.getSuperclass().getName());
        // 取得实现的父接口对象
        Class<?>[] iClass = cls.getInterfaces() ;
        for (Class<?> class1 : iClass) {
            System.out.println(class1.getName());
        }
    }
}
//xpu.edu
//java.lang.Object
//xpu.edu.IFruit
//xpu.edu.IMessage

通过反射可以取得类结构上的所有关键信息

2.2.反射调用构造——Constructor(描述类构造方法信息)

一个类中可以存在多个构造方法,如果要想调用类中构造,就可以使用Class类中提供的两个方法:

  • 取得本类中指定参数类型的构造方法
// 返回指定参数类型public的构造器
public Constructor <T> getConstructor(Class<?>... parameterTypes)
//返回指定参数类型的private和public构造器
public Constructor <T> getDeclareConstructor(Class<?>... parameterTypes)
  • 取得本类所有构造方法
public Constructor<?>[] getConstructors() throws SecurityException
  • Class类的newInstance()实际上调用的是类中的无参构造,如果类中没有无参构造或者权限不是public,此方法无用,只能调用Constructor类提供的newInstance方法使用有参构造来实例化对象
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException

取得类中所有构造信息示例如下:

class Person {
    public Person() {}
    public Person(String name) {}
    public Person(String name,int age) {}
}
public class Test {
    public static void main(String[] args) {
        Class<?> cls = Person.class ;
        // 取得类中的全部构造
        Constructor<?>[] constructors = cls.getConstructors() ;
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}

//public Person()
//public Person(java.lang.String)
//public Person(java.lang.String,int)

以上的操作是直接利用了Constructor类中的toString()方法取得了构造方法的完整信息(包含方法权限,参数列表),而如果你只使用了getName()方法,只会返回构造方法的包名.类名

在定义简单java类的时候一定要保留有一个无参构造,Class类通过反射实例化类对象(newInstance())的时候,实际上调用的是类中的无参构造,如果现在类中没有无参构造或者权限不是public,则无法使用此方法,只能够通过明确的构造调用实例化处理

//通过Constructor类实例化对象
class Person {
    private String name ;
    private int age ;
    public Person(String name,int age) {
        this.name = name ;
        this.age = age ;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
public class Test {
    public static void main(String[] args) throws InstantiationException,
            IllegalAccessException, NoSuchMethodException, SecurityException,
            IllegalArgumentException, InvocationTargetException {
        Class<?> cls = Person.class ;
        // 取得指定参数类型的构造方法对象
        Constructor<?> cons = cls.getConstructor(String.class,int.class) ;
        System.out.println(cons.newInstance("小可爱",29));
    }
}

//Person [name=小可爱, age=29]

2.3.反射调用普通方法(核心)

  • 获得所有的public修饰的方法(包含父列的方法也包含Object类)
public Method[] getMethods()
  • 获得所有成员方法,包括私有的但不包括继承的
public Method[] getDeclaredMethods()
  • 获得指定参数的权限为public的普通方法
public Method getMethod(String name,Class<?>... parameterTypes)
  • 获得指定参数的成员方法,权限任意,且只能是本类中的方法
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
  • Method类提供的反射调用普通方法
public Object invoke(Object obj,Object..args)
//反射调用普通方法,其中obj是实例化对象,args是普通方法参数

之前程序编写的简单java类中的getter、setter方法采用的都是明确的对象调用,而现在有了反射机制处理之后,即使你没有明确的对象(依然需要实例化对象,Object对象描述,所有的普通方法必须在有实例化对象之后才可以进行调用)也可以通过反射调用setter、getter方法,示例如下:

class Person {
    private String name ;
    private int age ;
    public Person() {}
    public Person(String name,int age) {
        this.name = name ;
        this.age = age ;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("Person") ;
        // 任何时候调用类中的普通方法都必须有实例化对象
        Object obj = cls.newInstance() ;
        // 取得setName这个方法的实例化对象,设置方法名称与参数类型
        Method setMethod = cls.getMethod("setName", String.class) ;
        // 随后需要通过Method类对象调用指定的方法,调用方法需要有实例化对象
        // 同时传入参数
        setMethod.invoke(obj, "小可爱") ; // 相当于Person对象.setName("yuisama") ;
        Method getMethod = cls.getMethod("getName") ;
        Object result = getMethod.invoke(obj) ; // 相当于Person对象.getName() ;
        System.out.println(result) ;
    }
}
//小可爱

2.4.反射调用类中属性

类中的所有属性一定在类对象实例化之后才会进行空间分配,所以此时如果要想调用类的属性,必须保证有实例化对象。在Class类中提供有两组取得属性的方法:

  • 第一组(父类中)取得类中全部属性:
public Field[] getFields() throws SecurityException
  • 第一组(父类中)取得类中指定名称属性::
public Field getField(String name) throws
NoSuchFieldException, SecurityException
  • 第二组(本类中)取得类中全部属性:
public Field[] getDeclaredFields() throws SecurityException
  • 第二组(本类中)取得类中指定名称属性:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

示例如下:

class Person {
    public String name ;
    public int age ;
}
class Student extends Person {
    private String school ;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("Student") ;
        {
            // 普通代码块
            // 第一组-取得类中全部属性
            Field[] fields = cls.getFields() ;
            for (Field field : fields) {
                System.out.println(field) ;
            }
        }
        System.out.println("------------------------");
        {
            // 第二组-取得类中全部属性
            Field[] fields = cls.getDeclaredFields() ;
            for (Field field : fields) {
                System.out.println(field);
            }
        }
    }
}

//public java.lang.String Person.name
//public int Person.age
//------------------------
//private java.lang.String Student.school

在实际开发之中,属性基本上都会进行封装处理,所以没有必要去关注父类中的属性,也就是说以后所取得的属性都以本类属性为主,而后就需要关注属性的核心描类:java.lang.reflect.Field,在这个类之中有两个重要方法:

  • 设置属性内容
public void set(Object obj, Object value) throws IllegalArgumentException,IllegalAccessException
  • 取得属性内容
public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException

在这里插入图片描述

2.5动态设置封装

在这里插入图片描述
在AccessibleObject类中提供有一个方法可以动态设置封装:

public void setAccessible(boolean flag) throws SecurityException

示例如下:

class Person {
    private String name ;
}
public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("Person") ;
        // 实例化本类对象
        Object obj = cls.newInstance() ;
        // 操作name属性
        Field nameField = cls.getDeclaredField("name") ;
        // 取消封装
        nameField.setAccessible(true) ;
        // ----------------------------
        nameField.set(obj, "小可爱") ; // 相当于对象.name = "小可爱"
        System.out.println(nameField.get(obj)); // 取得属性
    }
}
//小可爱

在Field类之中有一个方法可以取得属性类型:

public Class<?> getType()

示例如下:

class Person {
    private String name ;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("Person") ;
        // 实例化本类对象
        Object obj = cls.newInstance() ;
        // 操作name属性
        Field nameField = cls.getDeclaredField("name") ;
        // 包.类
        System.out.println(nameField.getType().getName()) ;
        // 类名称
        System.out.println(nameField.getType().getSimpleName()) ;
    }
}
//java.lang.String
//String

3.ClassLoader类加载器

3.1.认识ClassLoader

Class类描述的是整个类的信息,在Class类中提供的forName()方法,这个方法根据ClassPath配置的路径进行类的加载,如果说现在你的类的加载路径可能是网络、文件,这个时候就必须实现类加载器,也就是ClassLoader类的主要作用

  • 类加载:通过一个类的全名称来获取此类的二进制字节
  • 类加载器:实现类加载功能的代码模块

在这里插入图片描述

  • Bootstrap(启动类加载器):
  • 特点:使用本地代码实现(比如C),是JVM自身一部分,独立于JVM外部,并且无法被java程序直接引用
  • 作用:负责将存放于<Java_HOME>\lib目录下(或者被-Xbootclasspath参数指定路径中)能被虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到JVM内存中
  • ExtClassLoader(扩展类加载器):
  • 特点:使用 java语言实现,能被java程序直接引用
  • 作用:负责加载<Java_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量指定的路
    径中的类库
  • AppClassLoader(应用程序类加载器):
  • 特点:使用 java语言实现,如果用户没有自定义加载器,那么APPClassLoader就是程序中默认的类加载器
  • 作用:负责加载用户类路径(ClassPath)上指定的类库

3.2.双亲委派模型

四种类加载器加上自定义的类加载器的层次关系成为类加载器的双亲委派模型:
在这里插入图片描述
双亲委派模型工作流程:如果一个类加载器收到了类加载请求,首先自己不回去加载此类,而是将此加载请求委托给父加载器去完成,每一层加载器都是如此,只有当父加载器无法加载此类时(在自己搜索范围中没有找到此类),子加载器才会尝试自己去加载

双亲委派模型存在意义:双亲委派模型对于保证java程序的稳定运行十分重要,保证一个类在任何类加载器环境中都是同一个类。例如java.lang.Object,存放在rt.jar中,由于双亲委派模型的存在,无论哪一类类加载器加载此类,最终都是委派给处于顶端的启动类加载器进行加载,因此,Object类在程序的各种加载器环境中都是同一个类

3.3.比较两个类相等的前提

比较两个类是否相等的前提条件:这两个类是否由同一个类加载器加载,否则即使两个类来源于同一个class文件,被同一个JVM加载,只要加载这两个类的类加载器不同,这两个类注定不相同

4.反射与代理设计模式

4.1.基础代理设计模式

代理设计模式的核心本质在于:一个接口有两个子类,一个负责真实业务,一个负责与真实业务有关的所有辅助性操作,示例如下:

interface ISubject { // 核心操作接口
    public void eat() ; // 吃饭是核心业务
}
class RealSubject implements ISubject {
    @Override
    public void eat() {
        System.out.println("饿了要吃饭") ;
    }
}
class ProxySubject implements ISubject {
    private ISubject subject ;
    public ProxySubject(ISubject subject) {
        this.subject = subject;
    }
    public void prepare() {
        System.out.println("饭前收拾食材") ;
    }
    public void afterEat() {
        System.out.println("洗刷刷") ;
    }
    @Override
    public void eat() {
        this.prepare() ;
        this.subject.eat() ; // 核心吃
        this.afterEat() ;
    }
}
class Factory {
    private Factory(){}
    public static <T> T getInstance(String className) {
        T t = null ;
        try {
            t = (T) Class.forName(className).newInstance() ;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return t ;
    }
    public static <T> T getInstance(String className,Object obj) {
        T t = null ;
        try {
            Constructor<?> cons =
                    Class.forName(className).getConstructor(obj.getClass().getInterfaces()[0]) ;
            t = (T) cons.newInstance(obj) ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t ;
    }
}
public class Test {
    public static void main(String[] args) {
        ISubject subject =
                Factory.getInstance("ProxySubject",Factory.getInstance("RealSubject")) ;
                        subject.eat() ;
    }
}
//饭前收拾食材
//饿了要吃饭
//洗刷刷

以上程序如果结合反射之后,整体的处理会非常繁琐。不光开发端,使用者使用起来也很麻烦。对于以上操作,客户端最多只需要关系代理是谁,实际业务是谁即可,修改如下:

public static <T> T getInstance(String proxyClassName, String realClassName) {
    T t = null ;
    try {
         // 取得真实接口对象
         T realObj = getInstance(realClassName) ;
        Constructor<?> cons =
        Class.forName(proxyClassName).getConstructor(realObj.getClass().getInterfaces()[0]) ;
         t = (T) cons.newInstance(realObj) ;
    } catch (Exception e) {
        e.printStackTrace();
    }
        return t ;
}

public static void main(String[] args) {
    ISubject subject =
    
    Factory.getInstance("www.bit.java.agency.ProxySubject","www.bit.java.agency.RealSubject ") ;
    subject.eat();
}

在这里插入图片描述
现在的问题是:在开发中并不知道项目会有多少个接口,如果这些接口都需要使用到代理模式,那么就意味着每一个接口都需要编写两个子类,再假设这些接口的代理类的功能几乎都一样,之前的这种代理设计只是一种最简单的代理设计,所以这种代理设计只能够代理一个接口的子类对象,无法代理更多的接口子类对象,要想真正使用代理设计模式,我们需要引入动态代理设计模式

4.2.动态代理设计模式(核心)

在这里插入图片描述
要想进行动态代理设计的实现,代理类不再具体实现于某一个接口

/**
 * 动态代理实现的标识接口,只有实现此接口才具备有动态代理的功能
 */
public interface InvocationHandler {
    /**
     * invoke表示的是调用执行的方法,但是所有的代理类返回给用户的接口对象都属于代理对象
     * 当用户执行接口方法的时候所调用的实例化对象就是该代理主题动态创建的一个接口对象
     * @param proxy 表示被代理的对象信息
     * @param method 返回的是被调用的方法对象,取得了Method对象则意味着可以使用invoke()反射调用方
    法
     * @param args 方法中接收的参数
     * @return 方法的返回值
     * @throws Throwable 可能产生的异常
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

如果要想进行对象的绑定,那么就需要使用一个Proxy程序类,这个程序类的功能是可以绑定所有需要绑定的接口子类对象,而且这些对象都是根据接口自动创建的,该类有一个动态创建绑定对象的方法:

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

动态代理实现:

interface ISubject { // 核心操作接口
    public void eat(String msg, int num) ; // 吃饭是核心业务
}
class RealSubject implements ISubject {
    @Override
    public void eat(String msg ,int num) {
        System.out.println("我要吃 "+num + "分量的 "+msg) ;
    }
}
/**
 * 动态代理类
 */
class ProxySubject implements InvocationHandler {
    // 绑定任意接口的对象,使用Object描述
    private Object target ;
    /**
     * 实现真实对象的绑定处理,同时返回代理对象
     * @param target
     * @return 返回一个代理对象(这个对象是根据接口定义动态创建生成的代理对象)
     */
    public Object bind(Object target) {
// 保存真实主题对象
        this.target = target ;
        return
                Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this) ;
    }
    public void preHandle() {
        System.out.println("[ProxySubject] 方法处理前") ;
    }
    public void afterHandle(){
        System.out.println("[ProxySubject] 方法处理后") ;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.preHandle() ;
// 反射调用方法
        Object ret = method.invoke(this.target,args) ;
        this.afterHandle() ;
        return ret;
    }
}
public class Test {
    public static void main(String[] args) {
        ISubject subject =(ISubject) new ProxySubject().bind(new RealSubject()) ;
        subject.eat("宫保鸡丁",20) ;
    }
}
//[ProxySubject] 方法处理前
//我要吃 20分量的 宫保鸡丁
//[ProxySubject] 方法处理后
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值