五、获取运行时类的完整结构
通过反射技术调用Class的方法能够获取到类的完整结构:实现的全部接口、继承的父类、构造器、方法、属性、注解、权限修饰符、返回类型、参数列表参数、异常、包
接口:
public Class[] getInterfaces() :确定此对象所表示的类或接口实现的接口
父类:
public Class getSuperclass():表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
构造器:
public Constructor[] getConstructors():返回此 Class 对象所表示的类的所有public构造方法(该对象必须显示声明才能获取到,无法获取父类的构造器)。
public Constructor[] getDeclaredConstructors():返回此 Class 对象表示的类声明的所有构造方法(默认的无参构造也能够获取到)。
Constructor类:包含的方法
public int getModifiers():取得修饰符。
public String getName();:取得方法名称。
public Class[] getParameterTypes();:取得参数的类型。
方法:
public Method[] getMethods() :返回此Class对象所表示的类或接口的public的方法(包括所有父类public方法)。
public Method[] getDeclaredMethods():返回此Class对象所表示的类或接口的全部方法(不包括父类的方法)。
Method类:包含的方法
public Class getReturnType():取得全部的返回值。
public Class[] getParameterTypes():取得全部的参数。
public int getModifiers():取得修饰符。
public Class[] getExceptionTypes():取得异常信息。
属性(Field):
public Field[] getFields() :返回此Class对象所表示的类或接口的public的Field(包括所有父类public方法)。。
public Field[] getDeclaredFields() :返回此Class对象所表示的类或接口的全部Field。
Field类:包含的方法
public int getModifiers():以整数形式返回此Field的修饰符。
public Class getType():得到Field的属性类型。
public String getName():返回Field的名称。
注解(Annotation):
Annotation[] getAnnotations():获取该类上的注解。
泛型:
Type getGenericSuperclass():获取父类泛型类型。
getActualTypeArguments():获取运行时的带泛型的父类的泛型。
包:
Package getPackage():获取当前类的包。
说明:这里只是举了一小部分方法,还有其他许多方法都类似,一定要熟悉反射包的作用,其反射机制。
六、调用运行时类的指定方法
1、调用指定方法(invoke方法)
前面介绍了获取方法类Method的方式,这里来学习通过反射调用类中的方法(Method类中的invoke方法):
①首先我们需要从指定的Class中获取到指定的方法Method实例:
调用方法Method getMethod(String name, Class<?>... parameterTypes)
name(参数):表示指定方法的名称
parameterTypes(参数):填写方法参数的类型,如String.class
②接着使用指定Method实例来调用invoke方法
调用方法:Object invoke(Object obj, Object... args)
obj(参数):该方法被调用的对象,若该方法是静态方法可填null。
args(参数):该方法填入的参数,若参数为空可填null或不填。
Object(返回值):对应原方法的返回值,若原方法无返回值,则返回null。
注意点:若是该方法是私有private,使用getDeclaredMethod方法获取实例,并在调用invoke方法之前使用method.setAccessible(true);才可调用私有方法。
测试一下:
class Student{ private void say(){ System.out.println("新年快乐!"); } } public class Main { public static void main(String[] args) throws Exception { Class<Student> clazz = Student.class; Method method = clazz.getDeclaredMethod("say"); method.setAccessible(true);//不设置为true会报错java.lang.IllegalAccessException method.invoke(clazz.newInstance());//若是该方法是静态方法可以填null } }
2、调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
①首先取得Filed类(指定属性),使用Class类的getField(String name)或public Field getDeclaredField(String name)
name(参数):对应参数名称。
②通过调用Filed的方法取得与设置该属性的值。
public Object get(Object obj):取得指定对象obj上此Field的属性内容。
public void set(Object obj,Object value):设置指定对象obj上此Field的属性内容
参数obj对于静态属性时可以填null直接获取与上面调用方法一致。
注意点:获取Class中指定私有属性需要使用getDeclaredField(String name)方法,并且在调用get()或set()前使用Field类的setAccessible(true);方法才能获取到。
测试一下:
class Student{ private String name; } public class Main { public static void main(String[] args) throws Exception { //获取属性name Class<Student> clazz = Student.class; Field field = clazz.getDeclaredField("name"); field.setAccessible(true); System.out.println(field.get(clazz.newInstance())); //设置属性name为changlu Student student = clazz.newInstance(); field.set(student,"changlu");//设置属性 System.out.println(field.get(student)); } }
setAccessible方法说明
Method和Field、Constructor类都有void setAccessible(boolean flag)方法。
flag可填true或false。
true:指示反射的对象在使用时应该取消Java语言访问检查。能够提升反射的效率,并使得原本无法访问的private属性或方法能够访问。
false:默认为此参数,指示反射的对象应该实施Java语言访问检查。
注意:无论public、private的属性、方法、构造器都可以使用该方法并设置true来取消java访问检查,能够提升反射效率(频繁反射操作建议关闭)。需要最最注意一点就是若是想要通过反射访问private的属性,一定要将访问检查取消,也就是要调用setAccessible(true),并且前面在获取Filed、Method类…需要使用如getDeclaredField()否则会报异常。
七、反射应用(动态代理)
1、介绍动态代理
代理设计模式原理:通过使用一个代理将对象包装起来,然后使用该代理对象取代原始对象。任何原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
代理分类:静态代理与动态代理。
静态代理:代理类与目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。(最好就通过一个代理类完成全部的代理功能)
动态代理:指客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。使用场景如调试、远程方法调用。
动态代理相对于静态代理优点:抽象角色中(接口)声明的所有方法都转移到调用处理器一个集中的方法中处理,这样我们能够更加灵活和统一的处理众多的方法。
2、基于接口的动态代理
介绍Proxy、InvocationHandler
Java中动态代理的实现,关键两个类为Proxy、InvocationHandler:
Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供用于创建动态代理类和动态代理对象的静态方法:
static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces):创建一个动态代理类所对应的Class对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理类对象。
loader:传入类加载器。
interfaces:传入需要代理类实现的全部接口,数组形式。
h:实现InvocationHandler 接口的实现类实例。
InvocationHandler接口:需要实现该接口并实现其中的invoke()方法。
Object invoke(Object proxy, Method method, Object[] args):完成代理的具体操作。
proxy:代理类的对象。
method:此时调用的方法。
args:调用方法时的参数。
注意:在运行中创建的动态代理对象的父类就是Proxy类。
JDK实现动态代理(两个例子)
第一个动态代理程序
首先定义了一个接口Person,接着使用JDK提供的Proxy类的newProxyInstance方法来创建实现Person的接口代理类对象,这种没有实现接口类但是在运行期间创建了一个接口对象的方式,称为动态代码。这里JDK提供的动态创建接口对象的参数,叫做动态代理。
mport java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @ClassName ProxyTest * @Author ChangLu * @Date 2021/2/20 15:28 * @Description TODO */ //被代理类接口 interface Person{ void say(String name); String walk(); } //实现InvocationHandler接口,并重写方法 class OwnInvocationHandler implements InvocationHandler { //注意这里的proxy是真实对象的代理类(这里指的是Person接口的代理类,父类是Proxy类) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //通过method实例的getName()来判断当前调用的是哪个方法 if("say".equals(method.getName())){ System.out.println("我是"+args[0]); }else if("walk".equals(method.getName())){ System.out.println("在走路中....."); } return null; } } public class AnnotationTest { public static void main(String[] args) { //动态创建Person的代理类 Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new OwnInvocationHandler()); person.say("changlu");//我是changlu person.walk();//在走路中..... } }
第39行:这里的person实例实际上是Person接口的代理类,是在运行期间创建的一个接口对象。
对于实现哪个接口的代理类根据Proxy.newProxyInstance传入的第二个参数相关,传入指定接口的Class类用数组形式传递。
第35、36行:使用多态方式调用其接口方法时会执行OwnInvocationHandler(也就是实现InvocationHandler接口的实现类)的invoke方法。
针对于实现类进行动态代理
若我们想要一个实现接口类进行方法增强,可以使用通过动态代理的方式实现,在实现接口类指定方法的前后添加代码功能:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @ClassName ProxyTest * @Author ChangLu * @Date 2021/2/20 15:28 * @Description TODO */ //被代理类接口 interface Person{ void say(String name); String walk(); } //创建Person的实现类 class Student implements Person{ @Override public void say(String name) { System.out.println("student名字叫"+name); } @Override public String walk() { System.out.println("student跑步...."); return "1000公里"; } } class OwnInvocationHandler implements InvocationHandler{ private Object obj; public OwnInvocationHandler(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"方法执行前..."); //执行代理类调用的方法 Object invokeReturn = method.invoke(obj, args); System.out.println(method.getName()+"方法执行后..."); return invokeReturn;//将调用方法的返回值返回 } } public class ProxyTest { public static void main(String[] args) { //传入指定的实体类 Student student = new Student(); InvocationHandler handler = new OwnInvocationHandler(student); //动态创建Person的代理类 Person person = (Person) Proxy.newProxyInstance(Student.class.getClassLoader(), student.getClass().getInterfaces(),handler); //代理类开始执行方法了 person.say("changlu"); System.out.println("-----------------"); person.walk(); } }
重要重要:
第38行:要想使用指定接口实现类的方法,在这里很关键的是在实现InvocationHandler中多增加了一个有参构造,用于将对应接口实现类实例传入进来,便于之后在invoke()(InvocationHandler接口实现类中的)中使用method.invoke(obj,参数)来反射调用接口实现类的指定方法。
第46行:该行实际上就是通过反射来调用obj实例的方法,在此前后我们可以添加其他的相关操作。
第59行:newProxyInstance()方法中的传参说明,第一个参数就是传入一个类加载器(并不限制于如上的获取类加载器方法),第二个参数比较重要,也就是你想要在运行期间实现的接口。(这里student.getClass().getInterfaces()也可以使用new Class[]{Person.class},获取到对应接口class类并且以数组形式传入)
说明:这个例子也可以说是Spring中AOP的简单实现了,可用于日志记录等其他使用场景。
关于InvocationHandler接口中第一个参数proxy InvocationHandler接口如下: public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
这里的proxy指的是在运行期间动态创建的接口对象,即真实对象的真实代理对象,代理对象为com.sun.proxy.$Proxy0。
使用用途:通常情况下invoke方法都会返回真实对象的返回结果(如前面例2中调用实际类的方法返回值),该方法返回值为Object,我们也可以将proxy对象返回,可以通过这个返回对象做相关操作。
例子:返回proxy的用途如下
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @ClassName ProxyTest * @Author ChangLu * @Date 2021/2/20 15:28 * @Description TODO */ interface Account { Account deposit(double value); double getBalance(); } class OwnInvocationHandler implements InvocationHandler { private double balance; @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if ("deposit".equals(method.getName())) { Double value = (Double) args[0]; System.out.println("deposit: " + value); balance += value; return proxy; } if ("getBalance".equals(method.getName())) { return balance; } return null; } } public class ProxyTest { public static void main(String[] args) { Account account = (Account) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Account.class}, new OwnInvocationHandler()); account.deposit(100).deposit(200).deposit(300); System.out.println(account.getBalance()); account.deposit(100).deposit(200); System.out.println(account.getBalance()); } }
第23行:在invoke()方法中通过反射调用无返回值的方法,返回proxy实例即真实代理类。我们可以看到41行可以对该代理对象进行连续调用。
这里又会有一个问题就是为什么不返回this,而是返回proxy?
答:若是在invoke()方法中返回this,就是返回的是OwnInvocationHandler实例了,而不是真实的代理对象。
3、JDK动态代理原理分析
我们通过上面InvocationHandler第一个参数proxy案例来进行过程原理分析:
首先看该方法:Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Account.class}, new OwnInvocationHandler());
使用的是Proxy类中的newProxyInstance方法。
//Proxy类 public class Proxy implements java.io.Serializable { @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //通过传入方法的类加载器以及接口class类来创建出代理类 Class<?> cl = getProxyClass0(loader, intfs); ... } }
第18行:getProxyClass0方法返回的代理类是在运行期间中创建的类名为$Proxy0,暂存在JVM中,我们在JDK核心类库中是找不到该代理类的。(该代理类的名称后面的0是编号,有多个代理类会一次递增)
由于该代理类是动态生成的类文件,暂时缓存在jvm中,我们通过下面方法获取到class文件:
interface Account { Account deposit(double value); double getBalance(); } public class ProxyTest { public static void main(String[] args) throws IOException { //第一个为类名Proxy0,第二个为要实现的接口(数组形式传递) byte[]classFile = ProxyGenerator.generateProxyClass("Proxy0",new Class[]{Account.class}); File file =new File("Proxy0.class"); FileOutputStream fos =new FileOutputStream(file); fos.write(classFile); fos.flush(); fos.close(); } }
我们会在目录文件下获得Proxy0.class,在idea中打开即可进行反编译。
动态生成的Proxy0类如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import xyz.changlu.reflection.Account; public final class Proxy0 extends Proxy implements Account { private static Method m1; private static Method m3; private static Method m2; private static Method m4; private static Method m0; public Proxy0(InvocationHandler var1) throws { //这里就会触发调用Proxy的有参构造 super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final double getBalance() throws { try { //调用通过构造器传入的InvocationHandler实例调用方法,这里m3指的就是getBalance return (Double)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Account deposit(double var1) throws { try { //调用通过构造器传入的InvocationHandler实例调用方法,这里m4指的就是deposit return (Account)super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { //分别为equals方法、hashcode()方法、toString()方法 m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("xyz.changlu.reflection.Account").getMethod("getBalance"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("xyz.changlu.reflection.Account").getMethod("deposit", Double.TYPE); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
梳理一下其中做的事情:
①声明3个主要方法(equals()方法、hashcode()方法、toString()方法)以及实现接口的总共五个Mehtod,并在static代码块中对类进行初始化通过反射获取Mehtod实例。
②有一个有参构造器,传入的参数为InvocationHandler类,并且其中会触发调用Proxy的有参构造器,并传递InvocationHandler实例。
③各个方法中都通过反射来调用各自Method的invoke()方法,实现了动态代理。
我们接着上面的newProxyInstance()部分看下去:
public class Proxy implements java.io.Serializable { private static final Class<?>[] constructorParams = { InvocationHandler.class }; @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) .... /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //cl是运行中生成的代理类Class即上面的说明的Proxy0的Class类 //通过反射获取Proxy0的有参构造器,参数为constructorParams = { InvocationHandler.class }; final Constructor<?> cons = cl.getConstructor(constructorParams); //将方法参数中的InvocationHandler实例h传入ih final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //这里就是通过反射调用Proxy0的有参构造创建实例,并传入InvocationHandler实例h return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
这里通过反射获取Proxy0的有参构造器,并在最后调用有参构造并将方法参数中InvocationHandler实例传入。
在调用Proxy0有参构造器时也会创建Proxy的有参构造。
public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); //这里是给Proxy的InvocationHandler实例h赋值,实际上就是在调用newProxyInstance()传入的InvocationHandler实例 this.h = h; } }
之后我们通过Proxy0实例调用接口方法时,也就会执行其中的invoke()方法,我们看一下Proxy0中的getBalance()方法:
public final double getBalance() throws { try { //super指的是其父类Proxy,h即为Proxy中的InvocationHandler实例h(之前调用有参构造时进行赋值了),调用我们重写的实现InvocationHandler接口的invoke()方法,m3则是我们自定义接口实现类通过反射获得的getBalance()的Method实例 return (Double)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
动态代理就是这么来的,经过这一下子分析对于反射有了更加深的理解,我们通过Proxy调用newProxyInstance方法能够获取到一个在运行期间的实现指定接口的代理类。通过实现InvocationHandler接口中的invoke()方法我们能够对指定类的方法进行动态代理调用。
有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情,例如Spring框架中的IOC与AOP都使用到了动态代理。
通过上面的分析梳理,大致对JDK提供的这种动态代理的方式有了一些理解,之后会更加深入源码进行学习,若是有错误之处请指出。