一,关于切面的概念
1 五种通知类型
@Before 前置通知 可以拿到请求参数
@Around 环绕通知 可以拿到请求参数,返回值,及控制方法是否执行,还能抓取异常
@AfterReturning 后置通知 可以拿到请求参数,返回值
@After 最终通知 类似finally,通常用于释放资源,该方法一般在后置通知之前执行
@AfterThrowing 异常通知 ,有异常才会通知
2 需要了解的概念
切面(aspect) 通常是指通知的方法所在的类,这个类就是切面类,也可称为切面
连接点(JoinPoint)就是在切面类里面,把切入点(通常是个注解)和通知方法关联的那段代码,一个上面有注解的空方法
通知(Advice)就是对于这个切点要做的事的所在方法
切入点(PointCut)就是你的切面要作用在哪些方法,就是说你这个注解放在哪,通常是一组方法
目标对象(target)就是要作用的对象(方法,属性,或类)
二 代码实现
1 自定义注解
/**
* 系统日志注解
*
* @author xuxiaorong
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Syslog1 {
String value() default "默认值";
}
2 切面
@Aspect
@Component
public class SysLogAspect1 {//这个类就是切面
Logger logger = LoggerFactory.getLogger(SysLogAspect1.class);
@Pointcut("@annotation(com.fh.annotation.Syslog1)")//注解其实就是切点
public void log1PointCut() {
System.out.println("切点里面");
}
@Before("log1PointCut()")//前置通知
public void testBefore(JoinPoint point){//JoinPoint就是连接点,切点的实现方法就是通知
System.out.println("加在注解上的前置方法");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//请求的方法名
String className = point.getTarget().getClass().getName();
String methodName = signature.getName();
StringBuilder info = new StringBuilder();
info.append("参数:")
.append(JSON.toJSONString(((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getParameterMap()));
System.out.println(info);//获取请求参数
}
@Before("within(com.fh.controller.cuijimanage.CuijiManageController*)")//前置通知,可以用类路径的形式来写
public void log1(JoinPoint point){
System.out.println("--加在类路径的前置方法");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//请求的方法名
String className = point.getTarget().getClass().getName();
String methodName = signature.getName();
StringBuilder info = new StringBuilder();
info.append("参数:")
.append(JSON.toJSONString(((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getParameterMap()));
System.out.println(info);
}
@AfterReturning( pointcut= "execution(* com.fh.controller.cuijimanage.CuijiManageController.*(..))", returning="returnValue")//后置通知
public void log(JoinPoint point, Object returnValue) {
System.out.println("后置通知");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//请求的方法名
String className = point.getTarget().getClass().getName();
String methodName = signature.getName();
StringBuilder info = new StringBuilder();
info.append("参数:")
.append(JSON.toJSONString(((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getParameterMap()));
System.out.println(info);//获取方法的请求参数
System.out.println("返回值:"+JSON.toJSON(returnValue));//获取方法的返回值
}
@After("execution(* com.fh.controller.cuijimanage.CuijiManageController.*(..))")//最终通知,一般用于释放资源
public void releaseResource(JoinPoint point) {
System.out.println("最终方法");
System.out.println("@After:模拟释放资源...");
System.out.println("@After:目标方法为:" +
point.getSignature().getDeclaringTypeName() +
"." + point.getSignature().getName());
StringBuilder info = new StringBuilder();
info.append("参数:")
.append(JSON.toJSONString(((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getParameterMap()));
System.out.println("@After:参数为:" + info );//同样能获取方法的请求参数,一般不这样做
System.out.println("@After:被织入的目标对象为:" + point.getTarget().getClass().getName());
}
@Around("log1PointCut()")//环绕通知
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕通知");
long beginTime = System.currentTimeMillis();
//获取签名
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//请求的方法名
String className = point.getTarget().getClass().getName();
String methodName = signature.getName();
Syslog1 syslogAnnotation = method.getAnnotation(Syslog1.class);
Object result = null;
//操作记录 参数+数据
StringBuilder info = new StringBuilder();
Integer status = new Integer(0);
//设置IP地址 操作人+Ip
String ip = "";
try {
//系统注销时获取
ip = "192.168.188.133";
//执行方法
result = point.proceed();
try {
info.append("参数:")
.append(JSON.toJSONString(((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getParameterMap()))//获取方法的请求参数
.append(";返回值:")
.append(result == null ? "" : (String) JsonUtil.toJsonString(result));//获取方法的返回值
if (result instanceof ResultJSON && JSON.toJSONString(result).contains("false")) {
status = 1;
}
} catch (Exception ex) {
logger.error("转json异常", ex);
info.setLength(0);
info.append("异常:转json异常");
status = 1;
}
} catch (Throwable ex) {//异常通知
logger.error(className + "." + methodName + "():执行异常" + ex.getMessage(), ex);
info.setLength(0);
info.append("异常:" + JSON.toJSONString(ex.getStackTrace()[0]));
status = 1;
}
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
logger.info("操作日志信息{" + className + "." + methodName
+ "():执行" + syslogAnnotation.value() + "耗时" + "" + time + "毫秒}");
return result;
}
}
3 切点
/**
* 发短信,动作,0 立即发送 1 定时发送
*/
@Syslog1("催计-发短信")//这是一个切点
@RequestMapping(value = "/doMessage.do")
@ResponseBody
public Map<String, Object> doMessage(HttpServletRequest request) throws Exception {}
@Syslog1("催收详情-新增催收记录") //又一个切点,实现日志监控,只需加一个注解即可
@RequestMapping(value = "addCuishouRecord.do", method = RequestMethod.POST)
public ModelAndView addRecord(CallLoanLog callLoanLog) {}
4 效果展现
环绕通知
--加在类路径的前置方法
参数:{"contType":["2"],"contUserName":["唐豆豆1"],"tellPhone":["13524929813"],"callLoanNid":["2017101700005"],"callUser":["503"],"realName":["陈浩"],"mode":["0"],"message":["短信模板"],"sendTime":[""],"sendmode":["0"]}
加在注解上的前置方法
参数:{"contType":["2"],"contUserName":["唐豆豆1"],"tellPhone":["13524929813"],"callLoanNid":["2017101700005"],"callUser":["503"],"realName":["陈浩"],"mode":["0"],"message":["短信模板"],"sendTime":[""],"sendmode":["0"]}
最终通知
@After:模拟释放资源...
@After:目标方法为:com.fh.controller.cuijimanage.CuijiManageController.doMessage
@After:参数为:参数:{"contType":["2"],"contUserName":["唐豆豆1"],"tellPhone":["13524929813"],"callLoanNid":["2017101700005"],"callUser":["503"],"realName":["陈浩"],"mode":["0"],"message":["短信模板"],"sendTime":[""],"sendmode":["0"]}
@After:被织入的目标对象为:com.fh.controller.cuijimanage.CuijiManageController
后置通知
参数:{"contType":["2"],"contUserName":["唐豆豆1"],"tellPhone":["13524929813"],"callLoanNid":["2017101700005"],"callUser":["503"],"realName":["陈浩"],"mode":["0"],"message":["短信模板"],"sendTime":[""],"sendmode":["0"]}
返回值:{"num":1}
5 结论
1先执行环绕通知的 point.proceed()之前的部分,2然后执行前置通知,3执行目标方法,4执行最终通知,5执行后置通知