反射是在运行时发生的,对应的就是字节码。从字节码文件中获取这些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 方式
反射实现地方: 注解实现