使用spring AOP方式对请求数据进行去空,校验。
AOP是什么
我们来看百度给的解释:
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
说人话就是:
为什么叫面向切面编程,我们把程序的执行过程想象成一个流程图,切面编程的意思就是在程序的执行过程中横向的动态的切入代码。这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。请看下图:
AOP是能做什么
通过上面的流程图我们大致知道AOP是什么了,那AOP有什么用呢,能做什么呢?我们编程的时候都知道如果一块代码有多个地方会用到,我们都会抽取成一个方法,用到就去调用,这就是面向对象的思想。但是如果是某一层的方法都用到呢,每个方法都去调用一次公共的方法吗?比如说控制层我需要请求进来的参数做一次日志输出或者格式化参数,虽然说我们可以像上面说的那样每新写一个方法就调用一次公用方法。当时这样做不仅麻烦,而且如果忘记写这一行调用方法的代码呢?如果是多人并行开发呢,别人怎么知道要先调用的这个方法。所以综上所述,这个时候就应该引入一个叫做面向切面编程的思想也就是AOP。
AOP怎么做
下面将通过实战的方式演示实际开发中如何运用AOP。
需求是这样的,所有接口请求进来的参数都需要把参数的空格去掉。
不使用AOP前我们的接口是这样的
@RequestMapping("/hello")
public ServerResponse test(@RequestBody Class class) {
if (class.p1 != null) {
class.p1 = class.p1.trim();
}
if (class.p2 != null) {
class.p2 = class.p2.trim();
}
if (class.p3 != null) {
class.p3 = class.p3.trim();
}
// 其余去空代码
...
//业务代码
return ServerResponse.createBySuccess(vo);
}
每个接口都要写这种冗余的代码。这很明显不是一个明智的选择。
使用AOP后我们的接口是这样的
@RequestMapping("/hello")
public ServerResponse test(@RequestBody Class class) {
//业务代码
return ServerResponse.createBySuccess(vo);
}
瞬间是不是简洁很多,只需要关注业务代码就可以了,怎么做到的呢。
1、新建一个切面类,我们就命名为ControllerAspect。
public class ControllerAspect {
}
2、在类上写@Component@Aspect这两个注解
@component (把普通pojo实例化到spring容器中,相当于配置文件中的 )
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
@Aspect:作用是把当前类标识为一个切面供容器读取
@Component
@Aspect
public class ControllerAspect {
}
3、编写写入点
@Component
@Aspect
public class ControllerAspect {
// 定义切点
// 拦截Controller所在的包
@Pointcut("execution(* cn.xxxx.controller.*.*.*(..))")
public void excudeService() {
}
}
这里我controller包下还有具体那个业务的包如果你的controller是直接在controller包下的话表达式去掉一个*
就可以:* cn.xxxx.controller.*
.*(…)
4、编写具体的逻辑
@Component
@Aspect
public class ControllerAspect {
// 定义切点
// 拦截Controller所在的包
@Pointcut("execution(* cn.xxxx.controller.*.*.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
//重点 这里就是获取@RequestBody参数的关键 调试的情况下 可以看到arr变量已经获取到了请求的参数
Object[] args = pjp.getArgs();
if (args.length == 0) {
return pjp.proceed( new Object[]{null});
}
Object object = args[0];
if (object instanceof HttpServletRequest) { // 特殊处理
HttpServletRequest request = (HttpServletRequest) object;
Map<String, String[]> m = request.getParameterMap();
Map<String, String[]> newM = new HashMap<String, String[]>();
if (m != null) {
for (Map.Entry<String, String[]> entry : m.entrySet()) {
String key = entry.getKey();
String[] value = entry.getValue();
if (value == null) continue;
for (int j = 0; j < value.length; ++j) {
String temp = value[j].trim();
if (temp.length() > 0) {
value[j] = temp;
}
}
newM.put(key, value);
}
}
// 因为我们无法去改变getParameterMap的返回值
// 所以对里面的值去除空格是无效的
// 但是我们可以把参数的值去除空格之后把值放入setAttribute里面
// 这样request对象就携带了去除空格之后的值
request.setAttribute("The-result-after-removing-the-space", newM);
object = request;
} else {
// 考虑到类里面还有类 这里采用递归的方式处理
object = dfs(object.getClass(),object);
}
// 把 arr 放回去即可改变参数的值
return pjp.proceed( new Object[]{object});
}
private Object dfs(Class<?> aClass, Object object) throws IllegalAccessException {
if (object == null) return null;
// 如果是Java的基本数据类型就不需要继续递归了
if (isPrimitive(aClass)) {
if (object instanceof List){
List<String> objList = JSON.parseArray(JSON.toJSONString(object), String.class);
for (int i = 0; i < objList.size(); i++) {
if (objList.get(i).trim().length() == 0) {
objList.remove(i);
i--;
}
}
return objList;
}
if (object instanceof String) {
String temp = ((String) object).trim();
if (temp.length() >0) {
return temp;
}
}
return object;
}
// 返回类所有字段
// 注意: 会返回静态字段
// 不要在Dto、Vo定义静态属性
// 没有获取父类属性 可通过 “object.getClass().getSuperclass()” 获取
for (Field item : object.getClass().getDeclaredFields()) {
// static 和 final 修饰的字段忽略
if (Modifier.isStatic(item.getModifiers()) || Modifier.isStatic(item.getModifiers())) {
continue;
}
// 设置可见性
item.setAccessible(true);
// 递归处理
item.set(object, dfs(item.getType(), item.get(object)));
}
return object;
}
// 判断是否为Java的基本类型或包装类
// java.lang.* java.util.* 存放着Java的基本数据类型的包装类和集合类
// 基本数据类型和包装类是JVM虚拟机运行需要的类,已嵌入到JVM内核
// 不需要继承ClassLoader 用户自定义的需要
private boolean isPrimitive(Class<?> aClass) {
return aClass != null && aClass.getClassLoader() == null;
}
}