Spring-AOP编程

作者: zhl 分类: Spring 发布时间: 2023-11-02 15:49

一、静态代理设计模式

为什么需要代理设计模式?

  • 在JavaEE分层开发中,哪个层次对于我们来讲最重要
    DAO -> Service -> Controller
    JavaEE开发中,最重要的是Service层
  • 一个Service层中包含了哪些代码?
    Service层=核心功能(几十 上百代码)+ 额外功能(附加功能)

  • 额外功能书写在Service层中好不好?
    Service层的调用者角度(Controller):需要在Service层书写额外功能
    软件设计者的角度:Service层不需要额外功能
  • 现实生活中的解决方式

代理设计模式

  • 概念
    通过代理类,为原始类(目标类=Service层)增加额外的功能
    好处:利于原始类(目标)的维护

  • 名词解释

    1. 目标类 原始类
      指的是 业务类 (核心功能 ---> 业务运算 DAO调用)
    2. 目标方法 原始方法
      目标类(原始类)中的方法 就是目标方法(原始方法)
    3. 额外功能(附加功能)
      日志,事务,性能
  • 代理开发的核心要素
    代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口

    房东 --> public interface UserService{
                m1
                m2
            }
            UserServiceImpl implements UserService{
                m1  --> 业务运算  DAO调用
                m2
            }
            UserServiceProxy implements UserService{
                m1
                m2
            }
  • 编码
    静态代理:为每一个原始类,手工编写一个代理类(.java .class)

    public class UserServiceProxy implements UserService{
    private UserServiceImpl userService;
    @Override
    public void register(User user) {
        System.out.println("------register--log------");
        userService.register(user);
    }
    
    @Override
    public boolean login(String name, String password) {
        System.out.println("-------login--log--------");
        return userService.login(name,password);
    }
    }
  • 静态代理存在的问题

    1. 静态类文件数量过多,不利于项目管理
      UserServiceImpl UserServiceProxy
      OrderServiceImpl OrderServiceProxy
    2. 额外功能维护性差
      代理类中 额外功能修改复杂(麻烦)

二、Spring的动态代理开发

Spring动态代理的概念

概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护
搭建开发环境

<!--开启Spring动态代理-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.22</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

Spring动态代理的开发步骤

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

    public class UserServiceImpl implements UserService{
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");
    }
    
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
    }
    <bean id="userService" class="xxx.UserServiceImpl"/>
  2. 额外功能
    MethodBeforeAdvice 接口
    把额外的功能写在接口的实现中,运行在原始方法之前
    public class Before implements MethodBeforeAdvice {
    /**
     * 作用 :需要把运行在原始方法之前的额外功能 ,写在before方法中
     * @param method
     * @param args
     * @param target
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("------------MethodBeforeAdvice log-----------");
    }
    }
    <bean id="before" class="com.mysite.dynamic.Before"/>
  3. 定义切入点
    切入点: 额外功能加入的位置
    目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法
    简单的测试:在Spring配置文件中,将所有的方法都作为切入点,都加入额外的功能
    <aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    </aop:config>
  4. 组装(2 3 整合)
    <!--表达的含义:在Spring配置文件中,将所有的方法 都加入 before 的额外功能-->
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
  5. 调用
    目的:获得Spring工厂创建的动态代理对象,并进行调用
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");

    注意:

    1. Spring工厂通过原始对象的 id值获得的是代理对象
    2. 获得代理对象后,可以通过声明接口类型,进行对象的存储
      与静态代理一样,原始类和代理类都实现相同的接口
      
      UserService userService = (UserService)ctx.getBean("userService");
      userService.login("");
      userService.register("");
      ```
    3. </ol></li>
      </ol>
      <h3>动态代理细节分析</h3>
      <ol>
      <li>Spring创建的动态代理类在哪里?
      Spring框架在运行时,通过<strong>动态字节码技术</strong>,在JVM中创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。</li>
      </ol>
      <p>动态字节码技术是通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失</p>
      <p>结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影响项目管理的问题。</p>
      <p><img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/b2dd4860-410d-4930-b8f6-5ea2cd5538d0.png" alt="" /></p>
      <ol start="2">
      <li>动态代理编程简化代理的开发
      <strong>在额外功能不改变的前提下</strong>,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。</li>
      <li>动态代理额外功能的维护性大大增强
      修改额外功能,只需要在定义一个额外功能的类创建新的动态即可,在Spring配置文件中,对切入点和额外功能进行整合。</li>
      </ol>
      <h2>三、Spring动态代理详解-额外功能</h2>
      <ul>
      <li>MethodBeforeAdvice 分析
      MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作
      ```java
      public class Before1 implements MethodBeforeAdvice {
      /**

    • 作用:需要把运行在原始方法之前的额外功能 ,写在before方法中
    • @param method 额外功能所增加给的那个原始方法
    • @param args 额外功能所增加给的那个原始方法的参数
    • @param target 额外功能所增加给的那个原始对象 UserServiceImpl OrderServiceImpl
    • @throws Throwable
      */
      @Override
      public void before(Method method, Object[] args, Object target) throws Throwable {
      System.out.println("");
      System.out.println("------------New MethodBeforeAdvice log-----------");
      System.out.println("
      ");
      }
      }
      ```
      实际上before方法中的参数,在实战中几乎不会用</li>
      </ul></li>
      <li>MethodInterceptor(方法拦截器)
      MethodBeforeAdvice ----> 原始方法执行之前
      MethodInterceptor 提供的额外功能 可以运行在 原始方法之 前 后 前后
      ```java
      public class Around implements MethodInterceptor {
      /
      invoke 方法的作用:额外功能书写在invoke
      额外功能 原始方法之前
      原始方法之后
      原始方法执行之前 之后
      参数:MethodInvocation (Method):额外功能所增加给的那个原始方法
      login
      register
      invocation.proceed() ---> login 运行
      register 运行
      返回值:Object:原始方法的返回值
      /
      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {
      //额外功能运行在原始方法执行之前
      System.out.println("----------Before Log----------");
      Object ret = invocation.proceed();
      //额外功能运行在原始方法执行之后
      System.out.println("----------After Log----------");
      return ret;
      }
      }
      ```
      实际上 事务作为额外功能 需要在原始方法运行之前之后 运行
      <strong>MethodInterceptor 提供的额外功能 可以运行在原始方法抛出异常的时候</strong>
      ```java
      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {
      Object ret = null;
      try {
      ret = invocation.proceed();
      } catch (Throwable e) {
      System.out.println("-----原始方法抛出异常,执行的额外功能-----");
      throw new RuntimeException(e);
      }
      return ret;
      }
      ```
      MethodInterceptor 可以影响原始方法的返回值
      原始方法的返回值,直接作为invoke方法的返回值返回,不会影响原始方法的返回值
      MethodInterceptor 影响原始方法的返回值,不返回原始方法的运行结果即可(invocation.proceed();)
      ```
      public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("-------log-------");
      Object ret = invocation.proceed();
      return true;
      }
      ```
    • </ul>
      <h2>四、Spring动态代理详解-切入点表达式</h2>
      <h3>切入点详解</h3>
      <p>切入点决定额外功能加入位置(方法)
      ```xml
      <aop:pointcut id="pc" expression="execution( (..))"/>
      ```
      `execution(<em> </em>(..))` ----> 匹配了所有方法</p>
      <ol>
      <li>execution() 切入点函数</li>
      <li>`<em> </em>(..)` 切入点表达式</li>
      </ol>
      <h4>切入点表达式</h4>
      <ol>
      <li>
      <p>方法切入点表达式
      <img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/1280X1280-17.png" alt="" />
      ```

      • (..) --> 匹配所有方法
        第一个
        --> 修饰符 返回值
        第二个 * --> 方法名
        () --> 参数表
        .. --> 对于参数没有要求(参数有没有,有几个什么类型的都行)
        ```
      • </ul>
        <ul>
        <li>
        <p>定义login方法作为切入点
        ```

        • login(..)
          定义register方法作为切入点
        • register(..)
          ```
        • </ul>
          </li>
          <li>
          <p>定义login 方法且login 方法有两个字符串类型的参数 作为 切入点
          ```

          • login(String,String)
            -- 注意:非java.lang包中的类型,必须要写全限定名
          • register(com.mysite.proxy.User)
            .. 可以和具体的参数类型连用
          • login(String, ..) --> login(String),login(String,String),login(String,com.mysite.proxy.User)
            ```
          • </ul>
            </li>
            <li>
            <p>以上的切入点表达式还不够精准
            精准切入点限定
            <img src="https://zhouhuilong.cn/wp-content/uploads/2025/03/1280X1280-18.png" alt="" /></p>
            </li>
            </ul>
            </li>
            <li>
            <p>类切入点表达式
            指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能</p>
            <ul>
            <li>语法1
            <pre><code>
            # 类中的所有方法加入了额外功能</code></pre></li>
            <li>com.mysite.proxy.UserServiceImpl.*(..)
            <pre><code></code></pre></li>
            <li>语法2
            ```

            忽略包

    1. 类只存在一级包 com.UserServiceImpl
  6. .UserServiceImpl.(..)
    1. 类存在多级包 com.mysite.proxy.UserServiceImpl
  7. ..UserServiceImpl.(..)
    ```
  8. 包切入点表达式 实战中常用

    • 语法1
      # 切入点包中的所有类,必须在proxy包中,不能在其子包中
      com.mysite.proxy.*.*(..)
    • 语法2
      # 切入点当前包及其子包都生效
      com.mysite.proxy..*.*(..)

切入点函数

切入点函数:用于执行切入点表达式

  1. execution
    最重要的切入点函数,功能最全。
    执行 方法切入点表达式 类切入点表达式 包切入点表达式
    弊端: execution执行切入点表达式,书写麻烦
    execution(* com.mysite.proxy..*.*(..))
    注意:其他的切入点函数 简化是execution书写复杂度,功能上完全一致
  2. args
    作用:主要用于函数(方法)参数的匹配
    切入点:方法参数必须得是2个字符串类型的参数
    execution(* *(String,String))
    args(String,String)
  3. within
    作用:主要用于进行类、包切入点表达式的匹配
    # 切入点:UserServiceImpl这个类
    execution(* *..UserServiceImpl.*(..))
    within(*..UserServiceImpl)
    execution(* com.mysite..*.*(..))
    within(com.mysite..*)
  4. @annotation
    作用:为具有特殊功能的方法加入额外功能
    <aop:pointcut id="pc" expression="@annotation(com.mysite.Log)"/>
    • 切入点函数的逻辑运算
      指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求
    • and与 操作
      案例:login 同时 参数 2个字符串 
      方式1. execution(* login(String,String)) 
      方式2. execution(* login(..)) and args(String,String) 
      注意:与操作 不能用于同种类型的切⼊点函数 
      案例:register⽅法 和 login⽅法作为切⼊点
      execution(* login(..)) or execution(* register(..))
    • or或 操作
      案例:register⽅法 和 login⽅法作为切⼊点 
      execution(* login(..)) or execution(* register(..))

五、AOP编程

AOP概念

AOP(Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建

切面 = 切入点 + 额外功能

OOP (Object Oritened Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

POP (Producer Oriented Programing) 面向过程(方法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建

AOP的本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
注意:AOP编程不可能取代OOP,是对OOP编程的补充

AOP的开发步骤

与Spring动态代理的开发步骤完全一样

  1. 原始对象
  2. 额外功能(MethodInterceptor)
  3. 切入点
  4. 组装切面(额外功能+切入点)

切面的名词解释

切面 = 切入点 + 额外功能

六、AOP的底层实现原理

核心问题

  1. AOP如何创建动态代理类(动态字节码技术)
  2. Spring工厂如何加工创建代理对象
    为什么通过原始对象的id 值,获得的是代理对象

动态代理类的创建

JDK的动态代理

  • Proxy.newProxyInstance();方法参数详解

  • 编码

    public class TestJDKProxy {
    public static void main(String[] args) {
        //1.创建原始对象
        UserService userService = new UserServiceImpl();
        //2. JDK创建动态代理
        InvocationHandler invocationHandler = new InvocationHandler() {
            //这里实现的invoke方法
            // 方法名很像之前实现的 MethodInterceptor接口
            // 参数列表和名字很像之前实现的 MethodBefore接口
            //参数:
            // proxy:忽略掉,代表的是 代理对象
            // method:额外功能 所增加给的那个原始方法
            // args[]:原始方法的参数
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //额外功能
                System.out.println("-------JDKProxy Before Log-------");
                Object ret = method.invoke(userService, args);  //原始方法运行
                System.out.println("-------JDKProxy After Log--------");
                return ret;
            }
        };
        //newProxyInstance(classloader,interfaces,invocationHandler);
        //  classloader:借用一个类加载器,创建代理类的Class对象,进而可以创建代理对象
        //  interfaces: 原始对象 所实现的接口  
        //  userService.getClass().getInterfaces()  ==> 代理创建的第三要素:代理对象和原始对象实现相同的接口
        //  invocationHandler: 创建匿名内部类,实现 invoke 方法,返回 invoke的值
    
        //  classloader:通过类加载器把类的字节码文件加载到JVM,
        //              通过类加载器创建类的Class对象,进而创建这个类的对象
        ClassLoader classLoader = UserService.class.getClassLoader();
        // 类加载器的作用 ClassLoader
        //  1. 通过类加载器把对应类的字节码文件加载JVM
        //  2. 通过类加载器创建类的Class对象,进而创建这个类的对象
        //      如何获得类加载器:每一个类的.class文件 自动分配与之对应的ClassLoader
        /*
            在当前动态代理的创建中,需要ClassLoader创建动态代理类的Class对象,
            可是动态代理类没有对应的.class 文件,JVM也就不会为其分配ClassLoader,
            那怎么办?
                我们可以借用一个ClassLoader 借谁的都行
         */
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(classLoader,
                                                    userService.getClass().getInterfaces(),
                                                                    invocationHandler);
    
        //测试 
        userServiceProxy.register(new User());
        userServiceProxy.login("zhl","123");
    }
    }

CGlib的动态代理

  • CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)

  • 编码

    public class TestCglib {
    public static void main(String[] args) {
        //1.创建原始对象
        UserService userService = new UserService();
        //2.通过CGlib的方式创建动态代理对象
        /*
            Proxy.newProxyInstance(classloader,interfaces,invocationHandler);
    
            Enhancer.setClassLoader()
            Enhancer.setSuperClass()
            Enhancer.setCallback()  ----> MethodInterceptor(cglib) 在 cglib包下的
            Enhancer.create() ---> 代理
         */
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());
        MethodInterceptor interceptor= new MethodInterceptor() {
            //等同于 在JDK中创建动态代理类 InvocationHandler 中的 invoke方法
            @Override
            public Object intercept(Object o, Method method, Object[] args,
                                     MethodProxy methodProxy) throws Throwable {
                System.out.println("---------Cglib Before Log----------");
                //调用原始方法
                Object ret = method.invoke(userService, args);
                System.out.println("---------Cglib After Log----------");
                return ret;
            }
        };
        //MethodInterceptor extends Callback
        enhancer.setCallback(interceptor);
    
        UserService userServiceProxy = (UserService) enhancer.create();
        //测试
        userServiceProxy.login("zhl","123");
        userServiceProxy.register(new User());
    }
    }
  • 总结

    • JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
    • Cglib动态代理 Enhancer 通过继承父类创建的代理类

Spring工厂如何加工原始对象

  • 思路分析

    public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    /*
        Proxy.newProxyInstance(classloader,interfaces,invocationHandler)
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----------new Before Log---------");
                Object ret = method.invoke(bean, args);
                System.out.println("----------new after Log---------");
                return ret;
            }
        };
    
        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),invocationHandler);
    }
    }

    底层是通过实现BeanPostProcessor接口完成的,实现接口后,方法返回的是代理对象

七、基于注解的AOP编程

基于注解的AOP编程的开发步骤

  1. 原始对象
  2. 额外功能
  3. 切入点
  4. 组装切面

通过切面类 定义了 额外功能 @Around
定义了 切入点 @Around("execution(* login(..))")
定义了 切面类 @Aspect

/*
    1.额外功能
        public class MyAround implements MethodInterceptor{
            public Object invoke(MethodInvocation invocation){
                Object ret = invocation.proceed();
                return ret;
            }
        }
    2.切入点
        <aop:config>
            <aop:pointcut id="" expression="execution(* login(..))"/>
 */
@Aspect
public class MyAspect {
    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-----------aspect Before Log---------");
        Object ret = joinPoint.proceed();
        System.out.println("-----------aspect After Log---------");
        return ret;
    }
}
<bean id="userService" class="com.mysite.aspect.UserServiceImpl"/>
<!--
    额外功能
    切入点
    组装切面
-->
<bean id="aspect" class="com.mysite.aspect.MyAspect"/>

<!--告诉Spring现在基于注解进行aop编程-->
<aop:aspectj-autoproxy/>

细节

  • 切入点复用:在切面类中定义一个函数,添加@Pointcut注解 通过这种方式,定义切入点表达式,后续更加利于切入点复用

    @Aspect
    public class MyAspect {
    /**
     * 实现切入点表达式的复用,利于维护和复用
     */
    @Pointcut("execution(* login(..))")
    public void myPointCut(){}
    
    @Around(value = "myPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-----------aspect Log---------");
        Object ret = joinPoint.proceed();
        return ret;
    }
    
    @Around(value = "myPointCut()")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("-----------aspect tx---------");
        Object ret = joinPoint.proceed();
        return ret;
    }
    }
  • 动态代理的创建方式
    AOP底层实现 2种代理创建方式
    1. JDK 通过实现接口 做新的实现类方式 创建代理对象
    2. Cglib 通过继承父类 做新的子类 创建代理对象

默认情况AOP编程 底层应用JDK动态代理创建方式
如果切换 Cglib

  1. 基于注解的AOP开发
    <aop:aspectj-autoproxy proxy-target-class="true" />
  2. 传统的AOP开发
    <aop:config proxy-target-class="true" />

八、AOP开发的易错点

当业务比较复杂时,我们可能会在同一个业务操作中,调用另一个业务操作,这时我们只有在外层被调用的方法,才会调用额外功能,内层方法实际上的调用都是对原始方法的调用,没有对额外功能的调用。

我们可以创建一个Spring工厂,调用代理对象。但是Spring是一个重量级资源,一个应用中,应该只创建一个工厂。

所以可以在业务类中声明 ApplicationContext的Spring工厂作为成员变量,然后实现ApplicationContextAware接口,实现 setApplicationContext方法,为成员变量工厂赋值就拿到了工厂对象,然后获取代理对象即可。

public class UserServiceImpl implements UserService, ApplicationContextAware {
    //将工厂作为成员变量,方便调用代理对象
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //为成员变量工厂赋值 ,方便获取代理对象
        ctx = applicationContext;
    }

    @Log
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");
        /*
        实际上
            调用的是 代理对象的register方法(额外功能+核心功能) 和 原始对象的login 方法 ----》核心功能
        设计目的: 调用代理对象的 login方法 ---》额外功能 + 代理功能
        */        
        //this是调用 原始对象的 login方法
        //this.login("zhl","222");

        //通过工厂获取 代理对象
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("zhl","1111");       
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}

九、AOP阶段知识总结

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注