[Programação avançada Java] Mecanismo de reflexão Java

insira a descrição da imagem aqui

Mecanismo de reflexão Java

1. Visão geral da reflexão

1.1. Conteúdo principal deste capítulo

  • Visão geral do mecanismo de reflexão Java
  • Entenda a classe Class e obtenha a instância Class
  • Carregamento de classe e compreensão do ClassLoader
  • Crie um objeto da classe de tempo de execução
  • Obtenha a estrutura de conclusão da classe de tempo de execução
  • chamar a estrutura especificada da classe de tempo de execução
  • Aplicação de Reflexão: Proxy Dinâmico

1.2. Compreensão da reflexão

  • A reflexão (reflection) é considerada a chave para linguagens dinâmicas.O mecanismo de reflexão permite que o programa obtenha as informações internas de qualquer classe por meio da API Reflection durante a execução e pode manipular diretamente as propriedades de conteúdo e métodos de qualquer objeto.

Framework = reflexão + anotação + padrão de design

1.3. Experimente a "natureza dinâmica" do mecanismo de reflexão

/**
 * 体会反射的动态性
 */
@Test
public void test2 () {
    for (int i = 0; i < 100; i++) {
        int num = new Random().nextInt(3);
        String classPath = "";
        switch (num) {
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.atguigu.java.Person";
                break;
        }

        try {
            Object obj = getInstance(classPath);
            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 创建一个指定类的对象
 * @param classPath 指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return  clazz.newInstance();
}

1.4, as funções que o mecanismo de reflexão pode fornecer

  • Determinar a classe de qualquer objeto em tempo de execução
  • Construa um objeto de qualquer classe em tempo de execução
  • Determine as variáveis ​​de membro e métodos de qualquer classe em tempo de execução
  • Obter informações genéricas em tempo de execução
  • Chame variáveis ​​de membro e métodos de qualquer objeto em tempo de execução
  • Processando anotações em tempo de execução
  • Gerar proxy dinâmico

1.5. APIs relacionadas

  • java.lang.Class: fonte de reflexão
  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor

2. Compreensão da classe Class e obtenção de uma instância da classe

2.1, o entendimento da classe Class

  • processo de carregamento de classe
    • Após o programa passar o comando javac.exe, um ou mais arquivos bytecode (fim de .class) serão gerados. Em seguida, usamos o comando java.exe para interpretar e executar um arquivo bytecode. É equivalente a carregar um arquivo de bytecode na memória. Esse processo é chamado de carregamento de classe. A classe carregada na memória é chamada de classe de tempo de execução e essa classe de tempo de execução é uma instância de Class.
  • Em outras palavras, uma instância de Class corresponde a uma classe de tempo de execução
  • As classes de tempo de execução carregadas na memória são armazenadas em cache por um determinado período de tempo. Durante esse tempo, podemos obter essa classe de tempo de execução de diferentes maneiras.

2.2. Várias maneiras de obter instâncias de classe

@Test
public void test3() throws ClassNotFoundException {
    // 方式一:调用运行时类的属性:.class
    Class clazz1 = Person.class;
    System.out.println(clazz1);
    // 方式二:通过运行时类的对象,调用getClass()
    Person p1 = new Person();
    Class clazz2 = p1.getClass();
    System.out.println(clazz2);
    // 方式三:调用Class的静态方法:forName(String classPath)
    Class clazz3 = Class.forName("com.atguigu.java.Person");
    System.out.println(clazz3);
    // 方式四:使用类的加载器:ClassLoader(了解)
    ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
    System.out.println(clazz4);

    System.out.println(clazz1 == clazz2);// true
    System.out.println(clazz1 == clazz3);// true
    System.out.println(clazz1 == clazz4);// true
}

2.3. Resumo: a forma de criar objetos de uma classe

  • Método 1: novo + construtor
  • Método 2: Para criar um objeto da classe Xxx, você pode considerar: Verifique se existem métodos estáticos nas classes Xxx, Xxxs, XxxFactory e XxxBuilder. Você pode chamar seu método estático para criar um objeto xxx
  • Método 3: Através da reflexão

2.4. Descrição de quais estruturas as instâncias de classe podem ser

  • classe: classe externa, membro (classe interna do membro, classe interna estática), classe interna local, classe interna anônima
  • interface: interface
  • []: variedade
  • enum: enumeração
  • anotação: anotação @interface
  • tipo primitivo: tipo de dados básico
  • vazio

3. Entenda o ClassLoader

3.1, processo de carregamento de classe (entenda)

  • Quando um programa usa ativamente uma determinada classe, se a classe não tiver sido carregada na memória, o sistema inicializará a classe por meio das três etapas a seguir.
    • Carregamento de classe: leia o arquivo de classe da classe na memória e crie um objeto java.lang.Class para ele. Este processo é feito pelo carregador de classe
    • Vinculação de classes: mescla dados binários de classes no JRE
    • Inicialização de classe: JVM é responsável pela inicialização de classe

3.2, o papel do carregador de classe

  • A função do carregamento de classe: carrega o conteúdo de bytecode do arquivo de classe na memória e converte esses dados estáticos na estrutura de dados de tempo de execução da área de método e, em seguida, gera um objeto java.lang.Class representando essa classe no heap como A entrada de acesso de dados de classe na área de método.
  • Cache de classes: O carregador de classes JavaSE padrão pode procurar classes sob demanda, mas uma vez que uma classe é carregada no carregador de classes, ela permanecerá carregada (armazenada em cache) por um período de tempo. No entanto, o mecanismo de coleta de lixo da JVM pode reciclar esses objetos de Classe.

3.3. Classificação de carregadores de classe

  • Boot class loader: Escrito em C++, é o class loader que acompanha a JVM, responsável pela biblioteca núcleo da plataforma Java , e é utilizado para carregar a biblioteca de classes núcleo. O carregador não pode obter diretamente
  • Carregador de classe estendida: Responsável por pacotes jar no diretório jre/lib/ext ou pacotes jar no diretório especificado por -D java.ext.dirs na biblioteca de trabalho
  • Carregador de classes do sistema: responsável por carregar classes e pacotes jar no diretório apontado por java -classpath ou -D java.class.path, e é o carregador mais utilizado

3.4. O processo de execução da compilação e operação da classe Java

源程序
Java编译器
字节码
类装载器
字节码校验器
解释器
操作系统平台

3.5. Use o Classloader para carregar os arquivos de configuração no diretório src

@Test
public void test2() throws Exception {
    Properties pros = new Properties();
    // 读取配置文件的方式一:
    // FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
    // pros.load(fis);
    // 读取配置文件的方式二:
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    pros.load(is);

    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    System.out.println("user = " + user + ",password = " + password);
}

4. Aplicação de reflexão 1: Criar objetos de classes de tempo de execução

  • newInstance(): Chame este método para criar um objeto da classe de tempo de execução correspondente. Chama internamente o construtor da classe de tempo de execução com parâmetros vazios
  • Para que este método crie objetos da classe runtime normalmente, são necessários os seguintes requisitos:
    • 1. A classe de tempo de execução deve fornecer um construtor com parâmetros vazios
    • 2. O construtor com parâmetros vazios tem direitos de acesso suficientes. Normalmente, definido como público
  • Um construtor de parâmetro vazio público é necessário em JavaBean. razão:
    • 1. É conveniente criar objetos de classes de tempo de execução por meio de reflexão
    • 2. Quando as subclasses herdam essa classe de tempo de execução, ao chamar super() por padrão, certifique-se de que a classe pai tenha esse construtor
@Test
public void test1() throws InstantiationException, IllegalAccessException {
    Class<Person> clazz = Person.class;

    Person person = clazz.newInstance();
    System.out.println(person);
}

5. Aplicação de reflexão 2: Obtenha a estrutura completa da classe de tempo de execução

5.1. Obter propriedades

@Test
public void test1() {
    Class clazz = Person.class;
    // 获取属性结构
    // getFields():获取当前运行时类及其父类中声明为public访问权限的属性
    Field[] fields = clazz.getFields();
    for (Field f : fields) {
        System.out.println(f);
    }
    System.out.println();
    // getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field f : declaredFields) {
        System.out.println(f);
    }
}

5.2. Método de obtenção

@Test
public void test1 () {
    Class clazz = Person.class;
    // getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
    for (Method m : methods) {
        System.out.println(m);
    }
    System.out.println();
    // getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method m : declaredMethods) {
        System.out.println(m);
    }
}

5.3. Obtenha outras estruturas

/**
 * 获取构造器结构
 */
@Test
public void test1 () {
    Class clazz = Person.class;
    // getConstructors():获取当前运行时类中声明为public的构造器
    Constructor[] constructors = clazz.getConstructors();
    for (Constructor c : constructors) {
        System.out.println(c);
    }
    System.out.println();
    // getDeclaredConstructors():获取当前运行时类中声明的所有构造器
    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor c : declaredConstructors) {
        System.out.println(c);
    }
}
/**
 * 获取运行时类的父类
 */
@Test
public void test2 () {
    Class clazz = Person.class;
    Class superclass = clazz.getSuperclass();
    System.out.println(superclass);
}
/**
 * 获取运行时类的带泛型的父类
 */
@Test
public void test3 () {
    Class clazz = Person.class;
    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println(genericSuperclass);
}
/**
 * 获取运行时类的带泛型的父类的泛型
 */
@Test
public void test4 () {
    Class clazz = Person.class;
    Type genericSuperclass = clazz.getGenericSuperclass();
    ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    // 获取泛型类型
    Type[] actualTypeArguments = paramType.getActualTypeArguments();
    for (Type type : actualTypeArguments) {
        System.out.println(type.getTypeName());
    }
}
/**
 * 获取运行时类实现的接口
 */
@Test
public void test5 () {
    Class clazz = Person.class;
    Class[] interfaces = clazz.getInterfaces();
    for (Class c : interfaces) {
        System.out.println(c);
    }
    System.out.println();
    //获取运行时类的父类实现的接口
    Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
    for (Class c : interfaces1) {
        System.out.println(c);
    }
}
/**
 * 获取运行时类所在的包
 */
@Test
public void test6 () {
    Class clazz = Person.class;
    Package pack = clazz.getPackage();
    System.out.println(pack);
}
/**
 * 获取运行时类声明的注解
 */
public void test7 () {
    Class clazz = Person.class;
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation annos : annotations) {
        System.out.println(annos);
    }
}

6. Aplicação de reflexão três: chame a estrutura especificada da classe de tempo de execução

6.1. Chame o atributo especificado

@Test
    public void testField1 () throws Exception {
        Class clazz = Person.class;
        // 创建运行时类的对象
        Person p = (Person) clazz.newInstance();
        // 1、getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");
        // 2、保证当前属性时可访问的
        name.setAccessible(true);
        // 3、获取、设置指定对象的此属性值
        name.set(p, "Tom");
        System.out.println(name.get(p));
    }

6.2. Chame o método especificado

@Test
public void testMethod () throws Exception {
    Class clazz = Person.class;
    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();
    // 1、获取指定的某个方法
    // getDeclaredMethod():参数1指明获取的方法的名称 参数2指明获取的方法的形参类型
    Method show = clazz.getDeclaredMethod("show", String.class);
    // 2、保证当前方法是可访问的
    show.setAccessible(true);
    // 3、调用方法的invoke():参数1方法的调用者 参数2给方法形参赋值的实参
    // invoke()的返回值即为对应类中调用的方法的返回值
    Object returnValue = show.invoke(p, "CHN");
    System.out.println(returnValue);
    // 如何调用静态方法
    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    // 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
    // Object returnVal = showDesc.invoke(null);
    Object returnVal = showDesc.invoke(Person.class);
    System.out.println(returnVal);
}

6.3. Chame o construtor especificado

@Test
public void testConstructor() throws Exception {
    Class clazz = Person.class;
    // 1、获取指定的构造器
    // getDeclaredConstructor():参数指明构造器的参数列表
    Constructor constructor = clazz.getDeclaredConstructor(String.class);
    // 2、保证此构造器是可访问的
    constructor.setAccessible(true);
    // 3、调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);
}

7. Aplicação de Reflexão 4: Proxy Dinâmico

7.1. O princípio do modo proxy

  • Envolve um objeto com um proxy e, em seguida, substitui o objeto original pelo objeto proxy. Todas as chamadas para o objeto original passam pelo proxy. O objeto proxy decide se e quando encaminhar chamadas de método para o objeto original.

7.2. Proxy estático

// 被代理类
Class MyThread implements Runnable () {}
// 代理类
Class Thread implements Runnable() {}
// 代理操作
main () {
    MyThread t = new MyThread();
    Thread thread = new Thread(t);
    thread.start();// 启动线程;调用线程的run()
}
  • Desvantagens dos proxies estáticos
    • A classe proxy e a classe do objeto de destino são determinadas durante a compilação, o que não é propício para a expansão do programa.
    • Cada classe de proxy pode atender apenas a uma interface, portanto, muitos proxies inevitavelmente serão gerados no desenvolvimento do programa.

7.3. Características do proxy dinâmico

  • Proxy dinâmico refere-se ao método que o cliente chama outros objetos por meio da classe proxy e cria dinamicamente o objeto proxy da classe de destino de acordo com as necessidades quando o programa está sendo executado.

7.4. Realização da procuração dinâmica

  • Há duas questões principais que precisam ser abordadas:
    • Questão 1: Como criar dinamicamente uma classe de proxy e seus objetos com base na classe de proxy carregada na memória --> por meio de Proxy.newProxyInstance()
    • Questão 2: Ao chamar o método a através do objeto da classe proxy, como chamar dinamicamente o método a com o mesmo nome na classe proxy --> através da classe de implementação da interface InvocationHandler e seu método invoke()
/**
 * 动态代理体会:反射的动态性
 */
interface Human {
    String getBelief();
    void eat(String food);
}
// 被代理类
class SuperMan implements Human {
    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }
    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}
class HumanUtil {
    public void method1 () {
        System.out.println("=======通用方法一=======");
    }
    public void method2 () {
        System.out.println("=======通用方法二=======");
    }
}
class ProxyFactory {
    // 调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj) {// obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}
class MyInvocationHandler implements InvocationHandler {
    private Object obj;// 需要使用被代理类的对象进行赋值
    public void bind(Object obj) {
        this.obj = obj;
    }
    // 当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        HumanUtil util = new HumanUtil();
        util.method1();
        // method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        // obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);
        util.method2();
        // 上述方法的返回值就作为当前类中的invoke()返回值
        return returnValue;
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        // proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        // 当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("四川麻辣烫");
    }
}

Acho que você gosta

Origin blog.csdn.net/qq_51808107/article/details/131448541
Recomendado
Clasificación