Spring 动态代理

时间:2022-07-24
本文章向大家介绍Spring 动态代理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、静态代理

静态代理要

  1. 要实现相同的接口;
  2. 要有原始对象;
  3. 要有额外的功能。

如下就是一个静态代理的实例。

public class UserServiceProxy implements UserService {

    private UserServiceImpl userService = new UserServiceImpl();

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("UserServiceProxy.addUser");
    }

    @Override
    public void deleteUser() {
        userService.deleteUser();
        System.out.println("UserServiceProxy.deleteUser");
    }
}

静态代理最大的特点就是我们有一个原始类就要有一个代理类,静态的意思是代理类需要手工写出来一个源文件。

静态代理存在的问题:

  • 类文件数量过多,不利于项目管理;
  • 额外功能可维护性差,代理类中额外功能修改起来麻烦;

二、动态代理

Spring 动态代理

  1. 创建原始对象(目标对象); public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("UserServiceImpl.register 业务运算 + DAO"); } @Override public boolean login() { System.out.println("UserServiceImpl.login"); return true; } } 将其添加到容器: <bean class="edu.lsu.service.impl.UserServiceImpl" id="userService"/>
  2. 提供额外功能; Spring 提供了一个接口 MethodBeforeAdvice,额外的功能书写在接口的实现中,会在原始的方法运行之前运行。 public class Before implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("Before.before---MethodBeforeAdvice"); } } <bean class="edu.lsu.dynamic.Before" id="before"/>
  3. 定义切入点:额外功能加入的位置 目的:由程序员根据自己的需要,决定额外功能加入的位置。 <aop:config> <aop:pointcut id="pc" expression="execution(* *(..))"/> </aop:config>
  4. 组装 把切入点和额外的功能进行整合; <aop:config> <aop:pointcut id="pc" expression="execution(* *(..))"/> <!-- 所有的方法 都加入 before 的额外功能 --> <aop:advisor advice-ref="before" pointcut-ref="pc"/> </aop:config>
  5. 调用 目的:获得 Spring 工厂创建的动态代理对象,并进行调用;
    • Spring 的工厂通过原始对象的 id 值获得的是代理对象
    • 可以使用接口类型存储代理对象。

三、细节分析

Spring 创建的动态代理类在哪里?

Spring 框架在运行时,通过 动态字节码技术 ,在 JVM 创建时运行在 JVM 内部,等程序结束后会和 JVM 一起消失。

动态代理不需要定义类文件,都是 JVM 运行过程中动态创建的,所以不会造成 静态代理类文件数量过多影响项目管理 的问题。

动态代理的可维护性大大增强。

四、Spring 动态代理详解

MethodBeforeAdvice

我们通过实现 MethodBeforeAdvice 接口实现额外功能。

@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
      System.out.println("Before.before---MethodBeforeAdvice");
}

该接口有一个方法 before,他有 3 个参数:

  • method:额外功能所增加给的那个原始方法;
  • objects:原始方法的参数;
  • o:代表额外功能所增加给的那个原始对象。

MethodInterceptor

它也叫作方法拦截器

这里使用的是 org.aopalliance.intercept.MethodInterceptorcjlib 包中也提供一个,但是我们不用那个。

该接口有一个 invoke 方法,重写该方法之后就能让额外功能执行在原始方法之前或者之后,或者之前和之后。

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        return null;
    }
}

参数 MethodInvocation:额外功能所增加给的那个原始方法,methodInvocation.proceed(); 代表原始方法运行。

返回值代表原始方法执行后的返回值。

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("Around.invoke----原始方法之前运行.");
        Object res = methodInvocation.proceed();
        System.out.println("Around.invoke----原始方法之后运行.");
        return res;
    }
}
<bean class="edu.lsu.service.impl.UserServiceImpl" id="userService"/>
<bean class="edu.lsu.dynamic.Around" id="around"/>
<aop:config>
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <!--    所有的方法 都加入 around 的额外功能    -->
    <aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>

输出:

Around.invoke----原始方法之前运行.
UserServiceImpl.login
Around.invoke----原始方法之后运行.

Around.invoke----原始方法之前运行.
UserServiceImpl.register 业务运算 + DAO
Around.invoke----原始方法之后运行.

切入点表达式

* edu.lsu.service.*.*(..)
  • 访问修饰符可以省略;
  • 返回值可以使用通配符 * 表示任意返回值;
  • 包名可以是任意包,但是有几个包就写几个 *.
  • *.. 表示当前包及其子包。
  • 参数类型可以使用通配符表示任意类型,可以使用 .. 表示有无参数都行。

切入点函数

用于执行切入点表达式。

  1. execution 它是最重要的切入点函数,功能最全,但是写法复杂。
  2. args 用于函数(方法)参数的匹配 execution(* *(String, String)) == args(String, String)
  3. within 用于进行类、包切入点表达式的匹配 execution(* *..UserServiceImpl.*(..)) == within(*..UserServiceImpl) execution(* top.wsuo.proxy..*.*(..)) == within(top.wsuo.proxy..*)
  4. @annotation 为具有特殊注解的方法加入额外功能。 <aop:pointcut id="pc" expression="@annotation(edu.lsu.Log)"/> 将注解加到指定的方法之上就可以实现功能了。
  5. 切入点函数的逻辑运算 指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
    • and 与操作: execution(* login(String, String)) == execution(* login(..)) and args(String, String)
    • or 或操作: execution(* login(..)) or execution(* login(..))