JAVA知识点-Java代理模式

本文最后更新于:May 2, 2022 am

积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。

目录

静态代理

静态代理就和平常自己写一些工具类差不多。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。(简单说就是把被代理对象进行一次封装)

实现

以一个邮件发送为例。

接口

1
2
3
4
5
6
7
8
9
10
11
package com.proxyStu.staticProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:36
* @墨水记忆 www.tothefor.com
*/
public interface EmailSendService {
void sendMSG(String email,String msg); //接受者的邮箱,和发送的信息
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.proxyStu.staticProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:36
* @墨水记忆 www.tothefor.com
*/
public class EmailSendServiceImpl implements EmailSendService{
@Override
public void sendMSG(String email,String msg) {
System.out.println("发送给 "+email+" 的消息为:"+msg);
}
}

代理类

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 com.proxyStu.staticProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:39
* @墨水记忆 www.tothefor.com
*/
public class EmailSendProxy implements EmailSendService{

private EmailSendService emailSendService;

EmailSendProxy(EmailSendService ess){
this.emailSendService = ess;
}

@Override
public void sendMSG(String email, String msg) {

System.out.println("发送前的处理逻辑");
emailSendService.sendMSG(email,msg);
System.out.println("发送后的处理逻辑");

}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.proxyStu.staticProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:41
* @墨水记忆 www.tothefor.com
*/
public class ProxyTest {
public static void main(String[] args) {
EmailSendService emss = new EmailSendServiceImpl();
EmailSendProxy emailSendProxy = new EmailSendProxy(emss);
emailSendProxy.sendMSG("123465@qq.com", "Hello World!");

}
}

// 输出
发送前的处理逻辑
发送给 123465@qq.com 的消息为:Hello World!
发送后的处理逻辑

动态代理

动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

JDK动态代理

只能代理实现了接口的类或者直接代理接口。

介绍

Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

我们会使用 newProxyInstance() 来生成一个代理对象。源码中为:

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

而这个方法一共有 3 个参数:

  • loader :类加载器,用于加载代理对象。即加载我们自己写的类。

  • interfaces : 被代理类实现的一些接口;即类中的方法。

  • h : 实现了 InvocationHandler 接口的对象;自定义重写的一个类,该类实现了InvocationHandler接口,用于自定义一些处理逻辑。

要实现动态代理,还需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。源码

1
2
3
4
5
6
7
8
public interface InvocationHandler {

/**
* 当使用代理对象调用被代理对象的方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

三个参数:

  • proxy :动态生成的代理类

  • method : 与代理类对象调用的方法相对应,这里就有点涉及到反射。

  • args : 当前 method 方法的参数

通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 所以可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

步骤

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;这里我们可以封装成一个工厂类。

实现

还是以发送一个邮件为例。

接口
1
2
3
4
5
6
7
8
9
10
11
package com.proxyStu.jdkProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:36
* @墨水记忆 www.tothefor.com
*/
public interface EmailSendService {
void sendMSG(String email,String msg);
}

实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.proxyStu.jdkProxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:36
* @墨水记忆 www.tothefor.com
*/
public class EmailSendServiceImpl implements EmailSendService {
@Override
public void sendMSG(String email,String msg) {
System.out.println("发送给 "+email+" 的消息为:"+msg);
}
}

代理类
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
package com.proxyStu.jdkProxy;

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

/**
* @Author DragonOne
* @Date 2022/5/2 09:15
* @墨水记忆 www.tothefor.com
*/
public class myInvocationHandler implements InvocationHandler {

private Object target;

myInvocationHandler(Object obj){
this.target = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行前的其他逻辑");
Object res = method.invoke(target,args);
System.out.println("执行后的其他逻辑");
return res;
}
}

其实,这里就已经完成了代理。可以直接使用了。如下:

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
package com.proxyStu;

import com.proxyStu.staticProxy.EmailSendService;
import com.proxyStu.staticProxy.EmailSendServiceImpl;

import java.lang.reflect.Proxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:17
* @墨水记忆 www.tothefor.com
*/
public class proxyTest {
public static void main(String[] args) {
EmailSendService obj = new EmailSendServiceImpl();

EmailSendService o = (EmailSendService)Proxy.newProxyInstance( //创建代理对象
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new myInvocationHandler(obj)
);

o.sendMSG("123456@qq.com","Hello World2!");

}
}

// 输出
执行前的其他逻辑
发送给 123456@qq.com 的消息为:Hello World2!
执行后的其他逻辑

可以看见,在创建代理对象的时候是比较复杂的;如果需要创建多次时,那么就会出现代码的冗余,也不方便。所以,我们可以将其进行封装。

封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.proxyStu.jdkProxy;


import java.lang.reflect.Proxy;

/**
* @Author DragonOne
* @Date 2022/5/2 09:18
* @墨水记忆 www.tothefor.com
*/
public class myProxyFactory {
public static Object getProxy(Object obj){
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new myInvocationHandler(obj)
);
}
}

使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.proxyStu;

import com.proxyStu.staticProxy.EmailSendService;
import com.proxyStu.staticProxy.EmailSendServiceImpl;


/**
* @Author DragonOne
* @Date 2022/5/2 09:17
* @墨水记忆 www.tothefor.com
*/
public class proxyTest {
public static void main(String[] args) {
EmailSendService o = (EmailSendService)myProxyFactory.getProxy(new EmailSendServiceImpl());
o.sendMSG("123465@qq.com","Hello World3 !");

}
}

// 输出
执行前的其他逻辑
发送给 123465@qq.com 的消息为:Hello World3 !
执行后的其他逻辑

这样进行封装,极大的方便了使用和减少了代码量。

CGLIB 动态代理

CGLIB 可以代理未实现任何接口的类。 在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

总结

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。

  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀。