Spring AOP系列(一)— 代理模式

AOP(Aspect Oriented Programming)并没有创造或使用新的技术,其底层就是基于代理模式实现。因此我们先来学习一下代理模式。

基本概念

定义

代理模式,为对象提供一种代理,以控制对这个对象的访问。

角色

代理模式也称为委托模式,一般有以下三个角色

  • 抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
  • 具体主题角色:也被称为被委托角色、被代理角色,是具体业务逻辑的实际执行者。
  • 抽象主题角色:也被称为委托类,代理类。负责控制具体主题角色的访问和应用,并且可以在具体主题角色的业务逻辑执行前后进行预处理和后处理的逻辑。

    从“具体主题角色”的定义我们可以明确一点:代理模式中的被代理类和代理类的关系,并非是说代理类帮助被代理类完成了所有的工作。反而是最核心的工作仍然是由被代理类自己来完成。代理类在其中起的作用就是控制被代理类的访问、应用,以及在被代理类执行核心业务逻辑的前后进行其它的处理。

    下面的实践将会帮助我们更好地理解。

简单代理模式的基本实现

这里我们先按照基本概念中内容,完成一个简单代理模式的基本实现。设想这样一个场景:明星与经纪人。经纪人帮明星与外界洽谈合作,签订合同,计算佣金;明星完成演戏、唱歌等工作。

首先定义一个Actor.java

package com.leng.proxy;

/**
* @Classname Actor
* @Date 2020/9/12 23:30
* @Autor lengxuezhang
*/
public interface Actor {
/**
* 演戏
*/
public void act(); /**
* 唱歌
*/
public void sing();
}

艺人一般需要会唱歌和演戏。

接着分别实现明星类,实现Actor接口,并传入姓名。

package com.leng.proxy;

/**
* @Classname Star
* @Date 2020/9/12 23:32
* @Autor lengxuezhang
*/
public class Star implements Actor { private String name; public Star(String name) {
this.name = name;
} @Override
public void act() {
System.out.println(name + "在演戏");
} @Override
public void sing() {
System.out.println(name + "在唱歌");
}
}

最后实现经纪人

package com.leng.proxy;

/**
* @Classname Agent
* @Date 2020/9/12 23:36
* @Autor lengxuezhang
*/
public class Agent implements Actor { private Actor actor = null; // 要想实现经纪人代理明星,需要将被代理类对象传递给代理类
public Agent(Actor actor) {
this.actor = actor;
} @Override
public void act() {
this.before();
// 实际调用的是明星的act(),经纪人接活,但最后去演戏的肯定是明星自己
actor.act();
this.after();
} @Override
public void sing() {
this.before();
actor.sing();
this.after();
} // 预处理方法
private void before() {
System.out.println("谈好价钱,签好合同");
} // 后处理方法
private void after() {
System.out.println("清算演出费,和明星分钱");
}
}

客户端调用Client.java:

package com.leng;

import com.leng.proxy.Agent;
import com.leng.proxy.Star; /**
* @Classname Client
* @Date 2020/9/12 2:40
* @Autor lengxuezhang
*/
public class Client {
public static void main(String[] args) {
Star star = new Star("刘德华");
Agent agent = new Agent(star);
agent.act();
agent.sing();
}
}

运行结果:

谈好价钱,签好合同

刘德华在演戏

清算演出费,和明星分钱

谈好价钱,签好合同

刘德华在唱歌

清算演出费,和明星分钱

小结

  • 代理模式实现的关键是,代理类持有被代理类对象,这样才能执行被代理类本身想要执行的业务逻辑。
  • 代理类可以定义自己的预处理和后处理方法,使之在被代理类的业务逻辑前后执行。

    以上被称为基于代理模式概念的一个例子,实际上代理模式在不同场景或要求下会有一些新的特性。如接下来要介绍的普通代理和强制代理。

代理模式的扩展

普通代理

明星的工作一般比较忙,没有时间和外界有太多沟通和接触。导演想要找明星演戏,一般只能先去联系他的公司经纪人,是联系不到明星本人。“导演只能联系到经纪人,而不能联系明星”,这样的模式就被称为“普通代理”。换句话说:普通代理模式下,客户端只能访问代理类,不能访问被代理类。

上面的Client.java中,直接new了一个明星对象,这就算是直接访问了被代理类,不能称为普通代理。那如何才能实现普通代理呢。关键是让被代理类的创建只能由代理类来完成。修改代码如下:

只需要修改构造函数

package com.leng.proxy;

/**
* @Classname Star
* @Date 2020/9/12 23:32
* @Autor lengxuezhang
*/
public class Star implements Actor { private String name; public Star(Agent agent, String name) throws Exception {
// 必须是经纪人才能创建明星
if(agent == null) {
throw new Exception("非经纪人,不能创造明星");
} else {
this.name = name;
}
} @Override
public void act() {
System.out.println(name + "在演戏");
} @Override
public void sing() {
System.out.println(name + "在唱歌");
}
}
package com.leng.proxy;

/**
* @Classname Agent
* @Date 2020/9/12 23:36
* @Autor lengxuezhang
*/
public class Agent implements Actor { private Actor actor = null; // 告诉agent,我需要联系哪一个"明星"
public Agent(String name) {
try {
this.actor = new Star(this, name);
} catch (Exception e) {
System.out.println("创建agent异常");
}
} @Override
public void act() {
this.before();
actor.act();
this.after();
} @Override
public void sing() {
this.before();
actor.sing();
this.after();
} // 预处理方法
private void before() {
System.out.println("谈好价钱,签好合同");
} // 后处理方法
private void after() {
System.out.println("清算演出费,和明星分钱");
}
}

客户端类在使用时,也需要做修改,此时客户端可以不直接访问Star

package com.leng;
import com.leng.proxy.Agent;
/**
* @Classname Client
* @Date 2020/9/12 2:40
* @Autor lengxuezhang
*/
public class Client {
public static void main(String[] args) {
Agent agent = new Agent("刘德华");
agent.act();
agent.sing();
}
}

运行结果:

谈好价钱,签好合同

刘德华在演戏

清算演出费,和明星分钱

谈好价钱,签好合同

刘德华在唱歌

清算演出费,和明星分钱

可以看到运行结果并没有发生改变,但确实已经实现了只能以访问代理类方式访问非代理类。假如客户端直接访问代理类的话,是会抛出异常的。

普通代理模式的优点:

  • 调用者不关心被代理类的实现,被代理类的修改不会影响高层模块的调用。
  • 被代理类的访问权限收口在代理类。

强制代理

先来看一个场景:

王晶想找华仔拍戏,两人老相识了,于是王晶直接打电话问华仔:“华仔啊,有咩时间?搵你拍片喔"”。

华仔回复:“你先找我的经纪人谈一下吧,看一下我的工作安排”。

然后华仔给王晶自己经纪人的联系方式,王晶和经纪人谈好之后,华仔就准备去拍王晶的电影了。

这个场景里有个点,首先王晶实际上是不能直接找华仔拍戏,即使有联系方式,华仔也不一定给你拍;其次是王晶必须与华仔指定的经纪人沟通具体的拍戏工作。

强制代理的要求是:

  • 不能直接通过代理类访问被代理类。
  • 不能直接访问被代理类。
  • 只能听过被代理类指定的代理类访问被代理类。

    接下来逐个讲解如何修改之前的代码。首先抽象主题角色中需要增加一个获取自己代理类的方法,其它不变:
public interface Actor {
...
public Actor getProxy();
}

明星类实现该方法,一方面要找到自己的代理,另一方面其它的唱歌、演戏方法也需要检查是否是自己指定的代理类访问的。

package com.leng.proxy;

/**
* @Classname Star
* @Date 2020/9/12 23:32
* @Autor lengxuezhang
*/
public class Star implements Actor { private String name; private Agent agent = null; public Star(String name) {
this.name = name;
} @Override
public void act() {
if (isProxy()) {
System.out.println(name + "在演戏");
} else {
System.out.println("请使用指定的代理访问");
}
} @Override
public void sing() {
if (isProxy()) {
System.out.println(name + "在唱歌");
}else {
System.out.println("请使用指定的代理访问");
}
} @Override
public Actor getProxy() {
this.agent = new Agent(this);
return this.agent;
} /**
* 判断是否是指定的代理
* @return
*/
private boolean isProxy() {
if(agent == null) {
return false;
}
return true;
}
}

经纪人类

package com.leng.proxy;

/**
* @Classname Agent
* @Date 2020/9/12 23:36
* @Autor lengxuezhang
*/
public class Agent implements Actor { private Actor actor = null; // 告诉agent,我需要联系哪一个"明星"
public Agent(Actor actor) {
this.actor = actor;
} @Override
public void act() {
this.before();
actor.act();
this.after();
} @Override
public void sing() {
this.before();
actor.sing();
this.after();
} @Override
public Actor getProxy() {
//经纪人没有自己的经纪人,所以就返回自己
return this;
} // 预处理方法
private void before() {
System.out.println("谈好价钱,签好合同");
} // 后处理方法
private void after() {
System.out.println("清算演出费,和明星分钱");
}
}

现在来测试一下,首先看看如果直接访问被代理类,是否可行

public class Client {
public static void main(String[] args) { Star star = new Star("刘德华");
star.act();
star.sing(); }
}

运行结果:

请使用指定的代理访问
请使用指定的代理访问

显然访问失败了。接下来尝试一下如果通过代理类访问呢

package com.leng;

import com.leng.proxy.Actor;
import com.leng.proxy.Agent;
import com.leng.proxy.Star; /**
* @Classname Client
* @Date 2020/9/12 2:40
* @Autor lengxuezhang
*/
public class Client {
public static void main(String[] args) {
Actor star = new Star("刘德华");
Actor agent = new Agent(star);
agent.act();
agent.sing();
}
}

谈好价钱,签好合同

请使用指定的代理访问

清算演出费,和明星分钱

谈好价钱,签好合同

请使用指定的代理访问

清算演出费,和明星分钱

可以看到中间业务逻辑部分还是错误的。ok,那我就按照强制代理的规定来:

package com.leng;

import com.leng.proxy.Actor;
import com.leng.proxy.Star; /**
* @Classname Client
* @Date 2020/9/12 2:40
* @Autor lengxuezhang
*/
public class Client {
public static void main(String[] args) {
// 先定义一个明星
Actor star = new Star("刘德华");
// 由这个明星指定经纪人
Actor agent = star.getProxy();
agent.act();
agent.sing();
}
}

谈好价钱,签好合同

刘德华在演戏

清算演出费,和明星分钱

谈好价钱,签好合同

刘德华在唱歌

清算演出费,和明星分钱

运行结果显示成功。

小结:强制代理的核心就是通过被代理类指定的代理类,去访问被代理类的方法。

总结

本篇文章首先介绍了代理模式的基本概念和基本代码实现。然后进行了一些扩展,介绍了普通代理和强制代理的概念和实现。下一篇将会详细介绍动态代理的知识。

参考文献

《设计模式之禅》

Spring AOP系列(一)— 代理模式的更多相关文章

  1. Spring AOP 和 动态代理技术

    AOP 是什么东西 首先来说 AOP 并不是 Spring 框架的核心技术之一,AOP 全称 Aspect Orient Programming,即面向切面的编程.其要解决的问题就是在不改变源代码的情 ...

  2. Spring AOP系列(二) — 动态代理引言

    接上一篇Spring AOP系列(一)- 代理模式,本篇来聊聊动态代理. 动态代理与静态代理的区别 要想了解动态代理与静态代理的区别,需要有两个前置知识点:java程序是如何执行的以及类加载机制. j ...

  3. Spring AOP 系列总括

    Spring有两大核心,IOC和AOP.IOC在Java Web项目中无时无刻不在使用,然而AOP用的比较少,尤其是对一些初级程序员,在架构师搭好的框架上开发应用代码,AOP几乎是透明的.然而,项目中 ...

  4. 深入理解Spring AOP之二代理对象生成

    深入理解Spring AOP之二代理对象生成 spring代理对象 上一篇博客中讲到了Spring的一些基本概念和初步讲了实现方法,当中提到了动态代理技术,包含JDK动态代理技术和Cglib动态代理 ...

  5. 2018.12.24 Spring中的aop演示(也就是运用aop技术实现代理模式)

    Aop的最大意义是:在不改变原来代码的前提下,也不对源代码做任何协议接口要求.而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码. 1.spring中的aop演示 aop:面向方面编程.不 ...

  6. AOP 技术原理——代理模式全面总结

    前言 非常重要的一个设计模式,也很常见,很多框架都有它的影子.定义就不多说了.两点: 1.为其它对象提供一个代理服务,间接控制对这个对象的访问,联想 Spring 事务机制,在合适的方法上加个 tra ...

  7. 设计模式系列之代理模式(Proxy Pattern)——对象的间接访问

    说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...

  8. 死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  9. 循序渐进之Spring AOP(3) - 配置代理

    上一篇介绍了几种Advice(增强),并通过代码演示了生成代理的方式,下面来看通过配置文件配置方式把Advice织入目标类. 注意,配置文件方式仍然不是spring AOP的最好方式,学习配置方式也是 ...

随机推荐

  1. python练习 数字不同数之和+人名最多数统计

    数字不同数之和 描述 获得用户输入的一个整数N,输出N中所出现不同数字的和.‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬ ...

  2. [PyTorch 学习笔记] 4.2 损失函数

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py https:// ...

  3. 删除MBR分区如何使用光盘恢复

    1.备份MBR分区表 dd  if=/dev/sda  of=/data/mbr.bak  bs=1  count=64  skip=446 分区表前512字节分为三部分,第一部分446字节与启动相关 ...

  4. 【web系统UI自动化】关于UI自动化的总结

    实施过了web系统的UI自动化,回顾梳理下,想到什么写什么,随时补充. 首先,自动化测试不是手动测试的替代品,是比较好的补充,而且不是占大比重的补充. 70%的测试工作集中在底层接口测试和单元测试,2 ...

  5. 外包公司派遣到网易,上班地点网易大厦,转正后工资8k-10k,13薪,包三餐,值得去吗?

    作为一个人,我们必须时时刻刻清醒地看待自己,做到不卑不亢才能坚强地活下去. 请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/i ...

  6. i春秋公益赛之signin

    题目链接:https://buuoj.cn/challenges#gyctf_2020_signin 查看程序保护 只开了canary和NX保护,在IDA查看反编译出来的为代码时发现程序给了一个后门 ...

  7. Golang多线程简单斗地主

    多线程,通道,读写锁(单写多读),随机(洗牌),是本文涉及的主要知识点. 先看一下做出来的效果,因为是实验程序,跟真实的斗地主还是有差距,理解万岁! [发牌员]:洗牌咯. 刷刷刷... [发牌员]:牌 ...

  8. Java I/O流 复制文件速度对比

    Java I/O流 复制文件速度对比 首先来说明如何使用Java的IO流实现文件的复制: 第一步肯定是要获取文件 这里使用字节流,一会我们会对视频进行复制(视频为非文本文件,故使用之) FileInp ...

  9. shell数组的用法

    在shell里面想获取某个变量的值,使用$符开头,如:$a或者${a}即可. 获取数组长度 arr_length=${#arr_number[*]}或${#arr_number[@]}均可,即形式:$ ...

  10. 漏洞扫描工具acunetix破解安装步骤

    Acunetix 12破解版安装教程 下载地址: 链接:https://pan.baidu.com/s/1jsKkrhOcx_O7ib7FQ6pidw 提取码:pwdj 1.下载软件压缩包文件,首先点 ...