代理模式和Spring-AOP
代理模式
代理模式(Proxy Pattern) 是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式的核心思想是通过引入一个代理对象,在客户端和目标对象之间起到中介作用,从而可以在不改变目标对象的情况下,增加额外的功能或控制访问
代理模式的应用场景:
- 延迟初始化(懒加载)
- 访问控制
- 日志记录
- 缓存
- 远程代理(如 RPC 调用)
代理模式的结构
- Subject(抽象主题)
- 定义目标对象和代理对象的共同接口,客户端通过该接口访问目标对象。
- RealSubject(真实主题)
- 实际的目标对象,代理对象所代表的真实对象。
- Proxy(代理)
- 代理对象,持有对真实主题的引用,并控制对真实主题的访问。代理对象可以在调用真实主题的方法前后执行额外的操作。
代理模式的分类
- 静态代理
- 静态代理是在编译时就已经确定代理类和目标类的关系。代理类和目标类实现相同的接口,代理类持有目标对象的引用,并在调用目标对象方法前后添加额外的逻辑。
- 动态代理
- 动态代理是在运行时动态生成代理类。动态代理不需要显式定义代理类,而是通过反射机制在运行时生成代理对象。Java 提供了两种动态代理机制:
- JDK 动态代理:基于接口的动态代理。
- CGLIB 动态代理:基于类的动态代理。
- 动态代理是在运行时动态生成代理类。动态代理不需要显式定义代理类,而是通过反射机制在运行时生成代理对象。Java 提供了两种动态代理机制:
静态代理
-
假设有一个
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
- 在 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)。
- XML 配置 AOP 的步骤
- 定义目标对象
- 目标对象是一个普通的 Spring Bean
- 定义切面类
- 切面类是一个普通的 Java 类,包含通知方法
- 在 XML 中配置 AOP
- 通过
<aop:config>和相关子元素配置切面、切点和通知
- 通过
-
示例代码
-
目标对象
-
假设有一个
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; } }
-
-
-
在 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 自动代理功能。
注解开发步骤
-
引入依赖
-
在 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>
-
-
定义目标对象
- 目标对象是一个普通的 Spring Bean。
-
定义切面类
- 使用
@Aspect注解标记切面类,并在其中定义切点和通知。
- 使用
-
启用 AOP 自动代理
- 在配置类中使用
@EnableAspectJAutoProxy注解启用 AOP 自动代理
- 在配置类中使用
-
测试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:方法的修饰符(如public、protected),可选。return-type-pattern:方法的返回类型(如void、String)。declaring-type-pattern:方法所属的类或接口,可选。method-name-pattern:方法名(如addUser)。param-pattern:方法的参数列表(如(..)表示任意参数)。throws-pattern:方法抛出的异常类型,可选。
常见切点表达式
- 匹配所有方法
-
execution(* *(..))*:匹配任意返回类型。*:匹配任意方法名。(..):匹配任意参数列表。
-
匹配指定类的方法
-
execution(* com.example.service.UserService.*(..))com.example.service.UserService:匹配UserService类中的所有方法
-
-
匹配指定方法
-
execution(* com.example.service.UserService.addUser(..))
-
-
匹配指定包下的方法(不包括子包)
-
execution(* com.example.service.*.*(..))com.example.service.*:匹配com.example.service包下的所有类的所有方法。
-
-
匹配子包下的方法(包括子包)
-
execution(* com.example.service..*.*(..))com.example.service..*:匹配com.example.service包及其子包下的所有类的所有方法。
-
切点表达式的组合
-
切点表达式可以通过逻辑运算符(
&&、||、!)进行组合,实现更复杂的匹配规则。 -
execution(* com.example.service.UserService.addUser(..)) && execution(* com.example.service.UserService.deleteUser(..))- 匹配
addUser和deleteUser方法。
- 匹配
-
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 支持以下五种类型的通知
-
前置通知(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]); } }
-
-
后置通知(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); } }
-
-
异常通知(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()); } }
-
-
最终通知(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)"); } }
-
-
环绕通知(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注意点
-
代理模式:
- 默认情况下,Spring 使用 JDK 动态代理(基于接口)。如果目标对象没有实现接口,Spring 会使用 CGLIB 代理。
- 可以通过
@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用 CGLIB 代理。
-
通知方法的参数:
- 通知方法可以接收
JoinPoint或ProceedingJoinPoint参数,用于获取目标方法的信息。 - 例如:
aroundAddUser(ProceedingJoinPoint joinPoint)。
- 通知方法可以接收
-
切面的顺序:
-
如果多个切面作用于同一个切点,可以使用
@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"); } }
-
-
多个切面的执行顺序
- 如果多个切面作用于同一个切点,且每个切面定义了不同类型的通知,它们的执行顺序如下:
@Around通知的Before部分(按切面的@Order顺序执行)。@Before通知(按切面的@Order顺序执行)。- 目标方法执行。
@Around通知的After部分(按切面的@Order顺序执行)。@AfterReturning通知(按切面的@Order顺序执行)。@AfterThrowing通知(按切面的@Order顺序执行)。@After通知(按切面的@Order顺序执行)。
- 通知类型的执行顺序优先于切面的
@Order顺序。即,无论@Order值如何,@Before通知总是先于@After通知执行。@Order只影响同一类型通知的执行顺序。
- 如果多个切面作用于同一个切点,且每个切面定义了不同类型的通知,它们的执行顺序如下:

浙公网安备 33010602011771号