首页 > 酒店新闻 > 公司新闻

FB娱乐官方网站|首创主播互动真人游戏|Spring AOP中定义切点(PointCut)和通知(Advice)

发布时间:2025-10-15 17:58:13    次浏览

点击上面“天码营”,加入我们,快速成长~「内容简介」本文讨论一下SpringAOP编程中的两个关键问题,定义切点和定义通知,理解这两个问题能应付大部分AOP的场景。如果你还不熟悉AOP,请先看,本文的例子也沿用了中的例子。切点表达式切点的功能是指出切面的通知应该从哪里织入应用的执行流。切面只能织入公共方法。在Spring AOP中,使用AspectJ的切点表达式语言定义切点其中excecution()是最重要的描述符,其它描述符用于辅助excecution()。excecution()的语法如下execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)这个语法看似复杂,但是我们逐个分解一下,其实就是描述了一个方法的特征:问号表示可选项,即可以不指定。excecution(* com.tianmaying.service.BlogService.updateBlog(..)) modifier-pattern:表示方法的修饰符 ret-type-pattern:表示方法的返回值 declaring-type-pattern?:表示方法所在的类的路径 name-pattern:表示方法名 param-pattern:表示方法的参数 throws-pattern:表示方法抛出的异常 注意事项 其中后面跟着“?”的是可选项。 在各个pattern中,可以使用'*'来表示匹配所有。 在param-pattern中,可以指定具体的参数类型,多个参数间用“,”隔开,各个也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String)表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。 可以用(..)表示零个或多个任意的方法参数。 使用符号表示与关系,使用||表示或关系、使用!表示非关系。在XML文件中使用and、or和not这三个符号。在切点中引用BeanSpring还提供了一个bean()描述符,用于在切点表达式中引用Spring Beans。例如: excecution(* com.tianmaying.service.BlogService.updateBlog(..)) and bean('tianmayingBlog')这表示将切面应用于BlogService的updateBlog方法上,但是仅限于ID为tianmayingBlog的Bean。也可以排除特定的Bean:excecution(* com.tianmaying.service.BlogService.updateBlog(..)) and !bean('tianmayingBlog') 其它切点描述符其它可用的描述符包括: args() @args() execution() this() target() @target() within() @within() @annotation 当你有更加复杂的切点需要描述时,你可能可以用上这些描述符,通过这些你可以设置目标类实现的接口、方法和类拥有的标注等信息。具体可以参考Spring的。这里一共有9个描述符,execution()前面已经详细讨论过,其它几个可以做一个简单的分类: this()是用来限定方法所属的类,比如this(com.tianmaying.service.BlogServiceInterface)表示实现了com.tianmaying.service.BlogServiceInterface的所有类。如果this括号内是具体类而不是接口的话,则表示单个类。 @annotation表示具有某个标注的方法,比如@annotation(org.springframework.transaction.annotation.Transactional)表示被Transactional标注的方法 args 表示方法的参数属于一个特定的类 within 表示方法属于一个特定的类 target 表示方法所属的类 它们对应的加了@的版本则表示对应的类具有某个标注。 单独定义切点 详细了解了定义切点之后,在回顾上一节中的代码:package com.tianmaying.aopdemo.aspect;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect //1@Componentpublic class LogAspect { @Pointcut('execution(* com.tianmaying.aopdemo..*.bookFlight(..))') //2 private void logPointCut() { } @AfterReturning(pointcut = 'logPointCut()', returning = 'retVal') //3 public void logBookingStatus(boolean retVal) { //4 if (retVal) { System.out.println('booking flight succeeded!'); } else { System.out.println('booking flight failed!'); } }}可以看到通过标注方式定义切点只需要两个步骤: 定义一个空方法 使用@Piontcut标注,填入切点表达式 @AfterReturning(pointcut = 'execution(* com.tianmaying.aopdemo..*.bookFlight(..))', returning = 'retVal')中通过pointcout = 'logPointCut'引用了这个切点。当然也可以在@AfterReturning()直接定义切点表达式,如:@AfterReturning(pointcut = 'logPointCut()', returning = 'retVal') //3推荐使用前一种方法,因为这样可以在多个通知中复用切点的定义。切点定义实例这里我们给出一些切点的定义实例。@Pointcut('execution(public * *(..))') // 1private void anyPublicOperation() {}@Pointcut('within(com.xyz.someapp.web..*))') // 2private void inTrading() {}@Pointcut('anyPublicOperation() inTrading()') // 3private void tradingOperation() {}@within(org.springframework.transaction.annotation.Transactional) // 4private void transactionalClass() {}@annotation(org.springframework.transaction.annotation.Transactional) //5private void transactionalMethod() {}上面的代码定义了三个切点: 任意公共方法(实际应用中一般不会定义这样的切点) 在within(com.xyz.someapp.web包或者其子包下任意类的方法 同时满足切点1和切点2条件的切点,这里使用了符号 标注了Transactional的类的方法 标注了Transactional的方法 定义通知 依然回到TimeRecordingAspect的代码:@Aspect@Componentpublic class TimeRecordingAspect { @Pointcut('execution(* com.tianmaying.aopdemo..*.bookFlight(..))') private void timeRecordingPointCut() { } @Around('timeRecordingPointCut()') //1 public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //2 long start = System.currentTimeMillis(); Object retVal = pjp.proceed(); // 3 long duration = System.currentTimeMillis() - start; System.out.println(String.format( 'time for booking flight is %d seconds', duration)); return retVal; }}定义了切点之后,我们需要定义何时调用recordTime方法记录时间,即需要定义通知。AspectJ提供了五种定义通知的标注: @Before:前置通知,在调用目标方法之前执行通知定义的任务 @After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务 @After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务 @After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务 @Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务 通过标注定义通知只需要两个步骤: 将以上五种标注之一添加到切面的方法中 在标注中设置切点的定义 创建环绕通知 环绕通知相比其它四种通知有其特殊之处。环绕通知本质上是将前置通知、后置通知和异常通知整合成一个单独的通知。用@Around标注的方法,该方法必须有一个ProceedingJoinPoint类型的参数,比如上面代码中的recordTime的签名:public Object recordTime(ProceedingJoinPoint pjp) throws Throwable在方法体中,需要通过这个参数,以joinPoint.proceed();的形式调用目标方法。注意在环绕通知中必须进行该调用,否则目标方法本身的执行就会被跳过。比如在recoredTime的实现中:long start = System.currentTimeMillis();Object retVal = pjp.proceed();long duration = System.currentTimeMillis() - start;System.out.println(String.format('time for booking flight is %d seconds', duration));在目标方法调用前首先记录系统时间,然后通过pjp.proceed()调用目标方法,调用完之后再次记录系统时间,即可计算出目标方法的耗时。处理通知中参数有时我们需要给通知中的方法传递目标对象的一些信息,比如传入目标业务方法的参数。在前面的代码中我们曾经通过@AfterReturning(pointcut = 'logPointCut()', returning = 'retVal')在通知中获取目标业务方法的返回值。获取参数的方式则需要使用关键词是args。假设需要对系统中的accountOperator方法,做Account的验证,验证逻辑以切面的方式显示,示例如下:@Before('com.tianmaying.UserService.accountOperator() args(account,..)')public void validateAccount(Account account) { // ... // 这可以获取传入accountOperator中的Account信息}args()中参数的名称必须跟切点方法的签名中(public void validateAccount(Account account))的参数名称相同。如果使用切点函数定义,其中的参数名称也必须与通知方法签名中的参数完全相同,例如:@Pointcut('com.tianmaying.UserService.accountOperator() args(account,..)')private void accountOperation(Account account) {}@Before('accountOperation(account)')public void validateAccount(Account account) { // ...} 小节AOP的知识就介绍到这里,更复杂的场景还需要了解AOP更深入的一些知识,比如: AOP的生成代理的方式 多个切面的顺序 更复杂的参数类型(如泛型) 使用AspectJ的切面 ... 感兴趣的同学可以继续深入学习,最好的学习材料就是Spring的。天码营外围的网站开发的也基本只使用了我们介绍的这些知识点,可见这些关键知识点足以解决大部分复杂场景,确实需要用到更高级的特性时,再去参考文档即可。了解更多Java Web开发内容: Spring MVC实战开发:两周打造完整的博客系统 三个月时间如何成为Java Web全栈工程师? 点击下方“阅读原文”,可以获得更多天码营教程。