Spring AOP和代理模式

目录

 

一、什么是AOP?

二、什么是代理模式

2.1 静态代理

2.2 动态代理

2.2.1 JDK动态代理

2.2.2 CGLIB动态代理

三、Spring AOP采用哪种代理


一、什么是AOP?

一般面试官问到这个问题,面试者基本上都会回答:AOP 就是面向切面编程。其实这真的是句废话,这么回答真的没有任何意义。

或许你可以给面试官举个例子:歌星都有好多助理,歌星最重要的一件事就是唱歌,其他事他不用关注,比如唱歌前可能需要和其他人谈合作,还要布置场地,唱歌后还要收钱等等,这些统统交给他对应的助理去做。也许哪一天,这个歌星做慈善,免费唱歌了,不收钱了,那么就可以把收钱这个助力给辞退了。这就是 AOP,每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。AOP 的实现原理就是代理模式。

在程序中也是如此,通过代理,可以详细控制访问某个或者某类对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。

二、什么是代理模式

它的设计思路是:定义一个抽象角色,让代理角色真实角色分别去实现它

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。它只关注真正的业务逻辑,比如歌星唱歌。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作,比如谈合同,布置场地,收钱等等。

代理模式分为静态代理和动态代理。静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理,我们就不用管了。下面我详细介绍一下这两种代理模式。

2.1 静态代理

就举明星唱歌这个例子,根据上面提供的设计思路,首先我们需要创建明星这个抽象角色:

/**
* 明星接口类
*/
public interface Star {
   /**
    * 唱歌方法
    */
   void sing();
}

静态代理需要创建真实角色和代理角色,分别实现唱歌这个接口,真实角色很简单,直接实现即可,因为真实角色的主要任务就是唱歌。

/**
* 真实明星类
*/
public class RealStar implements Star {

   @Override
   public void sing() {
       System.out.println("明星本人开始唱歌……");
   }
}

代理类就需要做点工作了,我们思考一下,代理只是在明星唱歌前后做一些准备和收尾的事,唱歌这件事还得明星亲自上阵,代理做不了。所以代理类里面是肯定要将真实的对象传进来。有了思路,我们将代理类写出来。

/**
* 明星的静态代理类
*/
public class ProxyStar implements Star {

   /**
    * 接收真实的明星对象
    */
   private Star star;

   /**
    * 通过构造方法传进来真实的明星对象
    */
   public ProxyStar(Star star) {
       this.star = star;
   }

   @Override
   public void sing() {
       System.out.println("代理先进行谈判……");
       // 唱歌只能明星自己唱
       this.star.sing();
       System.out.println("演出完代理去收钱……");
   }
}

这样的话,逻辑就非常清晰了。在代理类中,可以看到,维护了一个Star对象,通过构造方法传进来一个真实的Star对象给其赋值,然后在唱歌这个方法里,使用真实对象来唱歌。所以说面谈、收钱都是由代理对象来实现的,唱歌是代理对象让真实对象来做。下面写个客户端测试下。

/**
* 测试客户端
*/
public class Client {

   /**
    * 测试静态代理结果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       Star proxy = new ProxyStar(realStar);

       proxy.sing();
   }
}

静态代理的缺点:

1.  静态代理是 无论是真实角色(歌星RealStar) 还是 代理角色(ProxyStar) 都需要实现 同一个抽象接口(Star), 这就存在一个问题就是 如果 抽象接口(Star)在以后的需求中,多加了几个方法,那么不仅仅是 真实角色(歌星RealStar) 要实现新加的方法,连所有代理角色(ProxyStar) 也要额外实现新加的方法。这回造成代码冗余,例如 如果明星不止 sing()方法,还新加了个dance()方法,那么 代理角色(ProxyStar) 中也要新加入 dance()方法。这会造成代码冗余。

2. 静态代理的 代理角色(ProxyStar) 只能代理一种类型的对象(如继承Star接口的对象)。如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

 

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

2.2 动态代理

静态代理比较简单。动态代理比静态代理使用的更广泛,动态代理在本质上,代理类不用我们来管,我们完全交给工具去生成代理类即可。动态代理一般有两种方式:JDK 动态代理和 CGLIB 动态代理。

2.2.1 JDK动态代理

动态代理类如下:

/**
* 动态代理处理类
*/
public class JdkProxyHandler {

   /**
    * 用来接收真实明星对象
    */
   private Object realStar;

   /**
    * 通过构造方法传进来真实的明星对象
    */
   public JdkProxyHandler(Star star) {
       super();
       this.realStar = star;
   }
   /**
    * 给真实对象生成一个代理对象实例
    */
   public Object getProxyInstance() {
       return Proxy.newProxyInstance(realStar.getClass().getClassLoader(),
               realStar.getClass().getInterfaces(), (proxy, method, args) -> {

                   System.out.println("代理先进行谈判……");
                   // 唱歌需要明星自己来唱
                   Object object = method.invoke(realStar, args);
                   System.out.println("演出完代理去收钱……");

                   return object;
               });
   }
}

上面的代码其实就指定了,调用 方法前,要打印“代理先进行谈判”,调用方法后,打印“演出完代理去收钱”的功能。

这里主要说一下 Proxy.newProxyInstance()方法,该方法接收3个参数:

第一个参数指定当前 真实角色(如歌星RealStar)的类加载器。

第二个参数指定当前 真实角色(如歌星RealStar)实现的接口类型。

第三个参数指定 动态处理器,网上针对第三个参数的写法都是 new 一个匿名类来处理,我这直接用的 Java8 里面的 lamda 表达式来写的,都一样。

最后还要说一下, method.invoke() 这个方法,这个方法有两个输入参数,第一个是  真实角色(如歌星RealStar) ,第二个是 调用哪一个方法,即调用方法的方法名。但是如上面代码写的就可以了,代码会自动处理的。

测试代码:

/**
* 测试客户端
*/
public class Client {

   /**
    * 测试JDK动态代理结果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       // 创建一个代理对象实例
       Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();

       proxy.sing();//可以直接调用方法
   }
}

我们对 JDK 动态代理做一个简单的总结:相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。

但是 JDK 动态代理有个缺憾,或者说特点:JDK 实现动态代理需要实现  类通过接口定义的业务方法。也就是说它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计就注定了这个遗憾。
 

2.2.2 CGLIB动态代理

由上面的分析可知,JDK 实现动态代理需要实现  类通过接口定义的业务方法,那对于没有接口的类,如何实现动态代理呢,这就需要 CGLIB 了。

CGLIB 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。我们来写一个 CBLIB 代理类。

/**
* cglib代理处理类
*/
public class CglibProxyHandler implements MethodInterceptor {

   /**
    * 维护目标对象
    */
   private Object target;

   public Object getProxyInstance(final Object target) {
       this.target = target;
       // Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
       Enhancer enhancer = new Enhancer();
       // 将被代理的对象设置成父类
       enhancer.setSuperclass(this.target.getClass());
       // 回调方法,设置拦截器
       enhancer.setCallback(this);
       // 动态创建一个代理类
       return enhancer.create();
   }

   @Override
   public Object intercept(Object object, Method method, Object[] args,
           MethodProxy methodProxy) throws Throwable {

       System.out.println("代理先进行谈判……");
       // 唱歌需要明星自己来唱
       Object result = methodProxy.invokeSuper(object, args);
       System.out.println("演出完代理去收钱……");
       return result;
   }
}

使用 CGLIB 需要实现 MethodInterceptor 接口,并重写intercept 方法,在该方法中对原始要执行的方法前后做增强处理。该类的代理对象可以使用代码中的字节码增强器来获取。接下来写个客户端测试程序。

/**
* 测试客户端
*/
public class Client {

   /**
    * 测试Cglib动态代理结果
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);

       proxy.sing();
   }
}

我们也对 CGLIB 动态代理做一下总结:

CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多

 

三、Spring AOP采用哪种代理

JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础

 Spring AOP 中的代理使用逻辑了:如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值