Spring由繁入简——Aop的基础:Spring动态代理


先简单举个例子,再进行概念介绍。

举个栗子

简单场景

首先定义一个用户登录的简单业务场景。

定义一个接口,和对应的实现类。

public interface UserService {
    void login();
}


public class UserServiceImpl implements UserService {
    @Override
    public void login() {
        System.out.println("登录");
    }
}

假如我们要在里面进行日志打印工作,你可能会想到直接在login()方法中进行操作,但是这种情况下会不符合设计模式的开闭原则,简单来说就是应该对于扩展是开放的,但是对于修改是封闭的,在不改变它的源代码的前提下变更它的行为

工作说明

日志打印并不属于相关的登录业务操作,直接在login()方法中进行日志打印工作不利于后续代码的维护。

创建代理

这种情况下我们通过动态创建代理对象可以解决,具体操作如下:

public class ProxyInvocationHandler implements InvocationHandler {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理开始执行");
        Object result = method.invoke(userService, args);
        System.out.println("代理执行结束");
        return result;
    }
}
  • 作用:实现了 InvocationHandler 接口,用于动态地创建代理对象,并在方法调用前后添加额外的逻辑。

  • 代码详解

    1. invoke()

    主要作用就是出在invoke()方法上。这是 InvocationHandler 接口的核心方法,当代理对象的方法被调用时,会调用此方法。参数解释如下:

    • proxy:代理对象本身。
    • method:被调用的方法对象。
    • args:被调用方法的参数数组。

    然后通过 method.invoke(rent, args) 调用目标对象的对应方法。

    2. Proxy.newProxyInstance()

    是 Java 动态代理中的一个核心方法,用于创建动态代理对象。它允许你在运行时为一个或多个接口动态地创建实现类的代理实例,并在调用这些代理实例的方法时,通过自定义的调用处理器(InvocationHandler)来拦截和处理方法调用。

    • ClassLoader loader:指定定义代理类的类加载器。类加载器用于加载代理类及其依赖的类。
    • Class<?>[] interfaces:指定代理类要实现的接口列表。代理对象必须实现这些接口。
    • InvocationHandler h:指定调用处理器,用于在代理对象的方法被调用时,自定义方法的执行逻辑。

测试

假如在之前情况下,添加日志相关操作,要么像上文提到的在业务代码中添加;要么在具体的调用方法中添加。不管什么方式都会影响原本的代码内容。

那么通过代理方式的话,具体的日志相关操作可以完全交给代理类完成,不论日志操作有哪些内容,我的主要代码都只用创建代理对象就可以,当日志操作修改的时候,我也不需要修改我的业务代码,只需要在代理类中进行相关业务操作。

public class Client {
    public static void main(String[] args) {
        UserService userService =new UserService();
        userService.login();
    }
}

=>

public class Client {
    public static void main(String[] args) {

        // 创建 ProxyInvocationHandler 实例
        ProxyInvocationHandler handler = new ProxyInvocationHandler();
        // 指定目标对象
        handler.setUserService(new UserServiceImpl());
        // 获取代理对象
        UserService userService = (UserService) handler.getProxy();
        userService.login();
    }
}

也可以再次对ProxyInvocationHandler进行简化,将set和get集成到一个方法中

public class ProxyInvocationHandler2 {
    public static UserService getProxy(UserService userService) {
        return (UserService) Proxy.newProxyInstance(
                ProxyInvocationHandler.class.getClassLoader(),
                new Class[]{UserService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理开始执行");
                        Object result = method.invoke(userService, args);
                        System.out.println("代理执行结束");
                        return result;
                    }
                });
    }
}

在使用的时候只需要,基本和之前new对象操作方式一样简单。并且可以完全将不相关的非业务代码移交给代理对象处理器来完成

public class Client {
    public static void main(String[] args) {
        UserService userService = ProxyInvocationHandler2.getProxy(new UserServiceImpl());
        userService.login();
    }
}

spring动态代理介绍

原理

上面的情况就是Spring 动态代理,相信用过spring的朋友们会发现,这个作用就和面向切面编程(aop)原理很类似。其实spring动态代理就是 Spring AOP 的核心实现机制之一,它允许在运行时动态地为接口或类创建代理对象,从而在目标方法执行前后添加额外的行为,如事务管理、日志记录、权限验证等。

以下是关于 Spring 动态代理的详细介绍:

spring动态代理有两种方式:

  • 基于接口的动态代理(JDK 动态代理) :这是上面案例中使用的方法,也是最常用的动态代理技术之一。当目标类实现了一个或多个接口时,JDK 的 Proxy 类可以为这些接口动态地创建一个代理类。这个代理类实现了与目标类相同的接口,并在方法调用时通过 InvocationHandler 来拦截方法调用,并执行自定义的逻辑。
    • Proxy 类和 InvocationHandler 接口:JDK 的 Proxy 类用于生成动态代理对象,其 newProxyInstance() 方法需要三个参数:类加载器、目标对象实现的接口列表以及一个调用处理器对象(实现了 InvocationHandler 接口)。
    • InvocationHandler 的 invoke 方法:当代理对象的方法被调用时,会自动调用 InvocationHandler 对象的 invoke() 方法。这个方法会接收三个参数:代理对象自身、被调用的方法对象、方法参数数组。
    • 创建代理对象和方法调用拦截:在 Spring 中,通过 ProxyFactory 创建代理对象,并通过 ProxyProcessor 处理代理逻辑。当调用代理对象的方法时,会首先调用 ProxyInvocationHandlerinvoke() 方法,在其中可以添加自定义的逻辑,如事务管理、日志记录等,然后再调用目标对象的相关方法。
  • 基于类的动态代理(CGLIB 动态代理) :如果目标类没有实现任何接口,Spring 会使用 CGLIB 库来创建动态代理。CGLIB 通过生成目标类的子类来实现动态代理功能。它会在内存中动态地生成一个新的类,该类继承了目标类,并重写了目标类的方法,在方法调用前后插入自定义的逻辑

应用场景

  • 事务管理 :在企业级应用中,事务管理是必不可少的。Spring 动态代理可以在方法执行前后自动开始和提交事务,从而确保数据的一致性和完整性。
  • 日志记录 :通过动态代理,可以在方法执行前后添加日志记录功能,记录方法的执行时间、参数、返回值等信息,方便进行问题排查和性能监控。
  • 权限验证 :在方法执行前进行权限检查,确保只有具有相应权限的用户才能调用特定的方法。
  • 性能监控 :在方法执行前后添加性能监控代码,测量方法的执行时间,帮助开发人员优化应用的性能。

优点

  • 低侵入性 :可以在不修改原有业务代码的情况下,通过动态代理的方式添加额外的功能,实现了业务逻辑与横切关注点的分离。
  • 可维护性强 :将通用的逻辑集中在一个地方,便于统一管理和维护,减少了重复代码。
  • 灵活性高 :可以在运行时动态地决定是否应用代理逻辑,以及应用哪些代理逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值