spring---面向切面(AOP @Pointcut 注解篇)
2.1 第一个实例
接下来,我们先看一个极简的例子:所有的get请求被调用前在控制台输出一句"get请求的advice触发了"。
具体实现如下:
1、创建一个AOP切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现advice:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @Description: * @author: 张重虎 * @Date: 2022/2/8 17:02 * @Version 1.0 */ @Aspect @Component public class LogAdvice { /** * 定义一个切点:所有被 GetMapping 注解修饰的方法都会被织入 advice */ @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") private void logAdvicePointcut(){} /** * 表示 logAdvice 将在目标方法执行前执行 */ @Before("logAdvicePointcut()") public void logAdvice(){ //这里只是一个示例,你可以写任何处理逻辑 System.out.println("切面 @Before 执行了"); } }
2、创建一个接口类,内部创建一个get请求:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description: * @author: 张重虎 * @Date: 2022/2/8 17:12 * @Version 1.0 */ @RestController @RequestMapping(value = "/aop") public class AopController { @GetMapping("/getTest") public JSONObject aopTest() { System.out.println("Get 方法代码执行中"); return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } @PostMapping("/postTest") public JSONObject aopTestTwo(){ System.out.println("Post 方法代码执行中"); return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } }
项目启动后,Get 方式请求http://localhost:8080/aop/getTest接口:
Post 方式请求http://localhost:8080/aop/postTest接口,控制台无输出,证明切点确实是只针对被GetMapping修饰的方法。
2.2 第二个实例
下面我们将问题复杂化一些,该例的场景是:
1、 自定义一个注解PermissionsAnnotation
2、 创建一个切面类,切点设置为拦截所有标注PermissionsAnnotation的方法,截取到接口的参数,进行简单的权限校验
3、 将PermissionsAnnotation标注在测试接口类的测试接口test上
具体的实现步骤:
1、 使用@Target、@Retention、@Documented自定义一个注解:
import java.lang.annotation.*; /** * @Description: 自定义注解 * @author: 张重虎 * @Date: 2022/2/8 17:29 * @Version 1.0 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PermissionAnnotation { }
2、创建第一个AOP切面类,,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现第一步权限校验逻辑:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @Description: * @author: 张重虎 * @Date: 2022/2/8 17:53 * @Copyright: Xi'an Dian Tong Software Co., Ltd. All Rights Reserved. * @Version 1.0 */ @Aspect @Component @Order(1) public class PermissionFirstAdvice { @Pointcut("@annotation(com.example.zhangchonghu.demo.controller.aop3.PermissionAnnotation)") private void permissionCheck(){} @Around("permissionCheck()") public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===================第一个切面===================:" + System.currentTimeMillis()); //获取请求参数,详见接口类 Object[] objects = joinPoint.getArgs(); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name); // id小于0则抛出非法id的异常 if (id < 0) { return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}"); } return joinPoint.proceed(); } }
3、创建接口类,并在目标方法上标注自定义注解 PermissionsAnnotation:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @Description: * @author: 张重虎 * @Date: 2022/2/8 17:58 * @Copyright: Xi'an Dian Tong Software Co., Ltd. All Rights Reserved. * @Version 1.0 */ @RestController @RequestMapping("/permission") public class TestController { @RequestMapping(value = "/check", method = RequestMethod.POST) // 添加自定义注解 @PermissionAnnotation() public JSONObject getGroupList(@RequestBody JSONObject request) { System.out.println("方法中打印请求参数"+request); return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } }
有人会问,如果我一个接口想设置多个切面类进行校验怎么办?这些切面的执行顺序如何管理?
很简单,一个自定义的AOP注解可以对应多个切面类,这些切面类执行顺序由@Order注解管理,该注解后的数字越小,所在切面类越先执行。
下面在实例中进行演示:
创建第二个AOP切面类,在这个类里实现第二步权限校验:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @Description: * @author: 张重虎 * @Date: 2022/2/8 18:07 * @Version 1.0 */ @Aspect @Component @Order(0) public class PermissionSecondAdvice { /** * 自定义注解作为切面,凡是被这个注解定义的方法,都会被切面拦截 */ @Pointcut("@annotation(com.example.zhangchonghu.demo.controller.aop3.PermissionAnnotation)") private void permissionCheck(){} @Around("permissionCheck()") public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===================第二个切面===================:" + System.currentTimeMillis()); //获取请求参数,详见接口类 Object[] objects = joinPoint.getArgs(); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name->>>>>>>>>>>>>>>>>>>>>>" + name); // name不是管理员则抛出异常 if (!"admin".equals(name)) { return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}"); } return joinPoint.proceed(); } }
重启项目,继续测试,构造两个参数都异常的情况:
响应结果,表面第二个切面类执行顺序更靠前:
2.3 总结
@Pointcut 注解,用来定义一个切面,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。 @Pointcut 注解指定一个切面,定义需要拦截的东西,这里介绍两个常用的表达式:一个是使用 execution(),另一个是使用 annotation()。
execution表达式:
@Aspect @Component public class LogAspectHandler { /** * 定义一个切面,拦截 com.itcodai.course09.controller 包和子包下的所有方法 */ @Pointcut("execution(* com.mutest.controller..*.*(..))") public void pointCut() {} }
以 execution(* * com.mutest.controller...(..))) 表达式为例:
第一个 * 号的位置:表示返回值类型,* 表示所有类型。
包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
第二个 * 号的位置:表示类名,* 表示所有类。
(..):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
annotation() 表达式:
annotation() 方式是针对某个注解来定义切面,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") public void annotationPointcut() {}
然后使用该切面的话,就会切入注解是 @PostMapping 的所有方法。这种方式很适合处理 @GetMapping、@PostMapping、@DeleteMapping不同注解有各种特定处理逻辑的场景。
还有就是如上面案例所示,针对自定义注解来定义切面:
@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)") private void permissionCheck() {}
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-切入点(pointcut)API
- spring aop学习2:切面表达式(Pointcut express)
- Spring中切面的<aop:advisor pointcut="execution参数解析
- Spring AOP:使用NameMatchMethodPointcutAdvisor实现切面编程
- SpringAOP配置声明式切面时报错error at ::0 formal unbound in pointcut
- Spring面向切面编程AOP(around)
- Spring AOP编程-aspectJ注解开发(@Pointcut声明切点)
- Spring AOP 之二:Pointcut注解表达式
- Java入门到精通——调错篇之Spring2.5利用aspect实现AOP时报错: error at ::0 can't find referenced pointcut XXX
- Spring事务管理—aop:pointcut expression解析
- Spring之面向切面编程AOP(二)
- Spring AOP的基本原理及面向切面编程的实现
- [Spring]面向切面编程AOP【学习笔记】
- Spring之AOP,面向切面编程
- Spring AOP(面向切面示例)
- Spring 3.x企业开发(三)之AOP面向切面编程
- 5.3 AOP的3个关键概念 & 5.4 Spring的3种切入点(Pointcut)实现
- Spring学习总结之面向切面(AOP)
- Spring笔记——使用Spring进行面向切面(AOP)编程
- spring的aop面向切面(听课笔记)