一. 基础概念信息
1. 切面(Aspect)
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,常见的有日志处理、事务处理、权限认证等等。
2. 切入点(PointCut)
要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。
3. 连接点(JoinPoint)
知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等。
4. 通知(Advice)
通知被织入方法,该如何被增强。定义切面的具体实现。@Pointcut规则中指明的方法即为切入点,@Before、@After是连接点,而下面的代码就是对应通知。
5. 目标对象(Target Object)
被一个或多个切面所通知的对象,即为目标对象。
6. AOP代理对象(AOP Proxy Object)
AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。
7. 织入(Weaving)
将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。
二. Java代码实现
1. Maven依赖
<!--AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 自定义注解类
(这里自定义的为参数非空判断)
ParamsCheckNull.java
import java.lang.annotation.*;
/***
* 自定义注解类
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Documented
public @interface ParamsCheckNull {
String[] params(); //参数属性名
String[] msg(); //提示信息
}
3. 自定义AOP类
ParamsCheckNullAspect.java
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.web.system.config.spring.code.exception.BaseErrorCode;
import com.web.system.config.spring.code.exception.BaseErrorException;
import com.web.system.config.tools.StringUtil;
import com.web.system.config.tools.UuidGenerate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;
/***
* 验证参数不能为空
* 处理通知连接点[ProceedingJoinPoint]只能在环绕通知上
*/
@Slf4j
@Aspect
@Component
public class ParamsCheckNullAspect {
private static String dateFormat = "yyyy-MM-dd HH:mm:ss";
//切入点
@Pointcut("@annotation(com.web.system.config.checkutil.checknull.ParamsCheckNull)")
public void paramNotNull(){}
/*@Pointcut("execution(public * com.modules..*.*Controller.*(..))")
public void pointcut(){}*/
@Around("paramNotNull()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("进入到环绕通知中");
// 1、记录方法开始执行的时间
long start = System.currentTimeMillis();
// 2、打印请求参数
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
//StringBuffer sb = new StringBuffer();
//String requestId = UuidGenerate.getUUID();
//sb.append("\n【请求 URL】:").append(request.getRequestURL());
//sb.append("\n【请求类名】:").append(joinPoint.getSignature().getDeclaringTypeName());
//sb.append("【请求方法名】:").append(joinPoint.getSignature().getName());
//sb.append("\n【body】:").append(JSONUtil.toJsonStr(joinPoint.getArgs()));
//sb.append("\n【请求参数】:").append(JSONUtil.toJsonStr(parameterMap));
//log.info(sb.toString());
//System.out.println("*****sb.toString():"+JSONUtil.toJsonStr(joinPoint.getArgs())+"*****");
//System.out.println("request:"+request.getRequestURI());
String target = joinPoint.getSignature().getDeclaringTypeName(); // 全路径类名
String classNm = target.substring(target.lastIndexOf(".") + 1, target.length()); // 类名截取
String method = joinPoint.getSignature().getName(); // 获取方法名
Map<String, Object> params = getRequestBody(joinPoint.getArgs()); // 获取请求参数
ParamsCheckNull check = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ParamsCheckNull.class); // 获取注解
String[] requiredFields = check.params(); // 获取注解参数
String[] msgList = check.msg(); //获取注解的提示语
if(requiredFields.length!=msgList.length){
throw new BaseErrorException(BaseErrorCode.DO_ERROR, "Code writing error");
}
// 3、必填参数非空校验
Map<String,Object> result = validParams(params, requiredFields, msgList);
if ((Boolean) result.get("boolean")) {
Object object = joinPoint.proceed(); // 必填参数非空校验通过,执行方法,获取执行结果
// 4、打印应答数据和方法耗时
long time = System.currentTimeMillis() - start;
log.info("{}.{} 应答数据: {}; 耗时 {} ms", classNm, method, JSONObject.toJSONStringWithDateFormat(object,
dateFormat, SerializerFeature.WriteMapNullValue), time);
return object;
} else {
// 必填参数非空校验未通过,抛出异常,由GlobalExceptionHandler捕获全局异常,返回调用方“参数缺失”
// result.get("param") 获取参数属性名
// result.get("message").toString() 获取参数属性提示语
throw new BaseErrorException(BaseErrorCode.PARAM_NOT_CORRECT, result.get("message").toString());
}
}
/***
* 校验传入参数params(非null)中是否必含requiredFields(非null)中的各个属性,且属性值非空
* @param params 传入参数
* @param requiredFields 设置的非空属性数组
* @return 校验通过返回true,否则返回false
*/
private Map<String,Object> validParams(Map<String, Object> params, String[] requiredFields, String[] msgList) {
Map<String, Object> map = new HashMap<>();
/*System.out.println("++++++++++++参数属性:"+JSON.toJSONString(requiredFields)+"+++++++++");
System.out.println("++++++++++++属性提示:"+JSON.toJSONString(msgList)+"+++++++++");
System.out.println("++++++++++++params:"+params+"+++++++");*/
if (requiredFields.length == 0) {
// 无必选参数,直接返回true
map.put("boolean", true);
return map;
} else {
for(int x=0; x<requiredFields.length; x++){
Object oneObj=params.get(requiredFields[x]);
if(oneObj instanceof String){
if(StringUtil.isEmpty(params.get(requiredFields[x]).toString())){
map.put("boolean", false);
map.put("param", requiredFields[x]);
map.put("message", msgList[x]);
return map;
}
}else if(null==oneObj){
map.put("boolean", false);
map.put("param", requiredFields[x]);
map.put("message", msgList[x]);
return map;
}
}
map.put("boolean", true);
return map;
}
}
/***
* 获取 @RequestBody注解的参数信息
*/
public static Map<String, Object> getRequestBody(Object[] obj){
String sssss=JSONUtil.toJsonStr(obj);
Map<String, Object> mmp= JSONObject.parseObject((sssss.substring(1,sssss.length()-1)));
return mmp;
}
/**
* 获取请求参数
*/
public static Map<String, String> getAllRequestParam(HttpServletRequest request) {
try {
//从前端获取输入字节流
ServletInputStream requestInputStream = request.getInputStream();
//将字节流转换为字符流,并设置字符编码为utf-8
InputStreamReader ir = new InputStreamReader(requestInputStream,"utf-8");
//使用字符缓冲流进行读取
BufferedReader br = new BufferedReader(ir);
//开始拼装json字符串
String line = null;
StringBuilder sb = new StringBuilder();
while((line = br.readLine())!=null) {
sb.append(line);
}
JSONObject json = JSONObject.parseObject(sb.toString());
}catch (Exception e){
System.out.println("eeeeeee:"+e);
}
Map<String, String> res = new HashMap<>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
if (StringUtil.isEmpty(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
/********AOP的多种通知*********/
/***
* 1.方法执行前执行
*/
@Before("paramNotNull()")
public void beforeMethod(JoinPoint joinPoint){
String methodName=joinPoint.getSignature().getName();
Object[] args=joinPoint.getArgs();
System.out.println("切面开始----->>>>> The method "+methodName +" begin with :"+ Arrays.asList(args));
}
/***
* 2.方法执行后通知
*/
@After("paramNotNull()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("切面结束打--->The method: " + methodName + " 结束");
}
/***
* 3.带返回值的通知
*/
@AfterReturning(value = "paramNotNull()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("方法: " + methodName + " 结束参数: " + result);
}
/***
* 4.异常结果处理通知
*/
@AfterThrowing(value = "paramNotNull()",throwing = "e")
public void afterThrowing(JoinPoint point, Exception e) throws Throwable {
String target = point.getSignature().getDeclaringTypeName(); // 全路径类名
String classNm = target.substring(target.lastIndexOf(".") + 1, target.length()); // 类名截取
String method = point.getSignature().getName(); // 获取方法名
log.error("{}.{} 【异常信息】: {}", classNm, method, e.getMessage());
}
/********AOP的多种通知*********/
}
4. 自定义注解实现
(这里的自定义注解在Controller的方法上使用)
@ResponseBody
@RequestMapping(value = "/get-monitorconfig-list", method = RequestMethod.POST)
@ParamsCheckNull(params = {"userStrId"}, msg = {"用户ID参数为空"})
public Map<String, Object> trainerGetMonitoringDeviceMethod(@RequestBody UserInfoPojo pram){
/**[此处为自己业务的代码]**/
return null;
}
上面的注解 @ParamsCheckNull 为自定义的注解,params 为@RequestBody参数UserInfoPojo类中需要判断处理的属性名,msg 为params对应下标的判断提示语,判断多个参数时:params = {"userStrId","userName"}, msg = {"用户ID参数为空","用户名不能为空"},如果params的元素个数不等于msg的元素个数,则会抛出自定义的错误:throw new BaseErrorException(BaseErrorCode.DO_ERROR, "Code writing error");