0%

动态代理那点事

序言

今天来总结Java代理。分为静态和动态。

什么是代理?

定义

给目标对象提供一个代理对象,并由代理对象控制目标对象的引用。

目的

  1. 通过引入代理对象的方式来简介访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
  2. 通过代理对象对原有的业务增强;

注意事项

  1. 代理对象和真实对象必须实现同一个接口
  2. 代理对象只是搬运工,代理对象必须包含真实的对象;

代理模式

给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。

代理模式是一种结构型设计模式。

代理模式角色分为 3 种:

Subject(接口):

定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法,其实就是一个功能接口;

RealSubject(真实类):真正实现业务逻辑的类,这就是真实的对象;

Proxy(代理类):用来代理和封装真实主题;

三者关系如图所示:

img

如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:

  • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
  • 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。(这个很牛逼)

静态代理

下面写个小demo看一下。

编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl:

接口 UserService:

1
2
3
4
public interface UserService {
public void select();
public void update();
}

接口实现类UserServiceImpl:

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {  
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}

代理类UserServiceProxy:

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
public class UserServiceProxy implements UserService{

private UserService target; // 包含被代理的对象

public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before();
target.select(); // 这里才实际调用真实主题角色的方法
after();
}
public void update() {
before();
target.update(); // 这里才实际调用真实主题角色的方法
after();
}

private void before() { // 在执行方法之前执行
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() { // 在执行方法之后执行
System.out.println(String.format("log end time [%s] ", new Date()));
}
}

写一个客户端:

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args){
UserService user = (UserService) new UserServiceImpl();
UserService proxy = new UserServiceProxy(user);
//调用代理实现类实现的方法
proxy.select();
proxy.update();
}
}

输出结果:

1
2
3
4
5
6
7
8
log start time [Mon Apr 13 21:28:09 CST 2020] 
查询 selectById
log end time [Mon Apr 13 21:28:09 CST 2020]
log start time [Mon Apr 13 21:28:09 CST 2020]
更新 update
log end time [Mon Apr 13 21:28:09 CST 2020]

Process finished with exit code 0

模板:

1
2
3
4
接口 obj = new 接口实现类();
代理类 proxy = new 代理类(obj);
proxy.method_a();
proxy.method_b();

静态代理的缺点

虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1/当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2/当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

动态代理

为什么类可以动态的生成?

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
  • 从数据库中获取等等

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。但是如何计算?如何生成?情况也许比想象的复杂得多,我们需要借助现有的方案。

JDK动态代理

两个核心类

创建动态代理类会使用到

java.lang.reflect.Proxy

java.lang.reflect.InvocationHandler接口。

目标类必须实现接口,没有接口只能用CGLIB。

java.lang.reflect.Proxy主要用于生成动态代理类Class、创建代理类实例,该类实现了java.io.Serializable接口。

  1. Proxy

    ,是调度器,帮助调度服务的员工,是所有动态代理的父类,它只管new实例出来,别的不插手。

  2. InvocationHandler

    是接口,用于调用Proxy类生成的代理类方法,该类只有一个invoke方法。

    只管“new的实例”的执行功能,别的不插手。

在程序运行过程中产生的代理类的对象,其实就是通过反射机制来生成的。

JDK提供的代理只能针对接口做代理。

每一个动态代理类都必须要实现InvocationHandler这个接口。

它的invoke方法表示代理对象要执行的功能代码。

proxy

Proxy这个类的作用就是用来动态创建一个代理对象类,它提供了许多的方法:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
copypackage java.lang.reflect;

import java.lang.reflect.InvocationHandler;

/**
* Creator: yz
* Date: 2020/1/15
*/
public class Proxy implements java.io.Serializable {

// 省去成员变量和部分类方法...

/**
* 获取动态代理处理类对象
*
* @param proxy 返回调用处理程序的代理实例
* @return 代理实例的调用处理程序
* @throws IllegalArgumentException 如果参数不是一个代理实例
*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException {
...
}

/**
* 创建动态代理类实例
*
* @param loader 指定动态代理类的类加载器
* @param interfaces 指定动态代理类的类需要实现的接口数组,这里的对象是接口实现类
* @param h 动态代理处理类
* @return 返回动态代理生成的代理类实例
* @throws IllegalArgumentException 不正确的参数异常
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
...
}

/**
* 创建动态代理类
*
* @param loader 定义代理类的类加载器
* @param interfaces 代理类要实现的接口列表
* @return 用指定的类加载器定义的代理类,它可以实现指定的接口
*/
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
...
}

/**
* 检测某个类是否是动态代理类
*
* @param cl 要测试的类
* @return 如该类为代理类,则为 true,否则为 false
*/
public static boolean isProxyClass(Class<?> cl) {
return java.lang.reflect.Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

/**
* 向指定的类加载器中定义一个类对象
*
* @param loader 类加载器
* @param name 类名
* @param b 类字节码
* @param off 截取开始位置
* @param len 截取长度
* @return JVM创建的类Class对象
*/
private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);

}

其中用的最多的就是 newProxyInstance ()方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* 创建动态代理类实例,也就是创建代理对象
*
* @param loader 指定动态代理类的类加载器
* @param interfaces 指定动态代理类的类需要实现的接口数组
* @param h 动态代理处理类
* @return 返回动态代理生成的代理类实例
* @throws IllegalArgumentException 不正确的参数异常
*/

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

这个方法的作用就是得到一个动态代理对象,其中接收三个参数:

loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理类对象进行加载;

实现类a,a.getClass().getClassLoader(),获取目标对象的类加载器。

interfaces参数:一个Interface接口对象数组,说明将要给被代理类对象提供一组什么样的接口,如果提供了一组接口给被代理类对象,那么该对象就宣称实现了该接口(多态),这样就能调用这组接口中的方法了;

目标对象实现的接口,也是反射获取的。a.getClass().getInterfaces()

h:一个InvocationHandler对象,表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上;

是我们自己写的,代理类要完成的功能,体现在invoke函数里。

返回值:就是代理对象。 把三个参数当作原料,加工出来代理对象。

Invocationhandler

作为InvocationHandler接口唯一的方法,invoke ()方法定义如下:

1
Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy参数:jdk创建的代理对象,无需赋值;

method参数:目标类中的方法;

args参数:Method参数中,接收的参数;

以上三个参数都是jdk帮忙创建的,无需人为赋值。

怎么用:

  1. 创建一个类,来实现InvocationHandler
  2. 重写invoke方法,把原来静态代理中代理类要完成的功能,放在重写之后的invoke方法中实现。

invoke方法表示代理对象要执行的功能代码。

还需要一个Object字段:

1
2
3
4
5
private Object target;
public MyHandler(Object target) {
this.target = target;
}
ret = method.invoke(target,args);

newProxyInstance 源码

看看java.lang.reflect.Proxy#newProxyInstance里面怎么实现的:

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
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
SecurityManager sm = System.getSecurityManager();
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());
}
}

这里面有一个关键函数是Class<?> cl = getProxyClass0(loader, intfs);,这一步主要就是生成代理类。

JDK会生成一个叫$Proxy0的代理类,这个类文件是放在内存中的,在创建代理类对象时,通过反射机制获得这个类的构造方法,然后创建代理类实例。

动态代理Demo

实现动态代理的步骤:

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类来实现接口
  3. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
    1. 调用目标方法
    2. 增强功能
  4. 使用Proxy类的newProxyInstance方法,来创建代理对象,代理对象来执行目标方法调用,其实是去执行handler中的invoke方法。invoke方法主要还是做两件事情:1.调用目标方法 2.功能增强。然后invoke返回目标方法执行结果。

这里再写一个代购小demo:

如果说静态代理阶段,我是一个只会提供固定商品的代购,那么现在我强大了,我有一个小公司,我的公司旗下可以代购各种商品,对于代购每一种商品的员工,都有熟练且强大的业务能力。

那么我先写好我的“代购帝国公司”:

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
38
39
40
41
42
43
44
45
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @auther : 0range
* @create : 2020 - 04 - 13 - 10:09 下午
*/
public class LisonCompany implements InvocationHandler {

//被代理的对象
private Object factory;

public Object getFactory(){
return factory;
}

public void setFactory(Object factory){
this.factory = factory;
}

//通过Proxy获取动态代理的对象,他是用来调度员工的
public Object getProxyInstance(){
return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);
}//这里面this就是InvocationHandler


//这里就是通过动态代理对象来对方法进行增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
dobefore();
//这里就是你要调用什么样的方法,作为参数传给我,我给你invoke
Object ret = method.invoke(factory,args);
doafter();
return ret;
}
//售前服务
private void dobefore(){
System.out.println("售前服务,精美包装,快递一条龙服务!");
}
//售后服务
private void doafter(){
System.out.println("售后服务,无忧退换货!");
}
}

这之后需要新的功能,只需加类,加接口就可以完成实现。

这里再具体写一下,假设有两个水果工厂:

1
2
3
4
5
6
7
public interface AppleFactory {
public void makeApple();
}

public interface OrangeFactory {
public void makeOrange();
}

分别写好对应实现类:

1
2
3
4
5
6
7
8
9
10
11
12
public class AppleFactoryImpl implements AppleFactory{
@Override
public void makeApple(){
System.out.println("新鲜大苹果!!!");
}
}
public class OrangeFactoryImpl implements OrangeFactory {
@Override
public void makeOrange(){
System.out.println("新鲜大橙子!!!");
}
}

那么代购客户端这样写:

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
public class FruitClient {
public static void main(String[] args) {
//苹果工厂实现类
AppleFactory apl = new AppleFactoryImpl();
//代购公司成立了
LisonCompany lisonComp = new LisonCompany();
//代购公司目标是苹果
lisonComp.setFactory(apl);
//分配1号员工来负责代购
AppleFactory lison1 = (AppleFactory)lisonComp.getProxyInstance();
//代购苹果
lison1.makeApple();

System.out.println("------------------------");

//橙子工厂实现类
OrangeFactory org = (OrangeFactory) new OrangeFactoryImpl();
//代购公司目标是橙子
lisonComp.setFactory(org);
//分配2号员工来负责代购
OrangeFactory lison2 = (OrangeFactory)lisonComp.getProxyInstance();
//代购橙子
lison2.makeOrange();
}
}

运行结果:

1
2
3
4
5
6
7
售前服务,精美包装,快递一条龙服务!
新鲜大苹果!!!
售后服务,无忧退换货!
------------------------
售前服务,精美包装,快递一条龙服务!
新鲜大橙子!!!
售后服务,无忧退换货!

流程图:

img

又写了一个好懂的版本:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Buyer {
public static void main(String[] args) {

Factory realfactory = new FactoryImpl();

InvocationHandler handler = new Company(realfactory);

Factory fac = (Factory) Proxy.newProxyInstance(handler.getClass().getClassLoader(),realfactory.getClass().getInterfaces(),handler);

fac.make("大苹果");
fac.taste("大西瓜");
}
}

interface Factory{
public void make(String name);
public void taste(String name);
}


class FactoryImpl implements Factory{
public void make(String name){

System.out.println("[*]买新鲜的"+name);

}

public void taste(String name) {
System.out.println("[*]尝一口"+name);
}
}

class Company implements InvocationHandler{


Object subject;

public Company(Object subject) {
this.subject = subject;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("来代理咯!");

method.invoke(subject,args);

System.out.println("代理完毕咯!");

return null;
}
}

结果:

image-20200915151948096

总结

JDK 动态代理

  • 为了解决静态代理中,生成大量的代理类造成的冗余;
  • JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
  • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
  • jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
  • 优点:解决了静态代理中冗余的代理实现类问题。
  • 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

通用模式:

1
2
3
4
接口 os = new 接口实现类();
Myhandler h = new Myhandler(os);//内部重写invoke方法,增强目标方法。
接口 proxy = Proxy.newproxyInstance(loader,interfaces,h);
proxy.目标方法();