常用设计模式系列(八)—代理模式
第一节
前言
一天不见,不知道各位大佬想我了没,想念我的设计模式系列课程的小伙伴看完以后抓紧分享出去,让你的朋友给我个关注,公众号涨涨粉,哈哈。几天没有斗图了,不知道各位大佬还适应吗,寒冷空气侵蚀着每一个角落,我在家里写设计模式的时候,都要把空调打开,否则手就真的没办法敲代码了,实在是像极了在寒风中瑟瑟发抖的可怜人。天这么冷,还是裹着被子看我的设计模式系列吧。
今天讲解的内容呢是结构型设计模式第二篇,今天讲解代理模式,代理模式其实很好理解,话说什么是代理模式呢?我们可能听说过动态代理、反向代理等等名词,代理这个字面的含义就是,当你无法去直接去做一件事情或者没有途径去做一件事情的时候,需要一个代理的角色来帮我们完成某一件事情,例如我们大学毕业后,选择去民营企业上班的同学,都会去办理档案代理保存,这时候找的就是人事代理,我们没有权限保留自己的档案,交给人事代理帮我们保存,这个模式就是代理模式给我们带来的方便。
第二节
代理模式
代理模式(Proxy)是常用的结构型设计模式之一,当一个对象无法直接访问某个对象或者访问某个对象存在困难时,可以通过一个代理对象间接访问。这时访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。为了保证客户端使用的透明性及通用性,代理对象必须要跟访问的目标对象实现相同的接口,保证用户使用方便。
一个代理关系,肯定要有申请人(客户端),代理人(代理类)和被代理人(真实主题类),代理人和被代理人代理的内容是一致的(抽象主题类),这样子才能完成代理关系。
目前比较流行的海外购,国内想要买国外商品的卖家,不能够或者不方便远到大海洋以外去购买商品,就应运而生出了海外代购,海外代购从国外卖家手中,买到国内卖家想要的商品,并完成交易后,送到买家手中,这个模式使用的就是代理模式。其中买家就是客户端,想要通过海外代购(代理类)买到国外商家(真实主题类)贩卖的的心仪商品,代理类和国外的卖家遵循的都是商品买卖原则(抽象主题类),且针对客户来讲,卖的都是同一个商品,才能够完成交付过程。
讲到代理,我们听说过的静态代理、动态代理、正向代理、反向代理都是什么呢?
静态代理
当用户不能访问或者不方便访问对象时,使用代理类来间接访问的模式,有多少个委托类,就有多少个代理类,这个模式就是静态代理模式,本文场景举例就是静态代理。
动态代理
提供一个统一的代理类完成系统中所有需要代理的请求,动态的去执行真实主题的代理模式,称为动态代理,Spring
AOP的核心就是动态代理。Spring动态代理有两种实现方式:JDK动态代理和CGLib动态代理,Spring会根据实际情况自行选择采用哪种方式,本文我们讲解基于JDK的动态代理方式。
正向代理
位于客户端与服务器之间的一种代理方式,客户端向代理服务器发送自己的请求并指定自己的目标,代理服务器转交请求并将获得的内容返回给客户端,客户端进相关配置才能使用正向代理。默认的代理技术都是正向代理,本文场景举例的代理也是正向代理,生活中我们使用的VPN访问指定网络的技术也是正向代理技术。
反向代理
反向代理服务也是介于客户端和服务器之间,但是反向代理技术中,用户无感知服务器,只是将反向代理服务器当做自己需要访问的服务器,反向代理服务器可以代理到多台主机做到负载均衡,客户端将请求发送给反向代理服务器,然后反向代理服务器转发请求及返回响应数据,客户端无需指定自己要访问的服务器地址,因为客户端就认为反向代理服务器就是自己需要的服务器。反向代理多应用于外网到内网之间的网络划分,负载均衡服务器的使用等场景。
不要简单的认为静态代理、动态代理与正向、反向代理是不同的分类,他们只是从不同的维度去分类代理模式,例如本文举例的场景,它是静态代理模式但是属于正向代理技术。
JDK的动态代理如何实现?
JDK的reflect(反射包)提供了一个代理类Proxy,java.lang.reflect.Proxy类提供用于创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。查阅API使用方法。
可以看到API中的四个方法,其中两个是判断代理类处理程序及校验接口,其中剩余两个方法(newProxyInstance与getProxyClass)的区别是什么?
getProxyClass()方法:用来获取类的构造方法和类的方法
newProxyInstance方法:使用JDK的Proxy代理类进行代理,可以获得代理类代理的真实主题类(代理类的实例)来执行,很显现我们需要的是这个方法。
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
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.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
需要传递的参数如下:
loader代理类的类加载器,需要提供。
interfaces 代理类实现的接口列表。
h 调度时处理程序的调度方法,需要调度才能执行,newProxyInstance方法需要执行InvocationHandler处理类的invoke才能获取到真实主题类。
InvocationHandler 是什么?查看源代码:
package java.lang.reflect;
public interface InvocationHandler {
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
需要写一个调度类并实现InvocationHandler类及抽象方法invoke()才能完成,那么使用invoke时,我们可以使用动态代理技术在执行前后完成自定义处理或者日志打印,SpringAOP不就是使用的这个方式吗,具体如何实现,我们等下看代码实现。
第三节
代码实现
1.创建抽象主题角色
package com.yang.proxy;
/**
* @ClassName Subject
* @Description 抽象主题角色
* @Author IT小白架构师之路
* @Date 2020/12/16 21:39
* @Version 1.0
**/
public interface Subject {
/**
* 售卖商品
*/
public void sale();
}
2.创建真实主题角色
package com.yang.proxy;
/**
* @ClassName RealSubject
* @Description 真实主题角色
* @Author IT小白架构师之路
* @Date 2020/12/16 21:41
* @Version 1.0
**/
public class RealSubject implements Subject{
@Override
public void sale() {
System.out.println("我是在国外售卖的商品");
}
}
3.创建代理角色
package com.yang.proxy;
/**
* @ClassName Proxy
* @Description 注释
* @Author IT小白架构师之路
* @Date 2020/12/16 21:43
* @Version 1.0
**/
public class Proxy implements Subject{
//成员对象,国外的卖商品的对象
private RealSubject realSubject;
/**
* 销售前的行为
*/
public void preSale() {
realSubject = new RealSubject();
System.out.println("找到国外的老板");
}
@Override
public void sale() {
//代理售卖
preSale();
realSubject.sale();
System.out.println("拿到商品");
afterSale();
}
/**
* 销售前的行为
*/
public void afterSale() {
System.out.println("把商品交给客户");
}
}
4.创建客户端测试类
package com.yang.proxy;
/**
* @ClassName Client
* @Description 客户端
* @Author IT小白架构师之路
* @Date 2020/12/16 21:47
* @Version 1.0
**/
public class Client {
public static void main(String[] args) {
//创建代理对象
Subject subject = new Proxy();
//购买商品
subject.sale();
}
}
5.运行结果如下,完成客户端测试,使用代理类买到了商品
找到国外的老板
我是在国外售卖的商品
拿到商品
把商品交给客户
代码实现动态代理
创建多个真实角色,使用统一的动态代理完成代理工作,并执行业务。
1.创建用户抽象主题角色
package com.yang.proxy.dynamic;
/**
* @ClassName AbstractUserDao
* @Description 抽象用户主题
* @Author IT小白架构师之路
* @Date 2020/12/16 21:59
* @Version 1.0
**/
public interface AbstractUserDao {
/**
* 抽象统计用户数量方法
* @return
*/
public int countUser();
}
2.创建菜单抽象主题角色
package com.yang.proxy.dynamic;
/**
* @ClassName AbstractMenuDao
* @Description 抽象菜单角色
* @Author IT小白架构师之路
* @Date 2020/12/16 22:02
* @Version 1.0
**/
public interface AbstractMenuDao {
/**
* 获取用户名称的抽象方法
* @param menuId
* @return
*/
public String getMenuName(String menuId);
}
3.创建用户真实主题角色
package com.yang.proxy.dynamic;
/**
* @ClassName UserDao
* @Description 用户真实主题角色
* @Author IT小白架构师之路
* @Date 2020/12/16 22:03
* @Version 1.0
**/
public class UserDao implements AbstractUserDao {
@Override
public int countUser() {
System.out.println("userDao:查到的用户数量为20");
return 20;
}
}
4.创建菜单真实主题角色
package com.yang.proxy.dynamic;
/**
* @ClassName MenuDao
* @Description 菜单真实主题角色
* @Author IT小白架构师之路
* @Date 2020/12/16 22:04
* @Version 1.0
**/
public class MenuDao implements AbstractMenuDao{
@Override
public String getMenuName(String menuId) {
System.out.println("MenuDao:菜单名称为菜单管理");
return "菜单管理";
}
}
5.创建代理处理程序DaoProxyHandler
package com.yang.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @ClassName DaoProxyHander
* @Description 代理机制,继承反射处理类,完成动态代理在业务前后增加自定义业务的功能
* @Author IT小白架构师之路
* @Date 2020/12/16 22:08
* @Version 1.0
**/
public class DaoProxyHandler implements InvocationHandler {
//内部成员
private Object object;
//有参构造
public DaoProxyHandler(Object object){
this.object = object;
}
public void beforeInvoke(){
//打印代理类代理的真实主题类名称
System.out.println(this.object.getClass().getName()+":执行前处理标识");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
//调用并返回
Object result = method.invoke(object,args);
afterInvoke();
return result;
}
public void afterInvoke(){
//打印代理类代理的真实主题类名称
System.out.println(this.object.getClass().getName()+":执行后处理标识");
}
}
6.创建动态代理类测试程序
package com.yang.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @ClassName DynamicProxyClient
* @Description 动态代理测试类
* @Author IT小白架构师之路
* @Date 2020/12/16 22:17
* @Version 1.0
**/
public class DynamicProxyClient {
public static void main(String[] args) {
//创建真实对象类
AbstractUserDao userDao = new UserDao();
//代理过程处理类
InvocationHandler handler = new DaoProxyHandler(userDao);
//创建代理类集成的接口列表
Class [] classes = new Class[]{AbstractUserDao.class};
//使用反射中的创建代理对象的类Proxy,创建UserDao的代理类,原代理类需要传递进去才能完成自定义处理
AbstractUserDao proxy = (AbstractUserDao) Proxy.newProxyInstance(AbstractUserDao.class.getClassLoader(),classes,handler);
//代理类执行
proxy.countUser();
System.out.println("-----------------------我是分割线---------------------------");
//创建真实主题类
AbstractMenuDao abstractMenuDao = new MenuDao();
//代理过程处理类 传入真实主题类
handler = new DaoProxyHandler(abstractMenuDao);
//创建代理类集成的接口列表
classes = new Class[]{AbstractMenuDao.class};
//使用反射中的创建代理对象的类Proxy,创建MenuDao的代理类,原代理类需要传递进去才能完成自定义处理
AbstractMenuDao menuProxy =(AbstractMenuDao)Proxy.newProxyInstance(AbstractMenuDao.class.getClassLoader(),classes,handler);
//代理类执行
menuProxy.getMenuName("11");
}
}
7.程序运行结果如下,完成了多个真实主题使用同一个代理类完成代理业务及增加相同处理逻辑的功能。
com.yang.proxy.dynamic.UserDao:执行前处理标识
userDao:查到的用户数量为20
com.yang.proxy.dynamic.UserDao:执行后处理标识
-----------------------我是分割线---------------------------
com.yang.proxy.dynamic.MenuDao:执行前处理标识
MenuDao:菜单名称为菜单管理
com.yang.proxy.dynamic.MenuDao:执行后处理标识
第四节
代理模式优缺点及适用场景
优点:
1.代理模式在客户端和服务端之间,起到了中介的作用,可以保护目标对象不被直接访问。
2.代理模式可以在原目标对象方法执行的基础上扩展更多的功能。
3.代理模式将客户端与目标对象(真实主题类)分类,降低了耦合度。
4.代理模式能将代理对象与真实被调用的目标对象分离。
缺点:
1.随着系统需要的目标对象增多,代理类的工作会变得繁重,如果代理对象崩溃,则全局不可用。
2.客户端与目标对象间增加了代理类,处理速度肯定会影响。
3.增加了系统的复杂度,代码阅读难度增加。
适合用场景:
1.当需要在客户端与服务器之间增加代理机制防止服务器地址暴露时。
2.需要记录系统中某些类执行时的出入参及日志。
3.在系统指定场景增加自定义业务处理时。
4.系统业务量超过当前承载,需要增加节点进行支撑,需要负载均衡服务做代理服务时(软件负载均衡)。
扫描二维码
关注我吧
IT小白架构师之路