Spring 动态代理

谁借莪1个温暖的怀抱¢ 2022-12-05 00:56 271阅读 0赞

一、静态代理

静态代理要

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

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

  1. public class UserServiceProxy implements UserService {
  2. private UserServiceImpl userService = new UserServiceImpl();
  3. @Override
  4. public void addUser() {
  5. userService.addUser();
  6. System.out.println("UserServiceProxy.addUser");
  7. }
  8. @Override
  9. public void deleteUser() {
  10. userService.deleteUser();
  11. System.out.println("UserServiceProxy.deleteUser");
  12. }
  13. }

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

静态代理存在的问题:

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

二、动态代理

Spring 动态代理

  1. 创建原始对象(目标对象);

    1. public class UserServiceImpl implements UserService {
    2. @Override
    3. public void register() {
    4. System.out.println("UserServiceImpl.register 业务运算 + DAO");
    5. }
    6. @Override
    7. public boolean login() {
    8. System.out.println("UserServiceImpl.login");
    9. return true;
    10. }
    11. }

    将其添加到容器:

    1. <bean class="edu.lsu.service.impl.UserServiceImpl" id="userService"/>
  2. 提供额外功能;

    Spring 提供了一个接口 MethodBeforeAdvice,额外的功能书写在接口的实现中,会在原始的方法运行之前运行。

    1. public class Before implements MethodBeforeAdvice {
    2. @Override
    3. public void before(Method method, Object[] objects, Object o) throws Throwable {
    4. System.out.println("Before.before---MethodBeforeAdvice");
    5. }
    6. }
    7. <bean class="edu.lsu.dynamic.Before" id="before"/>
  3. 定义切入点:额外功能加入的位置

    目的:由程序员根据自己的需要,决定额外功能加入的位置。

    1. <aop:config>
    2. <aop:pointcut id="pc" expression="execution(* *(..))"/>
    3. </aop:config>
  4. 组装

    把切入点和额外的功能进行整合;

    1. <aop:config>
    2. <aop:pointcut id="pc" expression="execution(* *(..))"/>
    3. <!-- 所有的方法 都加入 before 的额外功能 -->
    4. <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    5. </aop:config>
  5. 调用

    目的:获得 Spring 工厂创建的动态代理对象,并进行调用;

    • Spring 的工厂通过原始对象的 id 值获得的是代理对象
    • 可以使用接口类型存储代理对象。

三、细节分析

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

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

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

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

四、Spring 动态代理详解

MethodBeforeAdvice

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

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

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

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

MethodInterceptor

它也叫作方法拦截器

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

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

  1. public class Around implements MethodInterceptor {
  2. @Override
  3. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  4. return null;
  5. }
  6. }

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

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

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

输出:

  1. Around.invoke----原始方法之前运行.
  2. UserServiceImpl.login
  3. Around.invoke----原始方法之后运行.
  4. Around.invoke----原始方法之前运行.
  5. UserServiceImpl.register 业务运算 + DAO
  6. Around.invoke----原始方法之后运行.

切入点表达式

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

切入点函数

用于执行切入点表达式。

  1. execution

    它是最重要的切入点函数,功能最全,但是写法复杂。

  2. args

    用于函数(方法)参数的匹配

    1. execution(* *(String, String)) == args(String, String)
  3. within

    用于进行类、包切入点表达式的匹配

    1. execution(* *..UserServiceImpl.*(..)) == within(*..UserServiceImpl)
    2. execution(* top.wsuo.proxy..*.*(..)) == within(top.wsuo.proxy..*)
  4. @annotation

    为具有特殊注解的方法加入额外功能。

    1. <aop:pointcut id="pc" expression="@annotation(edu.lsu.Log)"/>

    将注解加到指定的方法之上就可以实现功能了。

  5. 切入点函数的逻辑运算

    指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求。

    • and 与操作:

      1. execution(* login(String, String))
      2. == execution(* login(..)) and args(String, String)
    • or 或操作:

      1. execution(* login(..)) or execution(* login(..))

发表评论

表情:
评论列表 (有 0 条评论,271人围观)

还没有评论,来说两句吧...

相关阅读

    相关 spring 03.动态代理

    概述:   动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理。