.java 类文件会被编译为 .class二进制流文件,类加载器将其加载进入内存中,JVM 堆内存中会生成一个 Class 对象,一个类对应于一个 Class对象,通过 Class 对象可以获得其在方法区中存储的类完整结构信息,包括字段,方法等属性 2. 何为反射
反射 :程序在运行期间,可以通过 java 反射机制,动态获取某个类的结构信息(属性,方法等)
动态 : 程序运行期间,无法修改源代码静态 :通过 new 创建对象,然后调用某个类的字段或者方法,明确知道要使用的某个确定的类 总结 :通过反射机制,就能在程序运行期间,动态(用户自定义规则)去获取某个类的字段方法等属性 3.反射流程
以调用 Person 类中 public get( ) 为例
正常方式 :
- import Person 类包名通过 new 关键字创建 Person 对象 p对象调用方法 p.get( )
- 通过 Person 全类名获取 Class 类对象通过 Class 对象 就可以得到访问当前 Person 类的 get( )
反射基本是许多框架中使用到的技术,框架需要兼容每个使用者的要求,降低代码耦合度
反射提供动态处理,可以实现按需加载,有效减少不必要的类加载到内存
例如: JDBC 获取数据库连接参数的方式 (见下 :实例讲解)
后续示例中使用到的类
注解类
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
Action 接口
interface Action{
void run();
public final String food = "肉";
//jdk11之后允许定义私有方法
private void privateEate(){System.out.println("Action --> privateEate()");}
private static void privateShow(){System.out.println("Action --> privateShow()");}
public static void publicShow(){System.out.println("Action --> publicShow()");}
}
World 类
@Test(value = "world - Test")
@MyAnnotation(value = "world - MyAnnotation")
public class World {
@MyAnnotation(value = "type")
private String type;
@MyAnnotation(value = "year")
public int year;
@Test(value = "origin")
protected String origin;
private String getType(){return type;};
public int getYear(){return year;}
protected String getOrigin(){return origin;}
}
Person 类
import java.util.ArrayList;
@MyAnnotation(value = "person-MyAnnotation")
@Test(value = "person Test")
class Person extends World implements Action{
@MyAnnotation(value = "name")
private String name;
@Test(value = "sex")
protected String sex;
@MyAnnotation(value = "age")
public int age = 20;
private ArrayList toys = new ArrayList<>();
public Person(String name, int age) { this.name = name;this.age = age;System.out.println("构造方法:Person(String name, int age)");}
public Person(String name) { this.name = name;System.out.println("构造方法:Person(String name)");}
public Person() {System.out.println("构造方法:Person()");}
@MyAnnotation(value = "getName")
private String getName(String name) {return name;}
public String getNamePblic() {return name;}
@MyAnnotation(value = "setName")
public void setName(String name) {this.name = name;}
protected int getAge() { return age;}
public void setAge(int age) {this.age = age;}
private void setAgePrivate(int age) {this.age = age;}
public static void eat(String some){System.out.println("eat "+some);}
@Override
public void run() {System.out.println("run()");};
public ArrayList getToys(){toys.add("钢铁侠");toys.add("蜘蛛侠");return toys;}
@Override
public String toString() {return "Person [age=" + age + ", name=" + name + ", toys=" + toys + "]";}
}
5. 通过反射如何获取 Class 对象
通过已知的具体类调用 .class
Class p1 = Person.class; // 使用泛型方式在获取Person对象时会自动转化,不需要强转 Classp2 = Person.class;
通过 对象 调用 getClass() 方法获取
Person person = new Person("王五");
Class extends Person> class1 = person.getClass();
注释:泛型通配符
extends Person> :表示泛型必须是 Person 类或者其子类,固定上限 super Person> :表示泛型必须是 Person 类或者其父类,固定下限
通过 Class.forName(全类名)
Class> forName = Class.forName("com.qin.Person");
通过类加载器来获取
Class> loadClass = Person.class.getClassLoader().loadClass("com.qin.Person");
6. 反射获取实例对象
通过 Class 对象获得当前类的构造器,然后通过构造器创建对象
通过反射创建类对象时,该类必须提供构造方法
Class 对象调用 newInstance(),该方法使用时需要提供 Person 类的无参构造方法,否则会抛出异常 ,jdk9之后不推荐使用
Class> clazz = Class.forName("com.qin.Person");
Person person1= (Person) clazz.newInstance();
System.out.println(person1.toString());
//执行结果:Person [age=0, name=null, toys=[]]
Constructor 构造器调用 newInstance() 方法,推荐使用,可以指定创建对象时,调用指定 Person 类的构造方法
//第一个参数表示构造方法的参数类型,第二个参数是实例化的值
Person person2 = (Person) clazz.getDeclaredConstructor(String.class).newInstance("王五");
Person person3 = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18);
System.out.println(person2.toString());
System.out.println(person3.toString());
//执行结果:
//构造方法:Person()
//Person [age=20, name=null, toys=[]]
//构造方法:Person(String name)
//构造方法:Person(String name, int age)
//Person [age=20, name=王五, toys=[]]
//Person [age=18, name=王五, toys=[]]
7. 获取类字段属性
getField ( String name ) :通过指定名称获取某个特定修饰符为 public 的字段
Class> clazz = Class.forName("com.qin.Person");
Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18);
//通过指定属性名获取 public 类型字段
Field age = clazz.getField("age");
//通过指定属性名获取 任意 类型字段
Field name = clazz.getDeclaredField("name");
//获取字段名称
System.out.println(age.getName());
//获取字段类型
System.out.println(age.getType());
//通过person对象获取字段的值
System.out.println(age.get(person));
获取所有修饰符为的 public 字段属性,包括父类中的字段
//获取所有的public字段属性,包括父类中的字段,包括接口中的字段
Field[] fields1 = clazz.getFields();
for (Field field1 : fields1) {
System.out.println(field1.getName());
}
//执行结果:
//age
//food
//year
获取当前类中的所有修饰符的字段包括 private
//获取当前类中的所有属性的字段包括private
Field[] fields2 = clazz.getDeclaredFields();
for (Field field2 : fields2) {
System.out.println(field2.getName());
}
//执行结果:
//name
//sex
//age
//toys
8.获取类方法属性
getMethod( "方法名", 类型名.class) :通过方法名和返回值获取修饰符为 public 方法属性
Method method1 = clazz.getMethod("setName", String.class);
getDeclaredMethod( "方法名", 类型名.class) :通过方法名和返回值获取所有修饰符的方法属性
//第二个参数表示返回值类型,void 可传入 null
Method method2 = clazz.getDeclaredMethod("getAge", null);
getMethods() :通过方法名获取当前类,所有父类中修饰符为 public 的方法,不包括接口中的 pulbic 方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
getDeclaredMethods() :获取当前类所有的方法,不包括构造方法
Method[] methods1 = clazz.getDeclaredMethods();
for (Method method1 : methods1) {
System.out.println(method1);
}
9. Class 类方法操作说明
通过反射获得的 Class 对象,可以获取类中的 字段,方法,类型,构造方法,修饰符,泛型,注解等信息
Class 类大多数方法 包含 Declared 与 不包含 Declared 的区别:
如果方法名中含有 Declared(比如:getDeclaredMethods(String name))表示获取类中的任意修饰符为的方法
如果方法名中没有 Declared 表示只能获取修饰符为 public 的方法
getMethods() 这类返回 Method 数组的不表示 返回当前类中的所有 public 方法,而是所有类,包括父类中修饰符为 public 的方法
都理解为不包含 Declared 只返回 public 修饰符的结构属性
10.获取注解信息
获取对象上所有的注解信息,无法获取继承下来的注解
Annotation[] annotations = clazz.getAnnotations();
Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
只有注解被标注为 @Retention(RetentionPolicy.RUNTIME) 的才能获取到该注解
RetentionPolicy.RUNTIME :该注解将会一直保留到运行时 runtimeRetentionPolicy.SOURCE :该注解只保留在源文件中,类似于标记作用RetentionPolicy.CLASS :该注解只保留到 class 文件中,也就是随着编译器编译到字节码中,运行时反射不对解析改注解 11.获取泛型信息
如何获取泛型信息
首先获得 Method 对象来获取方法上面的泛型
getGenericParameterTypes() 获取方法参数集合
如果参数类型为 ParameterizedType 类型,需要强转为 ParameterizedType 类
然后获取方法所有参数的实际类型 getActualTypeArguments()
通过调用 Type 类的 getTypeName() 来获取名称
//1、通过全类名获取class对象
Class> clazz = Class.forName("com.qin.Person");
//2、通过指定方法名获取名字为 setToys 的方法
Method method = clazz.getDeclaredMethod("setToys",ArrayList.class);
//3、获取方法的所有参数
Type[] types = method.getGenericParameterTypes();
//3、循环遍历方法参数
for (Type type : types) {
//4、如果参数是属于 ParameterizedType 类型
if(type instanceof ParameterizedType){
//5、获取实际的泛型参数类型
Type[] actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
//6、循环遍历获取泛型类型
for (Type aType : actualTypeArguments) {
System.out.println(aType.getTypeName());
if(aType instanceof ParameterizedType){
//7、获取实际的泛型参数类型
Type[] actualTypeArguments1 = ((ParameterizedType)aType).getActualTypeArguments();
//8、循环遍历获取泛型类型
for (Type aType1 : actualTypeArguments1) {
System.out.println(aType1.getTypeName());
}
}
}
}
}
12.获取字段以及方法调用操作实践
如何通过反射获取某个类中的字段,以及如何重置字段中的值
全类名获取 class 对象
通过 Constructor 中的 newInstance() 创建 Person 对象
根据字段修饰符执行不同的操作
如果字段修饰符为 Public ,可以使用 getField() 或者 getDeclaredField() 来获取字段
如果字段修饰符不为 Public ,则需要调用 getDeclaredField() 来获取字段
同时需要调用 setAccessible(true) 设置接入访问
否则会抛出如下异常:
Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
调用 get() 方法,传入类对象获取属性值
调用 set() 方法,传入类对象以及需实际值来设置属性的值
//1、通过全类名获取 class 对象
Class> clazz = Class.forName("com.qin.Person");
//2、通过Constructor创建 Person 对象
Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18);
//3、通过方法名获取方法:name 修饰符为 private
Field name = clazz.getDeclaredField("name");
//4、调用 get() 方法,传入类对象 获取属性值
String str = (String) name.get(person);
System.out.println(str);
//5、设置接入访问为 true
name.setAccessible(true);
//6、调用 set(),修改属性值
name.set(person, "李四");
System.out.println(person.getNamePblic());
如何通过反射调用类中的方法
全类名获取 class 对象
通过 Constructor 中的 newInstance() 创建 Person 对象
根据方法修饰符执行不同的操作
如果调用的方法修饰符为 Public ,可以使用 getMethod() 或者 getDeclaredMethod() 来获取方法结构 Method
如果调用的方法修饰符不为 Public ,则需要调用 getDeclaredMethod() 来获取方法
同时需要调用 setAccessible(true) 设置接入访问
否则会抛出如下异常:
Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
调用 invoke() 方法,传入对应类对象,以及参数值
invoke() 的返回值即是类中该方法的返回值
//1、通过全类名获取class对象
Class> clazz = Class.forName("com.qin.Person");
//2、通过Constructor(参数类型)中的newInstance(实参数值)创建 Person对象
Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18);
//3、通过方法名获取方法:setName为public 修饰符
Method setName = clazz.getMethod("setName", String.class);
//4、调用方法,传入类对象,以及参数值
setName.invoke(person, "张三");
System.out.println(person.getNamePblic());
//3、通过方法名获取方法:setAgePrivate 为私有方法
Method setAgePrivate = clazz.getDeclaredMethod("setAgePrivate", int.class);
//4、设置允许接入访问
setAgePrivate.setAccessible(true);
//5、、调用方法,传入类对象,以及参数值
setAgePrivate.invoke(person, 50);
System.out.println(person.getAge());
13.实例解析
动态代理
Proxy:完成代理的操作类,是所有动态代理类的父类
通过其静态方法 static Object newProxyInstance(ClassLoader loader, Class>...interface, InvocationHandler h) 直接创建一个动态代理对象
实现动态代理的步骤:
创建被代理的类,实现相应功能接口实现 InvocationHandler 接口,并重写其 invoke(Object proxy, Method method, Object[] args) 方法
方法内部通过 method.invoke(obj, args)直接调用 invoke() 方法实现被代理类的方法调用Object obj 参数可通过实现类构造方法传入或者提供一个赋值的方法 通过 Proxy.newProxyInstance() 创建代理对象转为接口,多态通过代理对象调用被代理类的方法
public class App {
public static void main(String[] args) throws Exception{
Action action = (Action) ProxyFactory.getProxyObject(new Person("王老五",30));
action.run();
}
}
class ProxyFactory{
public static Object getProxyObject(Object object){
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(object);
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public void bind(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(obj, args);
}
}



