转载

你对Spring AOP了解有多深?

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

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

1、Spring AOP是什么东西?

这个概念性的问题应聘者都能答出来(答不出来还有脸混java圈么~)差异无非体现在表达能力。表达不好也没啥关系,因为Java圈创造的名词概念实在太多了,只要能表达出主要应用场景即可。当然,如果应聘者能进一步表明自己在工作中用AOP实现过一个什么功能,那就更好了。

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

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

有关AOP的用法,不是本文的关注点,不过大家可以在网上很容易找到,有兴趣的童鞋可以找来看看。

2、Sping AOP是怎么实现的?

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

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

什么是动态?就是在程序运行时生成的,而不是编译时。编译时就生成的称为静态代理。很多人可能会把“静态代理”单纯理解为:需要为每一个目标类手动编写一个代理类。其实不太对,AspectJ框架其实也可以实现AOP的事情,它与Spring AOP不同之处是:AspectJ框架可以在编译时就生成目标类的“代理类”,在这里加了个冒号,是因为实际上它并没有生成一个新的类,而是把代理逻辑直接编译到目标类里面了(具体介绍大家可以看看文末贴的参考文章)。

什么是代理?就是代理模式中的代理,不懂的童鞋可以自己查一下代理模式。

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

3、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是否会生效?也就是说是否会检查缓存?

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

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一下,相信能找到答案。

4、既然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…

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

你对Spring AOP了解有多深?

原文  https://juejin.im/post/5d217430e51d4556da53d132
正文到此结束
Loading...