标签分类
技术文章
当前位置:主页 > 计算机编程 > java > 深度剖析java动态静态代理原理源码

java动态静态代理原理实例代码

  • 发布时间:
  • 作者:码农之家原创
  • 点击:71

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

这篇文章主要知识点是关于java,动态,静态,代理,源码,深度剖析java动态静态代理原理源码,java动态绑定和静态绑定用法实例详解 的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书

神经网络算法与实现 基于Java语言
  • 类型:神经网络大小:32 MB格式:PDF出版:人民邮电出版社作者:法比奥
立即下载

正文

关于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()中的代理逻辑。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

java动态绑定和静态绑定用法实例详解

本文实例讲述了java动态绑定和静态绑定用法。分享给大家供大家参考,具体如下:

背景

1.当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用的是父类的方法还是子类的重写方法呢(尤其是存在向上类型转换的情况)?

2.当一个类中存在方法名相同但参数不同(重载)的方法,程序在执行的时候该如何辨别区分使用哪个方法呢?

在java中存在绑定的机制解决以上疑问。

绑定

绑定:将一个方法的调用与方法所在的类(方法主体)关联起来。即决定调用哪个方法和变量。

在java中,绑定分为静态绑定和动态绑定。也叫作前期绑定和后期绑定。

静态绑定

在程序执行以前已经被绑定(即在编译过程中就已经知道这个方法到底是哪个类中的方法)。

java当中的方法只有final、static、private修饰的的方法和构造方法是静态绑定的。

private修饰的方法:private修饰的方法是不能被继承的,因此子类无法访问父类中private修饰的方法。所以只能通过父类对象来调用该方法体。因此可以说private方法和定义这个方法的类绑定在了一起。

final修饰的方法:可以被子类继承,但是不能被子类重写(覆盖),所以在子类中调用的实际是父类中定义的final方法。(使用final修饰方法的两个好处:(1)防止方法被覆盖;(2)关闭java中的动态绑定)。

static修饰的方法:可以被子类继承,但是不能被子类重写(覆盖),但是可以被子类隐藏。(这里意思是数哦如果父类里有一个static方法,它的子类里如果没有对应的方法,那么当子类对象调用这个方法时就会使用父类中的方法,而如果子类中定义了相同的方法,则会调用子类中定义的方法,唯一的不同就是:当子类对象向上类型转换为父类对象时,不论子类中有没有定义这个静态方法,该对象都会使用父类中的静态方法,因此这里说静态方法可以被隐藏而不能被覆盖。这与子类隐藏父类中的成员变量是一样的。隐藏和覆盖的区别在于,子类对象转换成父类对象后,能够访问父类被隐藏的变量和方法,而不能访问父类被覆盖的方法)。

构造方法:构造方法也是不能被继承的(因为子类是通过super方法调用父类的构造函数,或者是jvm自动调用父类的默认构造方法),因此编译时也可以知道这个构造方法方法到底是属于哪个类的。

因此,一个方法被继承,或者是被继承后不能被覆盖,那么这个方法就采用静态绑定

动态绑定

在运行时期根据具体对象的类型进行绑定。

若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的,但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。

动态绑定的过程:

1.虚拟机提取对象实际类型的方法表

2.虚拟机搜索方法签名

3.调用方法

java中重载的方法使用静态绑定,重写的方法使用动态绑定。

实验

package practice;
public class Bind{
 public static void main(String[] args) {
 Child c = new Child();
 Parent p = c;
 
 System.out.println(p.getPristr());
 System.out.println(c.pristr);
 c.print();
 p.print();
 
 c.print1();
 p.print1();
 
 c.print2();
 p.print2();
 
 }
}
class Parent{
 private String pristr = "parent private string";
 String pubstr = "public string";
 
 public String getPristr() {
 return pristr;
 }
 public void setPristr(String pristr) {
 this.pristr = pristr;
 }
 public Parent() {
 System.out.println("parent构造函数");
 }
 
  final public void print() {
   System.out.println("parent的print");
  }
  public static void print1() {
   System.out.println("parent的print1");
  }
  public void print2() {
   System.out.println("parent的print2");
  }
}
class Child extends Parent{
 String pristr = "child private string";
 String pubstr = "public string";
 
 public Child() {
 System.out.println("child构造函数");
 }
 
 public static void print1() {
 System.out.println("child的print1");
  }
 
 public void print2() {
   System.out.println("child的print2");
  }
}

结果

parent构造函数
child构造函数
parent private string
child private string
parent的print
parent的print
child的print1
parent的print1
child的print2
child的print2

更多java相关内容感兴趣的读者可查看本站专题:《Java面向对象程序设计入门与进阶教程》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

以上就是本次给大家分享的全部知识点内容总结,大家还可以在下方相关文章里找到儿童python编程入门书籍推、 详解vue axios封装请求状态、 spring+springmvc+mybatis整合注、 等java文章进一步学习,感谢大家的阅读和支持。

上一篇:java对synchronized的优化知识点总结

下一篇:Spring Security5中默认密码编码器详解

展开 +

收起 -

学习笔记
网友NO.438900

浅谈Java中的重载,重写,多态,静态绑定、动态绑定

本文主要研究的是关于Java中重载,重写,多态,静态绑定、动态绑定的相关内容,具体如下。 重载 ,英文名是overload,是指在一个类中定义了一个以上具有相同名称的方法,这些方法的参数个数、参数类型和顺序不能相同。返回类型可以相同,也可以不同。 public class TstaticOverload {static int height;TstaticOverload() {System.out.println ("Planting a seedling");height = 0;}TstaticOverload(int initialHeight) {height = initialHeight;System.out.println("Creating new Tree that is "+ height + " feet tall");}static void info() {System.out.println("Tree is " + height + " feet tall");}static void info(String s) {System.out.println(s + ": Tree is " + height + " feet tall");}} public class testSO {public static void main (String [] args) {TstaticOverload t = new TstaticOverload(5);TstaticOverload.info();TstaticOverload.info("overloading method");new TstaticOverload();}} out: Creating new Tree that is 5 feet tall Tree is 5 feet tall overloading method: Tree is 5 feet tall Planting a seedling 重写,英文名是override,是指在继承情况下,子类中定义了与其基类中方法具有相同名称、相同返回类型或兼容类型和相同参数的方法,就叫做子类把基类的方法重写了。这是实现多态必须的步骤。 多态:多态是同一个行为具有多个不同表现形式或形态的能力。 public class StaticSupper {public static String staticGet() {return "Bas……

网友NO.450134

JavaScript静态作用域和动态作用域实例详解

静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量)。 动态作用域–函数的作用域是在函数调用的时候才决定的 JavaScript采用的是词法作用域即静态作用域; // 静态作用域:var a = 10;function fn() { var b = 1; console.log(a + b);}fn(); // 11 在创建fn函数时的时候就已经确定了它可以作用哪些变量,如果函数fn里面有变量a就直接操作变量a, 如果没有就往上一级查找,这就是静态作用域 // 动态作用域:function foo() { console.log(a);}function bar() { var a = 3; foo();}var a = 2;bar(); // 2; bar 调用,bar里面foo被调用,foo函数需要查找变量a,由于JavaScript是词法作用域(即静态作用域),foo被解析时在全局作用域. 所以只能在全局作用域中找a,输出结果为2,而非bar作用域中的a。如果js采用的时动态作用域,那么foo在bar中调用,就会先在bar中查询a,输出为3。 ps:下面看下JavaScript之静态作用域 作用域是指代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前代码对变量的访问权限。 静态作用域和动态作用域 静态作用域是指函数的作用域在函数定义时就已经确定了,而动态作用域是指函数的作用域在运行时才确定。下面是一段代码: var value = 1; function foo() { consol……

<
1
>

Copyright 2018-2019 xz577.com 码农之家

版权责任说明