java之反射

反射是在运行时发生的,对应的就是字节码。从字节码文件中获取这些SourceCode里的 class信息,Method方法,Field属性,Modifiler。
读取出来后生成一个Class 对象 存放在内存中,JVM java运行时内存区里边,接下来开始调用这些方法或属性对象。
1:反射入口
            Class 对象 用来代表运行在java虚拟机中的类和接口。
        三种 Class 对象获取方式:
                1:通过 Object.getClass()           注意: 不适用于java 基本数据类型  必须要有对象实例
                            如 User user = new User();   Class userClass =  user.getClass();  
 
                2: 通过 Object.class        同样适用于java基本数据类型,不用创建这个对象就可以获取
                            如 Class userClass = User.class;  
 
                3: 通过Class.forName() 方法    
                        当我们既无法创建一个类的对象,又无法用 User.class 方式获取一个类的Class对象的时候。通过调用这种方式可以去JVM查看是否被加载。
                        Class userClass = Class.forName("com.dhc.test.User"); //这里参数为 包名+类名  注意 ClassNotFoundException异常。
 
2: Class 内容
                1: Class API
                        获取类名:     
                                    getName() --  这里注意数组结构基本类型或者类对象都有相应的编码
                                    getSimpleName() -- 这里返回小名   仅返回类名,不包含包名。 匿名内部类不包含这个名称
 
                        Class 获取修饰符  如 限制域(private protected,public)  提示子类复写(abstract)  静态类(static)  以及注解
                                User.class.getModifiers() 获取修饰符 编码  Modifier.toString(User.class.getModifiers())  获取修饰符类型
 
                        获取Class成员: 包括属性(Field)  方法(Method)
类型
方法
说明
属性常用方法
Field-属性
getDeclaredFiled(String name)
Field field= user.getDeclaredField( "name" );//获取私有属性
field.getName() = 获取属性名称
 
getField(String name)
Field field= user.getField( "name" );//非私有属性,可上溯父类属性
field.getType() = 获取属性类型
 
 
 
field.getGenericType()  = 获取属性类型(包括泛型等)
 
 
注意:如果想获取父类的私有Field 可以先通过当前class.getSuperClass() 获取到父类class,然后调用 DeclareField
String name = field.getInt(user) 获取属性值
 
 
 
field.setInt(user,123) 给属性赋值
method-方法
getDeclaredMethod(String name)
获取指定方法名的私有方法 这里只传入方法名的前提是没有参数,如果有参数的话还需要传递参数类型, 如
test(int a,float b){}
获取为: class1.getDeclaredMethod("test",int.class,float.class);
如果参数过多可以用数组:
class1.getDeclaredMethod("test",new Class[]{int.class,float.class});
Paremeters p = method. getParameters()  获取所有参数数组
      p.getName() 获取参数名称
      p.getType() 获取参数类型
      p.getModifiers() 获取参数修饰符
 
getMethod(String name)
获取指定方法名的非私有方法,可获取父类共有方法
method.getReturnType();//获取方法返回值类型
 
getDeclaredMethods()
获取所有方法
Class<?>[] params = m.getParameterTypes(); // 获取所有参数类型
 
getParameters()
获取方法所有返回值
Type[] params2 = m.getGenericParameterTypes(); // 获取所有参数类型 包括泛型
 
 
 
invoke(Object obj, Object ...args) 方法执行  核心
Constructor-构造方法
class.getConstructor(Class<T> param)
获取构造方法对象,参数为构造方法参数类型Class
 
 
method 中 invoke 详解:
            invoke 第一个参数是 Method 所依附的Class对应类的实例。如果这个方法是静态方法,那么obj 为 null,后面的可变参数Object 对应为参数。
            invoke 返回的对象是Object,所以执行的时候要强转。
            invoke 调用异常的时候,如果方法本身抛出异常,这个异常会经过包装,由Method统一抛出InvocationTar个体Exception,通过 InvocationTargetException.getCause() 可获取真正异常。
 
Constructor:java反射中创建对象的两种方式
                        1  Class.newInstace()   只能创建无参的对象,直接会抛出异常。要求构造方法为非私有。
                        2  Constructor.newInstance() 可以创建有参的对象 ,会把抛出的异常包装到 InvocationTargetException 中,和 Method一样。  可以访问到私有的构造方法。        
 
 
反射中的数组: 数组本身也是一个Class 对象,Class中有专门判断是否数组的方法 boolean  isArray()  
            如:
public class ArrayBean {
    private int[] array;
    private User[] users;
    private List<String> lists;        //这里是列表 而不是数组
    private Map<String,Object> maps;
}
public class ArrayTest {
    public static void main(String[] args) {
        Class arrayClass = ArrayBean.class;
        Field[] fields = arrayClass.getDeclaredFields();
        for(Field f:fields){
            System.out.println(f.getName()+" 数据类型 = "+f.getGenericType().getTypeName());
            Class fClass = f.getType();// 注意 这里调用的是Field 的 getType 方法 而非 getClass
            if(fClass.isArray()){
                System.out.println("*** "+fClass.getSimpleName()+"  -- "+fClass.getComponentType()); //getComponentType 获取数组里边的元素类型
            }
        }
 
        System.out.println("测试 反射在对象属性为数组时的get set方法测试-- start");// 数组动态创建与加载
        Class cls = ArrayBean.class;
        ArrayBean arrayBean = (ArrayBean) cls.newInstance();
        try {
            Field f = cls.getDeclaredField("array");
            f.setAccessible(true);
            //先创建一个数组对象并填充值 然后通过属性.set 把对象放进去
            Object obj =  Array.newInstance(int.class,4);
            Array.set(obj,0,99);
            Array.set(obj,1,999);
            Array.set(obj,2,9999);
 
            f.set(arrayBean,obj);
            int[] intArrays = arrayBean.getArray();
            System.out.println("填充进去的Array = "+ Arrays.toString(intArrays));//注意这里有默认的 0元素
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
打印结果为:
array 数据类型 = int[]
*** int[]  -- int
users 数据类型 = com.dhc.java.corejava.reflect.User[]
*** User[]  -- class com.dhc.java.corejava.reflect.User
lists 数据类型 = java.util.List<java.lang.String>
maps 数据类型 = java.util.Map<java.lang.String, java.lang.Object>
 
扩展:  Array[]  最高效,但是容量固定且无法变更。   使用场景:基于效率和类型检索,在已知数组大小情况下使用。
                    其本身师哥refrence引用,指向 heap内的某个对象。和其他基本类型区别: 其他基本类型持有基本型别之值,Array是持有引用。
                    转换 ArrayList  List<String> list=Arrays.asList(array);
       ArrayList  会自动扩展的Array    容量动态增长,但是牺牲了效率
                    转换Array  String[] array = (String[])list.toArray(new String[size]);
 
反射中动态创建数组:
        反射创建数组是同构 Array.newInstace() 获取
            如:  创建一个 int[2][3] 的数组   Array . newInstance ( int . class , 2 , 3 ) ;
        
Array的读取与赋值:
public static void set(Object array, int index, Object value)throws IllegalArgumentException,ArrayIndexOutOfBoundsException;
public static void setBoolean(Object array,int index,boolean z)throws IllegalArgumentException,ArrayIndexOutOfBoundsException;
public static Object get(Object array, int index)throws IllegalArgumentException,ArrayIndexOutOfBoundsException;
public static short getShort(Object array, int index)throws IllegalArgumentException, ArrayIndexOutOfBoundsException;
 
反射中的枚举
        枚举也是一个class,和类比较相识,有修饰符,有属性字段,有方法,甚至有构造方法
        如:
public enum State {
    IDLE,
    DRIVING,
    STOPPING,
    test();
    int test1() {
        return 0;
    }
}
        java 反射机制也提供了三个API方法操作枚举:
            1: Class.isEnum() 判断是否枚举
            2: Class.getEnumConstans()  获取所有的枚举常量
            3: java.lang.reflect.Field.isEnumConstant()  判断一个属性 Field是否是枚举常量 
 
反射中获取一个Class的内部类或接口
        API:           public Class<?>[] getClasses ()
             public Class<?>[] getDeclaredClasses ()
            如:
public class TestMember {
    class enclosingClass{};
    public interface testInterface{}
}
public class MemberTest {
    public static void main(String[] args) {
        Class clz = TestMember.class;
        Class[] members = clz.getDeclaredClasses();
        for ( Class c : members ) {
            System.out.println(c.toGenericString());
        }
    }
}
 
反射中获取一个类所有实现的接口:
 
public class A implements Runnable,Cloneable{
    @Override
    public void run() {
    }
}
public class MemberTest {
    public static void main(String[] args) {
        Class[] interfaces = A.class.getInterfaces();
        for ( Class c : interfaces ) {
            System.out.println(c.toGenericString());
        }
    }
}
打印结果:
public abstract interface java.lang.Runnable 
public abstract interface java.lang.Cloneable
 
 
invoke() 参数的秘密:
    
public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
    第一个Object 对象表示Class 对象的实例,第二个是可变参
        有一种特殊情况:泛型
public class TestT<T>{
     public void test(T t){}    //T表示接受任意参数
}
 
main{
     Method tMethod = clzT.getDeclaredMethod("test",Integer.class); tMethod.setAccessible(true); //这里传入Integer类型的class 
}    // 这种情况会报错 提示找不到这个方法,原因是 类型擦除    当一个方法有泛型参数的时候,编译期会自动向上转型, T 向上转型是 Object,所以实际是 void test(Object t);
        // 上面的代码试图去找 test(Integer t) 这个方法,自然是找不到。
main{
    Method tMethod = clzT.getDeclaredMethod("test",Object.class);// 这种才正常
    tMethod.setAccessible(true);
    tMethod.invoke(new TestT<Integer>(),1);
}
 
反射牛逼的地方在于绕开java安全机制,比如 修饰符下的 Field,Method,Constructor,并且还有能力改变final属性的Field;
 
 
 



反射技术在Spring中的应用
    场景1:定义一个接口和方法,然后创建多个功能类实现这个接口,运用反射技术返回不同类型的对象。
 
       作用:在运行时刻通过反射的方式去加载这些信息。比如Spring IOC&DI。  注意这里说的JVM运行时刻动态创建。因为是运行时刻创建,所以有生命周期并进行相应的管控。
 
反射的一些问题:
        1:反射调用会进行一系列的安全校验
        2:反射需要调用一系列的native方法来实现
        3: 寻找Class字节码的过程,比如通过Class.forName找到对应的字节码Class,然后进行加载,解析也会比较慢。而new 的方式无需查找Class字节码,
                因为在Linking的解析阶段已经将符号引用转为了直接引用。
        4:入参校验
        优点:非常灵活
                
    Class clazz = UserClass.class;// 将字节码文件的class对象赋值给java.lang.Class 类中clazz
    注意:类变量 static 修饰的类变量,放在方法区中(全局变量)
            实例变量:非static 非final修饰的变量,放在head内存区域
            常量: final 修饰的变量,放在方法区中。(成员变量)
 
Spring IOC 容器:
        控制反转,一种涉设计思想, 核心就是将预先设计的对象实例的创建控制权交给IOC容器。 IOC容器可以简单认为是一个KV的map集合。
        SpringIOC容器创建bean三种方式:
                1 空参构造创建        public A(){}
                        <bean id="a" class="com.dhcc.ioc.bean.A" />
 
                2  静态工厂创建        public static B createBObj()
                        <bean id="b" class="com.dhcc.ioc.bean.A" factory-method="createBObj" /> // createBObj 方法是static
 
                3:实例工厂创建                public C createCObj()            实例工厂模式必须有实例方法
                        <bean id="c" factory-bean="a" factoryMethod="createCObj" />
 
        开始实现IOC容器
                首先:解析前,读取Spring配置信息,解析配置信息。创建一个对象来解析 spring-ioc.xml 中的内容,创建一个IOC容器来存放解析后的内容。
                其次:解析中,通过反射创建对象。 先获取 Field,Constructor,Method 等属性,然后进行newInstance() 创建对象。
                最后:解析后,进行DI依赖注入,将对象之间的关系进行注入。还是通过反射的方式(getMethod("factory-method"))
                如:
for(BeanConfig beanConfig:beanConfigs){
    try{
        if(null != beanConfig.getClazz()){
            Class clazz = Class.forName(beanConfig.getClazz());
            //说明构造方法导入
            if(beanConfig.getFactoryMethod() != null){
                Method method = clazz.getDeclaredMethod(beanConfig.getFactoryMethod());
                // 静态方法 不需要传递 对象实例Object 就可以调用这个方法来获取对应的值
                IocContainer.putBean(beanConfig.getId(),method.invoke(null));
            }else{
                IocContainer.putBean(beanConfig.getId(),clazz.newInstance());
            }
        }else if(null != beanConfig.getFactoryBean()){
            //实例工厂 需要拿到一个实例对象
            Object obj = IocContainer.getBean(beanConfig.getFactoryBean());
            Method method = obj.getClass().getDeclaredMethod(beanConfig.getFactoryMethod());
            IocContainer.putBean(beanConfig.getId(),method.invoke(obj));
        }
    } catch (ClassNotFoundException | NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}
 
package com.dhc.spring.ioc.shouxie;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author :gxj
* @date :Created in 2020/11/25/025 23:42
* @description: 模拟Spring-ioc.xml 内容进行解析
* @modified By:
* @version: $
*/
public class BeanConfig {
    private String id;
    private String clazz;   //class的name
    private String factoryMethod;//静态工厂
    private String factoryBean;// 实例工厂
    //get/set method
}
        依赖查找两种实现方式:  构造器方式   
                                                set 方式
 
        反射实现地方: 注解实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值