使用 AOP 实现权限验证和日志打印实战(含完整代码)

小七学习网,助您升职加薪,遇问题可联系:客服微信【1099252741】 备注:来自网站

面向切面编程 AOP 在 Java 语言中运用非常广泛,无论在开源框架,还是公司项目,比比皆是。本文使用 AOP 实现了日常工作中经常用到的权限验证和日志打印,权限校验具体逻辑是拦截方法验证参数的合法…

面向切面编程 AOP 在 Java 语言中运用非常广泛,无论在开源框架,还是公司项目,比比皆是。本文使用 AOP 实现了日常工作中经常用到的权限验证和日志打印,权限校验具体逻辑是拦截方法验证参数的合法性,日志打印具体逻辑是打印每一个 controller 接口在被调用时的具体请求路径、执行方法名、传入的参数,返回参数及响应时间。

本文你将会获得以下知识:

  1. 统一验证方法入参实现权限验证
  2. 统一打印入参和返回参数等相关信息
  3. 为什么使用统一参数格式

适合人群: Java 初中级开发。



概念介绍

AOP

AOP 的英文全称是 Aspect Oriented Programming(面向切面编程),底层是利用预编译和运行期动态代理原理实现在方法的前后进行拦截处理,可以轻松实现权限验证、日志打印等功能。

@Aspect

Aspect 是切面的意思,此注解用于声明 Java 类为切面类,意味着切面类中可以定义 @Pointcut、@Before 等相关注解进行业务处理。

@Pointcut、execution

Pointcut 是切点的意思,即需要拦截的方法。execution 是用于匹配方法的表达式,可以精确匹配也可以模糊匹配。

@Before

Before 是在方法执行前,进行拦截处理,即前置通知。

@After

After 是在方法执行后,进行拦截处理,即后置通知,无论方法执行成功还是出现异常,都会执行后置方法。

@AfterRunning

AfterRunning 是在方法执行后,并且成功返回结果后,进行拦截处理,即返回通知,如果方法执行中出现异常,则不会会执行后置方法。

@AfterThrowing

AfterThrowing 是在方法执行过程中抛出异常后,进行拦截处理,即异常通知。

@Around

Around 是在方法的前后进行拦截处理,即环绕通知。

具体开发过程及代码分析

代码结构

在这里插入图片描述

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略,必须引入 spring-aop,代码如下:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>    <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">        <modelVersion>4.0.0</modelVersion>        <parent>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-parent</artifactId>            <version>2.5.5</version>            <relativePath/> <!-- lookup parent from repository -->        </parent>        <groupId>com.example</groupId>        <artifactId>demo-aop</artifactId>        <version>0.0.1-SNAPSHOT</version>        <name>demo-aop</name>        <description>Demo project for Spring Boot</description>        <properties>            <java.version>1.8</java.version>        </properties>        <dependencies>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter</artifactId>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-web</artifactId>            </dependency>            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-aop</artifactId>            </dependency>            <dependency>                <groupId>commons-lang</groupId>                <artifactId>commons-lang</artifactId>                <version>2.6</version>            </dependency>            <dependency>                <groupId>com.alibaba</groupId>                <artifactId>fastjson</artifactId>                <version>1.2.51</version>            </dependency>        </dependencies>        <build>            <plugins>                <plugin>                    <groupId>org.springframework.boot</groupId>                    <artifactId>spring-boot-maven-plugin</artifactId>                </plugin>            </plugins>        </build>    </project>

工程配置文件和启动类

application.properties 仅仅配置了端口号,启动类也是非常简单,具体代码如下:

    application.properties 文件内容:    server.port=8080    package com.example.demo;    import org.springframework.boot.SpringApplication;    import org.springframework.boot.autoconfigure.SpringBootApplication;    @SpringBootApplication    public class DemoAopApplication {        public static void main(String[] args) {            SpringApplication.run(DemoAopApplication.class, args);        }    }

Controller 类、Service 类及相关类

Controller 类定义了 4 个接口,Service 类定义了 3 个方法,用于演示不同参数在切面类中如何处理,代码如下:

    DemoController.java    package com.example.demo.controller;    import com.example.demo.base.WebResult;    import com.example.demo.service.DemoService;    import com.example.demo.vo.DemoVO;    import com.example.demo.base.ReqParam;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.web.bind.annotation.*;    import java.util.Map;    @RestController    public class DemoController {        @Autowired        private DemoService demoService;        @RequestMapping(value = \"/aop/test1/{companyId}\", method = RequestMethod.POST)        public void test1(@PathVariable String companyId, @RequestParam(name = \"param2\") String param2) {            demoService.updateTest1(companyId, param2);        }        @RequestMapping(value = \"/aop/test2\", method = RequestMethod.POST)        public void test2(@RequestBody Map paramMap, @RequestParam(name = \"param2\") String param2) {            demoService.updateTest2(paramMap, param2);        }        @RequestMapping(value = \"/aop/test3\", method = RequestMethod.POST)        public void test3(@RequestBody DemoVO demoVO) {            demoService.saveTest3(demoVO);        }        @RequestMapping(value = \"/aop/test4\", method = RequestMethod.POST)        public WebResult test4(@RequestBody ReqParam<DemoVO> reqParamVO) {            demoService.saveTest3(reqParamVO.getData());            //为了演示返回结果的日志打印效果,此处把入参当成返回结果。            return WebResult.ok(reqParamVO.getData());        }    }    DemoService.java    package com.example.demo.service;    import com.example.demo.vo.DemoVO;    import org.springframework.stereotype.Service;    import java.util.Map;    @Service    public class DemoService {        public void updateTest1(String companyId, String param2){            // do something....        }        public void updateTest2(Map paramMap, String param2){            // do something....        }        public void saveTest3(DemoVO demoVO){            // do something....        }    }    DemoVO.java    package com.example.demo.vo;    public class DemoVO {        private String companyId;        public String getCompanyId() {            return companyId;        }        public void setCompanyId(String companyId) {            this.companyId = companyId;        }        @Override        public String toString() {            return \"DemoVO [ \" + \"companyId=\'\" + companyId + \'\\\'\' + \']\';        }    }    ReqParam.java    package com.example.demo.base;    import java.io.Serializable;    public class ReqParam<T> implements Serializable {        private static final long serialVersionUID = 6306747792003091002L;        private String sign;        private Long timestamp;        private String sysCode;        private String version;        private String token;        private T data;        public T getData() {            return data;        }        public void setData(T data) {            this.data = data;        }        public String getSign() {            return sign;        }        public void setSign(String sign) {            this.sign = sign;        }        public Long getTimestamp() {            return timestamp;        }        public void setTimestamp(Long timestamp) {            this.timestamp = timestamp;        }        public String getSysCode() {            return sysCode;        }        public void setSysCode(String sysCode) {            this.sysCode = sysCode;        }        public String getVersion() {            return version;        }        public void setVersion(String version) {            this.version = version;        }        public String getToken() {            return token;        }        public void setToken(String token) {            this.token = token;        }        @Override        public String toString() {            return \"ReqParamBody [sign=\" + sign + \", timestamp=\" + timestamp + \", sysCode=\" + sysCode + \", version=\"                    + version + \", token=\" + token + \", data=\" + data + \"]\";        }    }    WebResult.java    package com.example.demo.base;    import java.io.Serializable;    import java.util.List;    public class WebResult implements Serializable {        private static final long serialVersionUID = 372525545013064618L;        /**         * 结果描述         */        private String message;        /**         * 结果编码         */        private String code;        /**         * 业务数据         */        private Object data;        public WebResult() {        }        /**         * 操作成功          * @return         */        public static WebResult ok() {            return ok(null);        }        public WebResult(String code, String message, Object data) {                this.code = code;                this.message = message;                this.data = data;        }        /**         * 操作成功         * @param data         * @return         */        public static WebResult ok(Object data) {            return new WebResult(\"200\", \"操作成功\", data);        }        /**         * 成功         * @param code         * @param message         * @return         */        public static WebResult ok(String code, String message) {            return new WebResult(code, message, null);        }        public static <T> WebResult okAndListData(List<T> data) {            return ok(data);        }        /**         * 失败         * @param code         * @param message         * @return         */        public static WebResult fail(String code, String message) {            WebResult result = new WebResult();            result.setCode(code);            result.setMessage(message);            return result;        }        /**         * 失败         * @param message  消息,code 默认为 400         * @return         */        public static WebResult fail(String message) {            return WebResult.fail(\"400\", message);        }        public String getMessage() {            return message;        }        public void setMessage(String message) {            this.message = message;        }        public Object getData() {            return data;        }        public void setData(Object data) {            this.data = data;        }        public String getCode() {            return code;        }        public void setCode(String code) {            this.code = code;        }    }    AopException.java    package com.example.demo.base;    public class AopException extends Exception {        private static final long serialVersionUID = 1L;        public AopException(String message) {            super(message);        }    }

两个核心 AOP 类

DemoAspect 主要逻辑是获取拦截的 service 方法的第一个参数,并检验这个参数的正确性而达到权限验证效果,因第一个参数可能存在多个类型,比如:字符串类型、Map 类型、自定义类型等等,所以需要先判断类型再获取待校验的值,否则获取不到相应的值导致校验出问题。CommonLoggerAspect 主要逻辑是打印每一个 controller 接口的具体请求路径、执行方法名、传入的参数,返回参数及响应时间。

    DemoAspect.java    package com.example.demo.aop;    import com.example.demo.base.AopException;    import com.example.demo.vo.DemoVO;    import com.example.demo.base.ReqParam;    import org.apache.commons.lang.StringUtils;    import org.apache.logging.log4j.Level;    import org.apache.logging.log4j.LogManager;    import org.apache.logging.log4j.Logger;    import org.aspectj.lang.JoinPoint;    import org.aspectj.lang.annotation.Aspect;    import org.aspectj.lang.annotation.Before;    import org.aspectj.lang.annotation.Pointcut;    import org.springframework.stereotype.Component;    import java.util.Map;    @Aspect    @Component    public class DemoAspect {        private Logger logger = LogManager.getLogger(getClass());        @Pointcut(\"execution(public * com.example.demo.service.DemoService.update*(..)) || execution(public * com.example.demo.service.DemoService.saveTest3(..))\")        public void checkCompanyId() {        }        @Before(\"checkCompanyId()\")        public void checkCompanyId(JoinPoint joinPoint) throws Throwable {            Object[] args = joinPoint.getArgs();            String companyId = \"\";            try {                //从参数中获取 companyId,args[0]代表第一个参数                if(args != null && args.length != 0){                    if(args[0] instanceof String){     //假如是 String 类型                        companyId = (String)args[0];                    }else if(args[0] instanceof Map){  //假如是 Map 类型                        Map map = (Map)args[0];                        companyId = map.get(\"companyId\") != null ? map.get(\"companyId\").toString() : \"\";                    }else if(args[0] instanceof DemoVO){  //假如是自定义 DemoVO 类                        DemoVO demoVO = (DemoVO)args[0];                        companyId = demoVO.getCompanyId();                    }else if(args[0] instanceof ReqParam){  //假如是自定义 ReqParamVO 类                        ReqParam reqParamVO = (ReqParam)args[0];                        DemoVO demoVO = (DemoVO)reqParamVO.getData();                        companyId = demoVO.getCompanyId();                    }                }            } catch (Exception e) {                logger.log(Level.ERROR, \"获取 companyId 出现异常,异常信息:{}\", e);                throw new AopException(\"获取 companyId 出现异常\");            }            if(StringUtils.isEmpty(companyId)){                logger.log(Level.ERROR, \"companyId 校验不通过,companyId 为空\");                throw new AopException(\"companyId 校验不通过,companyId 为空\");            }            if (!verifyCompanyId(companyId)) {                logger.log(Level.INFO, \"companyId({})校验不通过,简单说明校验不通过的原因,方便排查问题\", companyId);                throw new AopException(\"companyId(\"+companyId+\")校验不通过,简单说明校验不通过的原因,方便排查问题\");            }        }        private boolean verifyCompanyId(String companyId){            //根据业务要求校验 companyId,校验通过返回 true,否则返回 false            return true;        }    }    CommonLoggerAspect.java    package com.example.demo.aop;    import com.alibaba.fastjson.JSONObject;    import com.example.demo.base.AopException;    import com.example.demo.base.ReqParam;    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.aspectj.lang.reflect.MethodSignature;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.stereotype.Component;    import org.springframework.web.context.request.RequestContextHolder;    import org.springframework.web.context.request.ServletRequestAttributes;    import org.springframework.web.multipart.MultipartFile;    import javax.servlet.http.HttpServletRequest;    import java.lang.reflect.Method;    import java.util.Arrays;    import java.util.List;    @Aspect    @Component    public class CommonLoggerAspect {        private static final Logger log = LoggerFactory.getLogger(CommonLoggerAspect.class);        @Pointcut(\"execution (* com.example.demo.controller.*Controller.*(..))\")        public void controllerAspect() {        }        @SuppressWarnings(\"rawtypes\")        @Around(\"controllerAspect()\")        public Object controllerAround(ProceedingJoinPoint joinPoint) throws Throwable{            /**             * 接口增加日志             */            String classAndMethodName = null;            //获取当前请求属性集            ServletRequestAttributes  sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();            //获取请求            HttpServletRequest request = sra.getRequest();            //获取请求地址            String requestUrl = request.getRequestURL().toString();            //记录请求地址            log.info(\"请求开始:路径[{}]\", requestUrl);            //记录请求开始时间            long startTime = System.currentTimeMillis();            try {                Class<?> target = joinPoint.getTarget().getClass();                Class<?>[] par = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();                String methodName = joinPoint.getSignature().getName();                //获取当前执行方法                Method currentMethod = target.getMethod(methodName, par);                //拼接输出字符串                classAndMethodName = target.getName() + \"的\" + currentMethod.getName() + \"方法\";                log.info(\"正在执行:{}\", classAndMethodName);                //获取切点参数                List<Object> list = Arrays.asList(joinPoint.getArgs());                if (list != null && list.size() > 0) {                    for(Object object : list){                        if(object instanceof MultipartFile) {                            log.info(\"入参:{}\", ((MultipartFile) object).isEmpty() ? \"空文件\" : ((MultipartFile)object).getName());                        } else if(object instanceof ReqParam){                            log.info(\"入参:{}\", ((ReqParam) object).getData().toString());                        }else if(object == null) {                            log.info(\"入参: \");                        }else{                            log.info(\"入参:{}\", object.toString());                        }                    }                }            } catch (Exception e) {                log.error(\"记录日志的时出现异常,异常信息:{}\", e);                throw new AopException(\"记录日志的时出现异常\");            }            Object object = joinPoint.proceed();            if (object != null) {                log.info(\"执行方法:{},返回参数:{}\", classAndMethodName, JSONObject.toJSONString(object));            }else{                log.info(\"执行方法:{},无返回参数!\", classAndMethodName);            }            long endTime = System.currentTimeMillis();            log.info(\"请求结束:路径[{}]响应时间{}毫秒\", requestUrl, endTime-startTime);            return object;        }    }

接口测试

1. test1 接口,测试权限校验,第一个参数是字符串类型。

url:

http://127.0.0.1:8080/aop/test1/a028b8817b060b6b017b060d86230001?param2=aaa

在这里插入图片描述

2. test2 接口,测试权限校验,第一个参数是 Map 类型。

url:

http://127.0.0.1:8080/aop/test2?param2=bbb

Body 参数:

    {        \"companyId\": \"a028b8817b060b6b017b060d86230001\"    }

在这里插入图片描述

3. test3 接口,测试权限校验,第一个参数是自定义 DemoVO 类。

url:

http://127.0.0.1:8080/aop/test3

Body 参数:

    {        \"companyId\": \"a028b8817b060b6b017b060d86230001\",        \"param2\": \"ccc\"    }

在这里插入图片描述

4. test3 接口,测试权限校验,模拟 A 校 OP 验不通过,注意 body 中的参数名是 companyId2。

url:

http://127.0.0.1:8080/aop/test3

Body 参数:

    {        \"companyId2\": \"a028b8817b060b6b017b060d86230001\",        \"param2\": \"zzz\"    }

在这里插入图片描述

5. test4 接口,测试日志打印。

url:

http://127.0.0.1:8080/aop/test4

Body 参数:

    {      \"data\":{        \"companyId\": \"a028b8817b060b6b017b060d86230001\",        \"param2\": \"ddd\"      },      \"sign\":\"\",      \"sysCode\":\"demo-aop\",      \"timestamp\":\"\",      \"token\":\"\",      \"version\":\"\"    }

返回结果:

    {        \"message\": \"操作成功\",        \"code\": \"200\",        \"data\": {            \"companyId\": \"a028b8817b060b6b017b060d86230001\"        }    }

控制台打印结果:

    2021-10-12 17:53:15.105  INFO 14900 --- [nio-8080-exec-5] com.example.demo.aop.CommonLoggerAspect  : 请求开始:路径[http://127.0.0.1:8080/aop/test4]    2021-10-12 17:53:15.106  INFO 14900 --- [nio-8080-exec-5] com.example.demo.aop.CommonLoggerAspect  : 正在执行:com.example.demo.controller.DemoController 的 test4 方法    2021-10-12 17:53:15.106  INFO 14900 --- [nio-8080-exec-5] com.example.demo.aop.CommonLoggerAspect  : 入参:DemoVO [ companyId=\'a028b8817b060b6b017b060d86230001\']    2021-10-12 17:53:20.682  INFO 14900 --- [nio-8080-exec-5] com.example.demo.aop.CommonLoggerAspect  : 执行方法:com.example.demo.controller.DemoController 的 test4 方法,返回参数:{\"code\":\"200\",\"data\":{\"companyId\":\"a028b8817b060b6b017b060d86230001\"},\"message\":\"操作成功\"}    2021-10-12 17:53:20.682  INFO 14900 --- [nio-8080-exec-5] com.example.demo.aop.CommonLoggerAspect  : 请求结束:路径[http://127.0.0.1:8080/aop/test4]响应时间 5576 毫秒

总结

通过这次的使用 AOP 实现权限验证和日志打印实战,让我们掌握了如何使用 AOP 技术实现统一验证参数合法性、如何实现统一打印入参和返回参数等相关信息,关键点在于使用使用统一参数格式。

PS:如有写错请指正,感谢您阅读。

免责声明: 1、本站信息来自网络,版权争议与本站无关 2、本站所有主题由该帖子作者发表,该帖子作者与本站享有帖子相关版权 3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和本站的同意 4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责 5、用户所发布的一切软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。 6、您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 7、请支持正版软件、得到更好的正版服务。 8、如有侵权请立即告知本站(邮箱:1099252741@qq.com,备用微信:1099252741),本站将及时予与删除 9、本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章和视频仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。
(0)
上一篇 2022年8月29日 下午1:54
下一篇 2022年8月29日 下午1:54

小七学习网,助您升职加薪,遇问题可联系:客服微信【1099252741】 备注:来自网站

相关推荐

  • 🎰Thress.js 可视化企业实战WEBGL课

    🎰Thress.js 可视化企业实战WEBGL课 https://study.163.com/course/introduction/1212491801.htm?inLoc=ss...

    2023年1月13日
    131
  • Google资深工程师深度讲解Go语言

    〖课程介绍〗: Go作为专门为并发和大数据设计的语言,在编程界越来越受欢迎!不论是c/c++,php,java,重构首选Go。本课程特邀谷歌资深工程师,从Go基本语法到函数式编程、并发编程,最后构建分…

    2022年8月31日
    159
  • 拉勾-算法突击特训营3期|价值2299元|2022年|重磅首发|完结

    拉勾-算法突击特训营3期|价值2299元|2022年|重磅首发|完结 每天1道高频题 6周彻底攻克数据结构与算法 〖资源截图〗: 〖资源目录〗: ├──01、第一章 线性表、哈希表...

    2022年6月18日
    323
  • 02-开课吧/组件方式开发web app全站

    ——/我的资源/徒弟班全资料/课程资源/03-其他网校/03-其他网校等多个文件/03-其他网校/02-开课吧/组件方式开发web app全站/ ├──第01章 课程前期准备 | ├──1-1 课程介绍.mp4 22.74M | ├──1-2 课程安排.mp4 1.38M | ├...

    2022年8月25日
    266
  • 小迪安全2022

    🎰更新 小迪安全2022 https://mp.weixin.qq.com/s/6WHERij9e5AvVdngdvFuKg

    2023年1月9日
    1.0K
  • 【慕课】mksz404 – 全方位深入解析最新版SpringBoot源码

    全方位深化解析最新版SpringBoot源码 第1章 死磕源码,剑指荣耀【用源码配备你的竞争力】 首要点明SB结构的“江湖方位”,学源码三大优势:团队中心、通关面试、自我修炼。接着课程全体全貌,有图有内情。图解源码:看得见的流程;仿写结构:学得会的源码;学完源码:装的了的牛...

    2022年8月25日
    192
  • P7:Java互联网百万年薪课(技术专家班)

    P7:Java互联网百万年薪课(技术专家班) 今日【好课推荐】,我刚好收集整理了这个课程! 心得体会:老师教学中规中矩,每个人理解不一,评价不一,收获多少因人而异,学习是条没有尽头...

    2022年9月8日
    172
  • 遇问题可联系 / 客服微信【1099252741】
  • Java 调用 Linux 命令实战(含完整代码)

    在日常项目开发过程中,经常会遇到 Java 后台代码调用 Linux 命令场景,比如:重启 Keepalived、MySQL 等等。 本文详细介绍了如何使用 Java 语言调用 Linux 命令,其中…

    2022年8月29日
    203
  • ✔️原价💰 3000+ offer帮《量化Quant刷题冲刺班2022版本》

    ✔️原价💰 3000+ offer帮《量化Quant刷题冲刺班2022版本》 ͏  求职结果导向,高阶知技识‬能 + 真实题‬训 + 面试技巧全覆盖 Quant知识点全梳理,带你...

    2023年1月30日
    333
  • EasyRules 规则引擎,增加一个技术栈,没坏处!

    接手了一个运维项目,查看其中的代码,中间有接近 100 个 if else 判断,全部采用硬编码的方式。 代码特别长,规则十分混乱,不好管理。 如何处理这些问题? 采用什么方式,将业务规则抽象出来? …

    2022年8月29日
    308