Java学习笔记 14、反射与JDK动态代理(二)

简介: Java学习笔记 14、反射与JDK动态代理(二)

五、获取运行时类的完整结构


通过反射技术调用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提供的这种动态代理的方式有了一些理解,之后会更加深入源码进行学习,若是有错误之处请指出。

相关文章
|
7天前
|
Java C++
Java反射的简单使用
Java反射的简单使用
20 3
|
2天前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
3天前
|
Java
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
|
3天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
3天前
|
Java
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
|
3天前
|
Java API
【JAVA进阶篇教学】第一篇:JDK8介绍
【JAVA进阶篇教学】第一篇:JDK8介绍
|
4天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
14 0
|
5天前
|
Java API 开发者
解密Java反射机制与动态代理
解密Java反射机制与动态代理
10 0
|
5天前
|
Java 开发工具
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
|
6天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
20 0