今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西。

结构型设计模式

结构型设计模式又分为

  • 类 结构型
  • 对象 结构型

前者使用对象的继承机制来组织对象和类

后者采用组合聚合 的方式来组合对象。

代理模式 Proxy

理解代理一词 代理表达的是:为某个对象提供一种代理,用于控制该对象的访问,让客户端间接的访问该对象,从而限制、或者增强源对象的一些特性。

举个栗子

从国内科学SW,访问谷歌查阅一些资料的时候,我们肯定务必会借助一些代理器 也就是通常所说的VPN,代理的服务器可以帮助我们完成这些操作。

静态代理

画个图理解一下

需要说明的地方有:

  • 抽象父类或者接口:定义了这个代理可以代理的方法。比如定义了一个SearchSubject 实现它的子类必须要实现对应的search() 方法。
/**
* 抽象主题,可以进行搜索
*/
public abstract class SearchSubject {
/**
* 可以进行搜索的操作
*/
public abstract void search();
}
  • 真实对象:真实对象也就是具体将要被代理的方法,这个真实对象的方法我们要通过代理类间接的去访问

众所周知,国内访问不到Google,需要代理才行。

public class Google extends SearchSubject {
@Override
public void search() {
System.out.println("Google 搜索引擎");
}
}
  • 代理类:也就是VPN ,帮助我们访问真实对象 的某些方法,并且还可以做一些增强。比如在访问真实对象之前做一些事情,之后做一些事情。
/**
* VPN 代理
* 静态代理也需要实现抽象主题
*/
public class VPNProxy extends SearchSubject {
/**
* 含有真实主题
*/
private Google google; @Override
public void search() {
if (null == google) {
google = new Google();
}
this.before();
/**
* 调用真实对象的方法
*/
google.search();
this.after();
}
/**
* 增强方法
*/
public void before() {
System.out.println("VPN 开始执行。。。");
}
public void after() {
System.out.println("VPN 结束执行");
}
}

执行调用代理

VPNProxy proxy = new VPNProxy();
proxy.search();
------------------
VPN 开始执行。。。
Google 搜索引擎
VPN 结束执行

以上就是我们要学习的第一种代理方式:静态代理

动态代理

假设我们还需要代理一个对象呢?比如必应 假设必应搜索我们国内访问不到,必须使用代理的话,是不是又得重新创建两个对象

  • 真实对象必应搜索
  • 代理对象必应搜索的代理

这就不利于我们系统的扩展性,假设有很多需要代理的,那岂不是写一大堆。

因此,动态代理由此而生。

这里我们使用JDK 提供的动态代理

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){}
  • ClassLoader 类加载器
  • interfaces 加载的接口
  • InvocationHandler 增强处理器以及调用代理的类

创建一个可供实现的搜索接口

/**
* 搜索接口
*/
public interface SearchInterface {
String search();
}

谷歌搜索引擎实现了这个接口,并且将名称作为返回值返回。

public class GoogleSearch implements SearchInterface {
@Override
public String search() {
System.out.println("Google 搜索引擎");
return "Google";
}
}

创建一个搜索增强器,并且创建了两个方法的增强,在调用代理之前和之后,都加入了一些方法。

/**
* 搜索处理器
*/
public class SearchHandler implements InvocationHandler { private void before() {
System.out.println("handler start");
} private void after() {
System.out.println("handler stop");
} private SearchInterface obj; public SearchHandler(SearchInterface obj) {
this.obj = obj;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { this.before();
/**
* 执行代理方法
*/
Object result = method.invoke(obj, args);
System.out.println("result=" + result.toString());
this.after(); return result;
}
}

创建一个动态代理工厂,将我们需要代理的接口传入,并且传入接口的处理类,即可实现接口的增强处理。

/**
* 动态代理工厂
*/
public class ProxyFactory {
/**
* 目标对象
*/
private SearchInterface object;
private InvocationHandler handler; public ProxyFactory(SearchInterface obj, InvocationHandler handler) {
this.object = obj;
this.handler = handler;
}
/**
* 获取代理对象
* @return
*/
public Object getProxyObj() {
ClassLoader classLoader = object.getClass().getClassLoader();
Class<?>[] interfaces = object.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}

创建一个具体的接口对象,传入我们的代理工厂,并且将其处理器也同时传入,我们就可以得到一个代理对象。

        SearchInterface search = new GoogleSearch();

        System.out.println("1id=" + search);
InvocationHandler handler = new SearchHandler(search); ProxyFactory factory = new ProxyFactory(search, handler);
SearchInterface google = (SearchInterface) factory.getProxyObj(); System.out.println("2id=" + google);
google.search();
-----------------
1id=impl.GoogleSearch@1b6d3586 handler start
result=impl.GoogleSearch@1b6d3586
handler stop 2id=impl.GoogleSearch@1b6d3586
handler start
Google 搜索引擎
result=Google
handler stop

从上面的代码我们发现:

  • 代理的对象与我们创建的对象有所不同
  • 在生成代理对象的时候、已经执行了一遍invoke() 方法
  • 通过代理对象调用具体方法的时候也执行了一遍invoke()

老衲画个图

这样就好理解多了,代理工厂需要一个代理类、以及这个代理类的增强方法(处理器),通过代理工厂生成的代理对象,实现对对象的增强处理。

动态代理的总结

  • 代理类不需要实现接口,但是具体对象还是需要实现接口。

Cglib代理

上面两种代理,都是需要代理类、或者是具体的目标对象实现某个接口的基础上出现的,假设没有这个接口的显示,我只想在某个具体的对象上加入增强的话,如何实现呢?

Cglib代理又被称作子类代理,就是代理一个具体的子类

因为Spring 已经引入了相关的Cglib 的依赖,我们直接在Spring 的环境下进行测试。

创建一个具体的子类。没有实现任何的接口

public class BingSearch {
public void search() {
System.out.println("必应搜索。。。");
}
}

创建类实现一个方法拦截器,其实名字就是这样叫的。我们的代理对象,是通过工具类拿出来的。

public class ProxyFactory implements MethodInterceptor {

    //维护目标对象
private Object target; public ProxyFactory(Object target) {
this.target = target;
}
private void before() {
System.out.println("代理类前置处理。。");
}
private void after() {
System.out.println("代理类后置处理。。");
}
public Object getProxy() {
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { /**
* 执行方法
*/
this.before();
Object result = method.invoke(target, objects);
this.after();
return result;
}
}

在main 方法对一个具体的类进行增强代理。

    public static void main(String[] args) {

        ProxyFactory proxyFactory = new ProxyFactory(new BingSearch());

        BingSearch bing = (BingSearch) proxyFactory.getProxy();
bing.search();
}
---------
代理类前置处理。。
必应搜索。。。
代理类后置处理。。

小结

本节,将我们最常用的两种代理模式进行了一些讲解,其实最重要的是JDK动态代理Cglib 具体方法代理增强。因为大家已经拥抱Spring 的怀抱了,这两种代理还是很重要的,Spring的AOP 切面也是一种基于动态代理的方式实现。非常好用,在Spring 声明式事务当中,一个注解即可搞定许多冗余的编程式事务,这无不归功于 强大的动态代理

鸣谢&参考

https://www.cnblogs.com/leeego-123/p/10995975.html

代码

https://gitee.com/mrc1999/Dev-Examples

欢迎关注

最新文章

  1. c++ 调用模板函数时加template什么意思?
  2. iOS emoji表情转码 或者判断
  3. 降维技术---PCA
  4. JMeter学习(二)录制脚本
  5. 《Cortex-M0权威指南》之Cortex-M0技术综述
  6. ecstore 后台登陆跳转到 api失败,中心请求网店API失败
  7. linux重新增加硬盘容量
  8. 【HDU1856】More is better(并查集基础题)
  9. 【翻译】Sencha Ext JS 5公布
  10. 嵌套json
  11. (译)JToken的层次结构
  12. IPFS网络是如何运行的(p2p网络)
  13. vue关于数组使用的坑
  14. 一张图解释NIO原理
  15. 记一次springboot项目,maven引发的悲剧(Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletCont)
  16. eclipse起不起来web项目
  17. SVN插件和Tomcat插件地址
  18. MySQL存储过程定义中的特性(characteristic)的含义
  19. css3学习系列之初识 transform (一)
  20. lintcode-392-打劫房屋

热门文章

  1. Jmeter(十五) - 从入门到精通 - JMeter导入自定义的Jar包(详解教程)
  2. CSS基础面试题,快来查漏补缺
  3. redis(十四):Redis 有序集合(sorted set)
  4. bzoj3289Mato的文件管理
  5. vue : 无法加载文件 C:\Users\ui61895076\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。
  6. Python实现性能自动化测试的方法【推荐好文】
  7. Ethical Hacking - GAINING ACCESS(1)
  8. Ethical Hacking - NETWORK PENETRATION TESTING(5)
  9. xenomai内核解析之信号signal(一)---Linux信号机制
  10. 二进制图片blob数据转canvas