一、aop日志简述
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善,是spring中最核心的原则。在实际开发中,日志打印是非常重要的,一般都需要对请求的出参入参以及处理时间进行打印。为了提高代码的重用性,一般将这些日志打印放在aop中。
二、代码示例
1.引入jar包依赖
<!-- 对aop的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.aop编写
package com.wangcongming.aop;
import com.alibaba.fastjson.JSON;
import com.wangcongming.annotation.LogDescription;
import com.wangcongming.annotation.LogType;
import com.wangcongming.util.CollectionUtils;
import com.wangcongming.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
/**
* @author wangcongming
* @version V1.0
* @ClassName LogAspect
* @Description 日志切面
* @Pointcut 定义切点 有两种写法
* 1. @Pointcut("execution(public * com.learn.blog.controller.*.*(..))")
* 2. @Pointcut("@annotation(com.chhliu.springboot.aop.annotation.RedisCache)") 注解类型
* @Before 定义前置通知
* 获取请求信息
* ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
* HttpServletRequest request = attributes.getRequest();
* @date 2018/4/7 18:11
*/
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 定义切点
*/
@Pointcut("@within(com.wangcongming.annotation.AopLog)")
public void logPointcut(){}
/**
*
* @param joinPoint
* @throws Throwable
*/
@Before("logPointcut()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
}
@AfterReturning(returning = "ret", pointcut = "logPointcut()")
public void doAfterReturning(Object ret) throws Throwable {
}
//后置异常通知
@AfterThrowing(pointcut = "logPointcut()",throwing = "e")
public void error(JoinPoint jp, Throwable e){
log.error(getUrl() + ":请求异常<<<<<<<<<<<<<<<<<<<<<<<<<",e);
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("logPointcut()")
public void after(JoinPoint jp){
}
/**
* 用于打印日志
* 当未使用注解时
* @param pjp
* @return
*/
//环绕通知,环绕增强,相当于MethodInterceptor
@Around("logPointcut()")
public Object around(ProceedingJoinPoint pjp) {
long currentTime = System.currentTimeMillis();
try {
//拦截的实体类
Object target = pjp.getTarget();
//拦截方法的参数
Object[] args = pjp.getArgs();
//拦截的方法名称
String methodName = pjp.getSignature().getName();
LogDescription logDes = this.getLogDes(pjp);
String name = "";
if(logDes == null){
name = getUrl();
return dealLog(name,methodName,args,currentTime,pjp);
}else{
name = logDes.name();
name = StringUtils.isEmpty(name) ? getUrl() : name;
LogType[] type = logDes.type();
Map<Object, Object> map = CollectionUtils.convertMap(type);
if(map.get(LogType.ALL) != null){
return dealLog(name, methodName,args, currentTime,pjp);
}
if(map.get(LogType.PARAMETER) != null){
log.info("|--->{}<--|<<{}>>请求参数:{}",methodName,name,args);
}
//执行方法,并获取返回结果
Object o = pjp.proceed();
if(map.get(LogType.RESULT) != null){
log.info("|--->{}<--|<<{}>>请求返回结果:{}",methodName,name, JSON.toJSONString(o));
}
if(map.get(LogType.TIME) != null){
long endTime = System.currentTimeMillis();
log.info("|--->{}<--|<<{}>>请求耗时:{}ms******************",methodName,name,(endTime - currentTime));
}
return o;
}
} catch (Throwable e) {
log.error("aop异常",e);
return null;
}
}
private Object dealLog(String name,String methodName,Object[] args,long currentTime,ProceedingJoinPoint pjp){
log.info("|--->{}<--|<<{}>>请求参数:{}",methodName,name,args);
try {
Object o = pjp.proceed();
log.info("|--->{}<--|<<{}>>请求返回结果:{}",methodName,name, JSON.toJSONString(o));
long endTime = System.currentTimeMillis();
log.info("|--->{}<--|<<{}>>请求耗时:{}ms******************",methodName,name,(endTime - currentTime));
return o;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
private LogDescription getLogDes(ProceedingJoinPoint pjp){
Method method = getMethod(pjp);
if(method == null){
log.error("无法获取到目标方法对象。。。。。");
return null;
}
LogDescription annotation = method.getAnnotation(LogDescription.class);
return annotation;
}
/**
* 获取打印日志的标识符,优先使用注解LogDescription 中的name,获取不到则使用url
* @return
*/
private String getName(ProceedingJoinPoint pjp){
LogDescription logDes = this.getLogDes(pjp);
//注解不存在
if(logDes == null){
return getUrl();
}
String name = logDes.name();
//注解中name未指明值
if(StringUtils.isEmpty(name)){
return getUrl();
}
return name;
}
/**
* 获取所拦截的目标方法 Method
* @param pjp
* @return
*/
private Method getMethod(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
Class<?>[] argTypes = new Class[pjp.getArgs().length];
for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass();
}
Method method = null;
try {
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return method;
}
/**
* 获取当前请求的url
* @return
*/
private String getUrl(){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURI().toString();
return url;
}
/**
* 获取请求参数列表
* @param request
* @return
*/
private String getParams(HttpServletRequest request){
Enumeration<String> parameterNames = request.getParameterNames();
StringBuffer params = new StringBuffer();
while (parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
String value = request.getParameter(name);
params.append(String.format("参数:[%s=%s] ",name,value));
}
return params.toString();
}
}
代码中的logPointcut()是对注解进行切入的,也可以对包进行切入。使用注解切入,有一个好处就是,logaop可以单独封装成一个jar包,直接引入就可使用。
其中@AopLog @LogDescription都是自定义的注解,代码如下:
package com.wangcongming.annotation;
import java.lang.annotation.*;
/**
* @author wangcongming
* @version V1.0
* @ClassName AopLog
* @Description 自定义log注解 该注解只能被使用在controller层
* @date 2018/4/7 18:11
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AopLog {
String value() default "";
}
package com.wangcongming.annotation;
import java.lang.annotation.*;
/**
* @author wangcongming
* @version V1.0
* @ClassName LogDescription
* @Description 自定义log注解 该注解只能被使用在controller层
* @date 2018/4/7 18:11
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogDescription {
//标识符,建议使用url
String name() default "";
//打印日志类型
LogType[] type() default {};
}
三、pointcut的其他用法
Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的.
Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合.
args()
@args()
execution()
this()
target()
@target()
within()
@within()
@annotation
其中execution 是用的最多的,其格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
returning type pattern,name pattern, and parameters pattern是必须的.
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名,*代表所以,set*,代表以set开头的所有方法.
parameters pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个为String类型.
举例说明:
任意公共方法的执行:
execution(public * *(..))
任何一个以“set”开始的方法的执行:
execution(* set*(..))
AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.xyz.service..*.*(..))
定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
***> 最靠近(..)的为方法名,靠近.*(..))的为类名或者接口名,如上例的JoinPointObjP2.*(..))
pointcutexp包里的任意类.
within(com.test.spring.aop.pointcutexp.*)
pointcutexp包和所有子包里的任意类.
within(com.test.spring.aop.pointcutexp..*)
实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类.
this(com.test.spring.aop.pointcutexp.Intf)
***> 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.
带有@Transactional标注的所有类的任意方法.
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)
带有@Transactional标注的任意方法.
@annotation(org.springframework.transaction.annotation.Transactional)
***> @within和@target针对类的注解,@annotation是针对方法的注解
参数带有@Transactional标注的方法.
@args(org.springframework.transaction.annotation.Transactional)
参数为String类型(运行是决定)的方法.
args(String)