Java动态代理InvocationHandler和Proxy

java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心;

1.InvocationHandler接口

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

看下官方文档对InvocationHandler接口的描述:

 {@code InvocationHandler} is the interface implemented by
the <i>invocation handler</i> of a proxy instance. <p>Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance, the method
invocation is encoded and dispatched to the {@code invoke}
method of its invocation handler.

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

2.Proxy类就是用来创建一个代理对象的类

它提供了很多方法,但是我们最常用的是newProxyInstance方法。

 public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
     Returns an instance of a proxy class for the specified interfaces
that dispatches method invocations to the specified invocation
handler. This method is equivalent to:

这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载

interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。

h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

实例

动态代理中核心的两个接口和类上面已经介绍完了,接下来我们就用实例来讲解下具体的用法

首先我们定义一个接口People

package reflect;

public interface People {

    public String work();
}

定义一个Teacher类,实现People接口,这个类是真实的对象

package reflect;

public class Teacher implements People{

    @Override
public String work() {
System.out.println("老师教书育人...");
return "教书";
} }

现在我们要定义一个代理类的调用处理程序,每个代理类的调用处理程序都必须实现InvocationHandler接口,代理类如下:

package reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class WorkHandler implements InvocationHandler{ //代理类中的真实对象
private Object obj; public WorkHandler() {
// TODO Auto-generated constructor stub
}
//构造函数,给我们的真实对象赋值
public WorkHandler(Object obj) {
this.obj = obj;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在真实的对象执行之前我们可以添加自己的操作
System.out.println("before invoke。。。");
Object invoke = method.invoke(obj, args);
//在真实的对象执行之后我们可以添加自己的操作
System.out.println("after invoke。。。");
return invoke;
} }

上面的代理类的调用处理程序的invoke方法中的第一个参数proxy好像我们从来没有用过,而且关于这个参数的具体用法含义等下具体说一说。

接下来我们看下客户端类

package reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) {
//要代理的真实对象
People people = new Teacher();
//代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,最终代理对象的调用处理程序会调用真实对象的方法
InvocationHandler handler = new WorkHandler(people);
/**
* 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数
* 第一个参数:people.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象
* 第二个参数:people.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
* 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上
*/
People proxy = (People)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
//System.out.println(proxy.toString());
System.out.println(proxy.work());
}
}

看下输出结果:

before invoke。。。
老师教书育人...
after invoke。。。
教书

通过上面的讲解和示例动态代理的原理及使用方法,在Spring中的两大核心IOC和AOP中的AOP(面向切面编程)的思想就是动态代理,在代理类的前面和后面加上不同的切面组成面向切面编程。

上面我们只讲解了Proxy中的newProxyInstance(生成代理类的方法),但是它还有其它的几个方法,我们下面就介绍一下:

getInvocationHandler:返回指定代理实例的调用处理程序

getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。

isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。

newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

Java中InvocationHandler接口中第一个参数proxy详解

1.讲解前我们先列一下我们要说明的问题

proxy代表什么意思

proxy参数怎么用及什么时候用

proxy参数运行时的类型是什么

为什么不用this代替proxy

2.proxy代表什么意思

proxy是真实对象的真实代理对象,invoke方法可以返回调用代理对象方法的返回结果,也可以返回对象的真实代理对象(com.sun.proxy.$Proxy0)。

3.proxy参数怎么用及什么时候用

proxy参数是invoke方法的第一个参数,通常情况下我们都是返回真实对象方法的返回结果,但是我们也可以将proxy返回,proxy是真实对象的真实代理对象,我们可以通过这个返回对象对真实的对象做各种各样的操作。

  • 创建一个接口People,包含一个work方法,方法的返回对象是它本身

    package com.test.Application;
    
    public interface People {
    
        public People work(String workName);
    public String time();
    }
  • 创建一个接口People的实现类Student

package com.test.Application;

public class Student implements People{

    @Override
public People work(String workName) {
System.out.println("工作内容是"+workName);
return this;
}
@Override
public String time() {
return "2018-06-12";
}
}
  • 创建一个代理类的调用处理程序WorkHandler
package com.test.Application;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class WorkHandler implements InvocationHandler{ private Object obj; public WorkHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before 动态代理...");
System.out.println(proxy.getClass().getName());
System.out.println(this.obj.getClass().getName());
if(method.getName().equals("work")) {
method.invoke(this.obj, args);
System.out.println("after 动态代理...");
return proxy;
} else {
System.out.println("after 动态代理...");
return method.invoke(this.obj, args);
}
} }

我们可以看到上面的代理类调用处理程序打印了proxy参数对象,并且返回了proxy对象。

  • 客户端实例创建代理对象并输出结果
package com.test.Application;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) {
People people = new Student();
InvocationHandler handler = new WorkHandler(people); People proxy = (People)Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
People p = proxy.work("写代码").work("开会").work("上课"); System.out.println("打印返回的对象");
System.out.println(p.getClass()); String time = proxy.time();
System.out.println(time);
}
}

运行结果:

after 动态代理...
before 动态代理...
com.sun.proxy.$Proxy0
com.test.Application.Student
工作内容是上课
after 动态代理...
打印返回的对象
class com.sun.proxy.$Proxy0 class com.sun.proxy.$Proxy0
before 动态代理...
com.sun.proxy.$Proxy0
com.test.Application.Student
after 动态代理...
2018-06-12

我们可以看到WorkHandler代理调用处理程序打印proxy参数输出的结果是com.sun.proxy.$Proxy0,这也说明proxy参数是代理类的真实代理对象;Proxy类生成的代理对象可以调用work方法并且返回真实的代理对象,也可以通过反射来对真实的代理对象进行操作。

4.proxy参数运行时的类型是什么

上面我们已经打印出了proxy的类型是:com.sun.proxy.$Proxy0真实的代理对象

5.为什么不用this替代

因为this代表的是InvocationHandler接口实现类本身,并不是真实的代理对象。

【Java 基础】Java动态代理的更多相关文章

  1. Java基础-CGLIB动态代理

    JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继 ...

  2. Java基础-JDK动态代理

    JDK的动态代理依靠接口实现  代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代 ...

  3. Java基础加强——动态代理

    代理模式: 为其他对象提供一种代理以控制对这个对象的访问. 代理模式主要分为两类: 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了.  ...

  4. Java基础-jdk动态代理与cglib动态代理区别

    JDK动态代理 此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑. 代理模式在实际使用时需要指定具体的目标对象 ...

  5. 重学JAVA基础(三):动态代理

    1.接口 public interface Hello { public void sayHello(); } 2.实例类 public class Hello2 { public void sayH ...

  6. 黑马程序员:Java基础总结----静态代理模式&动态代理

    黑马程序员:Java基础总结 静态代理模式&动态代理   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 静态代理模式 public  class  Ts {   ...

  7. 杨晓峰-Java核心技术-6 动态代理 反射 MD

    目录 第6讲 | 动态代理是基于什么原理? 典型回答 考点分析 知识扩展 反射机制及其演进 动态代理 精选留言 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAnd ...

  8. java反射和动态代理实现与原理详细分析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式    代理模式是常用的java设计模式, ...

  9. 使用Java中的动态代理实现数据库连接池

    2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的 ...

  10. java 笔记(3) —— 动态代理,静态代理,cglib代理

    0.代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口. 代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 代理类与委托类之间通常会存 ...

随机推荐

  1. Mac卸载go

    1.删除go目录 一般目录是 /usr/local/go sudo rm -rf /usr/local/go 2.清除环境变量配置 3. mac安装go后自动创建的问题也需要删除 sudo rm -r ...

  2. Java 源码如何分析?

    如何阅读源码?万事开头难,源码从哪里开始看?我也是刚对源码的阅读研究不深,但是可以谈谈自己的源码阅读感受. 刚开始吧,只是对某些代码的实现原理感到好奇,好奇是怎么实现这种功能,实现这种效果的,对其背后 ...

  3. Part 33 Angular nested scopes and controller as syntax

    Working with nested scopes using $scope object : The following code creates 3 controllers  - country ...

  4. 1组-Alpha冲刺-4/6

    一.基本情况 队名:震震带着六菜鸟 组长博客:https://www.cnblogs.com/Klein-Wang/p/15553196.html 小组人数:7人 二.冲刺概况汇报 王业震 过去两天完 ...

  5. element ui tree回显 setCheckedNodes,setCheckedKeys,setChecked等函数报undefined问题

    在写项目的时候,需要用到tree组件进行回显来进行权限控制: 在回显过程中使用回显函数会报报undefined, 这时只需要给该函数包裹一层nextTick方法就行了, 在回显过程中我们有可能使用半选 ...

  6. [cf1458C]Latin Square

    维护$n^{2}$个三元组$(x,y,z)$,每一个三元组描述$a_{x,y}=z$ 对于RLDU这四个操作,即将所有三元组的$x$或$y$执行$\pm 1$(模$n$意义下) 对于IC这两个操作,即 ...

  7. CF1575G GCD Festival

    \(\sum\sum gcd(i,j) \times gcd(a_i,a_j)\) 考虑枚举这个 \(gcd(i,j)\) . \(\sum_d \varphi(d)\sum_{i|d}\sum_{j ...

  8. 【POJ1961 Period】【KMP】

    题面 一个字符串的前缀是从第一个字符开始的连续若干个字符,例如"abaab"共有5个前缀,分别是a, ab, aba, abaa, abaab. 我们希望知道一个N位字符串S的前缀 ...

  9. 洛谷 P4484 - [BJWC2018]最长上升子序列(状压 dp+打表)

    洛谷题面传送门 首先看到 LIS 我们可以想到它的 \(\infty\) 种求法(bushi),但是对于此题而言,既然题目出这样一个数据范围,硬要暴搜过去也不太现实,因此我们需想到用某种奇奇怪怪的方式 ...

  10. C++ and OO Num. Comp. Sci. Eng. - Part 2.

    本文参考自<C++ and Object-Oriented Numeric Computing for Scientists and Engineers>. 1. Basic Types ...