14、反射、动态代理

一、反射

  反射其实就是对字节码进行操作,构造函数对应Constructor类,成员变量对应Field类,成员方法对应Method类,其实这些类有一个特性,那就是通过字节码获取的。

1.1、反射解析

1、获取字节码的三种方式:

  • 类名.class 例如:System.class
  • 对象.class 例如:new Date().getClass()
  • Class.forName("类名"),例如:Class.forName("java.util.Date");
String str = "abc";
Class cls1 = str.getClass();//通过调用getClass方法获取字节码。
Class cls2 = String.class;//通过类名.class获取字节码。
Class cls3 = Class.forName("完整类名");//通过Class.forName()获取字节码,会出现异常。
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true

总结:获取字节码的三种方式,主要用第三种。可以将"java.util.Date"看成一个变量。

2、反射获取构造函数

Constructor类:

a) 得到某个类所有的构造方法。

Constructor[] cons  = Class.forName("java.lang.String").getConstructors();

b) 得到某一个构造方法:

Constructor constructor = Class.forName("java.lang.String").getConstructors(StringBuffer.class);

c) 创建实例对象:

通常方式:
        String str = new String(new StringBuffer("abc"));
反射方式:
        Constrctor cons = String.class.getConstructor(StringBuffer.class);
        String str = (String)cons.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象

String.class.getConstructor("可变参数列表");//StringBuffer.class,int.class.....

常用方法:

  • getConstructor(字节码...)://获取类中public修饰构造函数,获取时需要指定参数列表来确定具体哪一个.注意它是可变参数列表。
  • getDeclaredConstructor(字节码...)://获取类中public或private修饰的构造函数,获取时需要指定参数列表来确定具体哪一个.注意它是可变参数列表。
  • getConstructors()://获取类中所有被public修饰的构造函数,返回的是Constructor类型的数组。
  • getDeclaredConstructors()://获取类中所有被public以及private修饰的构造函数,返回值是Constructor类型的数组。

使用详解:

1.Contructor类中的newInstance()的返回值是Object类型,参数列表对应getConstruct()方法的参数列表锁指定的类型。

Constructor cons = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str = (String)cons.newInstance(new StringBuffer("abc"));

类型转换是因为编译时所指定的,运行时却不知道,所以我们必须再次指定,也可以简单的理解newInstance()返回的是Object类型。
注意:Class类中也有newInstance()方法,其实只是Constructor类的省略写法,省去了获取字节码的过程,它只能访问无参的构造函数。

3、成员变量的反射

Field类:Field类代表某个类中的一个成员变量。

  • getField(String name)://获取类中public修饰的且变量名和参数列表相同的成员变量。
  • getDeclaredField(String name)://获取类中public或者private修饰的且变量名和参数列表相同的成员变量。
  • getFields()://获取类中所有被public修饰的成员变量,返回值是Field类型的数组。
  • getDeclaredFields()://获取类中所有被public以及private修饰的成员变量,返回值是Field类型的数组。

使用详解:
(1) get和set方法:

Object get(Object obj)://返回指定对象上此 Field 表示的字段的值。根据返回值注意类型转换。
void set(Object obj, Object value)://将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
拓展:这系列方法还有很多,比如:getInt()、getBoolean()、setInt()、setBoolean().....等等 

(2) Class<?> getType():返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。

例:field.getType == String.class

注:这里判断用==,当然也可以用equals,只是==的话语意上才正确。

实例演示:

class ReflectPoint {
    private int x;
    public int y;
    ReflectPoint(int x,int y){
        this.x = x;
        this.y = y;
    }
}
//主函数
ReflectPoint rep = new ReflectPoint(3,5);
//Field不是对象身上的变量,而是类上,要用它去取对象身上的值。
//获取public修饰的成员变量。
Field fieldY = rep.getClass().getField("y");
System.out.println(fieldY.get(rep));

//获取被private修饰的成员变量(暴力反射)
Field fieldX = rep.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//设置为可以访问
System.out.println(fieldX.get(rep));
/*
我的写法:
ReflectPoint rep = new ReflectPoint(3,5);
Class cls = rep.getClass();
Field fieldY = cls.getField("y");
//暴力反射
Field fieldX = cls.getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(rep)+"::"+fieldY.get(rep));

练习:将一个类中所有String类型的成员变量的值中的a改成b。

(接上面,具体参考反射源码示例)
//获取类中所有的String类型成员变量,并将‘a‘改成‘b‘。
public static void changeValue(Object obj) throws Exception{
    Field[] fields = obj.getClass().getFields();
    //迭代器
    for(Field field : fields){
        if(field.getType() == String.class){
            String oldValue = (String)field.get(obj);
            String newValue = oldValue.replace(‘a‘,‘b‘);
            //替换后再将新值存进去
            field.set(obj,newValue);
        }
    }
}

4、成员方法的反射 

Method类:Method代表某个类中的一个成员方法。

Mehod charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);

调用方法:

1)通常方式:
            String str = "abc";
            System.out.println(str.charAt());
(2)反射方式:
            String str = "abc";
            Mehod Me = Class.forName("java.lang.String").getMethod("charAt",int.class);
            System.out.println(Me.invoke(str,1));
            //注:getMethod(name,type.class):第一个参数是方法名,第二个参数是数据类型的字节码。
            //invoke(Object obj,参数):第一个参数为对象,第二个参数为getMethod方法中所对应的方法名的参数。(charAt的参数)

如果传递给Method对的invoke()方法的第一个参数为null,说明该Method的对象对应的是一个静态方法。

常用方法:

  • getMethod(String name,字节码...)://获取类中被public修饰的方法,获取时需要指定方法名(name),以及字节码是该方法名的参数列表类型的字节码。
  • getDeclaredMethod(String name,字节码...)://获取类中被public或者private修饰的方法,获取时需要指定方法名(name),以及字节码是该方法名的参数列表类型的字节码。
  • getMethods()://获取类中被public修饰的所有方法,返回值是Method类型的数组。
  • getDeclaredMethods()://获取类中被public以及private修饰的所有方法,返回值是Method类型的数组

使用详解:

invoke(Object obj, Object... args):

(1) 它的返回值是Object类型,所以调用传值过程中注意类型转换。
(2) 它第一个参数对应被反射的类的引用(对象),如果被反射获取的方法是静态的,则参数值为null。

(3) 它第二个参数是可变参数列表,对应被反射获取的方法的参数列表,如果为无参方法,则invoke第二个参数值为0的数组或者null。

(4) 它具备了所有被反射的成员的方法的功能,可以破坏方法的限制,主要指定该方法所在类的对象,以及方法名。

5、数组的反射:

  • 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。
  • 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
  • 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
  • Arrays.asList()方法处理int[]和String[]时的差异。
  • Array工具类用于完成对数组的反射操作。

反射的方式操作数组。

public static void printObject(Object obj){
    //获取数组的字节码
    Class cls = obj.getClass();
    //判断是否是数组
    if(cls.isArray()){
        //是数组的话我们取其长度并打印
        int len = Array.getLength(obj);
        for(int x=0;x<len;x++){
            System.out.ptintln(Array.get(obj,x));
        }
    }else{
        //不是数组直接打印值
        System.out.println(obj);
    }
}

总结:数组的反射操作是通过数组的字节码和Array工具类结合来判断长度和获取元素。

6、反射总结

a) 总结一:

1.getFields()和getMethods()依次获得权限为public的成员变量和方法,将包含从父类(接口)中继承到的成员变量和方法;
2.而通过方法getDeclaredFields()和getDeclaredMethods()只是获得本类中定义的所有成员变量和方法,包括private修饰的。

3.getField以及getMethod和getFields()和getMethods()也是一样,带s的返回的是数组,不带s的可以在参数列表中指定具体某一个。

b) 总结二:

1.Constructor、Filed、以及Method类都有一个父类AccessibleObject,从中继承了一系列的方法,比如:setAccessible(boolean flag)。

2.setAccessible(boolean flag)方法主要是在访问被private修饰的构造函数、成员变量以及成员方法的时候,必须指定setAccessible(true)。

3.setAccessible(true)并不是将访问权限改成了public,而是取消java的权限控制检查。所以即使是public方法,其属性默认也是false。

了解:getFiled和getMethod以及带s的都是通过递归去查找公共成员变量或者方法,成员变量先接口再父类,成员方法先父类后接口。

带Declared的能访问一个类中所有的方法,不论是公开还是私有。

二、动态代理

动态代理:用来修改已经具有的对象的方法,控制方法是否执行,或在方法执行之前和执行之后做一些额外的操作

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
loader -- 类加载器
interfaces -- 指定代理对象实现哪些接口,通常代理对象要和被代理对象实现相同的接口,从而保证和被代理者具有相同的方法
InvocationHandler
    -- 处理器对象,当调用代理对象的任何方法时,都会导致此对象中的invoke方法执行,在这个方法中可以编写是否允许方法执行,
        以及在方法执行之前和之后做那些额外的操作

其中有一个比较重要的方法:

{
    Object invoke (Object proxy,   Method method,  Object[] args)
        proxy -- 代理者对象
         method -- 当前调用到的方法
         args -- 方法的参数
         返回值 -- 就是这个方法要返回什么
}

1、使用动态代理

a) 我们首先创建一个Person类,并实现接口IAction,接口中有两个方法sing和dance。

public class Person implements IAction {
    public void singe() {
        System.out.println("唱歌");
    }
    public void dance() {
        System.out.println("跳舞");
    }
}
interface IAction {
    public void singe();
    public void dance();
}

b) 接下来我们创建Person的代理类PersonProxy

public class PersonProxy {
    private Person person = new Person();
    public Object newPersonProxy(){
        IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(),
                person.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑
                  System.out.println("----------------------");
                return null;
            }
        });
        return iAction;
    }
}

c) 然后我们创建TestDynamicProxy类来测试下,运行可以看出-----被打印出来,并且调用任意方法都会走invoke的逻辑。

public class TestDynamicProxy {
    public static void main(String[] args){
        PersonProxy personProxy = new PersonProxy();
        IAction iAction = (IAction) personProxy.newPersonProxy();
        iAction.dance();
    }
}

d) 再次处理下invoke中的逻辑,完善代理类的操作

public class PersonProxy {
    private Person person = new Person();
    public Object newPersonProxy(){
        IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(),
                person.getClass().getInterfaces(), new InvocationHandler() {
            // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // 调用真实的person去执行行为操作
                if(method.getName().equals("dance")){
                    // 第一个参数为代理对象,第二个参数是方法的参数列表,如果不想让程序继续执行,则直接retuen
                    return method.invoke(person, args);
                }else if (method.getName().equals("sing")) {
                    return method.invoke(person, args);
                }else{
                    System.out.println("该方法暂时未开通");
                    return null;
                }
            }
        });
        return iAction;
    }
}

e) 接下来,我们对方法进行改造,比如加入点歌的操作,并且还带返回值,以表达观众的热情

public class Person implements IAction {
    public String sing(String name) {
        System.out.println("唱歌");
        return "歌唱完了,谢谢!";
    }
    public String dance(String name) {
        System.out.println("跳舞");
        return "跳舞完了,谢谢";
    }
}
interface IAction {
    public String sing(String name);
    public String dance(String name);
}

f) 可以看到运行没有问题,其实传递的参数会封装在invoke的args参数列表中,它是一个Object类型的可变数组,我们可以在invoke中打印查看args列表

public class TestDynamicProxy {
    public static void main(String[] args){
        PersonProxy personProxy = new PersonProxy();
        IAction iAction = (IAction) personProxy.newPersonProxy();
        String dance = iAction.dance("爵士舞");
        String sing = iAction.sing("黑色毛衣");
        System.out.println(dance);
        System.out.println(sing);
    }
}

g) 之后,假如唱歌和跳舞需要一些特殊的要求,比如要钱或者掌声等等,那么我们可以在invoke中处理这个逻辑

public class PersonProxy {
    private Person person = new Person();
    public Object newPersonProxy(){
        IAction iAction = (IAction) Proxy.newProxyInstance(person.getClass().getClassLoader(),
                person.getClass().getInterfaces(), new InvocationHandler() {
            // 当返回代理对象,调用代理对象的任何方法都会走invoke里面的逻辑
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // 调用真实的person去执行行为操作
                if(method.getName().equals("dance")){
                    // 第一个参数为代理对象,第二个参数是方法的参数列表,如果不想让程序继续执行,则直接retuen
                    System.out.println(args[0]);
                    System.out.println("跳舞需要三千块钱");
                    return method.invoke(person, args);
                }else if (method.getName().equals("sing")) {
                    System.out.println(args[0]);
                    System.out.println("唱歌需要二千块钱");
                    return method.invoke(person, args);
                }else{
                    System.out.println("该方法暂时未开通");
                    return null;
                }
            }
        });
        return iAction;
    }
}

总结:动态代理最大的作用是通过反射在不改变源代码的情况下,对方法进行增强操作。

2、动态代理原理

动态代理其实是在运行时,通过反射获取到字节码,在运行时期的内存中创建被代理类的实例对象,并返回。

动态代理中最重要的方法就是newProxyInstance()方法了,我们可以看下它的源码:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
     .....
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (Exception e) {

    }
}

通过源码我们可以看出,首先拿到被代理类的字节码,通过反射获取到构造函数,然后创建出被代理对象的实例并返回给代理对象。

简而言之,动态代理其实就是被代理类的实例构建出该类的另一个实例,这个另外的实例就是所谓的代理对象。

不过日常开发中,动态代理很少使用,一般主要用于方法增强和框架开发等,不过方法增强的方式有很多,不一定

非要使用动态代理,比如使用继承和装饰者模式,但是动态代理有一个优点,它可以不改变源代码来实现功能加强。

时间: 2024-10-23 18:39:39

14、反射、动态代理的相关文章

Java语言中反射动态代理接口的解释与演示

Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

反射,动态代理随笔

反射的基本概述 一个class文件被加载到内存的时候,JVM就会经行解剖,把这个class文件的所有成员全部解剖出来,然后JVM会创建一个Class对象,把这些成员信息全部都封装起来,所谓反射就是指:我们获取到这个Class对象,就相当于获取到了该类的所有成员信息,我们就能操又该类的所有成员. Java反射机制是在运行状态中,对于任意一个类,都能够知道这类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的细心以及动态调用它的任意一个方法和属性; 这种动态获取

【Java核心技术】类型信息(Class对象 反射 动态代理)

1 Class对象 理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息.Class对象就是用来创建所有"常规"对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似类型转换这样的操作. 每个类都会产生一个对应的Class对象,也就是保存在.class文件.所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类.Class对象仅在需要的时候才会

java 反射 动态代理

在上一篇文章中介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大的功能,可以原生实现AOP中 的方法拦截功能.正如英文单词reflection的含义一样,使用反射API的时候就好像在看一个Java类在水中的倒影一样.知道了Java类的内部 结构之后,就可以与它进行交互,包括创建新的对象和调用对象中的方法等.这种交互方式与直接在源代码中使用的效果是相同的,但是又额

JAVA 反射 动态代理与AOP

摘自 b站尚硅谷JAVA视频教程 原文地址:https://www.cnblogs.com/superxuezhazha/p/12358469.html

日志 动态代理

日志log4j.properties 框架: log4j配置文件:log4j.rootLogger=TRACE,console,f1 log4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%p] %m [%t] %c [%l]

JDK动态代理和CGLIB动态代理

转载自http://www.itzhai.com/java-dong-tai-dai-li-zhi-jdk-dong-tai-dai-li-he-cglib-dong-tai-dai-li-mian-xiang-qie-mian-bian-cheng-aop-yuan-li.html 静态代理 静态代理相对来说比较简单,无非就是聚合+多态: 参考:设计模式笔记 – Proxy 代理模式 (Design Pattern) 动态代理 我们知道,通过使用代理,可以在被代理的类的方法的前后添加一些处理方

什么静态/动态代理,内容详解,只要看就会懂

静态代理:自己创建代理类生成源代码再对其编译.在程序运行前代理类的.class文件就已经存在了. 动态代理: 自动:可以根据我们的真实对象接口,自动生成一个增强型代码,而不要手动创建代理类写增强逻辑 运行时:可以在代码运行时,生成这个代理类,而不需要事先把这个类写好 废话少说,直接上代码 代码设计原则: 静态代理实现: 一.创建接口 1 public interface IGamePlayer { 2 3 //登录 4 public void login(String username ,Str

java反射与动态代理

Java反射与动态代理 Java反射机制可以动态地获取类的结构,动态地调用对象的方法,是java语言一个动态化的机制.java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性.这些都是java的基础知识,要想成为一名合格的程序猿,必须掌握! Java反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为