基于aop思想实现日志打印
基于切点表达式实现:
- execution详解
execution的语法表达式如下:execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> )
其中,修饰符和异常是可选的,如果不加类路径,则默认对所有的类生效。它常用实例如下: - 通过方法签名、返回值定义切点:
execution(public * *Service(..))
:定位于所有类下返回值任意、方法入参类型、数量任意,public类型的方法execution(public String *Service(..))
:定位于所有类下返回值为String、方法入参类型、数量任意,public类型的方法
- 通过类包定义切点:
execution(* com.yc.controller.BaseController+.*(..))
:匹配任意返回类型,对应包下BaseController类及其子类等任意方法。execution(* com.*.(..))
:匹配任意返回类型,com包下所有类的所有方法execution(* com..*.(..))
:匹配任意返回类型,com包、子包下所有类的所有方法
注意.表示该包下所有类,…则涵括其子包。
- 通过方法入参定义切点
- 这里“*”表示任意类型的一个参数,“…”表示任意类型任意数量的参数
execution(* speak(Integer,*))
:匹配任意返回类型,所有类中只有两个入参,第一个入参为Integer,第二个入参任意的方法execution(* speak(..,Integer,..))
:匹配任意返回类型,所有类中至少有一个Integer入参,但位置任意的方法。
- 常用切点表达式
execution(* com.yc.service..*.*(..))
在配置service层的事务管理时常用,定位于任意返回类型(第一个”*”
) 在com.yc.service包以及(“..”
)子包下的(第一个“*”
)所有类(第二个”*”
)下的所有方法(第三个”*”
),且这个方法的入参为任意类型、数量(体现在“(..)“
)
/**
* Created by yangmin on 2020/10/26
*/
@Aspect
@Component
public class LoggerAop {
private static Logger log = LoggerFactory.getLogger(LoggerAop.class);
/**
* 此处的切点是注解的方式
* 只要出现 @LogAnnotation注解都会进入
*/
/**
* 此处的切点使用的是切点表达式
* execution(* com.yc.service.*.*(..))在配置service层的事务管理时常用,定位于任意返回类型(第一个”*”) 在com.yc.service包下的所有类(第二个”*”)下的所有方法(第三个”*”),且这个方法的入参为任意类型、数量(体现在 “(..)“)
*
*/
@Pointcut("execution(public * com.study.studyaop.service..*.*(..))")//切入点描述,这个是service包的切入点
public void logPointCut() {
}
/**
* 环绕增强,相当于MethodInterceptor
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("==============================================start==================================================");
Object result=null;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取类名
String className = joinPoint.getTarget().getClass().getName();
//获取方法名
String methodName = signature.getName();
long beginTime = System.currentTimeMillis();
//获取方法参数
Object[] args = joinPoint.getArgs();
//在参数中去除Request或者Response对象,(joinPoint.getArgs()返回的数组中携带有Request或者Response对象,导致序列化异常。)
Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
List<Object> logArgs = stream
.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
.collect(Collectors.toList());
String params = null;
if (args.length != 0) {
params = JSON.toJSONString(logArgs);
}
HttpServletRequest request = HttpContextUtil.getRequest();
//获取请求方法的类型:get 或则post
String method = request.getMethod();
//获取请求路劲
String url = request.getRequestURL().toString();
//获取ip
String ip = IpUtils.getIP(request);
log.info("请求的路径为 : {}",url);
log.info("请求ip : {}",ip);
log.info("方法请求类名 : {}",className);
log.info("方法名 : {}",methodName);
log.info("方法类型 : {}",method);
log.info("请求参数 : {}",params);
//执行方法
try {
result = joinPoint.proceed();
} catch (Exception e) {
log.error("异常信息 : {}",e.getMessage());
e.printStackTrace();
}finally {
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
log.info("执行时间 : {}",time);
log.info("==============================================end==================================================");
return result;
}
}
/**
获取request的方法
*/
public HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
}
/**
* 获取IP地址的方法
* @return
*/
public String getIpAddress() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
基于注解实现
annotation 此注解用于定位标注了某个注解的目标切点。下面我们来看一个模拟用户登录成功后日志记录的示例
1.自定义注解
/**
* 日志注解
*
* @author lastwhisper
*/
@Target(ElementType.METHOD) // 方法注解
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface LogAnno {
String operateType();// 记录日志的操作类型
int type();
}
- 目标方法
/**
*登录方法
* @param request
* @return
* @throws Exception
*/
@LogAnno(operateType = "登录",type=10)
@RequestMapping(value = "/tologin", method = RequestMethod.POST)
public String login(@RequestBody Map<String,String> map , HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = map.get("username");
String password = map.get("password");
String code = map.get("code");
LoginResult loginResult = loginService.login(code,username, password,request,response);
String s = JsonUtils.toString(loginResult);
return s;
}
3.增强
@Order(3)//优先级
@Component
@Aspect
public class LogAopAspect {
@Resource
private PermissApiClient permissApiClient;
@Around("@annotation(com.woyaoce.core.annotation.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 1.方法执行前的处理,相当于前置通知
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 获取方法
Method method = methodSignature.getMethod();
// 获取方法上面的注解
LogAnno logAnno = method.getAnnotation(LogAnno.class);
// 获取操作描述的属性值
String operateType = logAnno.operateType();
int type = logAnno.type();
// 创建一个日志对象(准备记录日志)
CmsLog cmsLog = new CmsLog();
cmsLog.setType(type);
String ip = HttpContextUtil.getIpAddress();
cmsLog.setIp(ip);
Object result = null;
try {
// 让代理方法执行
result = pjp.proceed();
// 2.相当于后置通知(方法成功执行之后走这里)
cmsLog.setStatus(0);
cmsLog.setLogMsg(operateType);// 设置操作结果
} catch (Exception e) {
// 3.相当于异常通知部分
cmsLog.setStatus(1);
cmsLog.setLogMsg(operateType);// 设置操作结果
} finally {
// 设置操作人,从session中获取,
Subject LoginUser = SecurityUtils.getSubject();
Session session = LoginUser.getSession();
String userName =(String) session.getAttribute("userName");
cmsLog.setLoginName(userName);
// 4.相当于最终通知
//cmsLog.setCreateDate(new Date());// 设置操作日期
// 添加日志记录
permissApiClient.addLog(cmsLog);
}
return result;
}
}