代理模式
代理模式是一种非常好理解的一种设计模式,生活中处处都有代理:
- 王宝强作为一个明星,不可能什么事都由他自己干(约电视剧、排期之类的),于是他请了经纪人
- 去医院挂号很麻烦怎么办?找黄牛帮我们挂号
- 王者荣耀技术水平不够,想要上分怎么办?请游戏代练
- 写点不正经的代码被警察捉走了怎么办?请律师帮我们打官司
无论是经纪人、黄牛、游戏代练、律师他们都是得帮我们干活。但是他们不能一手包办的,仅仅在“我”的基础上处理一些杂碎的东西(我们不愿意干、或者干不了的东西)。
这句话就说出来了代理模式的核心了,重要的东西我们干,一些我们不会的,不想干的让代理来干。外包不就是个代理嘛,简单的增删改查的代码让外包来写,核心的代码还是由公司自己来完成。
- 导演找了黄宝强的经纪人让王宝强去拍电影!
- 黄牛去排队让我们能挂上号!
- 游戏代练上分是我的微信账号!
- 律师帮我处理法律上的问题,如果打官司失败,牢还是由我来坐!
二、用代码描述代理模式(静态代理)
这里有一个程序员接口,他们每天就是写代码
public interface Programmer {
// 程序员每天都写代码
void coding();
}
Java3y也是一个程序员,他也写代码(每个程序员写的代码都不一样,所以分了接口和实现类)
public class Java3y implements Programmer {
@Override
public void coding() {
System.out.println("Java3y最新文章:......给女朋友讲解什么是代理模式.......");
}
}
此时Java3y已经是一个网红了,他不想枯燥地写代码。他在想:“在写代码时能赚钱就好咯,有人给我钱,我才写代码”。但是,Java3y的文笔太烂了,一旦有什么冬瓜豆腐,分分钟变成过气网红,这是Java3y不愿意看到的。
而知乎、博客园这种平台又不能自己给自己点赞来吸引流量(–>当前对象无法做)
所以Java3y去请了一个**程序员大V(代理)**来实现自己的计划,这个程序员大V会每次让Java3y发文章时,就给Java3y点赞、评论、鼓吹这文章好。只要流量有了,钱就到手了。
public class ProgrammerBigV implements Programmer {
// 指定程序员大V要让谁发文章(先发文章、后点赞)
private Java3y java3y ;
public ProgrammerBigV(Java3y java3y) {
this.java3y = java3y;
}
// 程序员大V点赞评论收藏转发
public void upvote() {
System.out.println("程序员大V点赞评论收藏转发!");
}
@Override
public void coding() {
// 让Java3y发文章
java3y.coding();
// 程序员大V点赞评论收藏转发!
upvote();
}
}
这代码写的牛逼了,这里充分的展现了,代理只是帮我们做额外的业务,写文章的业务还是由我们自己来完成。通过把对象以形参的方式注入。
文章(代码)还是由Java3y来发,但每次发送之后程序员大V都会点赞。
public class Main {
public static void main(String[] args) {
// 想要发达的Java3y
Java3y java3y = new Java3y();
// 受委托程序员大V
Programmer programmer = new ProgrammerBigV(java3y);
// 受委托程序员大V让Java3y发文章,大V(自己)来点赞
programmer.coding();
}
}
这样一来,不明真相的路人就觉得Java3y是真厉害,知识付费。
2.1透明代理(普通代理)
经过一段时间,Java3y尝到甜头了,觉得这是一条财路。于是Java3y给足了程序员大V钱,让程序员大V只做他的生意,不能做其他人的生意(断了其他人的财路)。
于是乎,程序员大V只做Java3y一个人的生意:
public class ProgrammerBigV implements Programmer {
// 指定程序员大V要给Java3y点赞
private Java3y java3y ;
// 只做Java3y的生意了
public ProgrammerBigV() {
this.java3y = new Java3y();
}
// 程序员大V点赞评论收藏转发
public void upvote() {
System.out.println("程序员大V点赞评论收藏转发!");
}
@Override
public void coding() {
// 让Java3y发文章了
java3y.coding();
// 程序员大V点赞评论收藏转发!
upvote();
}
}
于是乎,程序员大V想要赚点零花钱的时候直接让Java3y发文章就好了。
public class Main {
public static void main(String[] args) {
// 受委托程序员大V
Programmer programmer = new ProgrammerBigV();
// 受委托程序员大V让Java3y发文章,大V来点赞
programmer.coding();
}
}
此时,真实对象(Java3y)对外界来说是透明的。我不知道这个透明是怎么理解的,我下面的列子是租客和房东的关系,如果最后是因为组合和房东要见面,因为真实对象暴露所以理解的透明,还是因为什么。我现在还没懂透明的意思。
interface ZuFangZi{
public void rent();
}
class FandDong implements ZuFangZi{
@Override
public void rent() {
System.out.println("房东开始和租客签合同");
}
}
//中介作为一个代理他做的事情就是和租客一切东西都谈好,然后叫房东和租客签合同
//而不是自己就和组合把合同签了,这个一定要注意
class ZhongJie implements ZuFangZi{
private FandDong fandDong=new FandDong();//而且我们要知道java是面向对象的
//只有通过new的方式才会具体到某一个具体的东西,类是对许多东西的抽象
@Override
public void rent() {
System.out.println("中介和租客已经谈好了条件");
fandDong.rent();
}
}
public class DailMoShiTest {
public static void main(String[] args) {
ZhongJie zhongJie = new ZhongJie();
zhongJie.rent();
}
}
2.2代理类自定义方法
程序员大V看到Java3y一直顺风顺水,赚大钱了。觉得是时候要加价了,于是在点赞完毕后就跟Java3y说每点完一次赞加100块!
于是乎,程序员大V就增添了另外一个方法:addMoney()
public class ProgrammerBigV implements Programmer {
// ..省略了上面的代码
// 加价啦
public void addMoney() {
System.out.println("这次我要加100块");
}
@Override
public void coding() {
// 让Java3y发文章了
java3y.coding();
// 程序员大V点赞评论收藏转发!
upvote();
// 加价
addMoney();
}
}
于是乎程序员大V每次都能多100块:
interface ZuFangZi{
public void rent();
}
class FandDong implements ZuFangZi{
@Override
public void rent() {
System.out.println("房东开始和租客签合同");
}
}
//中介作为一个代理他做的事情就是和租客一切东西都谈好,然后叫房东和租客签合同
//而不是自己就和组合把合同签了,这个一定要注意
class ZhongJie implements ZuFangZi{
private FandDong fandDong=new FandDong();//而且我们要知道java是面向对象的
//只有通过new的方式才会具体到某一个具体的东西,类是对许多东西的抽象
@Override
public void rent() {
System.out.println("中介和租客已经谈好了条件");
fandDong.rent();
addMoney();
}
public void addMoney(){
System.out.println("事成之后房东会给中介500块钱");
}
}
public class DailMoShiTest {
public static void main(String[] args) {
ZhongJie zhongJie = new ZhongJie();
zhongJie.rent();
}
}
三、动态代理
几年时间过去了,Java3y靠着程序员的大V点赞还是没发财(本质上Java3y还没有干货,没受到大众的认可)。此时已经有很多人晋升成了程序员大V了,但是之前的那个程序员大V还是一直累加着钱…虽然在开始的时候Java3y尝到了甜头,但现在Java3y财政已经匮乏了。
Java3y将自己的失败认为:一定是那个程序员大V转门为我点赞被识破了,吃瓜群众都知道了,他收费又那么贵。
于是Java3y不请程序员大V了,请水军来点赞(水军便宜,只要能点赞就行了):
public class Main {
public static void main(String[] args1) {
// Java3y请水军
Java3y java3y = new Java3y();
Programmer programmerWaterArmy = (Programmer) Proxy.newProxyInstance(java3y.getClass().getClassLoader(), java3y.getClass().getInterfaces(), (proxy, method, args) -> {
// 如果是调用coding方法,那么水军就要点赞了
if (method.getName().equals("coding")) {
method.invoke(java3y, args);
System.out.println("我是水军,我来点赞了!");
} else {
// 如果不是调用coding方法,那么调用原对象的方法
return method.invoke(java3y, args);
}
return null;
});
// 每当Java3y写完文章,水军都会点赞
programmerWaterArmy.coding();
}
}
每当Java3y发文章的时候,水军都会点赞。
Java3y感叹:请水军真是方便啊~
看了这里以后我脑子里蹦出来的想法是,以我租房的例子为例,我不可能只找的一家中介,哪家的价格便宜我用哪家,所以我们要实现代理的切换,所以我的代理类还需要一个抽象代理,这样我才可以写出多个代理类,总不能这个代理我不用了,直接删了把代码改了,你以为删代码就不累嘛,所以我们写个抽象类,到时候需要什么代理自己写一下,客户端重新new一下就好了
interface ZuFangZi{
public void rent();
}
class FandDong implements ZuFangZi{
@Override
public void rent() {
System.out.println("房东开始和租客签合同");
}
}
abstract class ZhongJie implements ZuFangZi{
private FandDong fandDong=new FandDong();
public void rent() {
System.out.println("中介和租客已经谈好了条件");
fandDong.rent();
addMoney();
}
public abstract void addMoney();
}
//中介作为一个代理他做的事情就是和租客一切东西都谈好,然后叫房东和租客签合同
//而不是自己就和组合把合同签了,这个一定要注意
class HaiDianZhongJie extends ZhongJie{
private FandDong fandDong=new FandDong();//而且我们要知道java是面向对象的
//只有通过new的方式才会具体到某一个具体的东西,类是对许多东西的抽象
public void addMoney(){
System.out.println("事成之后房东会给中介500块钱");
}
}
class WangJingZhongJie extends ZhongJie{
@Override
public void addMoney() {
System.out.println("事成之后房东给中介100块钱");
}
}
public class DailMoShiTest {
public static void main(String[] args) {
HaiDianZhongJie haiDianZhongJie = new HaiDianZhongJie();
haiDianZhongJie.rent();
}
}
写了这个以后我们发现每次需要新的代理都需要自己写一个新的类,有点麻烦了,顿时就感觉动态代理存在的意义了。
3.1动态代理调用过程
我们来看看究竟是怎么请水军的:
Java提供了一个Proxy类,调用它的newProxyInstance方法可以生成某个对象的代理对象,该方法需要三个参数:
通过调用代理类的instance方法生成一个具体的代理对象,通过我们需要传3个参数,毕竟我们需要知道这个代理对象是替谁干活之类的东西吧。
- 参数一:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
- 参数二:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
- 参数三:生成的代理对象的方法里干什么事【实现handler接口,我们想怎么实现就怎么实现】
这三个参数我们以例子为例
- 第一个参数用的就是房东类,我们实现的是动态代理,代理我们也没写呀
- 第二个还是房东类
看到这里我再看了一下示例的代码,我发现两者的区别就是一个是调用的装载器方法,一个调用的是接口方法
- 第三个就是实现handler接口,看案例就懂了,懂得差不多我们先会用,其它的我们以后慢慢懂。
在编写动态代理之前,要明确几个概念:
- 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】
- 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
- 使用JDK动态代理必须要有接口【参数二需要接口】
上面也说了:代理对象会实现接口的所有方法,这些实现的方法交由我们的handler来处理!
代理对象会实现接口的所有方法,这不就是充分说明了代理类的作用嘛,目的就是帮主人做事情。
- 所有通过动态代理实现的方法全部通过
invoke()
调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBuIJC9N-1607139673929)(F:\bianchengruanjian\Typora\设计模式\Untitled.assets\image-20201205101909292.png)]
所以动态代理调用过程是这样子的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bT5GuxWK-1607139673933)(F:\bianchengruanjian\Typora\设计模式\Untitled.assets\image-20201205110545307.png)]
import java.lang.reflect.Proxy;
interface ZuFangZi{
public void rent();
}
class FangDong implements ZuFangZi{
@Override
public void rent() {
System.out.println("房东和租客签合同");
}
}
public class DongTaiDaiLi2 {
public static void main(String[] args1) {
FangDong fangDong = new FangDong();
ZuFangZi zuFangZi=(ZuFangZi) Proxy.newProxyInstance(fangDong.getClass().getClassLoader(),fangDong.getClass().getInterfaces(),(proxy,method,args)->{
if (method.getName().equals("rent")) {
System.out.println("我是中介,我已经和租户谈好了");
method.invoke(fangDong, args);
System.out.println("房东:干的不错给你50的报酬");
} else {
return method.invoke(fangDong, args);
}
return null;
});
zuFangZi.rent();
}
}
使用了动态代理以后代理类我随时随地创建,只要修改主人方法前后执行的操作就可以了。
这个就是死套路我们先会套就行了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mgkMHAIB-1607139673936)(F:\bianchengruanjian\Typora\设计模式\Untitled.assets\image-20201205111829235.png)]
3.2静态代理和动态代理的区别
很明显的是:
- 静态代理需要自己写代理类–>代理类需要实现与目标对象相同的接口
- 而动态代理不需要自己编写代理类—>(是动态生成的)
使用静态代理时:
- 如果目标对象的接口有很多方法的话,那我们还是得一一实现,这样就会比较麻烦
使用动态代理时:
- 代理对象的生成,是利用JDKAPI,动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型),并且会默认实现接口的全部方法。
四、典型应用
我们之前写中文过滤器的时候,需要使用包装设计模式来设计一个request类。如果不是Servlet提供了实现类给我们,我们使用包装设计模式会比较麻烦
现在我们学习了动态代理了,动态代理就是拦截直接访问对象,可以给对象进行增强的一项技能
4.1中文过滤器
public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
final HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setContentType("text/html;charset=UTF-8");
request.setCharacterEncoding("UTF-8");
//放出去的是代理对象
chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是不是getParameter方法
if (!method.getName().equals("getParameter")) {
//不是就使用request调用
return method.invoke(request, args);
}
//判断是否是get类型的
if (!request.getMethod().equalsIgnoreCase("get")) {
return method.invoke(request, args);
}
//执行到这里,只能是get类型的getParameter方法了。
String value = (String) method.invoke(request, args);
if (value == null) {
return null;
}
return new String(value.getBytes("ISO8859-1"), "UTF-8");
}
}), response);
}
五、总结
本文主要讲解了代理模式的几个要点,其实还有一些细节的:比如“强制代理”(只能通过被代理对象找到代理对象,不能绕过代理对象直接访问被代理对象)。只是用得比较少,我就不说了~~
要实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话我们可以考虑cglib代理。
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能!
这里我就不再贴出代码来了,因为cglib的代理教程也很多,与动态代理实现差不多~~~
总的来说:代理模式是我们写代码中用得很多的一种模式了,Spring的AOP底层其实就是动态代理来实现的–>面向切面编程。具体可参考我之前写的那篇文章:
其实只要记住一点:原有的对象需要额外的功能,想想动态代理这项技术!
~~
要实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话我们可以考虑cglib代理。
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能!
这里我就不再贴出代码来了,因为cglib的代理教程也很多,与动态代理实现差不多~~~
总的来说:代理模式是我们写代码中用得很多的一种模式了,Spring的AOP底层其实就是动态代理来实现的–>面向切面编程。具体可参考我之前写的那篇文章:
其实只要记住一点:原有的对象需要额外的功能,想想动态代理这项技术!
参考文章:https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484222&idx=1&sn=5191aca33f7b331adaef11c5e07df468&chksm=ebd7423fdca0cb29cdc59b4c79afcda9a44b9206806d2212a1b807c9f5879674934c37c250a1&scene=21###wechat_redirect