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

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

面向切面编程 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:如有写错请指正,感谢您阅读。

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

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