设计模式#1 从代理模式到Spring AOP

Posted 代码荣耀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式#1 从代理模式到Spring AOP相关的知识,希望对你有一定的参考价值。

点击上方蓝色小字,关注后端技术
碎片时间x体系学习
这是第148原创;距2019年还有147
交流微信:friendfb

00


Case:从一个例子讲起

 

 

社会进步来源于社会分工;所谓社会分工就是让专业人士执行专业事情。最近十年,房地产行业是如此的“繁荣”,也造就了一个有“钱途”的行业——房产中介。面对“芝麻开花节节高”的房价,程序员们省吃俭用,终于可以在奋斗的城市买房了,但是一个个具体的问题横亘在自己的面前:


房源在哪里?

首付不够,银行来凑。但是如何落实与银行贷款手续了?

购房的具体流程有哪些?

需要准备哪些资料?


面对以上林林总总的问题,程序员们心里打起了退堂鼓。一个人孤独的走在大街上,默然回头,看见了X家地产。这下,一切问题都变得简单了,这些自己不擅长或者不能做的事情,原来都可以让“中介”机构帮助我们解决了;我们不再需要与房东、银行、房管局打交道,只需要委托“中介”,就可以搞定一切。


类似这样的“中介”模式,在我们的日常生活中,衣食住行,无所不在。为我们的生活带来了极大的便利。在计算机的设计世界,我们为这样的模式取了一个专业名字——代理模式。


01


What:模式的定义



代理模式(Proxy)

为其他对象提供一种代理以控制对这个对象的访问;是一种对象结构型模式;也常被称为“委托模式”,是一种基本的设计技巧。


模式解读

  • 一个原对象对应一个代理;

  • 其他对象不会直接访问原对象,而是访问代理对象;

  • 代理对象需要访问原对象,但是原对象无法感知代理对象。


组成结构

  • Subject抽象主题类:该类可以是一个抽象类或接口,是一个普通的业务类型定义。

  • RealSubject具体主题类:被委托的、被代理的对象,是业务逻辑的具体执行者。

  • Proxy代理主题类:也称为委托类、代理类。负者对具体类的应用,把抽象主题类中定义的所有方法委托给具体主题类实现,并且在具体主题类处理完毕前、后做预处理和善后处理工作。

  • IOtherProxy类:该类非必需;主要是考虑对具体主题类的功能进行增强。



02


Why:模式的动机



当一个客户端对象不愿意或者不能直接引用一个目标对象时,此时我们可以通过一个代理对象实现间接引用。代理对象可以在客户端对象与目标对象之间起到中介作用;一方面,代理对象可以去掉不想让客户端看到某些目标对象的功能;另一方面,代理对象可以对目标对象功能进行增强。


简言之,我们可以通过引入一个新对象(即代理对象)来间接访问一个对象的部分或全部功能,同时还可对原有特定功能进行增强。


03


How:基本代理模式的实现


1、抽象主题类

package com.designpattern.proxy;

//抽象主题类
public interface Subject {
   public void request();
}


2、具体主题类

package com.designpattern.proxy;

//真实主题类
public class RealSubject implements Subject {
   
public void request() {
       System.out.println("Do real business!");
   
}
}


3、代理类

package com.designpattern.proxy;

//代理类
public class Proxy extends Subject {
   private Subject realSubject;
   private long
start;
   private long
end;


   public
Proxy(){
       //经典的代理模式是在构造函数中实例化具体主题类
       //realSubject = new RealSubject();
   
}

   @Override
   
public void request() {
       //进行功能增强1
       
this.doBefore();

       
//本例通过"虚拟代理"实现了延迟加载
       //即:只有真正执行操作时,才实例化对象
       
if(null == realSubject){
           realSubject = new RealSubject();
       
}
       //执行真实对象的操作
       
realSubject.request();

       
//进行功能增强2
       
this.doAfter();
   
}

   //记录执行起始时间
   
private void doBefore(){
       start = System.nanoTime();
       
System.out.println("Do something before!");

   
}

   //记录执行结束时间
   
private void doAfter(){
       end = System.nanoTime();
       
System.out.println("Do something after!");
   
}

   //获取计算耗时
   
public long getCost(){
       return end - start;
   
}

}

4、客户端测试类

package com.designpattern.proxy;

//测试类
public class Client {
   public static void main(String[] args){
       //客户端对象只需知道代理类,无需知道具体主题类
       
Subject proxy = new Proxy();
       
proxy.request();
       long
costTime = ((Proxy) proxy).getCost();
       
System.out.println("Cost time : " + costTime + " ns");
   
}
}

程序运行结果如下:

Do something before!

Do real business!

Do something after!

Cost time : 738389 ns

从上例我们可以看出:可以做到在不修改目标对象的功能前提下,对目标功能扩展。符合“开闭原则”——面向修改关闭,面向扩展开放。


以上我们的代码实现中,代理的对象是明确的,这种模式被称为静态代理。静态代理很简单但功能却有限。当我们要代理一个目标类时,就需要开发一个代理类;要代理多个目标类时,就要开发与之对应的多个代理类,这必然会造成“类膨胀”。此外,如果我们代理类对目标的预处理(before)与后处理(after)的功能增强是类似的话,那么同时会带来更多的重复代码。


面对如此场景,这时我们可以定义一个代理类,它能代理所有实现类的方法调用——根据传入具体业务类的类名及方法名实现具体的调用。这就是更复杂的但是更实用的一种代理模式——动态代理。


04


JDK动态代理


所谓动态代理,是指在运行过程中动态生成代理类。即:代理类的字节码将在运行时生成,并载入当前代理的 ClassLoader中。


与静态处理类相比,动态类有诸多好处。首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;其次,使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。



1、抽象主题类

package com.designpattern.dyproxy;

//抽象主题类
public interface Subject {
   public void request();
}


2、具体主题类

package com.designpattern.dyproxy;

//真实主题类
public class RealSubject implements Subject {
   public void request() {
       System.out.println("Do real business!");
   
}
}


3、通知接口

package com.designpattern.dyproxy;

public interface
IAdvice {
   public void exec();
}


4、具体通知类

package com.designpattern.dyproxy;

public class
BeforeAdvice implements IAdvice {
   @Override
   
public void exec() {
       System.out.println("Before Advice is Executed!");
   
}
}


5、动态代理的Handler类

package com.designpattern.dyproxy;

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

//动态代理的Handler类
public class MyInvocationHandler implements InvocationHandler {
   //被代理的对象
   
private Object target = null;

   public
MyInvocationHandler(Object obj){
       this.target = obj;
   
}

   @Override
   
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       return method.invoke(this.target,args);
   
}
}


6、动态代理类

package com.designpattern.dyproxy;

import
java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Proxy;

public class
DynamicProxy {
   public static <T> T newProxyInstance(Subject subject){

       //寻找AOP框架中定义的JoinPoint连接点
       
boolean isBeforeAdviceDefined = true;
       if
(isBeforeAdviceDefined){
           (new BeforeAdvice()).exec();
       
}

       //定义一个Handler
       
InvocationHandler handler = new MyInvocationHandler(subject);

       
//定义主题的代理
       
return (T)Proxy.newProxyInstance(subject.getClass().getClassLoader(),
               
subject.getClass().getInterfaces(),handler);

   
}
}


7、客户端测试类

package com.designpattern.dyproxy;

public class
Client {
   public static void main(String[] args){
       //定义一个主题
       
Subject subject = new RealSubject();

       
//定义一个代理
       
Subject proxy =DynamicProxy.newProxyInstance(subject);

       
//执行代理的行为
       
proxy.request();
   
}
}

程序运行结果如下:

Before Advice is Executed!

Do real business!


注意:上述我们利用JDK动态代理是一个通用的代理的框架。如果您要设计自己的AOP框架,完全可以上述框架为基础进行扩展;但是,要实现JDK动态代理的首先条件是:被代理类必须实现一个接口。


这是因为:JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。以下,我们将介绍另外一种实现动态代理的技术,该技术可以实现不需要接口也可以实现动态代理。


05


CGlib动态代理



1、具体主题类

package com.designpattern.cglibproxy;


//真实主题类
public class RealSubject {
   public void request() {
       System.out.println("Do real business!");
   
}
}


2、代理类

package com.designpattern.cglibproxy;

import
java.lang.reflect.Method;

import
net.sf.cglib.proxy.Enhancer;
import
net.sf.cglib.proxy.MethodInterceptor;
import
net.sf.cglib.proxy.MethodProxy;

//创建代理类
public class SubjectCglib implements MethodInterceptor {
   //业务类对象,供代理方法中进行真正的业务方法调用
   
private Object target;


   
//相当于JDK动态代理中的绑定
   
public Object getInstance(Object target) {
       //给业务对象赋值
       
this.target = target;

       
//创建加强器,用来创建动态代理类
       
Enhancer enhancer = new Enhancer();

       
//为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
       
enhancer.setSuperclass(this.target.getClass());

       
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
       
enhancer.setCallback(this);

       
// 创建动态代理类对象并返回
       
return enhancer.create();
   
}

   // 实现回调方法
   
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       doBefore();
       
//调用业务类(父类中)的方法
       
proxy.invokeSuper(obj, args);
       
doAfter();
       return null;
   
}

   private void doBefore(){
       System.out.println("Do something before!");

   
}

   private void doAfter(){
       System.out.println("Do something after!");
   
}

}


3、客户端测试类

package com.designpattern.cglibproxy;

public class
Client {
   public static void main(String[] args){
       SubjectCglib  cglib=new SubjectCglib();
       
RealSubject subject=(RealSubject)cglib.getInstance(new RealSubject());
       
subject.request();
   
}
}



06


几种代理模式的实现比较



静态代理是通过在代码中显式定义一个业务实现类和一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法。


JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法。


CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理。


07


When:应用场景


根据代理模式的使用目的,常见的代理模式有以下几种类型:

  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。

  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。


08


小结

  


综上,本文对代理模式的定义、特性、多种实现及应用场景进行了讨论,以下对代理模式的优缺点进行总结:


代理模式的优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。

  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。

  • 保护代理可以控制对真实对象的使用权限。


代理模式的缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。



One More Thing


推荐1:

上文1:

上文2: 


以上是关于设计模式#1 从代理模式到Spring AOP的主要内容,如果未能解决你的问题,请参考以下文章

从代理机制到Spring AOP,这篇给你安排的明明白白的

Spring的Aop是采用的啥设计模式?

代理模式 - spring aop 抛砖

代理模式 - spring aop 抛砖

Spring代理模式,AOP

spring-aop