面试问烂的Spring AOP,再搞不懂没脸回家过年了……

开发 后端
AOP可以通过代理模式来实现。下面介绍几种常见的代理模式。

面向切面的编程 AOP 是面向对象编程 OOP 的一个补充,它将编程中通用的关注点(如日志记录、安全检查、分布式事务和懒加载等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性。

AOP可以通过代理模式来实现。下面介绍几种常见的代理模式👇

代理模式

静态代理

静态代理指的是在编译期就对目标对象的方法进行增强。例如我们程序中有如下接口:

public class Order {

    interface OrderService{
        /**
         * 购买商品
         * @param goods
         */
        void payForGoods(String goods);
    }

    static class OrderServiceImpl implements OrderService{
        
        @Override
        public void payForGoods(String goods) {
            System.out.println("下单 " + goods + " 商品成功!");
        }
    }

    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        orderService.payForGoods("牛奶");
    }
    
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

如果想在购买商品时获取当前时间,可以通过静态代理增添代理类对 payForGoods 方法进行增强:

public class Order {

    interface OrderService{
        /**
         * 购买商品
         * @param goods
         */
        void payForGoods(String goods);
    }

    static class OrderServiceImpl implements OrderService{

        @Override
        public void payForGoods(String goods) {
            System.out.println("下单 " + goods + " 商品成功!");
        }
    }

    static class OrderProxy implements OrderService {
        

        private OrderService orderService;

        public OrderProxy(OrderService orderService) {
            this.orderService = orderService;
        }

        @Override
        public void payForGoods(String goods) {
            System.out.println("当前时间:" + LocalDateTime.now());
            orderService.payForGoods(goods);
        }
    }

    public static void main(String[] args) {
        OrderService orderProxy = new OrderProxy(new OrderServiceImpl());
        orderProxy.payForGoods("牛奶");
    }

}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

JDK动态代理

JDK动态代理主要涉及到 java.lang.reflect 包中的两个类 Proxy 和 InvocationHandler。

InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过 反射机制 调用目标类的代码,动态将横切逻辑和业务逻辑放在一起。

Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。实现代码如下:

public class Order {

    interface OrderService{
        /**
         * 购买商品
         * @param goods
         */
        void payForGoods(String goods);
    }

    static class OrderServiceImpl implements OrderService{

        @Override
        public void payForGoods(String goods) {
            System.out.println("下单 " + goods + " 商品成功!");
        }
    }

    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        OrderService orderProxy = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader(),
                OrderServiceImpl.class.getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("当前时间:" + LocalDateTime.now());
                        Object result = method.invoke(orderService, args);
                        return result;
                    }
                });
        orderProxy.payForGoods("牛奶");
    }
    
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

CGLib动态代理

CGLib 全称为 Code Generation Library,是一个强大的高性能的 代码生成类库,可以在运行期间扩展 Java 类与实现 Java 接口,CGLib 封装了ASM,可以在运行期间生成新的 class。

相对于 JDK动态代理 中只能为实现了接口的目标类创建实例,CGLib对于没有通过接口定义业务方法的类也可以创建动态代理。

public class Order {

    static class OrderServiceImpl{

        public void payForGoods(String goods) {
            System.out.println("下单 " + goods + " 商品成功!");
        }
    }

    public static void main(String[] args) {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceImpl orderProxy = (OrderServiceImpl) Enhancer.create(orderService.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("当前时间:" + LocalDateTime.now());
                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        orderProxy.payForGoods("牛奶");
    }

}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

上面三种代理方式对比如下:

Spring AOP

Spring中的AOP功能为我们提供更加简单便捷的代理实现方式:

@Aspect
@Component
public class OrderAspect {
    
    @Before("execution(public com.project.springaop.test.Order.payForGoods(..))")
    public void getLocalDateTimeBefore(){
        System.out.println("当前时间:" + LocalDateTime.now());
    }

    @After("execution(public com.project.springaop.test.Order.payForGoods(..))")
    public void getLocalDateTimeAfter(){
        System.out.println("当前时间:" + LocalDateTime.now());
    }
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

相关术语

源码分析

Spring提供了 JDK 和 CGLib 两种方式来生成代理对象,具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。

默认的策略是,如果目标类实现了接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

在 Spring Bean的生命周期中,代理对象的创建发生在初始化方法initializeBean后期的后置处理器中。

即BeanPostProcessor.postProcessAfterInitialization 方法中。

相关源码👇

Advice注解

@Before 前置通知

前置通知会在目标方法执行前执行

@Before("execution(public com.project.springaop.test.Order.payForGoods(..))")
public void before(JoinPoint joinPoint){
    System.out.println("执行前置通知,目标方法:" + joinPoint.getSignature().getName() + ",方法参数:" + joinPoint.getArgs());
}


  • 1.
  • 2.
  • 3.
  • 4.

@After 后置通知

后置通知会在目标方法执行后执行,此通知无论如何都会执行,即使目标方法执行出现了异常。

@After("execution(public com.project.springaop.test.Order.payForGoods(..))")
public void after(JoinPoint joinPoint){
    System.out.println("执行后置通知,目标方法:" + joinPoint.getSignature().getName() + ",方法参数:" + joinPoint.getArgs());
}


  • 1.
  • 2.
  • 3.
  • 4.

@AfterReturning 返回通知

返回通知会在目标方法成功执行后执行,如果目标方法出现了异常,则该通知不执行。

@AfterReturning(value = "execution(public com.project.springaop.test.Order.payForGoods(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result){
    System.out.println("执行返回通知,目标方法:" + joinPoint.getSignature().getName() + ",方法参数:" + joinPoint.getArgs() + ",执行结果" + result);
}


  • 1.
  • 2.
  • 3.
  • 4.

@AfterThrowing 异常通知

异常通知会在目标方法出现异常后执行,如果目标方法未出现异常则不执行。

@AfterThrowing(value = "execution(public com.project.springaop.test.Order.payForGoods(..))", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
    System.out.println("执行异常通知,目标方法:" + joinPoint.getSignature().getName() + ",方法参数:" + joinPoint.getArgs() + ",执行异常" + e);
}


  • 1.
  • 2.
  • 3.
  • 4.

@Around 环绕通知

包围一个连接点的通知,可以完成其他四种通知的所有功能。

@Around(value = "execution(public com.project.springaop.test.Order.payForGoods(..))")
public Object around(ProceedingJoinPoint joinPoint, Exception e){
    Object result = null;
    try{
        System.out.println("执行前置通知");
        result = joinPoint.proceed();
        System.out.println("执行返回通知");
    } catch (Throwable throwable) {
        System.out.println("执行异常通知");
    } finally {
        System.out.println("执行后置通知");
    }
    return result;
}


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

一个方法被 Aspect类拦截时,执行顺序如下:

@Around > @Before > 原方法 > @Around > @After > @AfterReturing / @AfterThrowing

多个 Aspect 的执行顺序可以通过 @Order 注解或者实现 Ordered 接口来控制

本文转载自微信公众号「贰予三四」,可以通过以下二维码关注。转载本文请联系贰予三四公众号。

责任编辑:武晓燕 来源: 贰予三四
相关推荐

2018-10-25 16:20:23

JavaSpring AOPSpringMVC

2022-05-20 16:50:33

区块链Web3加密资产

2019-12-24 09:44:02

界面12306系统

2023-04-03 07:23:06

Java线程通信

2013-01-31 10:02:25

产品经理火车

2019-04-26 14:12:19

MySQL数据库隔离级别

2019-02-03 10:33:56

2020-10-09 09:49:18

HTTPS网络 HTTP

2020-02-27 21:24:31

JavaAIOBIO

2021-02-21 10:28:34

C语言Python系统

2018-02-08 15:30:19

2013-01-22 09:31:20

猎豹浏览器

2020-03-27 16:27:03

Redis数据库

2019-02-12 15:24:50

C语言JavaPython

2024-07-01 00:00:02

2021-08-30 16:00:00

HarmonyOS原子化服务

2022-01-28 07:58:41

WPS数据整理

2023-11-27 08:17:05

SpringJava

2020-12-18 08:55:20

Python火车票代码

2023-07-27 08:59:19

线程同步Python
点赞
收藏

51CTO技术栈公众号