@After等注解来实现AOP
发布时间:2025-06-24 08:15:59 作者:北方职教升学中心 阅读量:007
这在实现特定的功能(如权限控制、
1. 静态代理
静态代理是指在编译时就已经确定了代理对象的类型,代理对象的代码由开发人员手动编写或者由工具自动生成。使用
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>
3.1 基于注解的方式
Spring提供了@Aspect
和@Before
、概念
Spring AOP主要包括以下几个核心概念:
切面(Aspect):切面是指横切关注点的模块化,通常包括功能如日志、
- 每个目标对象都需要一个代理类,导致类的数量增加,难以维护。
- 在切面类中定义通知方法,并使用相应的通知注解(如
@Before
、安全验证或事务管理等功能。
切点(Pointcut):切点定义了切面应用的具体位置,指定在哪些连接点上应用通知。例如,我们可以在方法执行前后加入日志打印。CGLIB生成的代理类是目标类的子类,可以覆盖目标类的方法。事务管理等。@After
等注解来实现AOP。
@Aspect@Component@Order(1) // 日志切面,优先级高,先执行public class LogAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Logging before method: " + joinPoint.getSignature().getName()); }}@Aspect@Component@Order(2) // 事务切面,优先级低,后执行public class TransactionAspect { @Before("execution(* com.example.service.*.*(..))") public void beginTransaction(JoinPoint joinPoint) { System.out.println("Starting transaction for method: " + joinPoint.getSignature().getName()); }}
@ execution表达式
execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
举例
@Aspect@Componentpublic class UserServiceAspect { // 匹配所有返回类型为void的方法 @Before("execution(void com.example.service.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before executing void method in UserService"); }}@Aspect@Componentpublic class UserServiceAspect { // 匹配参数为User类型的方法 @Before("execution(* com.example.service.UserService.addUser(com.example.model.User))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before executing addUser method with User parameter"); }}
切点表达式举例
//TestController 下的 public修饰, 返回类型为String ⽅法名为t1, ⽆参⽅法execution(public String com.example.demo.controller.TestController.t1())//省略访问修饰符execution(String com.example.demo.controller.TestController.t1())//匹配所有返回类型execution(* com.example.demo.controller.TestController.t1())//匹配TestController 下的所有⽆参⽅法execution(* com.example.demo.controller.TestController.*())//匹配TestController 下的所有⽅法execution(* com.example.demo.controller.TestController.*(..))//匹配controller包下所有的类的所有⽅法execution(* com.example.demo.controller.*.*(..))//匹配所有包下⾯的TestControllerexecution(* com..TestController.*(..))//匹配com.example.demo包下, ⼦孙包下的所有类的所有⽅法execution(* com.example.demo..*(..))
@annotation
@annotation
是Spring AOP中的一种切点表达式,用于匹配目标方法上带有特定注解的方法。
通知(Advice):通知是切面中的实际操作,它定义了在连接点上执行的动作。
简单来说,AOP是一种思想,是对某一类事务的集中处理
二、
三、
举个例子,在一个电商应用中,我们可能会在多个业务逻辑中用到日志记录、代理模式可以为真实对象提供一个替代对象,以便在不修改原始对象的情况下对其进行控制或增强功能。Spring中,切面通常是通过“@Aspect”注解来声明的。
代理模式(Proxy Pattern)是一种结构型设计模式,主要通过引入代理对象来控制对其他对象的访问。JDK动态代理需要目标类实现接口,而CGLIB动态代理通过继承目标类生成代理对象。
InvocationHandler
来调用目标对象的方法。传统的方式是将这些功能硬编码到各个业务模块中,这样会导致代码的重复和混乱。🔥个人主页: 中草药
🔥专栏:【Java】登神长阶 史诗般的Java成神之路
一、
Subject
接口,执行实际的业务逻辑。final
类进行代理。1.2 实现
假设我们有一个RealService
类,需要通过代理类ProxyService
来访问它。
在程序运行前, 代理类的 .class文件就已经存在了. (在出租房子之前, 中介已经做好了相关的工作,就等租户来租房子了)
2. 动态代理
动态代理是指在运行时,通过反射机制动态生成代理类,不需要在编译时预先定义代理类。概述
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过预定义的切面(Aspect)对程序进行动态扩展,从而减少代码重复并提升可维护性。
目标对象(Target Object):被代理的对象,也就是我们要增强功能的核心业务对象。Spring支持两种类型的代理:(后面详细介绍)
- JDK动态代理:基于接口的代理,只能对实现了接口的类进行代理。
注解
package org.example.aspect;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;//生命周期@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface TimeRecord {}
切面
package org.example.aspect;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Slf4j@Componentpublic class TimeAspect { //@Around("org.example.aspect.AspectDemo.pt()") @Around("@annotation(org.example.aspect.TimeRecord)") public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); long elapsedTime = endTime - startTime; log.info(joinPoint.getSignature()+"耗时"+elapsedTime + "ms"); return result; }}
使用
package org.example.controller;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.annotation.Pointcut;import org.example.aspect.TimeRecord;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j@RequestMapping("/testA")@RestControllerpublic class TestController { @TimeRecord @RequestMapping("/t1") public String t1(){ //int a = 10/0; log.info("执行T1方法"); return "t1"; }}
3.2 基于XML配置的方式
如果你更倾向于XML配置,可以按照以下步骤进行配置:
- 在
applicationContext.xml
中配置切面和通知。步骤:
- 创建切面类,并使用
@Aspect
注解标注。 连接点(JoinPoint):程序执行的某个点,通常是方法调用。事务管理、
示例:CGLIB动态代理实现
import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class RealService { public void performAction() { System.out.println("Performing the real service operation..."); }}// CGLIB动态代理class ProxyHandler implements MethodInterceptor { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Proxy: Before calling real service..."); Object result = proxy.invokeSuper(obj, args); // 调用目标对象的方法 System.out.println("Proxy: After calling real service..."); return result; }}// 客户端代码public class Client { public static void main(String[] args) { RealService realService = new RealService(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealService.class); // 设置目标类 enhancer.setCallback(new ProxyHandler(realService)); // 设置回调 RealService proxyService = (RealService) enhancer.create(); // 创建代理对象 proxyService.performAction(); // 调用代理对象的方法 }}
2.3 动态代理的优缺点
- 优点:
- 灵活性高,不需要手动编写代理类,代理类是在运行时动态生成的。
@After
)。安全、 - 代码冗余:每一个真实对象都需要对应一个代理类,如果有多个真实对象,代理类的数量就会增加,导致代码冗余。通知可以分为几种类型:
- 前置通知(Before):在目标方法执行前调用。
2.1 JDK动态代理
JDK动态代理是通过
java.lang.reflect.Proxy
类和InvocationHandler
接口实现的。
Spring AOP是Spring框架中的一个强大特性,它为开发者提供了一种优雅的方式来处理横切关注点。实现方式以及优缺点。
为其他对象提供⼀种代理以控制对这个对象的访问. 它的作用就是通过提供⼀个代理类, 让我们在调用目标方法的时候, 不再是直接对目标方法进行调用, 而是通过代理类间接调用.代理模式的组成
代理模式包含以下几个角色:
- Subject(抽象主题):定义了真实主题和代理主题的公共接口,客户端通过该接口来与真实主题或代理对象进行交互。
- Proxy(代理):也实现了
Subject
接口,持有RealSubject
的引用,并在其上添加额外的功能或控制访问。下面我们分别介绍静态代理和动态代理的特点、 - 后置通知(After):在目标方法执行后调用,无论方法是否异常。
- 前置通知(Before):在目标方法执行前调用。
3. 静态代理 vs 动态代理
特点 静态代理 动态代理 代理类生成方式 代理类在编译时就已经确定 代理类在运行时动态生成 代理对象的灵活性 每个真实对象需要一个代理类 可以为多个不同的真实对象动态生成代理类 是否要求目标类实现接口 需要(JDK动态代理) JDK动态代理需要目标类实现接口,CGLIB不需要 代码量 代理类代码冗余,手动编写 通过反射或字节码生成,无需手动编写代理类 性能开销 较低(没有反射开销) 反射和字节码生成带来一定的性能开销 4. 总结
- 静态代理:代理类在编译时就已经确定,适用于目标类较少的简单场景,存在代码冗余的问题。
- 灵活性高,不需要手动编写代理类,代理类是在运行时动态生成的。
- 缺点:
- 代理类需要手动编写,代码冗余较大,无法动态改变代理类。
- 返回通知(AfterReturning):目标方法成功执行后调用。
- 代理类的生成过程涉及反射,可能会导致一定的性能开销。切点通常是通过表达式来定义的,如
execution(* com.example.service.*.*(..))
表示所有com.example.service
包下的类的所有方法。 - 动态代理:代理类在运行时动态生成,适用于复杂或变化较大的场景,可以大大减少代码冗余。安全控制等功能,并且能够保持业务逻辑代码的简洁性和清晰度。通过Spring AOP,可以轻松地实现日志记录、
静态代理和动态代理都是代理模式的两种实现方式,它们的区别主要体现在代理对象的创建方式上。
- 优点:
- 缺点:
- JDK动态代理要求目标对象实现接口,不能对没有接口的类进行代理。
- 代理类实现接口:代理类通常实现与真实对象相同的接口,并通过代理类访问真实对象。
- CGLIB代理:基于子类的代理,可以对没有接口的类进行代理。日志记录等)时非常有用。
1.1 特点
- 编译时生成代理类:代理类在编译时就已经确定,代理类是由开发人员编写的。AOP的核心思想是把关注点从核心业务逻辑中分离出来,并通过切面(Aspect)将其实现。
// 真实对象(目标对象)public class RealService implements Service { @Override public void performAction() { System.out.println("Performing the real service operation..."); }}// 代理对象public class ProxyService implements Service { private RealService realService; public ProxyService() { realService = new RealService(); } @Override public void performAction() { System.out.println("Proxy: Before calling real service..."); realService.performAction(); // 调用真实对象的方法 System.out.println("Proxy: After calling real service..."); }}
1.3 使用
在使用静态代理时,客户端代码会通过
ProxyService
来访问RealService
:public class Client { public static void main(String[] args) { Service service = new ProxyService(); // 使用代理对象 service.performAction(); // 调用代理对象的方法 }}
1.4 优缺点
- 优点:
- 简单,易于理解。通常我们在需要多个相似的组件或者配置类时,使用
@Order
来确保它们的执行顺序。” — 乔布斯🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸
- 简单,易于理解。通常我们在需要多个相似的组件或者配置类时,使用
- 优点:
- 编译时生成代理类:代理类在编译时就已经确定,代理类是由开发人员编写的。AOP的核心思想是把关注点从核心业务逻辑中分离出来,并通过切面(Aspect)将其实现。
- 创建切面类,并使用
- 在
- JDK动态代理:基于接口的代理,只能对实现了接口的类进行代理。
“无谓的争斗让我们浪费了太多的时间与精力,专注于自己能做好的事,才是最明智的选择。通过
@annotation
,我们可以根据方法上是否标注了某个注解来决定是否执行切面逻辑。示例:JDK动态代理实现
//注意是同一个包import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;// 目标接口public interface Service { void performAction();}// 真实对象(目标对象)public class RealService implements Service { @Override public void performAction() { System.out.println("Performing the real service operation..."); }}// 动态代理处理器class ProxyHandler implements InvocationHandler { private Object target; public ProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Proxy: Before calling real service..."); Object result = method.invoke(target, args); // 调用真实对象的方法 System.out.println("Proxy: After calling real service..."); return result; }}// 客户端代码public class Client { public static void main(String[] args) { Service realService = new RealService(); Service proxyService = (Service) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new ProxyHandler(realService) ); proxyService.performAction(); // 调用代理对象的方法 }}
2.2 CGLIB动态代理
CGLIB(Code Generation Library)是一个功能强大的字节码生成库,通过继承目标类来创建代理对象,而不是要求目标类实现接口。而AOP允许我们将这些功能提取成独立的“切面”,并在需要的地方动态应用,从而保持代码的整洁和可维护性。无论是使用注解还是XML配置,Spring AOP都能让你的代码更加模块化、
代理(Proxy):代理是通过AOP生成的一个对象,它包含了增强逻辑。
示例代码:
<bean id="logAspect" class="com.example.aop.LogAspect" /><aop:config> <aop:aspect ref="logAspect"> <aop:before method="logBefore" pointcut="execution(* com.example.service.*.*(..))" /> <aop:after method="logAfter" pointcut="execution(* com.example.service.*.*(..))" /> </aop:aspect></aop:config>
四、它的值越小,表示加载的优先级越高(数字越小,优先级越高)。
示例代码:
package org.example.aspect;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Slf4j@Componentpublic class AspectDemo { //该注解把公共的切点表达式提取出来 @Pointcut("execution(* org.example.controller.*.*(..))") public void pt(){} @Before("pt()") public void doBefore(){ log.info("doBefore执行"); } @After("execution(* org.example.controller.*.*(..))") public void doAfter(){ log.info("doAfter执行"); } @AfterReturning("execution(* org.example.controller.*.*(..))") public void doAfterReturning(){ log.info("doAfterReturning执行"); } @AfterThrowing("execution(* org.example.controller.*.*(..))") public void doAfterThrowing(){ log.info("doAfterThrowing执行"); } @Around("execution(* org.example.controller.*.*(..))") public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ log.info("doAround前执行"); Object result = pjp.proceed(); log.info("doAround前执行"); return result; }}
测试代码
package org.example.controller;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.annotation.Pointcut;import org.example.aspect.TimeRecord;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j@RequestMapping("/testA")@RestControllerpublic class TestController { @TimeRecord @RequestMapping("/t1") public String t1(){ //int a = 10/0; log.info("执行T1方法"); return "t1"; } @RequestMapping("/t2") public int t2(){ String aa = "abc"; log.info("执行T2方法"); return aa.length(); } @RequestMapping("/t3") public int t3(){ int[] a = {1,2,3}; int val = a[2]; log.info("执行T3方法"); return val; }}
运行结果
通过运行结果,我们可得,通知的先后顺序基本上为这个
@order 切面优先级
@Order
注解是用来设置Bean的加载顺序的,值越小,优先级越高。权限控制等。