文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2021-03-11   1.2k

Java基础学习之动态代理

可以直接看这篇文章,由简到难,演进过程讲的很清楚:https://zhuanlan.zhihu.com/p/62534874

参考:

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

https://zhishihezi.net/b/5d644b6f81cbc9e40460fe7eea3c7925#open

我们都知道,Java中继承了intrerface接口的实现类必须实现接口中声明的方法,并且所有interface类型的变量总是通过向上转型并指向某个实例的:

1
CharSequence cs = new StringBuilder();

那么有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?Java中的动态代理机制可以完成这个任务,即动态代理机制可以在运行期间动态创建某个interface实例。所谓动态,就是和静态相对应的,首先来看静态代码是怎么写的。

定义接口:

1
2
3
public interface Hello{
void morning(String name);
}

编写实现类:

1
2
3
4
5
public class HelloWorld implements Hello{
public void morning(String name){
System.out.println("Good morning " + name);
}
}

创建实例,转型为接口并调用:

1
2
Hello hello = new HelloWorld();
hello.morning("ca01h");

还有一种方式是动态代码,仍然先定义接口Hello,但这次并不去实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

一个最简单的动态代理实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main{
public static void main(String[] args){
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println(method);
if (method.getName().equals("morning")){
System.out.println("Good morning " + args[0]);
}
return null;
}
};
Hello hello = (Hello)Proxy.newProxyInstance(
Hello.class.getClassLoader(),
new Class[] {Hello.class},
handler);
hello.morning("ca01h");
}
}

interface Hello {
void morning(String name);
}

从上面这个例子可以看出,在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

把上面的动态代理改写为静态实现类大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}

Java动态代理主要使用场景:

  1. 统计方法执行所耗时间。
  2. 在方法执行前后添加日志。
  3. 检测方法的参数或返回值。
  4. 方法访问权限控制。
  5. 方法Mock测试。

接下来就演示一下动态代理添加方法调用日志的示例。

假设我们有一个叫做FileSystem接口,UnixFileSystem类实现了FileSystem接口,我们可以使用JDK动态代理的方式给FileSystem的接口方法执行前后都添加日志输出。

接口FileSystem.java

1
2
3
4
5
6
7
8
package proxy;

import java.io.File;
import java.io.Serializable;

public interface FileSystem extends Serializable {
String[] list(File file);
}

实现接口UnixFileSystem.java

1
2
3
4
5
6
7
8
9
10
11
package proxy;

import java.io.File;

public class UnixFileSystem implements FileSystem{
@Override
public String[] list(File file) {
System.out.println("正在执行[" + this.getClass().getName() + "]类的list方法,参数[" + file + "]");
return file.list();
}
}

动态代理处理类JDKInvocationHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package proxy;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKInvocationHandler implements InvocationHandler, Serializable {
private final Object target;

public JDKInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("toString".equals(method.getName())){
return method.invoke(target, args);
}
System.out.println("即将调用[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法...");
Object obj = method.invoke(target, args);
System.out.println("已完成[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法调用...");

return obj;
}
}

FileSystemProxyTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package proxy;

import java.io.File;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class FileSystemProxyTest {
public static void main(String[] args) {
FileSystem fileSystem = new UnixFileSystem();

FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
FileSystem.class.getClassLoader(),
new Class[]{FileSystem.class},
new JDKInvocationHandler(fileSystem)
);
System.out.println("动态代理生成的类名:" + proxyInstance.getClass());
System.out.println("----------------------------------------------------------------------------------------");
System.out.println("动态代理生成的类名toString:" + proxyInstance.toString());
System.out.println("----------------------------------------------------------------------------------------");

// 使用动态代理的方式UnixFileSystem方法
String[] files = proxyInstance.list(new File("."));

System.out.println("----------------------------------------------------------------------------------------");
System.out.println("UnixFileSystem.list方法执行结果:" + Arrays.toString(files));
System.out.println("----------------------------------------------------------------------------------------");

boolean isFileSystem = proxyInstance instanceof FileSystem;
boolean isUnixFileSystem = proxyInstance instanceof UnixFileSystem;

System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是FileSystem类的实例:" + isFileSystem);
System.out.println("----------------------------------------------------------------------------------------");
System.out.println("动态代理类[" + proxyInstance.getClass() + "]是否是UnixFileSystem类的实例:" + isUnixFileSystem);
System.out.println("----------------------------------------------------------------------------------------");
}

}

执行结果:

Copyright © ca01h 2019-2021 | 本站总访问量