代理模式和Spring-AOP

代理模式

代理模式(Proxy Pattern) 是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式的核心思想是通过引入一个代理对象,在客户端和目标对象之间起到中介作用,从而可以在不改变目标对象的情况下,增加额外的功能或控制访问

代理模式的应用场景:

  • 延迟初始化(懒加载)
  • 访问控制
  • 日志记录
  • 缓存
  • 远程代理(如 RPC 调用)

代理模式的结构

  • Subject(抽象主题)
    • 定义目标对象和代理对象的共同接口,客户端通过该接口访问目标对象。
  • RealSubject(真实主题)
    • 实际的目标对象,代理对象所代表的真实对象。
  • Proxy(代理)
    • 代理对象,持有对真实主题的引用,并控制对真实主题的访问。代理对象可以在调用真实主题的方法前后执行额外的操作。

代理模式的分类

  • 静态代理
    • 静态代理是在编译时就已经确定代理类和目标类的关系。代理类和目标类实现相同的接口,代理类持有目标对象的引用,并在调用目标对象方法前后添加额外的逻辑。
  • 动态代理
    • 动态代理是在运行时动态生成代理类。动态代理不需要显式定义代理类,而是通过反射机制在运行时生成代理对象。Java 提供了两种动态代理机制:
      • JDK 动态代理:基于接口的动态代理。
      • CGLIB 动态代理:基于类的动态代理。

静态代理

  • 假设有一个 UserService 接口和一个实现类 UserServiceImpl,我们需要在调用 addUser 方法前后添加日志记录

  • 首先定义接口

    public interface UserService {
        void addUser(String username);
    }
    
  • 然后定义实现类

    public class UserServiceImpl implements UserService {
        @Override
        public void addUser(String username) {
            System.out.println("Adding user: " + username);
        }
    }
    
  • 随后定义代理类

    public class UserServiceProxy implements UserService {
        private UserService userService;
    
        public UserServiceProxy(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public void addUser(String username) {
            System.out.println("Before adding user");
            userService.addUser(username);
            System.out.println("After adding user");
        }
    }
    
  • 最后通过接口接收代理对象调用方法

    public class Client {
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            UserService proxy = new UserServiceProxy(userService);
            proxy.addUser("John");
        }
    }
    

动态代理

JDK动态代理

  • JDK 动态代理基于接口,要求目标对象必须实现至少一个接口

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JdkDynamicProxy {
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            UserService proxy = (UserService) Proxy.newProxyInstance(
                    userService.getClass().getClassLoader(),
                    userService.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("Before " + method.getName());
                            Object result = method.invoke(userService, args);
                            System.out.println("After " + method.getName());
                            return result;
                        }
                    }
            );
            proxy.addUser("John");
        }
    }
    

CGLIB 动态代理

  • CGLIB 动态代理基于类,适用于没有实现接口的类

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class CglibDynamicProxy {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserServiceImpl.class);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("Before " + method.getName());
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("After " + method.getName());
                    return result;
                }
            });
            UserService proxy = (UserService) enhancer.create();
            proxy.addUser("John");
        }
    }
    

AOP

Spring AOP(面向切面编程) 是 Spring 框架的核心模块之一,它允许开发者通过声明式的方式将横切关注点(如日志记录、事务管理、安全性等)与核心业务逻辑分离。AOP 通过动态代理机制实现,能够在运行时将额外的行为织入到目标对象的方法中

XML文件定义AOP

  1. 在 Spring 的 XML 配置中,AOP 相关的配置主要通过以下元素实现
  • <aop:config>:AOP 配置的根元素,用于定义切面、切点和通知。
  • <aop:aspect>:定义切面,指定切面类。
  • <aop:pointcut>:定义切点,指定匹配的方法。
  • <aop:before>:定义前置通知(Before Advice)。
  • <aop:after-returning>:定义后置通知(After Returning Advice)。
  • <aop:after-throwing>:定义异常通知(After Throwing Advice)。
  • <aop:after>:定义最终通知(After (Finally) Advice)。
  • <aop:around>:定义环绕通知(Around Advice)。
  1. XML 配置 AOP 的步骤
  • 定义目标对象
    • 目标对象是一个普通的 Spring Bean
  • 定义切面类
    • 切面类是一个普通的 Java 类,包含通知方法
  • 在 XML 中配置 AOP
    • 通过 <aop:config> 和相关子元素配置切面、切点和通知
  1. 示例代码

    • 目标对象

      • 假设有一个 UserService 接口和实现类 UserServiceImpl

      • public interface UserService {
            void addUser(String username);
        }
        
        public class UserServiceImpl implements UserService {
            @Override
            public void addUser(String username) {
                System.out.println("Adding user: " + username);
            }
        }
        
    • 切面类

      • 切面类 LoggingAspect 包含多个通知方法

      • public class LoggingAspect {
            public void beforeAddUser() {
                System.out.println("Before adding user");
            }
        
            public void afterReturningAddUser() {
                System.out.println("After returning from adding user");
            }
        
            public void afterThrowingAddUser() {
                System.out.println("After throwing exception in adding user");
            }
        
            public void afterAddUser() {
                System.out.println("After adding user (finally)");
            }
        
            public Object aroundAddUser(ProceedingJoinPoint joinPoint) throws Throwable {
                System.out.println("Before proceeding addUser");
                Object result = joinPoint.proceed();
                System.out.println("After proceeding addUser");
                return result;
            }
        }
        
  2. 在 XML 文件中配置 AOP:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/aop
               http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 定义目标对象 -->
        <bean id="userService" class="com.example.UserServiceImpl"/>
    
        <!-- 定义切面 -->
        <bean id="loggingAspect" class="com.example.LoggingAspect"/>
    
        <!-- 配置 AOP -->
        <!--  不需要显式启用AOP,<aop:config>标签已经隐式启用了AOP功能 -->
        <aop:config>
            <!-- 定义切点 -->
            <aop:pointcut id="addUserPointcut" expression="execution(* com.example.UserService.addUser(..))"/>
    
            <!-- 定义切面 -->
            <aop:aspect ref="loggingAspect">
                <!-- 前置通知 -->
                <aop:before method="beforeAddUser" pointcut-ref="addUserPointcut"/>
    
                <!-- 后置通知 -->
                <aop:after-returning method="afterReturningAddUser" pointcut-ref="addUserPointcut"/>
    
                <!-- 异常通知 -->
                <aop:after-throwing method="afterThrowingAddUser" pointcut-ref="addUserPointcut"/>
    
                <!-- 最终通知 -->
                <aop:after method="afterAddUser" pointcut-ref="addUserPointcut"/>
    
                <!-- 环绕通知 -->
                <aop:around method="aroundAddUser" pointcut-ref="addUserPointcut"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

注解开发AOP

使用 注解开发 AOP 是 Spring 框架中推荐的方式,它比 XML 配置更加简洁和直观。通过注解,开发者可以直接在代码中定义切面、切点和通知,而无需编写冗长的 XML 配置文件

AOP 的核心注解

  • @Aspect:标记一个类为切面。
  • @Pointcut:定义切点表达式。
  • @Before:定义前置通知(Before Advice)。
  • @AfterReturning:定义后置通知(After Returning Advice)。
  • @AfterThrowing:定义异常通知(After Throwing Advice)。
  • @After:定义最终通知(After (Finally) Advice)。
  • @Around:定义环绕通知(Around Advice)。
  • @EnableAspectJAutoProxy:启用 Spring 的 AOP 自动代理功能。

注解开发步骤

  1. 引入依赖

    • 在 Maven 项目中,需要引入 Spring AOP 和 AspectJ 的依赖

    • <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>5.3.22</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.7</version>
      </dependency>
      
  2. 定义目标对象

    • 目标对象是一个普通的 Spring Bean。
  3. 定义切面类

    • 使用 @Aspect 注解标记切面类,并在其中定义切点和通知。
  4. 启用 AOP 自动代理

    • 在配置类中使用 @EnableAspectJAutoProxy 注解启用 AOP 自动代理
  5. 测试AOP

    • 通过 Spring 容器获取目标对象并调用方法,验证 AOP 是否生效。

示例代码

  • 目标对象

    • 假设有一个 UserService 接口和实现类 UserServiceImpl

    • public interface UserService {
          void addUser(String username);
      }
      
      @Service
      public class UserServiceImpl implements UserService {
          @Override
          public void addUser(String username) {
              System.out.println("Adding user: " + username);
          }
      }
      
  • 切面类

    • 切面类 LoggingAspect 使用注解定义切点和通知:

    • import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          // 定义切点
          @Pointcut("execution(* com.example.service.UserService.addUser(..))")
          public void addUserPointcut() {}
      
          // 前置通知
          @Before("addUserPointcut()")
          public void beforeAddUser(JoinPoint joinPoint) {
              System.out.println("Before adding user: " + joinPoint.getArgs()[0]);
          }
      
          // 后置通知
          @AfterReturning("addUserPointcut()")
          public void afterReturningAddUser(JoinPoint joinPoint) {
              System.out.println("After returning from adding user");
          }
      
          // 异常通知
          @AfterThrowing("addUserPointcut()")
          public void afterThrowingAddUser(JoinPoint joinPoint) {
              System.out.println("After throwing exception in adding user");
          }
      
          // 最终通知
          @After("addUserPointcut()")
          public void afterAddUser(JoinPoint joinPoint) {
              System.out.println("After adding user (finally)");
          }
      
          // 环绕通知
          @Around("addUserPointcut()")
          public Object aroundAddUser(ProceedingJoinPoint joinPoint) throws Throwable {
              System.out.println("Before proceeding addUser");
              Object result = joinPoint.proceed();
              System.out.println("After proceeding addUser");
              return result;
          }
      }
      
  • 启用 AOP 自动代理

    • 在配置类中使用 @EnableAspectJAutoProxy 注解启用 AOP 自动代理:

    • import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      
      @Configuration
      @EnableAspectJAutoProxy
      public class AppConfig {
      }
      
  • 测试 AOP

    • 通过 Spring 容器获取目标对象并调用方法:

    • import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      
      public class Main {
          public static void main(String[] args) {
              ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
              UserService userService = context.getBean(UserService.class);
              userService.addUser("John");
          }
      }
      

切点表达式

切点表达式(Pointcut Expression) 是 Spring AOP 中用于定义哪些方法需要被拦截的关键部分。

切点表达式的通用语法如下:

execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?
  • modifiers-pattern:方法的修饰符(如 publicprotected),可选。
  • return-type-pattern:方法的返回类型(如 voidString)。
  • declaring-type-pattern:方法所属的类或接口,可选。
  • method-name-pattern:方法名(如 addUser)。
  • param-pattern:方法的参数列表(如 (..) 表示任意参数)。
  • throws-pattern:方法抛出的异常类型,可选。

常见切点表达式

  1. 匹配所有方法
  • execution(* *(..))
    
    • *:匹配任意返回类型。
    • *:匹配任意方法名。
    • (..):匹配任意参数列表。
  1. 匹配指定类的方法

    • execution(* com.example.service.UserService.*(..))
      
      • com.example.service.UserService:匹配 UserService 类中的所有方法
  2. 匹配指定方法

    • execution(* com.example.service.UserService.addUser(..))
      
  3. 匹配指定包下的方法(不包括子包)

    • execution(* com.example.service.*.*(..))
      
      • com.example.service.*:匹配 com.example.service 包下的所有类的所有方法。
  4. 匹配子包下的方法(包括子包)

    • execution(* com.example.service..*.*(..))
      
      • com.example.service..*:匹配 com.example.service 包及其子包下的所有类的所有方法。

切点表达式的组合

  • 切点表达式可以通过逻辑运算符(&&||!)进行组合,实现更复杂的匹配规则。

  • execution(* com.example.service.UserService.addUser(..)) && execution(* com.example.service.UserService.deleteUser(..))
    
    • 匹配 addUserdeleteUser 方法。
  • execution(* com.example.service.UserService.*(..)) && !execution(* com.example.service.UserService.deleteUser(..))
    
    • 匹配 UserService 类中除 deleteUser 方法外的所有方法。

通配符

  • *通配符
    • 匹配任意字符(除包分隔符 . 外)。
  • .. 通配符
    • 匹配任意子包或任意参数。
  • + 通配符
    • 匹配指定类型及其子类型。
    • 例如:execution(* com.example.service.UserService+.*(..)) 匹配 UserService 类及其子类的所有方法。

定义切点

  • 在切面类中,可以使用 @Pointcut 注解定义切点:

    @Aspect
    @Component
    public class LoggingAspect {
    
        @Pointcut("execution(* com.example.service.UserService.addUser(..))")
        public void addUserPointcut() {}
    
        @Before("addUserPointcut()")
        public void beforeAddUser() {
            System.out.println("Before adding user");
        }
    }
    

AOP通知类型

通知(Advice) 是切面在特定切点(Pointcut)执行的动作。Spring AOP 支持以下五种类型的通知

  1. 前置通知(Before Advice)

    • 前置通知在目标方法执行之前执行。它通常用于日志记录、权限检查等场景

    • 使用 @Before 注解标记通知方法。

    • 通知方法可以接收 JoinPoint 参数,用于获取目标方法的信息

    • import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          @Before("execution(* com.example.service.UserService.addUser(..))")
          public void beforeAddUser(JoinPoint joinPoint) {
              System.out.println("Before adding user: " + joinPoint.getArgs()[0]);
          }
      }
      
  2. 后置通知(After Returning Advice)

    • 后置通知在目标方法成功执行后执行。它通常用于记录方法返回值或执行清理操作

    • 使用 @AfterReturning 注解标记通知方法。

    • 通知方法可以接收 JoinPoint 和返回值(通过 returning 属性指定)。

    • import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          @AfterReturning(pointcut = "execution(* com.example.service.UserService.addUser(..))", returning = "result")
          public void afterReturningAddUser(JoinPoint joinPoint, Object result) {
              System.out.println("After returning from adding user: " + result);
          }
      }
      
  3. 异常通知(After Throwing Advice)

    • 异常通知在目标方法抛出异常后执行。它通常用于记录异常信息或执行错误处理。

    • 使用 @AfterThrowing 注解标记通知方法。

    • 通知方法可以接收 JoinPoint 和异常对象(通过 throwing 属性指定)。

    • import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          @AfterThrowing(pointcut = "execution(* com.example.service.UserService.addUser(..))", throwing = "ex")
          public void afterThrowingAddUser(JoinPoint joinPoint, Exception ex) {
              System.out.println("After throwing exception in adding user: " + ex.getMessage());
          }
      }
      
  4. 最终通知(After (Finally) Advice)

    • 最终通知在目标方法执行后执行,无论目标方法是否抛出异常。它通常用于释放资源或执行清理操作

    • 使用 @After 注解标记通知方法。

    • 通知方法可以接收 JoinPoint 参数。

    • import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          @After("execution(* com.example.service.UserService.addUser(..))")
          public void afterAddUser(JoinPoint joinPoint) {
              System.out.println("After adding user (finally)");
          }
      }
      
  5. 环绕通知(Around Advice)

    • 环绕通知在目标方法执行前后都执行,并且可以控制目标方法的执行。它通常用于性能监控、事务管理等场景

    • 使用 @Around 注解标记通知方法。

    • 通知方法需要接收 ProceedingJoinPoint 参数,并调用 proceed() 方法执行目标方法。

    • import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LoggingAspect {
      
          @Around("execution(* com.example.service.UserService.addUser(..))")
          public Object aroundAddUser(ProceedingJoinPoint joinPoint) throws Throwable {
              System.out.println("Before proceeding addUser");
              Object result = joinPoint.proceed();
              System.out.println("After proceeding addUser");
              return result;
          }
      }
      

AOP注意点

  1. 代理模式

    • 默认情况下,Spring 使用 JDK 动态代理(基于接口)。如果目标对象没有实现接口,Spring 会使用 CGLIB 代理。
    • 可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLIB 代理。
  2. 通知方法的参数

    • 通知方法可以接收 JoinPointProceedingJoinPoint 参数,用于获取目标方法的信息。
    • 例如:aroundAddUser(ProceedingJoinPoint joinPoint)
  3. 切面的顺序

    • 如果多个切面作用于同一个切点,可以使用 @Order 注解指定切面的执行顺序。

    • @Aspect
      @Component
      @Order(1) // 优先级较高,先执行
      public class LoggingAspect {
      
          @Before("execution(* com.example.service.UserService.addUser(..))")
          public void beforeAddUser() {
              System.out.println("Logging: Before adding user");
          }
      }
      
      @Aspect
      @Component
      @Order(2) // 优先级较低,后执行
      public class ValidationAspect {
      
          @Before("execution(* com.example.service.UserService.addUser(..))")
          public void beforeAddUser() {
              System.out.println("Validation: Before adding user");
          }
      }
      
  4. 多个切面的执行顺序

    • 如果多个切面作用于同一个切点,且每个切面定义了不同类型的通知,它们的执行顺序如下:
      • @Around 通知的 Before 部分(按切面的 @Order 顺序执行)。
      • @Before 通知(按切面的 @Order 顺序执行)。
      • 目标方法执行
      • @Around 通知的 After 部分(按切面的 @Order 顺序执行)。
      • @AfterReturning 通知(按切面的 @Order 顺序执行)。
      • @AfterThrowing 通知(按切面的 @Order 顺序执行)。
      • @After 通知(按切面的 @Order 顺序执行)。
    • 通知类型的执行顺序优先于切面的@Order顺序。即,无论@Order值如何,@Before通知总是先于@After通知执行。@Order只影响同一类型通知的执行顺序。
posted @ 2025-03-18 14:40  QAQ001  阅读(58)  评论(0)    收藏  举报