java学习笔记--反射( 后期学习补充用法)

  1. 云栖社区>
  2. 博客>
  3. 正文

java学习笔记--反射( 后期学习补充用法)

codingcoge 2018-05-09 14:46:48 浏览615
展开阅读全文

可以先参考下大佬的:

大佬1
大佬2
类字节码文件是在硬盘上存储的,是一个个的.class文件。我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个字节码信息。
字节码的信息包括:类名、声明的方法、声明的字段等信息。在Java中“万物皆对象”,这些信息当然也需要封装一个对象,这就是Class类、Method类、Field类。
通过Class类、Method类、Field类等等类可以得到这个类型的一些信息,甚至可以不用new关键字就创建一个实例,可以执行一个对象中的方法,设置或获取字段的值,这就是反射技术

方法区存的是类的信息,不是存类对象的,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的

反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName(“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。

Java 反射机制主要提供了以下功能

在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法

今天回过头来找了点视频来简单了解下了反射机制 以下纯属个人学习总结 如有问题 欢迎讨论 一起进步
问:*有人会问 反射到底是用来干什么的?*
答:目前学了感觉是没啥用 但是到了框架部分 就大有用处
就好比框架是个房子 里面的构造都已经给你搭建好了 你想要对其中的部分进行修改添加 这个时候就需要反射来动态修改了
主要的方法都在java.lang.reflect中
reflect是反射的英文
不知道这么说 是不是可以理解 因为我也没学到过框架 只是照葫芦画瓢在这瞎几把乱说 如有问题 请指出。
Class.forName是在bin层次下
new FileReader是在项目层次下 下面会用到 先简要了解下


这里写图片描述
反射(类的加载概述和加载时机)

  • 类的加载概述

    • 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
    • 加载
      • 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
    • 连接

      • 验证 是否有正确的内部结构,并和其他类协调一致
      • 准备 负责为类的静态成员分配内存,并设置默认初始化值
      • 解析 将类的二进制数据中的符号引用替换为直接引用
    • 初始化

  • 加载时机
    • 创建类的实例
    • 访问类的静态变量,或者为静态变量赋值
    • 调用类的静态方法
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类

反射(类加载器的概述和分类)

  • :类加载器的概述
    • 负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
  • :类加载器的分类
    • Bootstrap ClassLoader 根类加载器
    • Extension ClassLoader 扩展类加载器
    • Sysetm ClassLoader 系统类加载器
  • :类加载器的作用
    • Bootstrap ClassLoader 根类加载器
      • 也被称为引导类加载器,负责Java核心类的加载
      • 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
    • Extension ClassLoader 扩展类加载器
      • 负责JRE的扩展目录中jar包的加载。
      • 在JDK中JRE的lib目录下ext目录
    • Sysetm ClassLoader 系统类加载器
      • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

反射(反射概述)

  • :反射概述

    • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    • 对于任意一个对象,都能够调用它的任意一个方法和属性;
    • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
    • 要想解剖一个类,必须先要获取到该类的字节码文件对象。
    • 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
  • :三种方式

    • a:Object类的getClass()方法,判断两个对象是否是同一个字节码文件
    • b:静态属性class,锁对象
    • c:Class类中静态方法forName(),读取配置文件

举一个栗子:

package fuxi;
class Person{

}
public class One {

    public static void main(String args[]) throws ClassNotFoundException {  

      Person person=new Person();
      Class clazz1=person.getClass();      //根据实体类得到class字节码

      Class clazz2=Person.class;           //静态属性class加载

      Class clazz3=Class.forName("fuxi.Person");//路径要写全 java文件中的得到字节码
      System.out.println(clazz1.equals(clazz2));
      System.out.println(clazz1.equals(clazz3));
      System.out.println(clazz2.equals(clazz3));
    }
}

输出:

true
true
true

意思就是三个得到的字节码class都是同一个


用反射和配置文件解决问题:

package fuxi;
import java.io.BufferedReader;
import java.io.FileReader;

interface Animal{
    void run();
}
class Dog implements Animal{

    @Override
    public void run() {
        System.out.print("汪汪汪");
    }
}
class Cat implements Animal{

    @Override
    public void run() {
        System.out.print("喵喵喵");
    }
}
class Ox{
    public void run(Animal animal) {
        animal.run();
    }
}
public class One {

    public static void main(String args[]) throws Exception {   
        BufferedReader br = new BufferedReader(new FileReader("Animal.properties"));
        String name=br.readLine();
        System.out.println(name);
        Class clazz=Class.forName(name);
        Ox animal=new Ox();
        animal.run((Animal) clazz.newInstance());
    }
}

还有在项目下创建一个配置文件
这里写图片描述
内容是:

fuxi.Dog

如果是原来的多态 写死了 那么每次都要修改源文件
而用反射的话 就可以的动态记载配置文件里面的内容 只要修改配置文件就行 降低了耦合度


通过反射获得构造函数并使用
Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数,
就不能这样创建了,可以调用Class类的getConstructor
举个栗子:

package fuxi;

class Person {
    String name="xx";
    int age=3;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
         name="xx";
         age=3;
    }

    @Override
    public String toString() {
        return name + "  " + age;
    }
}

public class One {

    public static void main(String args[]) throws Exception {
        Class clazz = Class.forName("fuxi.Person");
        Person person = (Person) clazz.newInstance();
        System.out.print(person);
    }
}

输出:

xx  3

这个时候有无参的构造函数 所以可以 如果去掉这个无参的构造函数 那么就会报错
这个时候就得Class类的getConstructor获取class类的构造函数

package fuxi;

import java.lang.reflect.Constructor;

class Person {
    String name="xx";
    int age=3;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
         name="xx";
         age=3;
    }

    @Override
    public String toString() {
        return name + "  " + age;
    }
}

public class One {

    public static void main(String args[]) throws Exception {
        Class clazz = Class.forName("fuxi.Person");
        Constructor[] constructors=clazz.getConstructors();//获得这个类的构造函数数组 
        System.out.println(constructors[0]);
        System.out.println(constructors[1]);

        Constructor constructor=clazz.getConstructor(String.class,int.class);////获取有参构造
        System.out.println(constructor);
        Person person=(Person) constructor.newInstance("xiaoming",1);//通过有参构造创建对象
        System.out.print(person);
        }
}

输出:

public fuxi.Person(java.lang.String,int)
public fuxi.Person()
public fuxi.Person(java.lang.String,int)
xiaoming  1

通过反射获取成员变量并使用
Field
* Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值

package fuxi;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

class Person {
    public String name="小明";
    public int age=3;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
         name="xx";
         age=3;
    }

    @Override
    public String toString() {
        return name + "  " + age;
    }
}

public class One {

    public static void main(String args[]) throws Exception {
        Class clazz = Class.forName("fuxi.Person");
        Field[] fields=clazz.getFields();    //获取类属性的全部属性 前提的public 如果是包内访问和private都不行 
        System.out.println(fields[0]);
        System.out.println(fields[1]);
        Person person=(Person) clazz.newInstance(); //通过无参构造创建对象
        //Field field=clazz.getField("name");         //获取姓名字段
        //field.set(person, "小张");                   //修改姓名的值
        Field field=clazz.getDeclaredField("name");   //暴力反射获取字段
        field.setAccessible(true);                    //去掉私有权限
        field.set(person, "小张");                     
        System.out.print(person);
        }
}

输出:

public java.lang.String fuxi.Person.name
public int fuxi.Person.age
小张  3

总结: 属性权限如果不是公有的话
1. 要获取属性只能 clazz.getDeclaredField 暴力获取属性
2. 要修改属性只能 setAccessible 去掉权限限制

这么看来反射是不是很强盗 ? 也说明了他的无敌


通过反射获取方法并使用
* Method
* Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法

package fuxi;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.print.attribute.standard.Media;
class Person{
    String name;
    int age;
    public  Person(String name,int age) {
        this.name=name;
        this.age=age;
    }
    public void printf() {
        System.out.println(name);
    }
    public void printf(int age) {
        System.out.println(name+" "+age);
    }
}
public class One {

    public static void main(String args[]) throws Exception {
            Class clazz=Class.forName("fuxi.Person");
            Constructor constructor=clazz.getConstructor(String.class,int.class);
            Person person=(Person) constructor.newInstance("小明",10);
            Method method1=clazz.getMethod("printf");
            method1.invoke(person);
            Method method2=clazz.getMethod("printf", int.class);
            method2.invoke(person, 11);
        }
}

输出:

小明
小明 11

或许你们会有个疑惑 为什么clazz.getMethod(“printf”, int.class)?
其实这一部分还属于反射部分 还是字节码的部分 所以得传入class形式 最后invoke的时候才是具体的值

泛型反射:
这个之前学泛型的时候提到过
现在简单回顾一下 泛型检查是在编译器 运行时 class文件中类型都被泛型擦除为Object
这个时候我们可以通过泛型反射 往ArrayList<Integer>中 添加String类型

package fuxi;

import java.lang.reflect.Method;
import java.util.ArrayList;

public class One {

    public static void main(String args[]) throws Exception {
            ArrayList<Integer> arrayList=new ArrayList<Integer>();
            arrayList.add(1);
            arrayList.add(2);
            Class clazz=Class.forName("java.util.ArrayList");
            Method method=clazz.getMethod("add", Object.class);
            method.invoke(arrayList, "adb");
            System.out.print(arrayList);
        }
}

输出:

[1, 2, adb]

通过反射写一个通用的设置某个对象的某个属性为指定的值

package fuxi;
import java.lang.reflect.Field;
class Person{
    String name;
    int age;
    public Person(String name,int age) {
        this.name=name;
        this.age=age;
    }
}
public class One {

    public static void main(String args[]) throws Exception {
            Person person=new Person("小明", 10);
            tool(person, "name", "小王");
            System.out.print(person.name);
        }
    public static void tool(Object person,String name,Object object) throws Exception {
        Class clazz=Class.forName("fuxi.Person");
        Field field=clazz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(person, object);

    }
}

输出:

小王

总结:

1. Class.forName可以读取一个类的字节码文件即Class文件
Class可以通过newInstance()创建一个实体类

通过反射获取带参构造方法并使用

  • Constructor
    • Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象

通过反射获取成员变量并使用

  • Field
    • Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值

通过反射获取方法并使用

  • Method
    • Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法,Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)

通过反射越过泛型检查

动态代理

最后再来讲一下 动态代理:
代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
所以说动态代理其实就是通过反射来生成一个代理

优点:
可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
但是只能针对接口进行动态代理 所以 你要代理的类只能继承接口而不是一个类

在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
重要InvocationHandler接口

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口 作为调用处理器”拦截“对代理类方法的调用
当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑
最终会调用InvocationHandler的方法

 InvocationHandler Object invoke(Object proxy,Method method,Object[] args)    //意思 :调用处理器

Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理,这样说的比较笼统

接口:
package Father;

public interface FatherInter {
    public void login();

    public void submit();
}
继承接口实现类:
public class Son implements FatherInter {

    @Override
    public void login() {
        System.out.println("登录");
    }

    @Override
    public void submit() {
        System.out.println("提交");
    }
}
调用处理器:
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("权限校验");
        method.invoke(target, args);                    //执行被代理target对象的方法
        System.out.println("日志记录");
        return null;
    }
}
Main函数:
public class Main {

    public static void main(String[] args) {
        Son si = new Son();
        si.login();
        si.submit();

        System.out.println("-------------------------------");
        MyInvocationHandler m = new MyInvocationHandler(si);
        FatherInter s = (FatherInter)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);
        //Proxy提供用于创建动态代理类和实例的静态方法
        //newProxyInstance返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
        s.login();
        s.submit();
    }
}

输出:

登录
提交
-------------------------------
权限校验
登录
日志记录
权限校验
提交
日志记录

总结:
1. 动态代理 被代理的类要继承接口才行 基类不行
2. 写一个类继承InvocationHandler 作为中间层 给它传入你的实现类 额外添加的代码写在里面的invoke方法中
3. class类的getInterfaces()方法返回的是 实现接口数组
4. Proxy.newProxyInstance返回的是接口 而不是实现类
遇到这么一个问题
5. invoke(Object proxy, Method method, Object[] args)
proxy其实没有啥用 method是为了method.invoke(target, args)执行被代理类的方法args则是被调用方法的参数
6. Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象
Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);中getClassLoader()和getInterfaces()是不必动的 固定死了 代表加载器和实现类实现的接口 m是调用处理器:InvocationHandler的实现类

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Father.FatherInter
    at Father.Main.main(Main.java:13)

代理类没有实现接口,而是实现了某一基类。大家要注意了哦。
参考:
代理类:
https://blog.csdn.net/andyzhaojianhui/article/details/72833114

网友评论

登录后评论
0/500
评论
codingcoge
+ 关注