springAop实现权限管理数据校验操作日志的场景分析
作者:搞笑大爷 发布时间:2023-11-23 10:00:27
前言
作为一个写java的使用最多的轻量级框架莫过于spring,不管是老项目用到的springmvc,还是现在流行的springboot,都离不开spring的一些操作,我在面试的时候问的最多的spring的问题就是我们在平常的项目中使用spring最多的有哪几个点
在我看来无非就两个
spring的bean管理,说的高大上一点就是spring的ioc,di
spring的AOP
spring是一个很强大的轻量级框架,功能远不止这两点,但是我们用的最多的就是这两点。
spring bean 管理
想我们常用的 @Controller @Service @Component 等等都是将我们的bean交给spring管理,我们在获取bean的时候就直接通过 @Resource 就可以获取,当然@Resource 不是spring的,@Autowired 才是spring的,这样我们可以很方便的管理我们的各种bean,使用起来也很方便,不用到处new
springAOP
这个应该是spring面试最常问道的问题了,我面试的时候一般不直接问,我会说一个场景,如果面试者使用过aop立马就可以回答出来。
有这么一个场景,一个系统已经开发完成了,而且已经上线运行了一段时间,很稳定了,现在要加一个功能,就是想收集用户的操作日志,操作日志要比较细致,比如 某某人,在哪个时间点,操作了哪个模块,请求的参数是什么样子的,操作结果如何,等一些比较细致的操作。很多面试者第一时间想到的就是使用过滤器,试想一下,过滤器真的能做到记录这么细致的内容吗?有的可能会想到,我们定义一个公共的方法,所有需要记录日志的地方都去调用这个方法,等等。其实这些都不好,最好的当然是使用aop,使用aop侵入性最小,系统已经稳定运行了,不能去动之前的代码,我们做个aop就可以了,对原来的代码几乎0侵入,对系统影响最小。
那aop实现的方式有哪几种呢?aop实现的步骤又是怎么样的呢?aop还能做些什么呢?这些问题如果在实际项目中使用过,一定能回答出来,如果没有使用过,估计有点难回答,这些都是项目框架的东西,很多公司的项目这一块已经封装好了,很多人直接一直在用,但是没有去查看源码,就不知道具体的实现,实现起来其实也很简单。
接下来我们就以一个小例子来说明下如何使用aop
aop总结起来最常用的就两种方式
1、采用声明的方式来实现(基于XML) 胡子眉毛一把抓,哈哈
2、是采用注解的方式来实现(基于AspectJ)精准定位
我习惯使用注解的方式,更加灵活,使用起来也方便,接下来就以注解的方式来讲下如何使用aop来做权限校验,数据校验,操作日志记录
申明切面
我们使用springaop,首先要将定义的类交给spring管理,然后使用aspectj 定义切面,我们要额外引入
aspectj
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
@Component
@Aspect
public class OperationInterceptor {
}
定义切点
我们采用注解的方式,那么我们首先要申明一个注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface GlobalInterceptor {
/**
* 权限编码
*/
PermissionCodeEnum permissionCode();
/**
* 操作模块
*
* @return
*/
OperModuleEnum opObject() default OperModuleEnum.NO_OBJECT;
/**
* 操作描述
*
* @return
*/
String opDescription() default "";
}
然后在我们的切面中定义切点
@Component
@Aspect
public class OperationInterceptor {
@Pointcut("@annotation(com.xx.xx.GlobalInterceptor)")
private void opMethods() {
}
}
定义通知类型
我们要输入还想要输出,那么我们就要将目标方法包围,所以使用around
@Component
@Aspect
public class OperationInterceptor {
@Pointcut("@annotation(com.xx.xx.GlobalInterceptor)")
private void opMethods() {
}
@Around("opMethods()")
public Object doMethod(ProceedingJoinPoint point) throws BusinessException {
//TODO 业务代码
return null;
}
}
这样一个完整的切面就定义好了
使用切面
在我们的controller中直接使用,我们拿一个登录来讲
@RequestMapping("/login")
@GlobalInterceptor(permissionCode = PermissionCodeEnum.NO_PERMISSION, opObject = OperModuleEnum.OBJECT_LOGIN, opDescription = "登录账号:#{#param1}")
public AjaxResponseVO login(HttpSession session, @VerifyParam(required = true) String account, @VerifyParam(required = true) String password,@VerifyParam(required = true) String checkCode){
}
我们要传入 切面注解需要的参数
permissionCode 权限编码,我这里定义的是一个枚举,类型自己根据实际情况定义
opObject 操作模块
opDescription 操作描述,这里简单的组织下描述文字,参数的地方使用占位符,到时候根据占位符index获取具体的参数
这样我们在切面中就可以拿到这些参数
@Component
@Aspect
public class OperationInterceptor {
@Pointcut("@annotation(com.xx.xx.GlobalInterceptor)")
private void opMethods() {
}
@Around("opMethods()")
public Object doMethod(ProceedingJoinPoint point) throws BusinessException {
Object obj = null;
try {
/**
* 获取登录信息
*/
SessionUserDto sessionUserDto = getSessionUser();
/**
* 获取目标切点
*/
Object target = point.getTarget();
/**
* 获取参数
*/
Object[] arguments = point.getArgs();
/**
* 获取方法名
*/
String method = point.getSignature().getName();
/**
* 获取参数类型
*/
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
/**
* 获取具体的方法
*/
Method m = target.getClass().getMethod(method, parameterTypes);
GlobalInterceptor interceptor = m.getAnnotation(GlobalInterceptor.class);
if (null == interceptor) {
return obj;
}
/**
* 校验权限
*/
if (sessionUserDto != null) {
validatePermission(interceptor, sessionUserDto);
}
/**
* 校验参数
*/
validateParams(interceptor, m, arguments);
/**
* 获取描述信息,这里在执行方法之前组织好描述信息,当参数是传值引用时 执行具体方法后,会改变原始参数对象值
*/
String description = getDescription(arguments, interceptor.opDescription());
/**
* 执行操作
*/
obj = point.proceed();
/**
* 记录日志
*/
wirteLog(obj, description, interceptor, sessionUserDto);
} catch (BusinessException e) {
logger.error("全局 * 异常", e);
throw e;
} catch (Exception e) {
logger.error("全局 * 异常", e);
} catch (Throwable e) {
logger.error("全局 * 异常", e);
}
return obj;
}
}
这里只贴出了部分代码,完整的代码可以到这里获取
总结
回到最开始的问题,这样我们实现了一个对原有系统侵入极小,然后又实现了操作日志的解决方案。 我们使用spring的aop非常简单,我们使用aop结合反射,可以做很多事情。aop对代码的侵入非常小,不需要动原来的代码,只需要在原有的方法上加一个注解就可以完成对系统的改造,加权限,加日志 等等一系列操作。
来源:https://blog.csdn.net/weixin_45574655/article/details/114776505


猜你喜欢
- Java 判断字符串中是否包含中文的实例详解 Java判断一个字符串是否有中文是利用Unicode编码来判断,因为中
- /// <summary> /// 为图片生成缩略图 /// </summ
- 模型对象的作用主要是保存数据,可以借助它们将数据带到前端。常用的模型对象有以下几个:ModelAndView(顾名思义,模型和视图,既可以携
- 前言 今天在看Android ContentProvider实现的时候,突然想到了Java类在new的过
- 本文实例讲述了C#实现日期格式转换的公共方法类。分享给大家供大家参考,具体如下:这里演示了C#中一些日期格式的转换。创建公共方法类(Util
- Maven打包没有指定主类在使用IDEA开发项目的时候经常会遇到使用Maven打包项目(打成jar包或者zip包),但是之前不太清楚打包插件
- 先引用using System.Runtime.InteropServices; 的命名空间, 然后在合适的位置加上如下代码就OK。。注意:
- 一、静态静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可@PostMapping(
- 一, eclipse springboot打war包1. 配置pom.xml文件<packaging>war</packa
- Servlet注解之@WebInitParam多个InitParam使用百度了半天也没找到。。。。使用@WebInitParam配置多个In
- GradientTextViewGithub点我一个非常好用的库,使用kotlin实现,用于设置TexView的字体 渐变颜色、渐变方向 和
- 前言本问主要介绍DataBinding在Android App中的使用方法。数据绑定是将“提供器”的数据源与“消费者”绑定并使其同步的一种通
- 本文实例讲述了C#使用foreach语句遍历二维数组的方法。分享给大家供大家参考。具体分析如下:如果通过for语句循环遍历二维数组需要两重循
- 实例如下://图片到byte数组 public byte[] image2byte(String path){ byte[] d
- 本文实例为大家分享了Android实现简单旋转动画的具体代码,供大家参考,具体内容如下核心方法public void startAnimat
- Kotlin中StateFlow的使用StateFlow 是 Flow 的实现,是一个特殊的流,默认的 Flow 是冷流,而StateFlo
- 我们还是用一个小例子来看看自定义View和自定义属性的使用,带大家来自己定义一个带进度的圆形进度条,我们还是先看一下效果吧从上面可以看出,我
- 本文为大家分享了如何使用eclipse创建java项目,供大家参考,具体内容如下首先,打开Eclipse,在工具栏依次点击【File】>
- 本篇文章主要介绍泛型的应用。泛型是.NET work 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性、类型安全性和效
- 在 Java 语言中,运算符有算数运算符、关系运算符、逻辑运算符、赋值运算符、字符串连接运算符、条件运算符。算数运算符算数运算符是我们最常用