一、Java反射机制
在学习Java反射机制前,我们应该先明确两个概念:编译期和运行期。
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java反射机制是指在程序的运行状态中,可以得到任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
● 在运行时判断任意一个对象所属的类。
● 在运行时构造任意一个类的对象。
● 在运行时判断任意一个类所具有的成员变量和方法。
● 在运行时调用任意一个对象的方法。
● 生成动态代理。
二、Class类
1、Class类
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的实例(JVM内部创建,我们自己的Java程序是无法创建Class实例的)。由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段(成员变量)等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射。
获取一个class的Class实例(以String类为例)的三种方法:
(1)直接通过一个class的静态变量class获取:
Class cls = String.class;
(2)如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String str = "Java";
Class cls = str.getClass();
(3) 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");
Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例:
Class cls1 = String.class;
String str = "Java";
Class cls2 = str.getClass();
boolean sameClass = cls1 == cls2; // true
Class常用方法:
public class class01 {
public static void main(String[] args) {
Class cls = String.class;
printClassInfo(cls);
}
public static void printClassInfo(Class cls) {
System.out.println("类的名称:"+cls.getSimpleName());
System.out.println("完全限定名:"+cls.getName());
System.out.println("类的类型名称:"+cls.getTypeName());
Class[] clsArr=cls.getInterfaces();
System.out.println("当前类实现的接口:");
for(Class clss:clsArr) {
System.out.println(clss);
}
System.out.println("类所在包的名称:"+cls.getPackageName());
System.out.println("是否为接口:"+cls.isInterface());
System.out.println("是否为数组:"+cls.isArray());
System.out.println("是否为枚举:"+cls.isEnum());
System.out.println("是否为基本类型:"+cls.isPrimitive());
}
}
注意,数组(例如String[])也是一种类,而且不同于String.class,它的类名是[Ljava.lang.String;。此外,JVM为每一种基本类型如int也创建了Class实例,通过int.class访问。
动态加载机制:JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
2、调用构造方法
通过反射来创建新的实例,可以调用Class提供的newInstance()方法(只能调用该类的public修饰的无参数构造方法):
Student stu = Student.class.newInstance();
Constructor类(调用任意的构造方法):
public class class02 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
Class cls=Example.class;
Example ex1=(Example) cls.newInstance();
System.out.println(ex1);
//输出Example类中的所有构造方法
System.out.println("Example类中的所有构造方法:");
Constructor[] constructorArr=cls.getConstructors();
for(Constructor constructor:constructorArr) {
System.out.println(constructor);
}
//调用Example类的有一个参数的有参构造
Constructor constructor=cls.getConstructor(int.class);
Example ex2=(Example) constructor.newInstance(123);
System.out.println(ex2);
//调用Example类的有两个参数的有参构造
Constructor constructor2=cls.getConstructor(int.class,double.class);
Example ex3=(Example) constructor2.newInstance(123,12.3);
System.out.println(ex3);
}
}
class Example{
public Example() {
System.out.println("Example类的无参构造!");
}
public Example(int a) {
System.out.println("Example类的有一个参数的有参构造!");
}
public Example(int a,double b) {
System.out.println("Example类的有两个参数的有参构造!");
}
}
通过Class实例获取Constructor的方法如下:
● getConstructor(Class...):获取某个public的构造方法;
● getDeclaredConstructor(Class...):获取某个定义的构造方法;
● getConstructors():获取所有public的构造方法;
● getDeclaredConstructors():获取所有定义的构造方法。
3、获取继承关系
获取父类的Class(getSuperclass()方法):
Class cls = String.class;
System.out.println(cls.getSuperclass());
注意:Object的父类是null
获取interface(getInterfaces()方法):
Class cls = String.class;
Class[] is = cls.getInterfaces();
继承关系:
当我们判断一个实例是否是某个类型时,可以使用instanceof操作符:
Object obj=Integer.valueOf(7);
System.out.println("是否为Integer:"+(obj instanceof Integer)); //true
System.out.println("是否为Object:"+(obj instanceof Object)); //true
System.out.println("是否为Double:"+(obj instanceof Double)); //false
System.out.println("是否为Comparable:"+(obj instanceof Comparable)); //true
System.out.println("是否为Double:"+(obj instanceof Double)); //false
System.out.println("是否为Number:"+(obj instanceof Number)); //true
如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()方法:
Object obj=Integer.valueOf(7);
System.out.println("Integer<=Integer:"+Integer.class.isAssignableFrom(Integer.class)); //true
System.out.println("Integer<=NUmber:"+Integer.class.isAssignableFrom(Number.class)); //false
System.out.println("Integer<=Double:"+Integer.class.isAssignableFrom(Double.class)); //false
System.out.println("Integer<=Comparable:"+Integer.class.isAssignableFrom(Comparable.class); //false
System.out.println("NUmber<=Integer:"+Number.class.isAssignableFrom(Integer.class)); //true
4、访问字段(成员变量) :
获取Field 字段:
● Field getField(name):根据字段名获取当前类中某个public的field(包括父类)
● Field getDeclaredField(name):根据字段名获取当前类中定义的某个field(不包括父类)
● Field[] getFields():获取所有public的field(包括父类)
● Field[] getDeclaredFields():获取当前类中定义的所有field(不包括父类)
public class class03 {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException {
Class cls=Book.class;
Field[] fields=cls.getDeclaredFields(); //获取Book类中定义的所有field(不包括父类)
for (Field field : fields) {
System.out.println("成员变量访问修饰符(int):"+field.getModifiers());
System.out.println("成员变量访问修饰符:"+Modifier.toString(field.getModifiers()));
System.out.println("成员变量类型:"+field.getType());
System.out.println("成员变量名称:"+field.getName());
System.out.println();
}
//设置成员变量authorName的值
Field field=cls.getDeclaredField("authorName");
Object obj=cls.newInstance();
field.set(obj, "南派三叔");
System.out.println(obj.toString());
}
}
class Book{
public String authorName;
public double price;
private String bookName;
private String publishTime;
public String getName() {
return authorName;
}
public void setName(String authorName) {
this.authorName = authorName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getTime() {
return publishTime;
}
public void setTime(String publishTime) {
this.publishTime = publishTime;
}
//重写toString()方法
public String toString() {
return String.format("书名:%s,姓名:%s,价格:%f,出版时间:%s", bookName,authorName,price,publishTime);
}
public int createRandomNumber(int i) {
Random ran=new Random();
int a=ran.nextInt(i);
System.out.println(a);
return a;
}
public int createRandomNumber2() {
int a=new Random().nextInt(100);
System.out.println(a);
return a;
}
}
获取字段值(成员变量的值):
public class class04 {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
Book book=new Book();
book.authorName="南派三叔";
book.price=44.4;
book.setBookName("盗墓笔记");
book.setTime("2012-2-2");
printInfo(book);
}
public static void printInfo(Object obj) throws IllegalArgumentException, IllegalAccessException {
Class cls=obj.getClass();
Field[] fields=cls.getDeclaredFields();
for (Field field : fields) {
System.out.println("成员变量名称:"+field.getName());
if(!field.isAccessible()) {
field.setAccessible(true);
}
System.out.println("成员变量内容:"+field.get(obj)); //获取字段值
}
}
}
设置字段值:
代码在class03类中。注意:修改非public字段,需要首先调用setAccessible(true),然后在调用set()方法。
5、调用方法
Methond类:我们可以通过Class实例获取所有方法(Method类型的对象)。
● Method getMethod(name, Class...):获取某个public的Method(包括父类)
● Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
● Method[] getMethods():获取所有public的Method(包括父类)
● Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
public class class05 {
public static void main(String[] args) {
Class cls = Book.class;
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法的访问修饰符:" + Modifier.toString(method.getModifiers()));
System.out.println("方法的返回值类型:" + method.getReturnType());
System.out.println("方法的名称:" + method.getName());
Class[] pArr = method.getParameterTypes();
for (Class p : pArr) {
System.out.println("方法的参数类型:"+p.getName());
}
System.out.println();
}
}
}
调用方法 :
public class class06 {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class cls = Book.class;
Object obj = cls.newInstance();
//调用Book类的有参方法
Method method = cls.getMethod("createRandomNumber", int.class);
method.invoke(obj, 1000);
//调用Book类的无参方法
Method method2 = cls.getMethod("createRandomNumber2");
method2.invoke(obj);
}
}
调用静态方法(调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null):
public class class07 {
public static void main(String[] args) throws Exception {
//获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
//调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
System.out.println(n);
}
}
调用非public方法:
在调用非public方法时,与修改非public字段,我们需要首先调用setAccessible(true),然后在调用invoke()方法。
多态:
public class class08 {
public static void main(String[] args) throws Exception {
//获取Person的hello方法:
Method h = Person.class.getMethod("hello");
//对Student实例调用hello方法:
h.invoke(new Student());
}
}
class Person {
public void hello() {
System.out.println("Person:hello");
}
}
class Student extends Person {
public void hello() {
System.out.println("Student:hello");
}
}
上述代码输出的结果是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法。
三、静态代理和动态代理
1、代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。代理模式角色分为 3 种:
● Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
● RealSubject(真实主题角色):真正实现业务逻辑的类;
● Proxy(代理主题角色):用来代理和封装真实主题;
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。
2、静态代理:所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
案例:假设有UserService接口及其实现类UserServiceImpl,我们需要在不改变实现类代码的基础上,增加日志记录的功能(select和update)。
UserService接口:
public interface UserService {
void select();
void update();
}
UserServiceImpl类(真正的实现类):
//真正的实现类
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("select......");
System.out.println("数据库中完成用户信息的查询执行!");
}
public void update() {
System.out.println("update......");
System.out.println("数据库中用户状态的更新执行!");
}
}
UserServiceProxy类(代理类):
public class UserServiceProxy implements UserService{
private UserService target;
public UserServiceProxy() {
target=new UserServiceImpl();
}
public void select() {
before();
target.select();
after();
}
public void update() {
before();
target.update();
after();
}
private void before() {
System.out.println("------方法执行前!------");
}
private void after() {
System.out.println("------方法执行后!------");
System.out.println();
}
}
Client类:
public class Client {
public static void main(String[] args) {
UserService user=new UserServiceProxy();
user.select();
user.update();
}
}
3、动态代理:动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。JDK动态代理主要涉及两个类:Proxy类 和 InvocationHandler类。我们通过编写一个调用逻辑处理器 LogInvocationHandlerImpl类案例来提供日志增强功能,并实现 InvocationHandler 接口。(由JDK帮我们创建代理类)
案例:同静态代理
UserService接口:
public interface UserService {
void select();
void update();
}
UserServiceImpl类(真正的实现类):
//真正的实现类
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("select......");
System.out.println("数据库中完成用户信息的查询执行!");
}
public void update() {
System.out.println("update......");
System.out.println("数据库中用户状态的更新执行!");
}
}
LogInvocationHandlerImpl类:
public class LogInvocationHandlerImpl implements InvocationHandler {
private Object traget;
public LogInvocationHandlerImpl(Object target) {
this.traget = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.printf("方法%s开始执行、、、、、、、\n",method.getName());
Object returnValue =method.invoke(traget, args);
System.out.printf("方法%s结束执行、、、、、、、\n",method.getName());
System.out.println();
return returnValue;
}
}
Client类:
public class Client {
public static void main(String[] args) {
//创建目标对象和InvocationHandler
LogInvocationHandlerImpl handler = new LogInvocationHandlerImpl(new UserServiceImpl());
//创建UserService接口的动态代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
new Class[] { UserService.class }, handler);
proxy.select();
proxy.update();
}
}