1.什么是反射
官方解释:反射允许对封装类的字段、方法和构造方法的信息进行编程访问。
虚拟机加载类文件后,会在方法区生成一个类对象,包含了类的结构信息,如字段、方法、构造方法等。反射是一种能够在程序运行时动态访问、修改类对象中任意属性的机制(包括private属性)。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
获取类对象的四种方式
plain
`1.通过对象获取到类对象.getClass();
Class personClass = person.getClass();
2.通过 Class.forName()传入类的全路径获取;装入类,并做类的静态初始化,返回Class的对象
Class personClass = Class.forName("全类名");
3.通过类属性 ClassName.Class获取类对象;
Class personClass = Person.Class;
4.通过ClassLoader.getSystemClassLoader().loadClass("全类名");通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
`
注意:全类名是包名+类名,一般是程序src目录下的包名+类名。
类对象中描述各个部分的对象:
-
Class:描述整个类的对象
-
Constructor:描述构造方法的对象
javaClass clazz = Class.forName("Person"); //获取类的所有构造方法 Constructor[] constructorAll = clazz.getDeclaredConstructors(); for(Constructor c: constructorAll){ System.out.println(c); } Constructor constructor1 = clazz.getDeclaredConstructor(); Constructor constructor2 = clazz.getDeclaredConstructor(String.class); System.out.println(constructor1); System.out.println(constructor2); //可以获取构造方法中的一些内容 int modifiers = constructor1.getModifiers(); System.out.println(modifiers); int parameterCount = constructor1.getParameterCount(); System.out.println(parameterCount); Parameter[] parameters = constructor2.getParameters(); for(Parameter p: parameters){ System.out.println(p); } //使用构造方法创建对象 Person person = (Person) constructor1.newInstance(); System.out.println(person);
-
Field:描述成员变量的对象
javaClass clazz = Class.forName("Person"); //获取私有/非私有属性 Field[] fields = clazz.getDeclaredFields(); for(Field f: fields){ System.out.println(f); } Field field = clazz.getDeclaredField("name"); System.out.println(field); //获取权限修饰符 int modifiers = field.getModifiers(); System.out.println(modifiers); //获取数据类型 String value = field.getName(); System.out.println( value);
-
Method:描述成员方法的对象。
2.反射的功能
1.获取一个类里面所有的信息和其他业务逻辑。
2.结合配置文件,动态的创建对象并调用方法。
1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2、Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
3.序列化、反序列化
Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化,将文件中的字节码重新转成对象就是反序列化。
ObjectOutputStream 类用来序列化一个对象,可将对象序列化到一个文件中。
注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。
java
`public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建文件输出流对象
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Serializable\\Person.ser");
//创建序列化对象
Person person = new Person("name",22,"女","black","boy");
//创建对象输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//将对象序列化
objectOutputStream.writeObject(person);
//创建文件输入流对象
FileInputStream fileInputStream = new FileInputStream("D:\\Serializable\\Person.ser");
//创建对象输入流
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//反序列化
Person person1 = (Person) objectInputStream.readObject();
System.out.println(person1);
}
}
`
三、序列化和反序列化的注意点:
1.序列化时,只对对象的属性进行保存。
2.当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。
3.当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。
4.声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
5.Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的。
6.序列号id,用于标识序列号对象的版本,在反序列化的过程中,会使用序列化id来检查序列化对象的版本与当前类的版本是否匹配。
功能
- 对象随着程序的运行而创建,程序结束对象就销毁,序列化可以让对象持久的保持在内存中
- 可以在进程之间进行对象传输
4.动态代理
无侵入的为对象的方法增加新功能。
动态代理的实现步骤:
- 定义接口,并定义要在代理对象中实现的方法(包括返回类型、参数列表、方法名)
- 创建一个实现InvocationHandler接口的类,并重写invoke方法,invoke方法在代理对象调用方法时触发,并允许在方法调用前后增加额外的逻辑。
- 使用Proxy类创建代理对象java.lang.reflect.Proxy类的静态方法newProxyInstace来创建代理对象,该方法有三个参数。参数1:加载代理类的类加载器;参数2:目标对象实现的接口;参数3:InvocationHandler对象,表示实现invoke方法的对象。
- 调用代理对象的方法,当代理对象的方法被调用时,invoke方法会被触发。
java
public class Person implements IPerson{
private String name;
public Person(){
}
public Person(String name){
this.name = name;
}
@Override
public String eat(String thing){
System.out.println(this.name+"正在吃"+thing);
return thing;
}
@Override
public void run(){
System.out.println(this.name+"正在run");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public interface IPerson {
//把需要代理的方法定义在接口当中
public abstract String eat(String name);
public abstract void run();
}
public class ProxyUtil {
/**
*为给定的对象生成一个代理对象
* @param person
*/
public static IPerson createProxy(Person person) {
/**
* 参数1:指定类加载器,去加载生成的代理类
* 参数2:指定接口,这个接口用于指定生成的代理长什么,也就是有哪些方法
* 参数3:用来指定生成的代理对象要干什么事情
*/
IPerson iPerson = (IPerson) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader()
, new Class[]{IPerson.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 参数1:代理的对象
* 参数2:要运行的方法
* 参数3:调用方法时传递的实参
*/
if("eat".equals(method.getName())){
System.out.println("拿筷子");
System.out.println("拿碗");
}else if("run".equals(method.getName())){
System.out.println("带水");
System.out.println("穿裤子");
}
return method.invoke(person,args);
}
});
return iPerson;
}
}
实现原理:通过代理类生成字节码文件,使用类加载器将文件加载到内存,最终创建代理对象。
注意:动态的生成字节码文件,是在运行时发生的。