玩命加载中 . . .

反射


反射

关于java.lang.Class类的理解

类加载过程:

程序经过javac.exe命令编译以后,会生成一个或多个字节码文件(.class结尾)。

  1. 加载:接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就成为运行时类,此运行时类,就作为Class的一个实例,我们可以通过这个实例获取相关信息(不需要new,直接赋值)。类缓存:一旦被加载,他就会缓存一段时间。

  2. 链接:设置类变量的默认初始化值

  3. 初始化:将属性进行赋值

image-20200901163906013

反射:程序可以访问、检测和修改它本身状态或行为的一种能力。

java.lang.reflect包

反射的动态性(作用):反射机制允许程序在执行时借助API取得任何类的内部信息,从而直接操作任意对象的内部属性和方法。换句话说,当我们只有在程序运行时才知道该创 建哪个对象时(就是传参),才会创建哪个对象。在框架中大多使用反射,模板写好,只需要我们动态传入少量的参数,通过反射来调用参数对应的类创建对象,执行操作。

好处:

  1. 可以在程序运行中,操作这些对象
  2. 可以解耦,提高程序的可扩展性

类加载器

作用:用于将类加载到内存当中

  1. 引导类加载器:主要负责加载Java核心类库,无法加载自定义类,无法获得
  2. 扩展类加载器:主要加载jre/ext目录下的jar包,可以通过系统类加载器.getParent()获得
  3. 系统类加载器:负责加载自定义类,可以通过类名.class.getClassLoader()获得

使用类加载器读取配置文件

Properties pros = new Properties();
//方式1:使用输入流的方式
//此时文件默认在当前module下
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);

//方式2:使用类加载器读取
//此时文件默认在当前module的src目录下
ClassLoader classLoader = test.class.getClassLoader();
InputSteam is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);

==所有的类都属于Class类==

获取Class对象的方式

  1. (常用)Class.forname(“全类名”):调用Class的静态方法,将字节码文件加载进内存,返回Class对象

    • 多用于配置文件,将类名定义在配置文件中,读取文件加载类
    Class clazz = Class.forName("com.jm.java.Person");
    
  2. 类名.class:通过类名的属性class获取

    • 多用于参数的传递
    Class clazz = Person.class;
    
  3. 对象.getClass():通过运行时类的对象,调用getClass(),方法在object类中定义着

    • 多用于对象的获取字节码方式
    Person p1 = new Person();
    Class clazz = p1.getClass();
    
  4. 使用类的加载器:ClassLoader

    ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class clazz4 = classLoader.loadClass("com.jm.java.Person");
    

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪种方式获取的Class对象都是同一个。

Class对象功能:

  • 获取功能:Class对象.

    1. 获取成员变量们

      • Filed[] getFields() :获取运行时类及其父类所有==public==修饰的成员变量
      • Filed[] getField(String org)
      • Filed[] getDeclaredFields():不考虑修饰符,获取所有(不包含父类)的成员变量
      • Filed[] getDeclaredField()
    2. 获取构造方法

      • Constructor getConstructor(xxx.class,xxx.class):获取有参构造器,一般很少用得到,用的都是clazz.newInstance()使用空参构造器创建对象
      • Constuctor getDeclaredConstructor(xxx.class)
    3. 获取运行时类的父类

      • Class getSuperclass()
      • Class getGenericSuperclass():获取带泛型的父类
    4. 获得成员方法

      • Method[] getMethods():获取运行时类及其父类所有==public==修饰的方法

      • Method getMethod(String s,xxx.class……)

      • Method[] getDeclaredMethods(String name,String.class参数列表):不考虑修饰符,获取所有(不包含父类)的方法

    5. 获取类名

      • String getName()
  • 操作运行时类指定的属性:

    • 成员变量.get(Object o):获取哪个对象的值
    • 成员变量.set(Object o,String s):给哪个对象设置值
    • 成员变量.setAccessible(true):暴力反射,忽略私有限制,同样可以获取成员变量
  • 操作运行时类指定的构造方法:

    • T newInstance(org) :==创建对象==
    • 要求:
      • 运行时类必须提供空参构造器
      • 空参构造器的访问权限需要是public
        • 引申:在javaBean中要求提供一个Public的空参构造器,原因:
          • 便于通过反射,创建运行时类的对象
          • 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
    • 简化:直接用类对象来创建对象:personClass.newInstance()
  • 操作运行时类指定的方法:

    • invoke(Object o,org) : 执行方法。

      • 参数1:方法的调用者 参数2:给方法形参赋值的实参
      • 返回值就是方法的返回值
      • 静态方法的调用者是:运行时类.class
      //1.获取class对象
      Class clazz = person.class;
      //2.获取运行时类对象
      Person p = (Person) clazz.newInstance();
      //3.获取方法对象
      Method method = clazz.getMethod("study",String.class);
      //4.0 暴力反射,忽略修饰符
      method.setAccessible(true);
      //4.执行方法
      method.invoke(p,"好好学习");
      
    • getName() : 获取方法名称

    • getAnnotations():获取方法注解(前提是注解的生命周期必须是RUNTIME,因为反射是runtime)

动态代理

代理模式

使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何调用原始对象都需要通过代理,代理对象再决定方法逻辑和是否要转到原始对象方法

//静态代理
interface User{
     void save();
}

class userImpl implements User{
    @Override
    public void save() {
        System.out.println("保存操作");
    }
}

class userProxy implements User{
     private User u;
     public userProxy(User u){
         this.u = u ;
     }

    @Override
    public void save() {
        System.out.println("开启事务");
        u.save();
        System.out.println("结束事务");
     }
}

class Test{
    public static void main(String[] args) {
        userImpl u = new userImpl();
        userProxy userProxy = new userProxy(u);
        userProxy.save();
    }
}

代理模式优点:在不修改原码的基础上,对方法进行增强

静态代理的缺点:

每个代理实现类实现的接口都写死了,如果每个业务都需要增加功能,都需要创建响应的代理类,

所以动态代理油然而生,写一个通用的代理实现类,当运行时根据调用的对象来动态生成代理类,

其动态代理类的特点在于:不知道要创建哪个被代理对象的代理类,不知道被代理对象的方法,因此使用到反射来获取其代理对象和代理方法。

动态代理

普通静态代理,是指代理类和原始对象的类都已经在编译时期确定下来,不利于程序的扩展,而且一个代理类只能为一个接口服务。而动态代理客户通过代理类调用其他对象的方法,并且在程序运行时根据需要动态创建代理对象

//第一步:创建接口,JDK动态代理基于接口实现,所以接口必不可少(准备工作)
    //接口
    interface Man{
        void save();
    }
    //被代理类
    class ManImpl implements Man{
        @Override
        public void save() {
            System.out.println("说话!");
        }
    }
//第二步:实现InvocationHandler接口,重写invoke方法(准备工作)
    //动态代理实现类
    class MyHandler implements InvocationHandler{
        //创建handler对象时,初始化obj
        private Object obj ;
        public MyHandler(Object obj){
            this.obj = obj;
        }
        //执行方法
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("前置操作");
            method.invoke(obj,args);
            System.out.println("后置操作");
            return null;
        }
//第三步:调用Proxy的静态方法newProxyInstance方法生成代理实例(生成实例时需要提供类加载器,我们可以使用接口类的加载器即可)
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
        }
    }

    public static void main(String[] args) {
            Man man = new ManImpl();//创建被代理对象
            MyHandler handler = new MyHandler(man);//创建handler
            //Man manImpl = (Man) handler.getProxyInstance();//创建代理类
            Man manImpl = (Man) Proxy.newProxyInstance(ManImpl.class.getClassLoader(),ManImpl.class.getInterfaces(),handler);
//第四步:使用新生成的代理实例调用某个方法实现功能。
            manImpl.save();//执行方法
    }

案例

需求:写一个”框架”,可以帮我们创建任意类的对象,并且执行其中任意方法

思路:创建配置文件,写入全类名和方法名,将配置文件加载进内存,获取配置文件的值,创建对象,执行方法

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        //获取properties对象
        Properties properties = new Properties();
        //加载配置文件
        InputStream is = ReflectTest.class.getClassLoader().getResourceAsStream("pro.properties");
        properties.load(is);
        //获取配置文件的值
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //获取class对象
        Class cls = Class.forName(className);
        //创建对象,执行方法
        //这里cls.newInstance()被废弃了
        Object obj = cls.getDeclaredConstructor().newInstance();
        Method method = cls.getMethod(methodName);
        method.invoke(obj);
    }
}

问题

  1. 什么时候使用反射的方式

    反射是视为动态语言的关键,反射机制允许程序在运行期借助api获取任意类的内部信息,并可以直接操作任意对象的内部属性和方法
    反射的特征:动态性。当不确定实例化哪个对象和方法的时候,才用得到反射
    
  2. 反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?

    不矛盾,两个的目的性不一样
    面向对象的封装性的目的就是为了让人知道,哪些属性推荐你调用的,哪些属性并不需要调用也能实现相同的效果。
    反射机制的目的是表明自己可以调用这些属性,虽然不推荐调用,但是如果真的想调用也不是不可以。
    
  3. 写出获取Class实例的三种常见方式

    Class clazz = Person.class;
    
    Class clazz = Class.forName("com.jm.java.Person");
    
    Person person = new Person();
    Class clazz = person.getClass();
    
  4. 关于Class类的理解

    1.Class实例对应加载到内存中的一个运行时类
    2.Class实例调取其中的方法,就是调取对应运行时类的结构
    
  5. 创建Class对应运行时类的对象的通用方法,以及这样操作,需要对应运行时类构造器方面满足的要求

    Object obj = clazz.newInstance();
    1.必须要有空参构造器
    2.空参构造器的权限修饰符的权限要够,通常为public
    
  6. 调用方法

    Class clazz = Person.class;
    Person person = (Person)clazz.newInstance();
    Method method = clazz.getDeclaredMethod();
    method.setAccessable(true);
    method.invoke(person);
    
  7. 读取配置文件

    Properties props = new Properties();
    ClassLoader classloader = test.class.getClassLoader();
    InputStream is = classloader.getResourceAsStream();
    props.load(is);
    
  8. 反射机制能提供的功能

    在运行时判断任意一个对象所属的类
    在运行时构造任意一个类的对象
    在运行时判断任意一个类所具有的成员变量和方法
    在运行时获取泛型信息
    在运行时调用任意一个对象的成员变量与方法
    在运行时处理注解
    生成动态代理
    
  9. 相关API

    java.lang.Class
    java.lang.reflect.Method
    java.lang.reflect.Field
    
  10. 创建类的对象的方式

1. new+构造器
2. 工具类中是否有静态方法存在
3. 通过反射
反射机制就是程序.java文件在编译之后生成class文件,通过类加载器将class文件加载进内存,加载进内存的就称之为运行时类,而运行时类就对应着一个class实例,我们可以通过操作这个实例来获取运行时类对应的属性及方法。
反射的动态性:因为是操控class实例,已经经过了编译过程进入运行过程,换句话说我们只有在运行程序时才知道到底创建了哪个对象,从而进行相应对象的逻辑操作

文章作者: 小苏
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小苏 !
评论
  目录