精华内容
下载资源
问答
  • 切面实现
    千次阅读
    2017-12-05 19:51:06

    什么是基于注解的切面实现

    我们使用切面来非侵入式操作程序方法,常用的场景如日志记录、权限判断等。
    下面我实现权限判断的切面。

    分析:
    要实现基于注解的切面,我们要定义“注解”,定义切面,定义权限验证,定义权限返回。

    1. 定义注解:PermissionCheck.java
    @Target({ElementType.TYPE, ElementType.METHOD}) // 注解类型, 级别
    @Retention(RetentionPolicy.RUNTIME) // 运行时注解
    public @interface PermissionCheck {
    
        String value() default "";
    
    }
    
    1. 定义权限校验方法,这里定义 AuthService 和它的实现。
    public interface AuthService {
    
        boolean checkAccess();
    }
    
    
    @Service
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public class AuthServiceImpl implements AuthService {
    
    
        @Override
        public boolean checkAccess() {
            return true;
        }
    }
    

    根据需要可改写 checkAccess 方法。

    1. 定义一个advice 来处理校验结果:
    @ControllerAdvice
    public class PermissionAdvice {
    
        @ExceptionHandler(value = PermissionCheckException.class)
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        public String dealWithPermissionCheckException(PermissionCheckException exception) {
            System.out.println(exception.getMessage());
            return exception.getMessage();
        }
    
    
        public String dealWithPermissionCheckException() {
    
            return null;
        }
    }
    
    
    1. 接下来就是组合进切面了
    @Aspect // 切面标识
    @Component // 交给spring容器管理
    public class PermissionAspect {
    
        @Autowired
        private AuthService authService;
    
        /**
         * 选取切入点为自定义注解
         */
        @Pointcut("@annotation(com.honeywen.credit.annotation.PermissionCheck)")
        public void permissionCheck(){}
    
    
        @Before(value = "permissionCheck()")
        public void before(JoinPoint joinPoint) throws NoSuchMethodException {
            // 获取连接点的方法签名对象
            Signature signature = joinPoint.getSignature();
            if (!(signature instanceof MethodSignature)) {
                throw new PermissionCheckException("user permission check failed , stop the request!");
    
            }
            MethodSignature methodSignature = (MethodSignature) signature;
            Object target = joinPoint.getTarget();
            // 获取到当前执行的方法
            Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
            // 获取方法的注解
            PermissionCheck annotation = method.getAnnotation(PermissionCheck.class);
            System.out.println(annotation);
            System.out.println("我是在执行业务逻辑之前");
            // 权限检查
            authService.checkAccess();
    
    
    
        }
    
        @After(value = "permissionCheck()")
        public void after(JoinPoint joinPoint) {
            System.out.println("我是在执行业务逻辑之后");
    
        }
    
    
    
    
    }
    

    总结:
    主要过程在定义切面, 然后就是切面在哪里执行:

        @Pointcut("@annotation(com.honeywen.credit.annotation.PermissionCheck)")
        public void permissionCheck(){}

    这里声明的是标注 @PermissionCheck 注解的方法执行。

    更多相关内容
  • 攻防演习的艰难决定 问题与挑战分析 解决思路 实现方案 总结(Take Away)
  • SpringBoot AOP切面实现

    千次阅读 2022-04-27 09:53:10
    文章目录一、AOP简介二、AOP体系与概念三、AOP实例1、创建SpringBoot工程2、添加依赖3、AOP相关注解3.1、@Aspect3.2、@...AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(其余两

    一、AOP简介

    AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(其余两个:IOC - 控制反转DI - 依赖注入)。


    那么AOP为何那么重要呢?

    在我们的程序中,经常存在一些系统性的需求,比如 权限校验日志记录统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护,那么面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。


    二、AOP体系与概念

    简单地去理解,其实AOP要做三类事:

    • 在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。

    • 在什么时候切入,是业务代码执行前还是执行后。

    • 切入后做什么事,比如做权限校验、日志记录等。

    AOP的体系图:
    请添加图片描述
    一些概念:

    概念说明
    Pointcut切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和 annotation 方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
    Advice处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
    Aspect切面,即 PointcutAdvice
    Joint point连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
    Weaving织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

    三、AOP实例

    1、创建SpringBoot工程

    如何创建详见:IDEA 创建 SpringBoot 项目


    2、添加依赖

    <!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    3、AOP相关注解

    package com.cw.tsb.app.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    
        @Around("pointCut()")
        public Object doAround(ProceedingJoinPoint joinPoint) {
            System.out.println("------------- doAround.");
            Object obj = null;
            try {
                obj = joinPoint.proceed();
            } catch (Throwable t){
                t.printStackTrace();
            }
            return obj;
        }
    
        @After("pointCut()")
        public void doAfter(JoinPoint joinPoint){
            System.out.println("------------- doAfter.");
        }
    
        @Before("pointCut()")
        public void doBefore(JoinPoint joinPoint){
            System.out.println("------------- doBefore.");
        }
    
        /**
         * 后置返回
         *      如果第一个参数为JoinPoint,则第二个参数为返回值的信息
         *      如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
         * returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
         *      参数为Object类型将匹配任何目标返回值
         */
        @AfterReturning(value = "pointCut()", returning = "result")
        public void doAfterReturning(JoinPoint joinPoint, String result){
            System.out.println("doAfterReturning result = " + result);
        }
    
        @AfterThrowing(value = "pointCut()", throwing = "t")
        public void doAfterThrowing(JoinPoint joinPoint, Throwable t){
            System.out.println("------------- doAfterThrowing throwable = " + t.toString());
        }
    }
    

    3.1、@Aspect

    该注解要添加在类上,声明这是一个切面类,使用时需要与@Component注解一起用,表明同时将该类交给spring管理。

    @Component
    @Aspect
    public class ControllerAspect {
    }
    

    3.2、@Pointcut

    用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。

    该注解需要添加在方法上,该方法签名必须是 public void 类型,可以将@Pointcut 中的方法看作是一个用来引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此 @Pointcut 中的方法只需要方法签名,而不需要在方法体内编写实际代码

    该注解有两个常用的表达式:execution()annotation()


    3.2.1、execution()

    @Aspect
    @Component
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    }
    

    表达式为:

    execution(* com.cw.tsb.app.controller..*.*(..))
    
    • 第一个 * :表示返回值类型,* 表示所有类型;

    • 包名:标识需要拦截的包名;

    • 包名后的 ..:表示当前包和当前包的所有子包,在本例中指 com.cw.tsb.app.controller 包、子包下所有类;

    • 第二个 * :表示类名,* 表示所有类;

    • 最后的 *(..) :星号表示方法名,* 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。


    3.2.2、annotation()

    annotation() 方式是针对某个注解来定义切点,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:

    @Aspect
    @Component
    public class ControllerAspect {
        @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    }
    

    然后使用该切面的话,就会切入注解是 @PostMapping 的所有方法。这种方式很适合处理 @GetMapping@PostMapping@DeleteMapping不同注解有各种特定处理逻辑的场景。

    还有就是如上面案例所示,针对自定义注解来定义切面。

    @Aspect
    @Component
    public class ControllerAspect {
        @Pointcut("@annotation(com.cw.tsb.app.annotation.PermissionsAnnotation)")
        private void permissionCheck() {
        	//该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    }
    

    3.3、@Around

    @Around 注解用于修饰 Around 增强处理,Around增强处理非常强大,表现在:

    • @Around 可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法。这个特性的实现在于,调用 ProceedingJoinPoint 参数的 procedd() 方法才会执行目标方法。

    • @Around 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。

    Around 增强处理有以下特点:

    • 当定义一个 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型(至少一个形参)。在增强处理方法体内,调用 ProceedingJoinPointproceed 方法才会执行目标方法:这就是 @Around 增强处理可以完全控制目标方法执行时机、如何执行的关键;如果程序没有调用 ProceedingJoinPointproceed 方法,则目标方法不会执行。

    • 调用 ProceedingJoinPointproceed 方法时,还可以传入一个 Object[] 对象,该数组中的值将被传入目标方法作为实参 —— 这就是 Around 增强处理方法可以改变目标方法参数值的关键。这就是如果传入的 Object[] 数组长度与目标方法所需要的参数个数不相等,或者 Object[] 数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。

    @Around 功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的@Before@AfterReturning 就能解决的问题,就没有必要使用 Around 了。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用 Around 。尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法的返回值时,则只能使用 Around 增强处理了。


    3.4、@Before

    @Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,比如 获取用户的请求 URL 以及 用户的 IP 地址等等,这个在做个人站点的时候都能用得到,都是常用的方法。例如下面代码:

    @Aspect
    @Component
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    
    	/**
         * 在上面定义的切面方法之前执行该方法
         * @param joinPoint jointPoint
         */
        @Before("pointCut()")
        public void doBefore(JoinPoint joinPoint) {
            // 获取签名
            Signature signature = joinPoint.getSignature();
            // 获取切入的包名
            String declaringTypeName = signature.getDeclaringTypeName();
            // 获取即将执行的方法名
            String funcName = signature.getName();
            log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);
    
            // 也可以用来记录一些信息,比如获取请求的 URL 和 IP
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 获取请求 URL
            String url = request.getRequestURL().toString();
            // 获取请求 IP
            String ip = request.getRemoteAddr();
        }
    }
    

    JointPoint 对象很有用,可以用它来获取一个签名,利用签名可以获取请求的包名、方法名,包括参数(通过 joinPoint.getArgs() 获取)等。


    3.5、@After

    @After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。

    @Aspect
    @Component
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    
    	/**
         * 在上面定义的切面方法之后执行该方法
         * @param joinPoint jointPoint
         */
        @After("pointCut()")
        public void doAfter(JoinPoint joinPoint) {
            log.info("==== doAfter 方法进入了====");
            Signature signature = joinPoint.getSignature();
            String method = signature.getName();
            log.info("方法{}已经执行完", method);
        }
    }
    

    到这里,我们来写个 Controller 测试一下执行结果,新建一个 AopController 如下:

    @RestController
    @RequestMapping("/aop")
    public class AopController {
    
        @GetMapping("/{name}")
        public String testAop(@PathVariable String name) {
            return "Hello " + name;
        }
    }
    

    启动项目,在浏览器中输入:http://localhost:8080/aop/csdn,观察一下控制台的输出信息:

    ====doBefore 方法进入了====  
    即将执行方法为: testAop,属于com.itcodai.mutest.AopController包  
    用户请求的 url 为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1  
    ==== doAfter 方法进入了====  
    方法 testAop 已经执行完
    

    从打印出来的 Log 中可以看出程序执行的逻辑与顺序,可以很直观的掌握 @Before@After 两个注解的实际作用。


    3.6、@AfterReturning

    @AfterReturning 注解和 @After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理,例如:

    @Aspect
    @Component
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    
    	/**
         * 后置返回
         *      如果第一个参数为JoinPoint,则第二个参数为返回值的信息
         *      如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
         * returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
         *      参数为Object类型将匹配任何目标返回值
         */
        @AfterReturning(value = "pointCut()", returning = "result")
        public void doAfterReturning(JoinPoint joinPoint, String result){
            // 实际项目中可以根据业务做具体的返回值增强
        }
    }
    

    需要注意的是,在 @AfterReturning 注解 中,属性 returning 的值必须要和参数保持一致,否则会检测不到。该方法中的第二个入参就是被切方法的返回值,在 doAfterReturning 方法中可以对返回值进行增强,可以根据业务需要做相应的封装。


    3.7、@AfterThrowing

    当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。

    @Aspect
    @Component
    public class ControllerAspect {
    
        @Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")
        public void pointCut() {
            //该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。
        }
    
    	@AfterThrowing(value = "pointCut()", throwing = "t")
        public void doAfterThrowing(JoinPoint joinPoint, Throwable t){
            System.out.println("------------- doAfterThrowing throwable = " + t.toString());
            // 处理异常的逻辑
        }
    }
    



    展开全文
  • 2.2 切面业务日志的核心实现流程 建立日志拦截器,自定义模板 创建日志处理切面 在业务接口(controller)方法上增加日志注解。 3 切面日志的实现 3.1 创建日志拦截器 修饰符 @interface 注解名 { 属性类型 属性名() ...

    1 业务日志的标准

    业务日志,也叫操作日志。主要功能: 记录用户行为,方便业务数据回溯与统计。

    1.1 操作日志记录内容

    操作日志记录主要内容:用户是谁,在什么时间,对什么数据,做了什么样的更改。
    逻辑中必须增加业务日志的位置:
    (1)业务数据的变更处(新增、修改、删除)
    (2)特别分支条件、边界条件处。

    1.2 业务常见的日志记录形式

    ● 动态的文本记录,比如:2022-03-12 10:00 订单创建,创建用户“小新”,订单号:NO.123456 ”。
    ● 修改类型的文本,包含修改前和修改后的值,比如:2021-03-12 11:00 用户“小新”修改了订单收货人:“小新”修改成“小花” 。

    public Result applyOrder(orderRequest request){
        // 业务逻辑blabla...
        OperateLogModel operateLogModel = new  OperateLogModel();
        operateLogModel.setOperateIp();          //1、业务操作IP地址
        operateLogModel.serO[erateMis();         //2、操作人mis
        operateLogModel.setOperateDegist();      //3、业务操作具体描述           
        operateLogModel.setUserOperateType();    //4、权限操作类型; 0:增加 1:删除 2:修改
        operateLogModel.setCreateTime();         //5、操作日志生成时间 
    }
    

    2 背景知识

    AOP(Aspect-Oriented Programming)中文意思是面向切面编程。通过运行期的动态代理,实现在不修改源代码的情况下给程序动态添加功能的编程范式。可以对业务逻辑的各个模块进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    2.1 AOP实现的四个关键元素

    切面(Aspect):用于封装通用部分的组件(或者模块),比如日志组件。让日志模块可以被切入到其他目标业务方法上。
    连接点(JointPoint):程序执行的某个特定位置,是抽象概念,不涉及代码实现。(Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强)
    切入点(PointCut):用于指定哪些组件方法调用方面(共通)处理, 切点相当于查询条件,连接点相当于记录,一个切点可以匹配多个连接点
    通知(Advice):常被称为“增强”,满足切入点的一段执行代码。Spring的AOP,会将 advice 模拟为一个拦截器(interceptor),并且在 join point 上维护多个 advice,进行层层拦截。
    前置通知(Before):在目标方法调用前调用通知功能;
    后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
    返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
    异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
    环绕通知(Around):通知方法会将目标方法封装起来,在目标方法调用之前和之后执行自定义的行为。

    2.2 切面业务日志的核心实现流程

    1. 建立日志拦截器,自定义模板
    2. 创建日志处理切面
    3. 在业务接口(controller)方法上增加日志注解。

    3 切面日志的实现

    3.1 创建日志拦截器

    修饰符 @interface 注解名 {
        属性类型 属性名() default 默认值;
    }
    

    在实际工程中,首先建立以下日志注解接口,作为业务日志拦截器。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogApi {
    
        /**
         * 用户操作类型
         * @return
         */
        String operateType();
    }
    

    3.2 统一日志处理切面

         从切面的建设来说,通常可能覆盖: 1. 业务服务层 2. 数据持久层 3. 中间件访问层 4. 远程rpc/http服务层 5. 工具层
    

    其中,1是建议建设的一层,业务服务是所有出入口都经过的一层,通常即@Service 2,3,4其实都可以认为是第三方依赖层,建设这一层日志有助于更细级别的追踪,因为通常业务服务可能会组合多个操作 5层暂定是一些工具类、转换类等,需要有统一的特征(可以约定)来通过切面拦截

    @Slf4j
    @Aspect
    @Component
    public class LogAspect {
    
        /**
         * 定义切面
         */
        @Pointcut("@annotation(com.gitee.theskyone.bird.LogAspect.LogApi)")
        public void logPointcut() {
            throw new UnsupportedOperationException();
        }
    
        @Around("logPointcut()")
        public Object handleAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            //获取当前请求对象,记录请求信息
            Object[] args = proceedingJoinPoint.getArgs();
            Object result = proceedingJoinPoint.proceed();
            //获取方法连接点的相关信息
            MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
            String methodName = methodSignature.getName();
            Method method = methodSignature.getMethod();
            LogApi logApi =  method.getAnnotation(LogApi.class);
            log.info("用户操作类型:  {}", logApi.operateType());
            log.info("请求方法      : 【{}】", methodName );
            log.info("请求参数      :  {}", args);
            log.info("返回结果      :  {}", Objects.isNull(result) ? "" : result);
            log.info("方法执行总耗时 :  {} ms", System.currentTimeMillis() - start);
            return result;
        }
    
        /**
         * 日志记录(实际工程中可改成异步日志持久化)
         * @param joinPoint
         * @param userName
         * @return
         */
        ServiceLog saveLog(ProceedingJoinPoint joinPoint, String userName) {
            return ServiceLog.builder()
                .traceId(UUID.randomUUID().toString())
                .createTime(LocalDateTime.now())
                //  暂只支持类名.方法名方式
                .operation(joinPoint.getSignature().toShortString())
                .operator(userName)
                .build();
        }
    }
    
    

    3.3 日志注解使用于业务

    在项目中做自定义日志切面,业务中以注解方式记录。
    (1)在进入 Controller 方法前,打印出请求参数、调用的方法名。
    (2) 在方法逻辑执行后,打印出结果以及耗时。

    @RestController
    @RequestMapping("/log")
    public class LogController  extends HandlerInterceptorAdapter {
        //LoggerFactory是slf4j的日志对象工程
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @LogApi(operateType = "create")
        @RequestMapping(value = "/userOperation", method = RequestMethod.GET)
        public List<String> getUserInfo(@RequestParam(value = "userName") String userName,
            @RequestParam(value = "orderNumber") String orderNumber) {
            //建立虚拟返回参数
            List<String> resultList = new ArrayList<>();
            resultList.add("原订单收货人:newBird");
            resultList.add("新订单收货人:flyBird");
            return resultList;
        }
    
    }
    

    补充代码逻辑避坑

    (1)Json记录业务日志耗能
    json编码对CPU损耗非常大,如果只是日志记录,别用这么重的编码形式,精简日志或者简单字符串拼接会更经济。
    (2)代码中的shopInfoVO若为NULL,会引入了异常导致影响业务主流程

    catch(Exception e){ **加粗样式**
        logger.error("[getShopInfoVo] error shopInfoVO={}",shopInfoVO,e); 
    }
    
    展开全文
  • Spring AOP 切面实现参数校验

    千次阅读 2020-04-16 17:20:22
    Spring AOP 切面实现参数校验 思路与原理 整个实现需要两个注解和一个切面,分别是: @Verifys : 用于标记需要进行参数校验的方法。 @Verify : 用于标记具体属性,包括是否需要校验和校验的规则。 切面 : 利用...

    Spring AOP 切面实现参数校验

    思路与原理

    整个实现需要两个注解和一个切面,分别是:

    1. @Verifys : 用于标记需要进行参数校验的方法。
    2. @Verify : 用于标记具体属性,包括是否需要校验和校验的规则。
    3. 切面 : 利用反射实现校验的逻辑。

    首先根据注解获取需要进行检验的方法,之后利用java反射的机制,获取每个被@Verify注解标记的字段,最后进行校验。代码量很少,思路也比较简单。

    代码

    /**
     * @author 
     * @version 1.0
     * @date 2020/4/16
     **/
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Verifys {
    }
    
    /**
     * @author 
     * @version 1.0
     * @date 2020/4/16
     **/
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Verify {
        boolean required() default true;
    
        String regular() default "";
    }
    
    /**
     * @author 
     * @version 1.0
     * @date 2020/4/16
     **/
    @Component
    @Aspect
    @Slf4j
    public class FieldVerify {
        @Before(value = "@annotation(com.utils.Verifys)")
        public void fieldVerify(JoinPoint point) throws IllegalAccessException {
            // 获取方法的请求参数并遍历
            Object[] requestParams = point.getArgs();
            for (Object requestParamObj : requestParams) {
                Field[] declaredFields = requestParamObj.getClass().getDeclaredFields();
                // 遍历所有属性,并针对有@Verify注解的属性进行校验
                for (Field field : declaredFields) {
                    field.setAccessible(true);
                    if (field.isAnnotationPresent(Verify.class)) {
                        Verify verify = field.getAnnotation(Verify.class);
                        boolean required = verify.required();
                        String regular = verify.regular();
                        Object fieldObj = field.get(requestParamObj);
                        if (required) {
                            if (Objects.isNull(fieldObj)) {
                                log.error("萌萌的前端将 {} 传了 null ", field.getName());
                                throw new RuntimeException("萌萌的前端将 " + field.getName() + " 传了null");
                            }
                        }
                        if (!StringUtils.isEmpty(regular)) {
                            Pattern pattern = Pattern.compile(regular);
                            if (!pattern.matcher(String.valueOf(fieldObj)).matches()) {
                                log.error("萌萌的前端将 {} 传了不合要求的参数", field.getName());
                                throw new RuntimeException("萌萌的前端将 " + field.getName() + " 传了不合要求的参数");
                            }
                        }
                    }
                }
            }
        }
    }
    

    使用

    首先在需要进行校验的方法打上@Verifys注解。

        @PostMapping("/create")
        @ApiOperation("")
        @Verifys
        public ResponseEntity createAward(@RequestBody CreateSupplyAwardDto createSupplyAwardDto) {
            return supplyflowAwardProcessService.createProcess(createSupplyAwardDto);
        }
    

    之后标记需要校验的属性就可以,支持正则匹配,还是挺方便的。

    @Data
    public class CreateSupplyAwardDto {
    
        @Verify
        @ApiModelProperty(value = "")
        private String workflowId;
    
        @Verify
        @ApiModelProperty(value = "创建人角色")
        private Integer createUserRole;
    
        @Verify(regular = "[0-5]")
        @ApiModelProperty(value = "")
        private Integer processType;
    
        @Verify
        @ApiModelProperty(value = "创建人")
        private Long createUser;
    
        @ApiModelProperty(value = "备注")
        private String Remark;
    }
    
    展开全文
  • 2020 CIS基于安全切面实现银行级默认安全.pdf
  • 2020CIS基于安全切面实现银行级默认安全 漏洞检测
  • 「漏洞分析」基于安全切面实现银行级默认安全 - WAF 移动安全 安全知识 应急响应 安全防护 勒索软件
  • 第一步先创建一个注解类 @Retention(RetentionPolicy.RUNTIME) ...第二步,定义切面 @Aspect @Component public class LogAspect { //定义切点,指向一个我刚定义的注解 @Pointcut("@annotation(org.siteinfo.c
  • Java 利用AOP切面实现自定义注解示例

    千次阅读 2019-07-22 16:30:21
    * AOP为Aspect Oriented Programming的缩写,意为:面向切面编程 * AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构 * AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到...
  • 今天主要说说如何通过自定义注解的方式,在 Spring Boot 中来实现 AOP 切面统一打印出入参日志。小伙伴们可以收藏一波。 废话不多说,进入正题 ! 目录 一、先看看切面日志输出效果 二、添加 AOP Maven
  • AOP切面实现方法日志打印耗时计算

    千次阅读 2018-04-12 17:40:57
    很简单,通过AOP实现每个方法访问时候统一进行日志打印和耗时计算,相关配置:1、spring配置在spring配置xml文件中设置启用aop &lt;aop:aspectj-autoproxy proxy-target-class="true" /&gt;2、aop...
  • 项目中经常会看到在所有的Rpc接口实现中记录请求参数,以及try catch,每个方法都来一次,看着就不舒服,类似下面这段代码 public CloudServerResponse<Boolean> updateExpressInfo(SendAppraisalExpressParam...
  • 1.只需要在controller层增加自定义@RequestLog注解就可以实现了。 @RequestLog功能参数如下: 功能一:是否记录请求参数 功能二:是否记录请求日志 功能三:是否记录返回值 功能四:是否以debug形式记录 功能五:日志类型 ...
  • 此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为...
  • AOP切面实现

    2019-07-25 16:27:08
    AOP切面实现 AOP的全称是Aspect Orient Programming,即面向切面编程。是对OOP(Object Orient Programming)的一种补充,战门用于处理一些具有横切性质的服务。常常用于日志输出、安全控制等。 最近遇到增加操作...
  • NULL 博文链接:https://zyssnh.iteye.com/blog/1866867
  • 文章目录1、导入相关的依赖2、创建要保存的数据信息实体类3 、编写对应的sql语句4、使用spring 的 aop 技术切到自定义注解上,所以先创建一个自定义注解类5、 创建aop切面实现类6、在实体类中的具体应用7、实现的...
  • 何为AOP,AOP的注解详解,AOP的使用详例,以上内容尽在本文
  • AOP面向切面编程的三种实现方式

    千次阅读 2021-10-13 20:34:24
    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要...
  • spring-aop 三种实现切面的方式

    千次阅读 2021-07-09 13:51:01
    AOP (Aspect Oriented Programming)意为∶面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式...
  • 拦截切面实现 /** * 统一缓存自定义注解拦截实现 * * @author mengq */ @Aspect @Component public class CacheAspect { private static final Logger log = LoggerFactory.getLogger(CacheAspect.class);...
  • Java实现AOP切面记录日志实例

    千次阅读 2020-01-19 16:19:19
    注:一个可以直接拿去用的aop切面保存系统操作日志的实例,应用框架为...3、实现监听注解,调用切面实现类 4、配置启动对@AspectJ注解的支持及监听类 5、业务Controller引用 具体实现: 1、建一个接口类,内容如下 ...
  • 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个...
  • AOP(面向切面编程)的三种实现方式

    千次阅读 2021-08-21 16:42:28
    1.切面(Advisor)= 切点(Pointcut)+ 通知(Advice) 2.前置通知(MethodBeforeAdvice接口)、后置通知(AfterReturningAdvice接口)、环绕通知(MethodInterceptor接口)、异常通知(throwsAdvice) 3.配置代理...
  • Spring切面实现自定义注解

    千次阅读 2019-06-06 11:23:16
    分别测试一下hello和hello2这两个接口,输入: http://localhost:8080/aspect/hello和http://localhost:8080/aspect/hello2,然后去看代码,可以看到注解中的配置已经被读取到了,而且在运行两个接口之前,切面也...
  • 对应的博客链接:http://blog.csdn.net/JQ_AK47/article/details/60469034#t12
  • 一,AOP切面实现 首先在pom里依赖aop,版本号:2.1.0.RELEASE 这里用Aop主要实现日志及异常处理,首先我们在接口层(lyn-web)创建一个Aop的切面类,如下: 定义好切面,然后写前置通知,后置通知,环绕通知...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 181,641
精华内容 72,656
关键字:

切面实现