Reflection反射
- 反射是什么?
- 反射的工作原理
- 反射的用途
- Class类
- 1.获取类对应的字节码的对象的三种方式:
- 2. 通过该Class实例来创建对应类型的实例
- 3.Class类常用方法
- Constructor类
- 1.获取构造方法:
- 2.调用构造方法:
- Field类
- 1.获取Field 字段
- 2.通过反射获取和设置字段的值
- 3.获取字段的值
- Method类
- 1.获取方法:
- 2.调用方法:
- 3.调用静态方法:
- 4.多态:
- 动态代理
- 1.代理模式:
- 2.静态代理:
- 3.动态代理:
- 4.动态代理的用途:
- 总结
反射是什么?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射的工作原理
当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。
反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。
反射的用途
- 动态加载类:在运行时根据某些条件动态地加载和使用类。
- 动态获取类的信息:可以获取类的属性、方法和构造函数等信息。
- 动态调用方法:可以在运行时根据方法名动态地调用类的方法。
- 动态修改属性:可以在运行时获取和设置类的属性值。
- 实现通用框架和工具:反射使得我们可以编写更通用、灵活的代码,例如自动化测试框架、ORM(对象关系映射)框架等
Class类
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.
为实现方法的使用,以下所有方法均以Book类为例进行操作:
class Book extends Product{
public String bookName;
public double price;
private int pageNum;
private double salt;
public Book() {
}
public Book(String bookName) {
}
public void dosth(){
}
public int dosth(int r){
Random rand = new Random();
int r =rand.nextInt(n);
return r;
}
public void dosth(int a,double b,float c) {
}
@Override
public String toString() {
return "Book [bookName=" + bookName + ", price=" + price + ", pageNum=" + pageNum + ", salt=" + salt + "]";
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public double getSalt() {
return salt;
}
public void setSalt(double salt) {
this.salt = salt;
}
}
1.获取类对应的字节码的对象的三种方式:
//方式1:通过类名访问class
Class bookCls1 = Book.class;
//方式2:通过实例访问getClass()
Book book= new Book();
Class bookCls2 = book.getClass();
//方式3:通过Class类的静态方法forName(类名)
Class bookCls3 = Class.forName("java.lang.package com.apesource.demo2.Book");
2. 通过该Class实例来创建对应类型的实例
newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
//创建(获取)Class对象
Class cls = Book.class;
//调用无参构造方法创建String类型的对象
Book book = (Book) cls.newInstance();
3.Class类常用方法
//类名
System.out.println("类(接口)的名称:"+cls.getSimpleName());
System.out.println("完全限定名:"+cls.getName());
System.out.println("类(接口)的类型称:"+cls.getTypeName());
//父类
Class superCls = cls.getSuperclass();
System.out.println("类的父类"+superCls);
//实现的接口
Class[] interfaceCls = cls.getInterfaces();
System.out.println("实现的接口:");
System.out.println(Arrays.toString(interfaceCls));
//package包
Package pck = cls.getPackage();
if(pck!=null) {
System.out.println("类所在的包的名称:"+pck.getName());
}
//判断类型
System.out.println("是否为接口:"+cls.isInterface());
System.out.println("是否为数组:"+cls.isArray());
System.out.println("是否为枚举:"+cls.isEnum());
System.out.println("是否为基本类型:"+cls.isPrimitive());
Constructor类
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象是一个构造方法,调用结果总是返回实例:
1.获取构造方法:
Class cls = Book.class;
....
//getConstructors():获取所有权限为 public 的构造方法
Constructor[] constructorArr= cls.getConstructors();
//getDeclaredContruectors():获取当前对象的所有构造方法
//Constructor[] constructorArr= cls.getDeclaredContruectors();
for(Constructor construct : constructorArr) {
System.out.println(construct);
}
2.调用构造方法:
无参构造方法:
Class cls = Book.class;
//调用无参构造方法创建Example类型的对象
Book b1 = (Book)cls.newInstance();
有参构造方法:
//有一个参数的构造方法
Constructor constructor2 = cls.getConstructor(String.class);
//执行构造方法,创建String类型的对象
Book b2 = (Book) constructor2.newInstance("sssss");
//获取私有的有参构造方法
Constructor privateConstructor = cls.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);
Book b3 = (Book)privateConstructor.newInstance("sss");
Field类
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息。Class类提供了以下几个方法来获取字段:
1.获取Field 字段
Class cls = Book.class;
//获取所有public修饰的字段(包含父类)
Field[] fields = cls.getFields();
//获取所有定义的字段(仅自己)
// Field[] fields = cls.getDeclaredFields();
//
for (Field field : fields) {
System.out.println("成员变量修饰符:"+field.getModifiers());
System.out.println("成员变量修饰符:"+Modifier.toString(field.getModifiers()));
System.out.println("成员变量的类型:"+field.getType());
System.out.println("成员变量的名称:"+field.getName());
System.out.println("----------------------------");
}
2.通过反射获取和设置字段的值
//运行期通过反射完成对象类型和成员变量的访问
//public修饰的字段
//获取Class类型对象
Class cls = Book.class;
//通过反射创建Book类型对象
Object obj = cls.newInstance();
//按照字段名称,获取指定字段
Field field1 = cls.getField("bookName");
//设置成员变量的值
//参数1:目标Book对象,参数2:存入成员变量中的值
field1.set(obj, "GGBond三打白骨精");
//private和protected修饰的字段:
Field field2 =cls.getDeclaredField("pageNum");
field2.setAccessible(true);
field2.set(obj, 1123);
3.获取字段的值
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));
System.out.println("-----------------------------------");
}
Method类
每一个方法都会被封装成一个Method对象,同样的,可以通过Class实例获取所有方法(Method类型的对象)。Class类提供了以下几个方法来获取Method:
1.获取方法:
Class cls = Book.class;
//获取所有public方法(包含父类)
//Method[] methods = cls.getMethods();
//获取所有定义的方法(仅自己)
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());
//获取所有的参数类型:
//System.out.println("方法的参数类型:");
//Class[] paramTypes = method.getParameterTypes();
//获取所有的参数对象
Parameter[] parm = method.getParameters();
for (Parameter p : parm) {
System.out.println("("+p.getType()+" "+p.getName()+")");
System.out.println("-----------");
}
}
}
2.调用方法:
//获取class对象
Class cls = Book.class;
//创建Base对象
Object obj = cls.newInstance();
//按照方法名称和“参数类型”获取method方法对象
//creat()
// Method method = cls.getMethod("dosth");
//creat(int n)
Method method = cls.getMethod("creat",int.class);
//invoke():以反射的方式执行方法
int r = (int)method.invoke(obj,1000);
System.out.println(r);
}
3.调用静态方法:
//计算以10为底的对数(获取某个数字的位数)
System.out.println((int)(Math.log10(1235332))+1);
//以反射的方式实现
Class cls = Math.class;
Method method = cls.getMethod("log10", double.class);
int size = Double.valueOf((double)method.invoke(null, 1235332)).intValue()+1;
System.out.println(size);
4.多态:
通过反射调用方法时,仍然遵循多态原则:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class cls = Person.class;
Method method = cls.getMethod("hello");
method.invoke(new Sutdent());
}
}
class Person{
public void hello() {
System.out.println("Person:Hello");
}
}
class Sutdent extends Person{
@Override
public void hello() {
// TODO Auto-generated method stub
System.out.println("Student:Hello");
}
}
动态代理
1.代理模式:
代理模式是Java众多设计模式中的一种,代理模式就是给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。其本质就是在不改变原实现对象的情况下,通过"代理类"来完成对程序逻辑的扩展。
代理模式角色分为 3 种:
- Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
- RealSubject(真实主题角色):真正实现业务逻辑的类;
- Proxy(代理主题角色):用来代理和封装真实主题;
以下面这个案例为例:
//接口
public interface UserService {
void select();
void update();
}
// 真正的实现类
public class UserServiceImpl implements UserService {
@Override
public void select() {
System.out.println("select * ..................");
System.out.println("数据库中完成用户信息的查询执行!");
}
}
2.静态代理:
自己手动创建"代理类",通过代理,对行为逻辑的扩展:
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy() {
target = new UserServiceImpl();
}
@Override
public void select() {
before();
target.select();//通过目标对象,真正实现业务逻辑处理
after();
}
private void before() {
System.out.println("方法开始执行........");
}
private void after() {
System.out.println("方法结束执行........");
}
}
3.动态代理:
通过Proxy.newProxyInstance()方法,由Jvm在运行期为我们自动生成一个看不见的代理类,通过InvocationHandler接口的实现类完成对程序逻辑的扩展。
/InvocationHandler接口实现类:代理类中扩展逻辑和抽取封装
public class LogInvoctionHandlerImpl implements InvocationHandler {
private Object targer;
public LogInvoctionHandlerImpl(Object target) {
this.targer=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.printf("方法%s正在被执行。。。\n",method.getName());
//执行目标对象的目标方法
Object resultValue=method.invoke(targer, args);//invoke(对象名,方法参数)
System.out.printf("方法%s执行结束。。。\n",method.getName());
return resultValue;
}
}
测试类:
public class Client {
public static void main(String[] args) {
//静态代理:
UserService user = new UserServiceProxy();
user.select();
//动态代理
//创建InvocationHandler
InvocationHandler handler = new LogInvoctionHandlerImpl(new UserServiceImpl());
//创建UserService接口的动态代理对象
UserService proxy = (UserService)Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(),
new Class[] {
UserService.class
},
handler);
System.out.println(proxy.getClass());
//通过代理对象调用方法====>LogInvoctionHandlerImpl实现类的invoke
proxy.select();
4.动态代理的用途:
动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
总结
- 通过Class实例获取class信息的方法称为反射(Reflection);
- 获取一个class对应的Class实例后,就可以获取该class的所有信息;
- Java的反射API提供的Constructor对象封装了构造方法的所有信息;
- Java的反射API提供的Field类封装了字段的所有信息;
- 通过反射读写字段是一种非常规方法,它会破坏对象的封装;
- Java的反射API提供的Method对象封装了方法的所有信息:
- 通过反射调用方法时,仍然遵循多态原则;
- 动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。