首页 文章资讯内容详情

Spring AOP用法详解

2026-06-01 3 花语

本文内容纲要:

什么是AOP

AOP:AspectOrientedProgramming,中文翻译为”面向切面编程“。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码

AOP把软件的功能模块分为两个部分:核心关注点横切关注点

。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离

使用AOP有诸多好处,如:

1.集中处理某一关注点/横切逻辑 2.可以很方便的添加/删除关注点 3.侵入性少,增强代码可读性及可维护性 AOP的术语

1.Joinpoint(连接点) Spring官方文档的描述:

Apointduringtheexecutionofaprogram,suchastheexecutionofamethodorthehandlingofanexception.InSpringAOP,ajoinpointalwaysrepresentsamethodexecution.

程序执行过程中的一个点,如方法的执行或异常的处理。在SpringAOP中,连接点总是表示方法的执行。通俗的讲,连接点即表示类里面可以被增强的方法

2.Pointcut(切入点)

Pointcutareexpressionsthatismatchedwithjoinpointstodeterminewhetheradviceneedstobeexecutedornot.PointcutusesdifferentkindsofexpressionsthatarematchedwiththejoinpointsandSpringframeworkusestheAspectJpointcutexpressionlanguage

切入点是与连接点匹配的表达式,用于确定是否需要执行通知。切入点使用与连接点匹配的不同类型的表达式,Spring框架使用AspectJ切入点表达式语言。我们可以将切入点理解为需要被拦截的Joinpoint

3.Advice(增强/通知)

所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知和环绕通知(切面要完成的功能)

4.Aspect(切面) Aspect切面表示Pointcut(切入点)和Advice(增强/通知)的结合 SpringAOP用法

示例代码

/** *设置登录用户名 */ publicclassCurrentUserHolder{ privatestaticfinalThreadLocal<String>holder=newThreadLocal<>(); publicstaticStringget(){ returnholder.get(); } publicstaticvoidset(Stringuser){ holder.set(user); } } /** *校验用户权限 */ @Service("authService") publicclassAuthServiceImplimplementsAuthService{ @Override publicvoidcheckAccess(){ Stringuser=CurrentUserHolder.get(); if(!"admin".equals(user)){ thrownewRuntimeException("该用户无此权限!"); } } } /** *业务逻辑类 */ @Service("productService") publicclassProductServiceImplimplementsProductService{ @Autowired privateAuthServiceauthService; @Override publicLongdeleteProductById(Longid){ System.out.println("删除商品id为"+id+"的商品成功!"); returnid; } @Override publicvoiddeleteProductByName(Stringname){ System.out.println("删除商品名称为"+name+"的商品成功!"); } @Override publicvoidselectProduct(Longid){ if("100".equals(id.toString())){ System.out.println("查询商品成功!"); }else{ System.out.println("查询商品失败!"); thrownewRuntimeException("该商品不存在!"); } } }

1.使用within表达式匹配包类型

//匹配ProductServiceImpl类里面的所有方法 @Pointcut("within(com.aop.service.impl.ProductServiceImpl)") publicvoidmatchType(){} //匹配com.aop.service包及其子包下所有类的方法 @Pointcut("within(com.aop.service..*)") publicvoidmatchPackage(){}

2.使用this、target、bean表达式匹配对象类型

//匹配AOP对象的目标对象为指定类型的方法,即ProductServiceImpl的aop代理对象的方法 @Pointcut("this(com.aop.service.impl.ProductServiceImpl)") publicvoidmatchThis(){} //匹配实现ProductService接口的目标对象 @Pointcut("target(com.aop.service.ProductService)") publicvoidmatchTarget(){} //匹配所有以Service结尾的bean里面的方法 @Pointcut("bean(*Service)") publicvoidmatchBean(){}

3.使用args表达式匹配参数

//匹配第一个参数为Long类型的方法 @Pointcut("args(Long,..)") publicvoidmatchArgs(){}

4.使用@annotation、@within、@target、@args匹配注解

//匹配标注有AdminOnly注解的方法 @Pointcut("@annotation(com.aop.annotation.AdminOnly)") publicvoidmatchAnno(){} //匹配标注有Beta的类底下的方法,要求annotation的Retention级别为CLASS @Pointcut("@within(com.google.common.annotations.Beta)") publicvoidmatchWithin(){} //匹配标注有Repository的类底下的方法,要求annotation的Retention级别为RUNTIME @Pointcut("@target(org.springframework.stereotype.Repository)") publicvoidmatchTarget(){} //匹配传入的参数类标注有Repository注解的方法 @Pointcut("@args(org.springframework.stereotype.Repository)") publicvoidmatchArgs(){}

5.使用execution表达式

execution表达式是我们在开发过程中最常用的,它的语法如下:

modifier-pattern:用于匹配public、private等访问修饰符

ret-type-pattern:用于匹配返回值类型,不可省略

declaring-type-pattern:用于匹配包类型

modifier-pattern(param-pattern):用于匹配类中的方法,不可省略

throws-pattern:用于匹配抛出异常的方法

代码示例: @Component @Aspect publicclassSecurityAspect{ @Autowired privateAuthServiceauthService; //匹配com.aop.service.impl.ProductServiceImpl类下的方法名以delete开头、参数类型为Long的public方法 @Pointcut("execution(public*com.aop.service.impl.ProductServiceImpl.delete*(Long))") publicvoidmatchCondition(){} //使用matchCondition这个切入点进行增强 @Before("matchCondition()") publicvoidbefore(){ System.out.println("before前置通知......"); authService.checkAccess(); } }

单元测试:

@RunWith(SpringRunner.class) @SpringBootTest publicclassSpringBootApplicationTests{ @Autowired privateProductServiceproductService; @Test publicvoidcontextLoads(){ //设置用户名 CurrentUserHolder.set("hello"); productService.selectProduct(100L); productService.deleteProductByName("衣服"); productService.deleteProductById(100L); } }

运行结果(只有deleteProductById方法拦截成功):

查询商品成功! 删除商品名称为衣服的商品成功! before前置通知...... java.lang.RuntimeException:该用户无此权限! atcom.aop.service.impl.AuthServiceImpl.checkAccess(AuthServiceImpl.java:15) atcom.aop.security.SecurityAspect.before(SecurityAspect.java:50)

可以在多个表达式之间使用连接符匹配多个条件,如使用||表示“或”,使用&&表示“且”

//匹配com.aop.service.impl.ProductServiceImpl类下方法名以select或delete开头的所有方法 @Pointcut("execution(*com.aop.service.impl.ProductServiceImpl.select*(..))||"+ "execution(*com.aop.service.impl.ProductServiceImpl.delete*(..))") publicvoidmatchCondition(){} //使用matchCondition这个切入点进行增强 @Before("matchCondition()") publicvoidbefore(){ System.out.println("before前置通知......"); authService.checkAccess(); }

单元测试:

@Test publicvoidcontextLoads(){ CurrentUserHolder.set("admin"); productService.selectProduct(100L); productService.deleteProductByName("衣服"); productService.deleteProductById(100L); }

运行结果(所有方法均拦截成功):

before前置通知...... 查询商品成功! before前置通知...... 删除商品名称为衣服的商品成功! before前置通知...... 删除商品id为100的商品成功!

6.Advice注解

Advice注解一共有五种,分别是:

1.@Before前置通知

前置通知在切入点运行前执行,不会影响切入点的逻辑

2.@After后置通知

后置通知在切入点正常运行结束后执行,如果切入点抛出异常,则在抛出异常前执行

3.@AfterThrowing异常通知

异常通知在切入点抛出异常前执行,如果切入点正常运行(未抛出异常),则不执行

4.@AfterReturning返回通知

返回通知在切入点正常运行结束后执行,如果切入点抛出异常,则不执行

5.@Around环绕通知

环绕通知是功能最强大的通知,可以在切入点执行前后自定义一些操作。环绕通知需要负责决定是继续处理joinpoint(调用ProceedingJoinPoint的proceed方法)还是中断执行

示例代码: //匹配com.aop.service.impl.ProductServiceImpl类下面的所有方法 @Pointcut("execution(*com.aop.service.impl.ProductServiceImpl.*(..))") publicvoidmatchAll(){} @Around("matchAll()") publicObjectaround(ProceedingJoinPointjoinPoint){ Objectresult=null; authService.checkAccess(); System.out.println("befor在切入点执行前运行"); try{ result=joinPoint.proceed(joinPoint.getArgs());//获取参数 System.out.println("after在切入点执行后运行,result="+result); }catch(Throwablee){ System.out.println("after在切入点执行后抛出exception运行"); e.printStackTrace(); }finally{ System.out.println("finally......"); } returnresult; }

单元测试:

@Test publicvoidcontextLoads(){ CurrentUserHolder.set("admin"); productService.deleteProductById(100L); productService.selectProduct(10L); }

运行结果:

before在切入点执行前运行 删除商品id为100的商品成功! after在切入点执行后运行,result=100 finally...... before在切入点执行前运行 查询商品失败! after在切入点执行后抛出exception运行 java.lang.RuntimeException:该商品不存在! atcom.aop.service.impl.ProductServiceImpl.selectProduct(ProductServiceImpl.java:41) atcom.aop.service.impl.ProductServiceImpl$$FastClassBySpringCGLIB$$f17a76a2.invoke(<generated>) finally......

在执行ProceedingJoinPoint对象的proceed方法前相当于Before前置通知;执行proceed方法相当于运行切入点(同时可以获取参数);在方法执行之后相当于After后置通知,如果运行切入点抛出异常,则catch中的内容相当于AfterThrowing异常通知;finally中的内容无论切入点是否抛出异常,都将执行

本文内容总结:

原文链接:https://www.cnblogs.com/liantdev/p/10125284.html