你对Spring AOP了解有多深?

网上写Spring AOP的文章不少,写得好的也很多。为何我还要写?由于我蜜汁自信我能写得更易懂,更有深度。

做为技术面试官,每当看到应聘者简历写着“熟悉/精通Spring”的时候,我都会按照下面的套路来看看应聘者的掌握程度。html

一、Spring AOP是什么东西?

这个概念性的问题应聘者都能答出来(答不出来还有脸混java圈么~)差别无非体如今表达能力。表达很差也没啥关系,由于Java圈创造的名词概念实在太多了,只要能表达出主要应用场景便可。固然,若是应聘者能进一步代表本身在工做中用AOP实现过一个什么功能,那就更好了。

官方一点的说法:AOP(Aspect Oriented Programming),面向切面编程,普遍应用于处理一些具备横切性质的系统级服务,如事务管理、缓存、权限校验、日志记录等等。java

通俗点的说法:AOP,字面解释就是面向切面编程,它可以在不侵入业务代码的状况下(也就是说不修改任何业务类的代码),在指定的业务类的方法前或者方法后或者方法抛出异常时,执行一些通用逻辑。
git

有关AOP的用法,不是本文的关注点,不过你们能够在网上很容易找到,有兴趣的童鞋能够找来看看。github

二、Sping AOP是怎么实现的?

这个问题除了小部分应聘者彻底不了解外,大部分人都能说到是经过动态代理来实现的,而后可以进一步说出动态代理有两种实现方式:JDK Proxy和CGLib 而且能说清二者的区别的大概有三分之一

Spring AOP是经过动态代理来实现的,咱们经过咬文嚼字来解释一下:动态代理=动态+代理。面试

什么是动态?就是在程序运行时生成的,而不是编译时。编译时就生成的称为静态代理。不少人可能会把“静态代理”单纯理解为:须要为每个目标类手动编写一个代理类。其实不太对,AspectJ框架其实也能够实现AOP的事情,它与Spring AOP不一样之处是:AspectJ框架能够在编译时就生成目标类的“代理类”,在这里加了个冒号,是由于实际上它并无生成一个新的类,而是把代理逻辑直接编译到目标类里面了(具体介绍你们能够看看文末贴的参考文章)。
什么是代理?就是代理模式中的代理,不懂的童鞋能够本身查一下代理模式。spring

Spring AOP的动态代理有两种实现方式:JDK Proxy 和CGLib. 它们主要区别:一、前者只能代理接口类,后者则没有此限制;二、前者实现被代理类实现的接口,后者经过继承被代理类来生成代理类。据网上资料说是CGLib性能更好一点,我本身没有验证。默认状况下,Spring对实现了接口的类使用JDK Proxy方式,不然的话使用CGLib。不过能够经过配置指定Spring AOP都经过CGLib来生成代理类。编程

三、Spring AOP同一个类内部嵌套调用能生效吗?

这个问题实际上是考察你对动态代理的本质理解。大部分人都答不出来,或者答出来了可是解释不清楚缘由。这个不难理解,由于要掌握上面几个问题,只须要网上找一篇文章看一遍便可。可是若是没有独立思考过或者专门研究过的话,很难一会儿想到本题的答案

为了更好的说明问题,我经过一个示例代码进行描述:设计模式

@Service
public class OrderService {
    
    @Cacheable
    public Order getOrder(Integer orderId){
        Order order=null;
        //get order from db (代码略)
        return order;
    }
    public List<Order> getOrders(List<Integer> orderIds){
        List<Order> resultList=new ArrayList<Order>(orderIds.size());
        for (Integer orderId : orderIds) {
            resultList.add(this.getOrder(orderId));
        }
        return resultList;
    } 
}复制代码

问题是:上述代码中getOrders方法在调用getOrder的时候,@Cacheable是否会生效?也就是说是否会检查缓存?缓存

答案是:不会。若是你理解动态代理生成的代理类大概是什么样子,你就能想到答案。其实,生成的代理类能够简单示意成以下样子(真实样子不是这样,会复杂不少,在这里只是为了方便理解):bash

public class OrderServiceProxy extends OrderService {
    
    private OrderService orderService;
    
    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }
    @Override
    public Order getOrder(Integer orderId) {
        Order order = getFromCache(orderId);
        //miss cache
        if (order == null) {
            order = this.orderService.getOrder(orderId);
            putIntoCache(orderId, order);
        }
        return order;
    }
    @Override
    public List<Order> getOrders(List<Integer> orderIds) {
        return this.orderService.getOrders(orderIds);
    }
    private Order getFromCache(Integer orderId) {
        //get from cache(代码略)
        return null;
    }
    private void putIntoCache(Integer key, Order value) {
        //save to cache(代码略)
    }
}复制代码

你们能够看到代理类是持有被代理类的一个实例对象的(orderService)。在代理类中,getOrders方法是直接调用被代理对象的对应方法,其先后并无增长任何代码,而getOrder方法则先检查了缓存,从缓存里面找不到才去调用被代理对象的对应方法,而后再将返回值存到缓存中。看到这里,你们应该能想清楚为何调用getOrders方法缓存不起做用了吧。

下面贴一下JDK Proxy生成的代理类(我私下将OrderService改成实现一个接口IOrderService,这样Spring就使用JDK Proxy来生成代理类了)。说明一下,这里贴出来的代码不是所有,我删掉了一些无关的方法及代码以方便阅读:

package com.sun.proxy;

import com.demo.aop.IOrderService;
import com.demo.aop.Order;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public final class $Proxy53 extends Proxy implements IOrderService, SpringProxy, Advised, DecoratingProxy {
    private static Method m1;
    private static Method m4;

    public $Proxy53(InvocationHandler var1) throws  {
        super(var1);
    }

    public final Order getOrder(Integer var1) throws  {
        try {
            return (Order)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final List getOrders(List var1) throws  {
        try {
            return (List)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m4 = Class.forName("com.demo.aop.IOrderService").getMethod("getOrder", new Class[]{Class.forName("java.lang.Integer")});
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}复制代码

能够看到这个代理类的每一个方法实现都是直接调用一个类型为InvocationHandler的invoke方法。那么InvocationHandler又是什么呢?它的源代码也很容易看懂,由于代码比较长就不贴在这里了,感兴趣的童鞋能够打开这个地址直接查阅:

github.com/spring-proj…

到此上面的问题就解释完了。若是想在Spring中让getOrders也能用到缓存,也是有“歪门邪道”的方法实现的,你们能够Google一下,相信能找到答案。

四、既然CGLib是经过继承生成代理类的,是否是CGLib自己是支持让getOrders能用到缓存的呢?

这个问题已经跟Spring无关了,我也不会问应聘者。这里只是单纯扩展一下CGLib的知识。

当初我学习Spring AOP的时候,我是有上述疑问的。试想一下,若是生成的代理类以下所示,getOrders不就能够用上缓存了么?

public class OrderServiceProxy2 extends OrderService {
    @Override
    public Order getOrder(Integer orderId) {
        Order order = getFromCache(orderId);
        //miss cache
        if (order == null) {
            order = super.getOrder(orderId);
            putIntoCache(orderId, order);
        }
        return order;
    }
    
    @Override
    public List<Order> getOrders(List<Integer> orderIds) {
        return super.getOrders(orderIds);
    }
    private Order getFromCache(Integer orderId) {
        //get from cache(代码略)
        return null;
    }
    private void putIntoCache(Integer key, Order value) {
        //save to cache(代码略)
    }
}复制代码

对比一下以前的OrderServiceProxy,上面的“代理类”是不持有被代理类的对象的。为何代理类这三字我加了双引号,由于这样实现的话,跟咱们理解的代理模式就不太匹配了,咱们学习到的代理设计模式的实现方式都是说要持有被代理类对象的。抛开这个回到问题,CGLib到底支不支持生成相似上述的“代理类”,答案是:支持。感兴趣你们能够去实践一下。

References

[1] 李刚- Spring AOP 实现原理与 CGLIB 应用:

www.ibm.com/developerwo…

喜欢的童鞋,欢迎关注公众号:字节观

相关文章
相关标签/搜索