Java反射机制与动态代理

  1. 概述
    Java 反射机制与动态代理我们平时写代码可能用得比较少,但在各种常见的框架(Spring、MyBatis 等)中却屡见不鲜。有句话叫“无反射,不框架;无代理,不框架”。

由于以后打算阅读和学习框架的源码,这里先简单回顾反射机制和动态代理(暂不深入分析实现原理),为后面做些准备 。

  1. 反射机制
    Java 反射机制是在 Java 程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

从面向对象的角度来看,我们平时用到的"类"、"构造器"、"属性"、"方法"其实也是一个"类",它们在 JDK 中分别对应 Class、Constructor、Field、Method 类。其中 Class 相当于"类的类",可称为"元类",从这个角度看,我们平时自定义的"类"可以理解为 Class 的一个对象。

简单来说(个人理解),反射机制就是通过 Class、Constructor 等"元类"来操作其他的普通类(创建对象、调用方法等)。下面以简单代码示例。

2.1 准备代码
定义一个普通的 Java 类 Human,如下:

package com.jaxer.example.reflection;
public class Human {
  public String gender;
  private int age;
  protected void hello() {
  }
  public void hi() {
  }
}
定义一个普通的 Person 类,继承自 Human,如下:
public class Person extends Human {
  // 两个属性
  public String name;
  private int age;
  // 各种修饰符的构造器
  public Person() {
  }
  private Person(String name) {
    this.name = name;
  }
  protected Person(int age) {
    this.age = age;
  }
  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  // getter, setter 方法
  public String getName() {
    return name;
}
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  // 自定义方法
  private void test() {
    System.out.println("test is invoked.");

  }
  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
  }
}

该 Person 类定义了 name 和 age 两个成员变量及其 getter/setter 方法,还有四个构造器,分别使用不同的修饰符,自定义了一个 test 方法,重写了 toString 方法。

PS: 这两个类仅供参考,只是为了演示。

2.2 获取 Class
获取 Person 的 Class 对象通常有下面三种方式:

// 方式一:从对象获取
Person person = new Person();
person.getClass();
// 方式二:类名.class
Person.class;
// 方式三:Class.forName
Class<?> aClass = Class.forName("com.jaxer.example.reflection.Person");
/*
  这三种方式的获取到的都是:
    class com.jaxer.example.reflection.Person
  而且可以证明,这三种方式得到的同一个 Class 对象,即同一个 Person 类
*/
获取到了 Class 对象,就可以使用 Class 对象来创建 Person 对象或者做一些其他操作,例如:
// 获取 Class 对象
Class<?> aClass = Class.forName("com.jaxer.example.reflection.Person");
// 使用 Class 创建 Person 对象
System.out.println(aClass.newInstance()); // 创建 Person 对象
// 获取 Person 的父类(Human)
System.out.println("superClass-->" + aClass.getSuperclass());
/*  输出结果:
 *  Person{name='null', age=0}
 *  superClass-->class com.jaxer.example.reflection.Human
 */

2.3 获取 Constructor
Class 中获取构造器(Constructor)的方法如下:
getConstructors: 获取所有 public 构造器;
getDeclaredConstructors: 获取所有构造器(包括 private 类型);
getConstructor(Class… parameterTypes): 获取指定参数的 public 构造器;
getDeclaredConstructor(Class… parameterTypes): 获取指定参数的构造器(包括 private 类型)。

测试代码:

private static void testConstructors(Class<?> aClass) throws Exception {

    // 1. 获取所有public构造器

    Constructor<?>[] constructors = aClass.getConstructors();

    for (Constructor<?> constructor : constructors) {

        System.out.println("constructor-->" + constructor);

    }

    // 2. 获取所有构造器
    Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
        System.out.println("declaredConstructor-->" + declaredConstructor);
    }
    // 4. 获取指定的构造器(根据参数类型)
    Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
    // 修改访问权限(用这种方式可以使用 private 类型构造器创建对象)
    constructor.setAccessible(true);
    // 使用该构造器创建 Person 对象
    Object instance = constructor.newInstance("Ace");
    System.out.println(instance);
}

输出结果如下:

constructor-->public com.jaxer.example.reflection.Person()
declaredConstructor-->com.jaxer.example.reflection.Person(java.lang.String,int)

declaredConstructor-->protected com.jaxer.example.reflection.Person(int)

declaredConstructor-->private com.jaxer.example.reflection.Person(java.lang.String)

declaredConstructor-->public com.jaxer.example.reflection.Person()
Person{name='Ace', age=0}

2.4 获取 Field
获取 Class 属性(Field)的方法如下:
getFields(): 获取 public 属性(包含父类);
getDeclaredFields(): 获取所有属性(包括 private 类型);
getField(String name): 获取指定名称的 public 属性;
getDeclaredField(String name): 获取指定名称的属性(包括 private 类型)。
测试代码:

private static void testFields(Class<?> aClass) throws Exception {
    // 1. 获取 public 属性(包含父类的 public 属性)
    Field[] fields = aClass.getFields();
    for (Field field : fields) {
        System.out.println("field-->" + field);
    }
    // 2. 获取所有属性
    Field[] declaredFields = aClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println("declaredField-->" + declaredField);
    }
    // 4. 获取指定的属性(这里获取 age 属性)
    Field age = aClass.getDeclaredField("age");
    System.out.println("age-->" + age);
    // 给指定对象的属性赋值(private 类型不能赋值,需要修改访问权限)
   Object obj = aClass.newInstance();
    age.setAccessible(true); // 修改访问权限
    age.set(obj, 18); // 设置新值
    System.out.println("obj-->" + obj);
}

输出结果:

field-->public java.lang.String com.jaxer.example.reflection.Person.name
field-->public java.lang.String com.jaxer.example.reflection.Human.gender
declaredField-->public java.lang.String com.jaxer.example.reflection.Person.name
declaredField-->private int com.jaxer.example.reflection.Person.age
age-->private int com.jaxer.example.reflection.Person.age
obj-->Person{name='null', age=18}

2.5 获取 Method
获取 Class 方法(Method)的方法如下:
getMethods(): 获取所有 pubic 方法(包括父类及 Object 类);
getDeclaredMethods(): 获取所有方法(包括 private 方法);
getMethod(String name, Class… parameterTypes): 获取指定名称和参数的 public 方法;
getDeclaredMethod(String name, Class… parameterTypes): 获取指定名称和参数的方法(包括 private 方法)。
测试代码:

private static void testMethods(Class<?> aClass) throws Exception {
  // 1. 获取所有 public 方法(包括父类及 Object 类)
  Method[] methods = aClass.getMethods();
  for (Method method : methods) {
    System.out.println("method-->" + method);
  } 
  // 2. 获取该类声明的所有方法
  Method[] declaredMethods = aClass.getDeclaredMethods();
  for (Method declaredMethod : declaredMethods) {
    System.out.println("declaredMethod-->" + declaredMethod);
  }
  // 4. 获取指定的方法并调用
  Method method = aClass.getDeclaredMethod("test");
  method.setAccessible(true); // 修改访问权限
  System.out.println("方法名:" + method.getName());
  System.out.println("方法修饰符:" + Modifier.toString(method.getModifiers()));
  Object instance = aClass.newInstance();
  System.out.println(method.invoke(instance));
}

输出结果:

// getMethods 方法返回结果:
  // Person 类的 public 方法:
method-->public java.lang.String com.jaxer.example.reflection.Person.toString()
method-->public java.lang.String com.jaxer.example.reflection.Person.getName()
method-->public void com.jaxer.example.reflection.Person.setName(java.lang.String)
method-->public void com.jaxer.example.reflection.Person.setAge(int)
method-->public int com.jaxer.example.reflection.Person.getAge()
  // Human 类的 public 方法:
method-->public void com.jaxer.example.reflection.Human.hi()
  // Object 类的 public 方法:
method-->public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method-->public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method-->public final void java.lang.Object.wait() throws java.lang.InterruptedException
method-->public boolean java.lang.Object.equals(java.lang.Object)
method-->public native int java.lang.Object.hashCode()
method-->public final native java.lang.Class java.lang.Object.getClass()
method-->public final native void java.lang.Object.notify()
method-->public final native void java.lang.Object.notifyAll()
// getDeclaredMethods 方法返回结果:
declaredMethod-->public java.lang.String com.jaxer.example.reflection.Person.toString()
declaredMethod-->public java.lang.String com.jaxer.example.reflection.Person.getName()
declaredMethod-->public void com.jaxer.example.reflection.Person.setName(java.lang.String)

declaredMethod-->private void com.jaxer.example.reflection.Person.test()

declaredMethod-->public void com.jaxer.example.reflection.Person.setAge(int)

declaredMethod-->public int com.jaxer.example.reflection.Person.getAge()
// 调用 test 方法:

方法名:private void com.jaxer.example.reflection.Person.test()
方法修饰符:private
test is invoked.
null

框架中常用的反射机制主要是以上那些,这里暂不深究其实现原理,以后有需要再行补充。下面简要分析代理相关的内容。

  1. 代理
    使用代理的主要目的:对目标对象(的方法)进行功能增强,例如 Spring 的 AOP。

既然是对目标对象的方法进行增强,代理对象的方法中一定会调用目标对象的方法。而且一般会在目标对象的方法调用前后(或者其他时机)做一些其他的处理以达到增强的效果。

代理模式通常分为「静态代理」和「动态代理」,静态代理使用较少;而动态代理常用的有 JDK 动态代理和 CGLib 动态代理。下面简要分析。

3.1 准备代码
一个普通的 Java 接口及其实现类如下:

// 接口
public interface UserService {
    void save(String name);
}
// 实现类
public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保存用户名:" + name);
    }
}

无论静态代理还是动态代理,都是对该类的 save 方法进行增强。

此处以在该方法执行前后各打印一句话来演示。

3.2 静态代理
主要思想:创建一个 UserService 接口的实现类 UserServiceProxy(代理对象),并且该类持有 UserServiceImpl 对象(目标对象),在 UserServiceProxy 类的 save 方法中调用 UserServiceImpl 的 save 方法,示例代码如下:

public class UserServiceProxy implements UserService {
  private UserService userService;
  public UserServiceProxy(UserService userService) {
    this.userService = userService;
  }
  @Override
  public void save(String name) {
    System.out.println("---静态代理:方法执行前---");
    userService.save(name); // 调用目标类的方法
    System.out.println("---静态代理:方法执行后---");
  }
}

测试代码:

private static void testStaticProxy() {
  UserService userService = new UserServiceImpl();
  UserService proxy = new UserServiceProxy(userService);
  proxy.save("jack");
}
/* 运行结果:

  ---静态代理:方法执行前---
保存用户:jack
  ---静态代理:方法执行后---
*/

显而易见,使用 UserServiceProxy 可以增强 UserServiceImpl 的 save 方法。但由于代码是固定的(编码期间写好的),不够灵活,因此静态代理使用较少,通常使用动态代理。

PS: 此处代码实现形式可能不尽相同,但思路相近。

3.3 动态代理
与静态代理相比,动态代理则是在运行时动态生成一个代理类,该类可以对目标对象的方法进行功能增强。动态代理常用的有 JDK 动态代理和 CGLib 动态代理,下面简要分析。

3.3.1 JDK 动态代理
JDK 的动态代理实现方式:

使用 Proxy 类的 newProxyInstance 方法创建代理对象,使用 InvocationHandler 来实现增强的逻辑(通常创建一个 InvocationHandler 接口的实现类,在其 invoke 方法中实现增强的逻辑)。示例代码如下(仅供参考):

// JDK 代理工厂,作用是生成代理对象
public class JDKProxyFactory {
    // 获取 target 的代理对象(其中 target 为目标对象)
    public Object getProxy(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new MyInvocationHandler(target));
    }
    // 自定义 InvocationHandler
    private static class MyInvocationHandler implements InvocationHandler {
        // 目标对象
        private Object target;
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
       // 实现增强逻辑
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("目标类方法执行前-----");
            Object result = method.invoke(target, args); // 调用目标对象的方法
            System.out.println("目标类方法执行后-----");
            return result;
        }
    }
}

测试代码:

private static void testJdkProxy() {
    UserService userService = new UserServiceImpl(); // 目标对象
    JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(); // 代理工厂
    // 使用代理工厂生成代理对象
    UserService proxy = (UserService) jdkProxyFactory.getProxy(userService);
    proxy.save("jack"); // 调用代理对象的方法(已对目标对象进行增强)
}
/* 运行结果:
    目标类方法执行前-----
    保存用户:jack
    目标类方法执行后-----
*/

实现原理可参考:https://blog.csdn.net/yhl_jxy/article/details/80586785

3.3.2 CGLib 动态代理
主要思路:

创建一个目标类的子类 Enhancer,在子类中设置回调对象(MethodInterceptor),并在回调方法(intercept)中实现对目标对象的增强功能逻辑。示例代码如下:

// CGLIB 工厂,用于生成代理对象
public class CglibProxyFactory {
    // 获取代理对象(对目标对象进行增强)
    public Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new MyMethodInterceptor()); // 设置回调
       return enhancer.create();
    }
    // 自定义方法拦截 MethodInterceptor
   private static class MyMethodInterceptor implements MethodInterceptor {
       @Override
        public Object intercept(Object obj, Method method, Object[] args, 
                   MethodProxy proxy) throws Throwable {
            System.out.println("cglib方法调用前");
            Object o = proxy.invokeSuper(obj, args); // 调用目标对象的方法
            System.out.println("cglib方法调用后");
            return o;
        }
    }
}

测试代码:

private static void testCglibProxy() {
  UserService userService = new UserServiceImpl();
  CglibProxyFactory cglibProxyFactory = new CglibProxyFactory();
  UserService proxy = (UserService) cglibProxyFactory.getProxy(userService);
  proxy.save("jack");
}
/* 运行结果:
  cglib方法调用前
  保存用户:jack
  cglib方法调用后
*/

PS: 使用 CGLib 需要导入第三方 jar 包(cglib 和 asm)。

实现原理可参考:https://blog.csdn.net/yhl_jxy/article/details/80633194

二者主要区别:

JDK 动态代理不依赖第三方库,CGLib 需要依赖第三方库;

若目标对象实现了接口,两种方式都可以使用;若未实现接口,则只能使用 CGLib。

  1. 小结
    反射机制:简单来说,反射机制主要是通过 Class、Constructor 等"元类"来操作其他的普通类,以达到在运行期间动态创建对象、动态调用方法等目的。

静态代理&动态代理:二者主要区别在于时机,静态代理 在编码 期已写好代理类,而动态代理则是在运行期间动态生成代理类。

JDK 动态代理& CGLib 动态代理的主要区别:

JDK 动态代理不依赖第三方库,CGLib 则要 依赖第三方 库 ;

JDK 使用 InvocationHandler 接口实现增强逻辑, 使用 Proxy.newProxyInstance 生成代理对象;而 CGLib 使用 MethodInterceptor 接口实现增强逻辑,使用 Enhancer 生成代理对象;

若目标对象实现了接口,两种方式都可以使用;若未实现接口,则只能使用 CGLib。

原文链接: http://mp.weixin.qq.com/s?__biz=MzU4NzYyMDE4MQ==&mid=2247484102&idx=1&sn=f83d5843206cf73b5eaea533944ca558

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容