深度剖析java动态静态代理原理源码

作者:chen_hao 时间:2021-10-25 08:10:31 

正文

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

静态代理
1、静态代理

静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

2、静态代理简单实现

根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,

班长就是学生的代理。

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。


/**
* 创建Person接口
* @author ChenHao
*/
public interface Person {
//上交班费
void giveMoney();
}

Student类实现Person接口。Student可以具体实施上交班费的动作。


public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。


/**
* 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
* @author ChenHao
*
*/
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Student stu) {
this.stu = stu;
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
stu.giveMoney();
}
}

下面测试一下,看如何使用代理模式:


public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Student zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}

运行结果:

深度剖析java动态静态代理原理源码

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:


/**
* 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
* @author ChenHao
*
*/
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Student stu) {
this.stu = stu;
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
System.out.println("张三最近学习有进步!");
stu.giveMoney();
}
}

深度剖析java动态静态代理原理源码

模式优缺点

优点

1、代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

2、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的

缺点

1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

*

1. *

前面介绍了静态代理,虽然静态代理模式很好用,但是静态代理还是存在一些局限性的,比如使用静态代理模式需要程序员手写很多代码,这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多,或者需要同时代理多个对象的时候,这无疑会增加很大的复杂度。

代理类在程序运行时创建的代理方式被成为 * 。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而 * ,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, * 的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

2、 * 简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK * 类和 * 对象。


public class DynamicProxyTest {
interface IHello {
void sayHello();
}
static class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("hello world");
}
}
static class DynamicProxy implements InvocationHandler {
Object originalObj;
Object bind(Object originalObj) {
this.originalObj = originalObj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
}
/**
*Object proxy是代理的对象, Method method是真实对象中调用方法的Method类, Object[] args是真实对象中调用方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
return method.invoke(originalObj, args);
}
}
public static void main(String[] args) {
IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayHello();
}
}

运行结果如下:


welcome
hello world

* 原理分析

上面说到, * 的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上述代码里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。

在JDK * 中涉及如下角色:

业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的 * 类$Proxy0

* 原理图:

深度剖析java动态静态代理原理源码

说白了, * 的过程是这样的:

1.Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;

2.Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy0的字节码文件;

* 的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler),我们跟进源代码看看


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
// handler不能为空
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
// 通过loader和接口,得到代理的Class对象
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
// 创建代理对象的实例
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}

我们看一下newInstance方法的源代码:


private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}

讲解完了代理类的生成源码,我们一定想要看看代理类的代码是什么样的,下面提供一个生成代理类的方法供大家使用:


/**
* 代理类的生成工具
* @author ChenHao
* @since 2019-4-2
*/
public class ProxyGeneratorUtils {
/**
* 把代理类的字节码写到硬盘上
* @param path 保存路径
*/
public static void writeProxyClassToHardDisk(String path) {
// 第一种方法
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
// 第二种方法
// 获取代理类的字节码
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");
}
}

此时就会在指定的C盘x文件夹下生成代理类的.class文件,我们看下反编译后的结果:


package org.fenixsoft.bytecode;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy
implements DynamicProxyTest.IHello
{
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
/**
*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
/**
*
*这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*this.h.invoke(this, m3, null); this.h就是父类Proxy中保存的InvocationHandler实例变量
*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
*再联系到InvacationHandler中的invoke方法。嗯,就是这样。
*/
public final void sayHello()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码
// 这3个方法的内容与sayHello()非常相似。
static
{
try
{
m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}

这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现 ,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用 * 的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。

来源:https://www.cnblogs.com/java-chen-hao/p/10643744.html

标签:java,动态,静态,代理,源码
0
投稿

猜你喜欢

  • Android studio 去除版本控制教程

    2022-03-21 20:39:43
  • java中如何获取相关参数

    2023-11-17 20:12:39
  • 解决使用IDEA时跳转到.class的问题

    2022-07-12 20:46:37
  • 解决springboot配置logback-spring.xml不起作用问题

    2022-09-10 11:21:24
  • C#访问C++动态分配的数组指针(实例讲解)

    2021-08-20 10:34:27
  • CentOS 7下JDK8的详细安装步骤

    2022-02-18 12:06:28
  • java多线程之CyclicBarrier的使用方法

    2023-11-04 21:52:54
  • java Springboot实现教务管理系统

    2023-01-18 00:28:12
  • C#中实现任意List的全组合算法代码

    2022-09-23 01:06:48
  • Android实现图片文字轮播特效

    2021-07-25 18:44:11
  • Java基础知识之ByteArrayInputStream流的使用

    2023-10-27 14:37:53
  • Android DataBinding的官方双向绑定示例

    2023-09-15 04:39:27
  • Java使用WatchService监控文件内容变化的示例

    2023-02-15 10:10:19
  • mvn中dependencyManagement的使用详解

    2021-11-05 10:19:20
  • Android实现屏幕旋转四个方向准确监听

    2022-06-07 08:57:32
  • Java 抽象类特点总结

    2023-07-28 10:39:46
  • 剑指Offer之Java算法习题精讲链表与二叉树专项训练

    2022-01-12 19:19:01
  • SpringMVC中RequestBody注解的List参数传递方式

    2023-06-29 09:56:04
  • C# 利用Selenium实现浏览器自动化操作的示例代码

    2023-08-10 23:43:15
  • 一文教你如何使用Databinding写一个关注功能

    2023-09-17 12:26:47
  • asp之家 软件编程 m.aspxhome.com