JAVA反射

Java反射定义

指在Java程序运行过程中

  • 对于给定的类对象(Class),可以获取这个Class对象,从而获得所有属性和方法;
  • 对于给定的一个对象,能够调用他的任意一个属性和方法(包括被private修饰的属性和方法)。

这种动态获取类的内容以及动态调用对象的方法和获取属性的机制,就叫做JAVA的反射机制

Java反射优缺点

优点

  • 增加程序的灵活性,避免将固定的逻辑程序写死在代码里
  • 代码简洁,可读性强,可以提高代码复用率

缺点

  • 在量大的情境下,相较于直接调用反射性能下降
  • 存在内部隐患和安全隐患
public class Main {
    public static void main(String[] args){
        String Key = "Word";

        long reflectStartTime = System.currentTimeMillis();

        for(int i =0;i<10000000;i++){
            
            Office office = getInstanceReflectByKey(Key);
        }
        long reflectEndTime = System.currentTimeMillis();

        System.out.println("通过反射所花费时间:"+(reflectEndTime-reflectStartTime));
        
        //下面通过直接调用的方式实例化
        Key = "PPT";	//	![在这里插入图片描述](https://img-blog.csdnimg.cn/20200308130858470.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pvYW5fb2ZfYXJjX0FsdGVy,size_16,color_FFFFFF,t_70)
        long instanceStartTime = System.currentTimeMillis();

        for(int i =0;i<10000000;i++){
            Office office = getInstanceByKey(Key);
        }
        long instanceEndTime = System.currentTimeMillis();

        System.out.println("通过实例化直接调用所花费时间:"+(instanceEndTime-instanceStartTime));

    }

    /**
     * 传入key创建不同的对象
     */
    public static Office getInstanceByKey(String key){
        if("Word".equals(key)){
            return new Word();
        }

        if("Excel".equals(key)){
            return new Excel();
        }

        if("PPT".equals(key)){
            return new PPT();
        }
        return null;
    }

    /**
     * 通过反射机制动态链接
     */
    public static  Office getInstanceReflectByKey(String key){
        String packageName = "main.java.com.demo2";

        Office office= null;
        try{
            //性能下降原因 1.JNI 2.newInstance的安全检查 3.寻找class字节码
            Class clazz = Class.forName(packageName+"."+key);
            office = (Office)clazz.newInstance();
        }catch(Exception e){
            e.printStackTrace();
        }
        return office;
    }
}

反射与直接调用性能比较

反射为什么慢,慢在哪?

  • 寻找类(Class)字节码的过程
  • 安全管理机制的权限验证过程
  • 需要调用native方法调用时JNI接口的使用

反射技术主要组成

万物皆对象,我们定义的类其实从面向对象的角度分析,也是一个具体的对象,是一个描述类的实例,描述这个类的属性、行为的实例对象,比如我们创建Dog类

Class Dog{
	String Name;
	int age;
	public void bark(){
		//TODO
	}
}

我们可以基于这个类创建不同年龄和姓名的Dog实例(new Dog)。每一个实例都具有名字、年龄,狗叫的行为。
通过简单的例子,我们可以这样理解Java中Class的定义,是创建对象的统一模板。既然不管是Java默认的类还是我们自定义创建的类都是为了创建具有相同行为属性的对象的模板,那么每一个类我们在定义的时候是不是也可以抽取共性的东西比如包名、属性、行为(方法),构造器等等。既然每个类都具备这样的内容,那么这些类对象实例应该也可以抽取成一个公有的模板,用于创建类对象实例的模板。所以在Java中,这个类定义的创建模板就是java.lang.Class 类。在Class的模板中我们也可以发现大家耳熟能详的模板类如Method、Constructor、Field等等
Class
通过上图我们可以了解到我们创建的每一个自定义Class实例都是基于他的模板类java.lang.Class类。每一个类实例中,都会定义这个类的包名、类名、访问域、特征符、构造器、字段、函数、父类、接口等等。这些内容在Class类中也都提供相应的获取方法进行获取。

  • 类的修饰符
    int modify = clazz.getModify(); //获取当前类的修饰符
    从Oracle的文档中有这么一张关于修饰符数值的图
    修饰符数值
    我们可以发现修饰符的value值全部都是2n 在这些value值转换成二进制的时候我们可以很方便的通过查看value值哪一位有值就可以确定某个类、方法、属性的修饰符是什么。
    例如一个类定义为 public abstract Office{ }
    int modify = clazz.getModifer();
    返回值为public + interface = 1025 = 10000000001
  • 类的包名,类名,类的加载器,父类,接口
Package package=clazz.getPackage() 				获取包名 
String fullName = clazz.getName() 				获取类全路径名 
String simpleName = clazz.getSimpleName() 		获取简单类名 
Classloader cl = clazz.getClassLoader() 		获取当前类的类加载器 
Class[] cs = clazz.getInterfaces() 				获取当前类实现的接口 
Class fc= clazz.getSuperclass() 				获取当前类的父类
  • 类的属性操作
Field[] fields=clazz.getDeclaredFields() 	获取当前类定义的所有属性(公有,私有) 
Field field=clazz.getDeclaredField(“xxx”) 	获取当前类定义的具体属性名的属性(公 有,私有) 
Field[] fields=clazz.getFields() 			获取类中拥有的所有的公有属性(含继承, 实现) 
Field field=clazz.getField(“xxx”) 			获取类中拥有的具体属性名的公有属性 (含继承,实现) 
Field 对象操作 field.get(object) 			获取 object 对象的具体属性值 
field.set(object,objectValue) 				给指定的对象的属性赋值 objectValue 
field.set(null,objectValue) 				给静态变量赋值 objectValue(static 修饰) 
int modifers =filed.getModifers() 			获取变量的修饰(参见类的修饰)

如果属性是私有的 private 修饰,需在set方法或者setXXX调用前,设置可访问权限filed.setAccessable(true) 这样可以进行强制访问,这也是反射机制存在安全隐患的原因。

  • 类的方法操作
Method[] methods = clazz.getDeclaredMethods(); 				//获取当前类中定义的方法(公 有,私有) 
Method method = clazz.getDeaclaredMethod(“xxx”,Class ...); 	//获取指定方法名的当前类定 义的方法 
Method[] methods = clazz.getMethods(); 						//获取类中拥有的公有方法(含 继承,实现) 
Method method = clazz.getMethod(“xxx”); 					//获取类中拥有的指定名公有 方法(含继承,实现) 
int modifers = method.getModifers(); 						//获取方法的修饰符 
Class cls = method.getReturnType(); 						//获取方法的返回值类型 
String methodName = m.getName(); 							//获取方法的名字 
Class[] clazzes = m.getParameterTypes(); 					//获取方法参数列表的类型 
Class[] clazzes =m.getExceptionTypes(); 					//获取方法抛出的异常类型 
Object obj=method.invoke(obj,Object ...); 					//在指定的对象上执行方法 
Object obj=method.invoke(null,Object ...); 					//执行类的静态方法

同样的,若方法是私有方法,权限不允许操作,可以执行method.setAccessable(true),设置方法使用权之后,再执行invoke

  • 类的构造器操作
Constructor[] constructors = clazz.getConstructors() 			//获取类中所有公有的构造器
Constructor[] constructors = clazz.getDeclaredConstructors() 	//获取类中所有构造器(包括私有)
Constructor constructor = clazz.getDeclaredConstrunctor() 		//获取类中的无参构造器
constructor.getModifers()										//获取构造器的修饰符 
constructor.newInstance(Object... values) 						//调用有参构造器
constructor.setAccessable(true)									//强制访问私有构造器
clazz.newInstance()												//调用默认无参构造器
  • Class中newInstance函数本质

通过查看源码,我们得知他底层调用constructor对象进行newInstance函数调用默认无参构造函数
Class 类中 newInstance 部分源码
在这里插入图片描述

  • 反射破坏单例模式

单例模式可以被好几种方式破坏掉,其中之一就是反射机制。我们这里以懒汉模式为例。

/**
 * 懒汉模式的单例
 */
public class Lazy {
    private static Lazy instance;
    
    private Lazy(){}
    
    public static Lazy getInstance(){
        if(instance == null){
            synchronized (Lazy.class){
                if (instance == null){
                    instance = new Lazy();
                }
            }
        }
        return instance;
    }
}

import java.lang.reflect.Constructor;

public class SingletonDestoryer {
    
    public static void main(String[] args) throws Exception, InstantiationException {
        
        Lazy lazyInstance = Lazy.getInstance();

        Constructor constructor = Lazy.class.getDeclaredConstructor();
        
        constructor.setAccessible(true);
        Lazy lazyInstanceReflect = (Lazy)constructor.newInstance();//调用不成功,强制访问后成功

        System.out.println(lazyInstance == lazyInstanceReflect);    //false证明两个对象不是同一个对象
    }
}

  • IOC和DI

先来一段老生常谈的IOC介绍
IOC(Inversion of Control) 控制反转,他是一种设计思想.并非实际的技术.最核心的思想就是将预先设计的对象实例创建的控制权交给程序(IOC容器)

IOC 容器一个管理所有控制反转过程中创建的对象的 key-value 容器结构(简单理解 hashMap)

那么Spring中的IOC(控制反转)对象实例构建的方式有

  • 构造函数
  • 类的静态方法构造
  • 对象的实例方法构造

举例spring-ioc.xml文件配置部分内容

//方式1:无参构造
<bean id="a" class="main.com.java.demo.A"/>

//方式2:静态工厂
//调用A的createBobj方法创建b对象放入容器
<bean id="b" class="main.com.java.demo.A"factory-method="createBObj"/>

//方法3:实例化工厂
//调用实例a的createCObj方法创建c对象放入容器
<bean id="c" factory-bean="a" factory-method="createCObj"/>

Class A的代码

public class A {
    
    public A(){
        System.out.println("A的无参Constructor被调用");
    }
    
    public static B createBObj(){
        System.out.println("A的静态方法被条用");
        return new B();
    }

    public static C createCObj(){
        System.out.println("A的实例方法被条用");
        return new C();
    }
}

通过对配置文件的阅读,我们可以发现IOC和DI的相关配置都是基于反射的相关知识内容,即IOC和DI的实现是通过反射技术将对象的控制权交给IOC容器进行管理,DI的实现基于JavaBean的规范也是通过对方法的寻找和调用完成对象的注入。

  • 手写Spring IOC Bean实例创建的三个方式
  1. 无参构造方法
    1.获取字节码对象Class clazz = Class.forName(“XXX”); //XXX为某个类的全路径名称
    2.获得对象aa : A aa = (A)clazz.newInstance(); //以a为key,aa为value存入IOC,其本质是一个k-v结构的map

  2. 静态工厂创建
    1.获取字节码对象Class clazz = Class.forName(“XXX”);
    2.获取A类静态方法CreateB():Method method = class.getDeclaredMethod(“createB”); //对应静态工厂的静态方法
    3.调用该方法返回对象b :B bb = method.invoke(null); //通常写null,也可以new Object()。以K-V结构存入IOC

  3. 实例工厂创建
    1.获取字节码对象Class clazz = a.getClass();
    2.获得createC() : Method method = clazz.getDeclaredMethod(“createC”);
    3.调用createC : C cc= method.invoke(a); //传入a对象。以K-V结构存入IOC

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值