原文同步发表至个人博客【夜月归途】

原文链接:http://www.guitu18.com/se/java/2018-06-29/17.html

作者:夜月归途
出处:http://www.guitu18.com/
本博客中未标明转载的文章归作者夜月归途和博客园所有。
欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本博客关于Java动态代理相关内容直达链接:

  1. JDK动态代理浅析
  2. Cglib动态代理浅析
  3. JDK动态代理深入理解分析并手写简易JDK动态代理(上)
  4. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

Java中的动态代理设计模式是非常经典且非常重要的设计模式之一,在感叹设计者的天才设计至于,我们想去探究一下这个设计模式是如何来实现的;

著名的spring框架的AOP的原理就是Java的动态代理机制;

在Spring中动态代理是实现有两种:JDK动态代理和Cglib动态代理,本篇分析的是JDK动态代理的实现;

查看下篇 [ Cglib动态代理浅析 ]

在探究动态代理之前,先看一下静态代理,这可以帮助我们更好的理解动态代理;

以公司老板和员工为例,现在Boss有一些项目要完成(定义接口)

public interface Boss {
public void doSomethinig();
public void finishTasks(String name);
}

这些工作肯定是需要雇员工去做的(去实现老板定义的接口)

public class Employee implements Boss {
@Override
public void doSomethinig() {
System.out.println("员工工作中 ...");
}
@Override
public void finishTasks(String name) {
System.out.println("员工正在完成项目 " + name + " ...");
}
}

我们先使用静态代理,这时候我们雇一个项目经理负责代理员工完成工作,可能有点不恰当哈,先这么理解吧;(同样需要实现老板的接口,代理员工执行任务)

public class Manager implements Boss {
private Employee employee = new Employee();
@Override
public void doSomethinig() {
// 员工完成工作之前
System.out.println(">>>>>员工完成工作之前");
// 员工正在完成工作
employee.doSomethinig();
// 员工完成工作之后
System.out.println("员工完成工作之后>>>>>");
}
@Override
public void finishTasks(String name) {
// 员工完成工作之前
System.out.println(">>>>>员工完成工作之前");
// 员工正在完成工作
employee.finishTasks(name);
// 员工完成工作之后
System.out.println("员工完成工作之后>>>>>");
}
}

写个测试类

public class TestProxy {
public static void main(String[] args) {
Manager manager = new Manager();
manager.doSomethinig();
manager.finishTasks("Java静态代理");
}
}

刚开始,我也一直很疑惑,员工都已经实现Boss的所有方法了,而且在测试类中也直接new出来员工对象了,直接员工.方法执行不就完了,为什么还要多此一举使用一个代理类来完成执行呢?先看测试执行结果:

>>>>>员工完成工作之前
员工工作中 ...
员工完成工作之后>>>>> >>>>>员工完成工作之前
员工正在完成项目 Java静态代理 ...
员工完成工作之后>>>>>

可以看出在项目经理代理类中,我们其实是可以在员工方法执行之前和执行之后做一些操作的,具体可以做什么和能做什么,你们发挥自己想象力;

代理模式应用在实际项目中,是能够帮我们完成很多事情的,比如个大框架中最常见的日志记录,数据库事务控制,权限验证等等,就不一一列举了(当然它们使用的肯定不是静态代理,而是接下来要说的动态代理了);

但是静态代理又一个非常大的弊端,就是每个接口都需要一个代理类去实现和执行,随着业务的数量越来越复杂的时候,代理类的代码量也是十分惊人的,这对于项目来说是很难去管理维护的;而动态代理的出现,正是用来解决这个问题的;

在Java的动态代理机制中,有两个至关重要的对象:

InvocationHandler 接口
Proxy 类

我们看看JDK API是怎么描述InvocationHandler 的(摘自JDK API 1.6.0中文版)

public interface InvocationHandler
InvocationHandler 是代理实例的调用处理程序实现的接口。
每个代理实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
invoke方法描述(摘自JDK API 1.6.0中文版) invoke Object invoke(Object proxy,Method method,Object[] args)throws Throwable
在代理实例上处理方法调用并返回结果。
在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 参数:
proxy
- 在其上调用方法的代理实例,指代我们所代理的那个真实对象
method
- 对应于在代理实例上调用的接口方法的 Method 实例。
- Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
- 指代的是我们所要调用真实对象的某个方法的Method对象
args
- 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。
- 基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
- 指代的是调用真实对象某个方法时接受的参数
返回:
从代理实例的方法调用返回的值。
如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;
否则,它一定是可分配到声明返回类型的类型。
如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。
否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。

我们再来看看Proxy类(摘自JDK API 1.6.0中文版)

public class Proxy extends Object implements Serializable
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
Proxy.newProxyInstance方法(摘自JDK API 1.6.0中文版) public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
- 此方法相当于:
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
- Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。 参数:
loader
- 定义代理类的类加载器
- ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces
- 代理类要实现的接口列表
- Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口
- 如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h
- 指派方法调用的调用处理程序
- InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

现在,我们使用动态代理来决绝刚才的问题,Boss和员工还是原来的

public interface Boss {
public void doSomethinig();
public void finishTasks(String name);
}

员工

public class Employee implements Boss {
@Override
public void doSomethinig() {
System.out.println("员工工作中 ...");
}
@Override
public void finishTasks(String name) {
System.out.println("员工正在完成项目 " + name + " ...");
}
}

这时候我们使用动态代理,雇一个领导,来代理所有的事情;

public class Leader implements InvocationHandler {
private Object target;
// 返回一个代理对象,并绑定被代理对象
public Object getProxyInstance(Object object) {
this.target = object;
// Proxy的newProxyInstance方法需要三个参数
return Proxy.newProxyInstance(
// 1.一个类加载器,通常可以从已经被加载的对象中获取类加载器
object.getClass().getClassLoader(),
// 2.希望代理实现的接口列表
object.getClass().getInterfaces(),
// 3.一个InvocationHandler接口的实现
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里我们简单输出一下invoke的第二第三个参数
System.out.println("method : " + method);
System.out.println("args : " + args);
if (args != null) {
System.out.print("arg : ");
for (Object arg : args) {
System.out.print(arg + " ");
}
System.out.println();
}
// 员工完成工作之前
System.out.println(">>>>>员工完成工作之前");
// 员工正在完成工作
Object invoke = method.invoke(target, args);
// 员工完成工作之后
System.out.println("员工完成工作之后>>>>>");
return invoke;
}
}

测试类:

public class LeaderTest {
public static void main(String[] args) {
Employee employee = new Employee();
Leader leader = new Leader();
Boss boss = (Boss) leader.getProxyInstance(employee);
boss.doSomethinig();
boss.finishTasks("Java动态代理");
}
}

控制台输出:

Method : public abstract void com.guitu18.Boss.doSomethinig()
args : null
>>>>>员工完成工作之前
员工工作中 ...
员工完成工作之后>>>>> Method : public abstract void com.guitu18.Boss.finishTasks(java.lang.String)
args : [Ljava.lang.Object;@1c7c054
arg : Java动态代理
>>>>>员工完成工作之前
员工正在完成项目 Java动态代理 ...
员工完成工作之后>>>>>

我们在方法中打印invoke方法的第二个参数Method method,从控制台看出,我们在调用方法的时候,实际上使用的是代理对象来调用真实对象的方法的:

Method : public abstract void com.guitu18.Boss.doSomethinig()
Method : public abstract void com.guitu18.Boss.finishTasks(java.lang.String)

而打印invoke方法的第三个参数Object[] args时我们发现,该数组其实就是真实方法接收的参数,如果没有则为null;

args : null

args : [Ljava.lang.Object;@1c7c054
arg : Java动态代理

现在,我们可以在method.invoke方法的前后都加上自己的一些操作;

在各大框架中,使用动态代理的比比皆是,比如如著名的Spring框架的AOP原理,比如Shiro的权限验证,拦截器等等;

从以上代码中,我们也可以看出,使用动态代理之后,代理类的代码量已经被固定下来了,不会随着业务的复杂和庞大而变得越来越多;

其实在这里,我们已经可以把代码写成代理工厂模式了,简化一些操作:

public class ProxyFactory {
public static Object getProxyInstance(final Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法执行前
System.out.println("开始事务...");
// 执行方法
Object invoke = method.invoke(target, args);
// 方法执行后
System.out.println("提交/回滚事务...");
return invoke;
}
});
}
}

在ProxyFactory里,提供了一个静态方法,我么仅需要传递进来一个代理类的具体实现,就能够获得一个代理对象,在这个方法中,Proxy.newProxyInstance需要的第三个参数,是以匿名内部类的方式去提供和定义的,更加简洁和灵活;

编写测试类:

public class ProxyFactoryTest {
public static void main(String[] args) {
Boss boss = (Boss) ProxyFactory.getProxyInstance(new Employee());
boss.doSomethinig();
boss.finishTasks("Java动态代理");
}
}

控制台输出:

开始事务...
员工工作中 ...
提交/回滚事务... 开始事务...
员工正在完成项目 Java动态代理 ...
提交/回滚事务...

在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。

并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求;

例如:

数据库事务的操作,在方法执行前开启事务,执行完毕提交事务,如果方法执行出错,回滚事务; 拦截用户的访问请求,以检查用户是否有访问权限,有权限继续执行,没权限不执行做其他操作; 增强,动态为某个对象添加额外的功能;

尾巴:

  1. 动态代理是十分强大的设计模式,代理类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
  3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变;

JDK动态代理浅析的更多相关文章

  1. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  2. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  3. Cglib动态代理浅析

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2018-06-29/18.html 作者:夜月归途 出处:http://www.guitu ...

  4. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  5. java jdk动态代理模式举例浅析

    代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...

  6. jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  7. JDK动态代理

    一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...

  8. 静态代理和利用反射形成的动态代理(JDK动态代理)

    代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...

  9. Spring中的JDK动态代理

    Spring中的JDK动态代理 在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层 ...

随机推荐

  1. VS之设置文件编码格式

    VS2012默认格式为 "GB2312-80",很多时候可能出现乱码情况,就是编码问题,如何在VS里修改呢? 文件->“高级保存选项 ”  选择gb2312

  2. 马昕璐 201771010118《面向对象程序设计(java)》第十八周学习总结

    实验十八  总复习 实验时间 2018-12-30 1.实验目的与要求 (1) 综合掌握java基本程序结构: (2) 综合掌握java面向对象程序设计特点: (3) 综合掌握java GUI 程序设 ...

  3. framework7 入门(基础布局)

    个人认为framework7是个很好的移动端框架,自带路由.下拉刷新.无限加载.时间线.各种dialog.环形图等,基本能满足大部分app的需求,可以和vue,webpack 等各种搭配. 这里讲一下 ...

  4. 在vue项目中mock数据

    第一步:安装: 在命令行中执行: npm install mockjs; 第二步:定义index.js文件 我们新建一个mock文件夹,此文件夹中建一个index.js文件:在index.js中输入以 ...

  5. Deployment Characteristics of "The Edge" in Mobile Edge Computing

    移动边缘计算中的“边缘”部署特性 本文为SIGCOMM 2018 Workshop (Mobile Edge Communications, MECOMM)论文. 本文翻译了论文的关键内容. 摘要 移 ...

  6. Java中的线程协作之Condition

    一.Condition接口 1.Condition接口的常用方法介绍 /** * 已经获取到锁的线程调用该方法会进入等待状态,知道其他持有锁的线程通知(signal)等待队列中的线程或者被中断退出等待 ...

  7. PHP7CMS 无条件前台GETSHELL

    PHP7CMS 无条件前台GETSHELL Version:2018-10-09 //最新版中以修复此漏洞 这个漏洞很简单,如果作者在写代码的时候考虑到一点点安全方面,其实都可以避免的.   01 0 ...

  8. [Swift]LeetCode203. 移除链表元素 | Remove Linked List Elements

    Remove all elements from a linked list of integers that have value val. Example: Input: 1->2-> ...

  9. [Swift]LeetCode889. 根据前序和后序遍历构造二叉树 | Construct Binary Tree from Preorder and Postorder Traversal

    Return any binary tree that matches the given preorder and postorder traversals. Values in the trave ...

  10. 交叉编译 tcpdump

    目录 1. 下载 tcpdump 2. 交叉编译 3. 相关说明 1. 下载 tcpdump 官网:http://www.tcpdump.org/ 2. 交叉编译 交叉编译libpcap: $ wge ...