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. JetBrains IntelliJ IDEA汉化

    JetBrains IntelliJ IDEA汉化 开启 IntelliJ IDEA,点击右下角Configure菜单,选择 Plugins.在弹出的 Plugins窗口里,切换至 Marketpla ...

  2. Centos8上安装Mysql8.X

    一.下载Mysql 下载地址:https://dev.mysql.com/downloads/mysql/ 二.将压缩包通过ftp软件服务器的目标位置:并解压 1.我的是放在:/root/softwa ...

  3. Java try catch语句块中try()的括号中代码作用

    了解过Mybatis,都知道DefacltSqlSession是线程不安全的.每次执行查询都需要新建一个sqlSession.因此官方给的建议写法如下: Mybatis3 从 SqlSessionFa ...

  4. 教你用SQL进行数据分析

    摘要:采用 SQL 作为数据查询和分析的入口是一种数据全栈的思路. 本文分享自华为云社区<如何使用 SQL 对数据进行分析?>,作者:zuozewei . 前言 我们通过 OLTP(联机事 ...

  5. [hdu7033]Typing Contest

    为了避免浮点运算,不妨将$f_{i}$​​​乘上$C=10^{2}$​​​,问题即求$\max_{S\subseteq [1,n]}\frac{\sum_{i\in S}(C^{2}-(\sum_{j ...

  6. [loj2978]杜老师

    假设所有素数从小到大依次为$p_{1},p_{2},...,p_{k}$,我们将$x$转换为一个$k$位的二进制数,其中从低到高第$i$位为1当且仅当其$p_{i}$的幂次为奇数 不难发现以下两个性质 ...

  7. [cf997E]Good Subsegments

    一个区间为好区间当且仅当$\max_{l\le i\le r}a_{i}-\min_{l\le i\le r}a_{i}=r-l$,考虑固定右端点$r$,维护所有左端点$l$的上述式子左-右的值,那么 ...

  8. 力扣 - 剑指 Offer 54. 二叉搜索树的第k大节点

    题目 剑指 Offer 54. 二叉搜索树的第k大节点 思路1 二叉搜索树的特性就是中序遍历结果为递增序列,而题目要求的是第 k 大节点,所以就应该是要遍历结果为降序, 按照先遍历左子树.输出节点.遍 ...

  9. page_fault_in_nonpaged_area异常解决方案(已解决)

    电脑在运行虚拟机的时候异常重启,多次打开结果一样,问了客服告诉我导致蓝屏的原因很多,可能是驱动不兼容,系统,或其他.{没点有用的} 百度到这个方案: 打开->此电脑->右键属性->高 ...

  10. Python之阶乘代码

    #coding=utf-8 while True:     num = int(input("请输入要阶乘的正整数数字,按负数退出:"))     jiec=1     if nu ...