AOP-实现日志管理
程序员文章站
2022-03-02 15:39:43
...
AOP
实现aop的四种方式
- AspectJ
- AspectWerkz
- SpringFramework
- JBoss
AOP核心概念
- 1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点 - 2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象 - 3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造 - 4、切入点(pointcut)
对连接点进行拦截的定义 - 5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类 - 6、目标对象
代理的目标对象 - 7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程 - 8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
切入点语法
- [email protected]("下面的东西")
任何类的任何返回值的任何方法
- 1. execution(public * *(..))
任何类的set开头的方法
- 2.execution(* set*(..))
任何返回值的规定类里面的方法
- 3.execution(* com.example.demo.AccountService.*(..))
任何返回值的,规定包或者规定包子包的任何类任何方法
- 4.execution(* com.example.demo.service..*.*(..))
- [email protected]("@annotation(自定义注解)")
环绕通知(前置通知+执行方法+后置通知)语法
- @Around("logPointCut()") //@Pointcut打在哪个方法,就填哪个方法
- @Around(execution(* com.example.demo.service...(..)))
注意:执行方法:method(ProceedingJoinPoint joinPoint),要带joinpoint这个参数
AspectJ
- AspectJ是目前最完善的AOP语言
- AspectJ是对Java编程语言的扩展,通过增加了一些新的构造块支持对横切关注点的模块化封装,通过对源代码级别的代码混合实现织入,是一种典型的使用静态织入的AOP实现机制。
- AspectJ提供了两种横切实现机制
- 动态横切(DynamicCrosscutting)
- 另一种称为静态横切(StaticCrosscutting)。
简要api
Joinpoint
- java.lang.Object[] getArgs()/*获取连接点方法运行时的入参列表*/
- Signature getSignature() /*获取连接点的方法签名对象*/
- java.lang.Object getTarget() //获取连接点所在的目标对象
- java.lang.Object getThis() //获取代理对象本身
ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法
//通过反射执行目标对象的连接点处的方法
- java.lang.Object proceed() throws Throwable
//通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
- java.lang.Object proceed(java.lang.Object[] args) throws Throwable
示例代码
使用aop进行日志管理(自定义注解方式)
自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log{
String value() default ""; //用于描述方法作用
/**
* 是否忽略返回值,仅方法上有效
* @return
*/
boolean ignoreReturn() default false;
}
切面类
/**
* 日志切面
*/
@Aspect
//切面优先级
@Order(100)
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
private static final String STRING_START = "\n--------------------------->\n";
private static final String STRING_END = "\n<----------------------------\n";
/**
* 切点
*/
/**
* 不使用注解方式打切点
* //@Pointcut("execution (* com.example.demo.web..*(..))")")
* 表示在使用Log注解的地方切入
*/
@Pointcut("@annotation(Log)")
public void logPointCut() {
}
//@Around("logPointCut()")---> 用于controller层
public Object controllerAround(ProceedingJoinPoint joinPoint) {
try {
return printLog(joinPoint);
} catch (Throwable throwable) {
log.error(throwable.getMessage(), throwable);
return true;
}
}
//通知:拦截到连接点之后要执行的代码
@Around("logPointCut()")
private Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
//获取连接点的方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法签名里的方法:方法签名里有两个方法:getReturnType getMethod
Method method = signature.getMethod();
//获取类
Class<?> targetClass = method.getDeclaringClass();
StringBuffer classAndMethod = new StringBuffer();
// 获取目标方法上的Log注解
Log methodAnnotation = method.getAnnotation(Log.class);
// 判断是否有LOG注解以及是否带有ignore参数
if (methodAnnotation != null) {
classAndMethod.append(methodAnnotation.value());
}
//拼接目标切入的类名称和方法名称
String target = targetClass.getName() + "#" + method.getName();
// 请求参数转JSON,对日期进行格式转换并打印出所有为null的参数
String params = JSONObject.toJSONStringWithDateFormat(joinPoint.getArgs(), dateFormat, SerializerFeature.WriteMapNullValue);
//日志打印拼接的调用信息
log.info(STRING_START + "{} 开始调用--> {} 参数:{}", classAndMethod.toString(), target, params);
long start = System.currentTimeMillis();
//proceed()通过反射执行目标对象的连接点处的方法;
Object result = joinPoint.proceed();
long timeConsuming = System.currentTimeMillis() - start;
if (methodAnnotation != null && methodAnnotation.ignoreReturn()) {
log.info("\n{} 调用结束<-- {} 耗时:{}ms" + STRING_END, classAndMethod.toString(), target, timeConsuming);
return result;
}
// 响应参数转JSON,对日期进行格式转换并打印出所有为null的参数
log.info("\n{} 调用结束<-- {} 返回值:{} 耗时:{}ms" + STRING_END, classAndMethod.toString(), target, JSONObject.toJSONStringWithDateFormat(result, dateFormat, SerializerFeature.WriteMapNullValue), timeConsuming);
return result;
}
}
实现类
@Service
@Slf4j
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper usermapper;
@Override
@Log
public User getUser(Integer id) {
User user=usermapper.getById(id);
log.info("message: {}",user);
return usermapper.getById(id);
}
}
控制台输出
--------------------------->
开始调用--> com.example.demo.service.serviceimpl.UserServiceImpl#getUser 参数:[2]
2019-01-19 21:24:57.027 [http-nio-9091-exec-1] INFO com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-01-19 21:24:57.406 [http-nio-9091-exec-1] INFO com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-01-19 21:24:57.413 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : ==> Preparing: SELECT * FROM user u WHERE u.id = ?
2019-01-19 21:24:57.436 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : ==> Parameters: 2(Integer)
2019-01-19 21:24:57.468 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : <== Total: 1
2019-01-19 21:24:57.470 [http-nio-9091-exec-1] INFO c.e.d.s.serviceimpl.UserServiceImpl : message: User(id=2, name=321, birthday=2019-01-17, address=4)
2019-01-19 21:24:57.471 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : ==> Preparing: SELECT * FROM user u WHERE u.id = ?
2019-01-19 21:24:57.471 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : ==> Parameters: 2(Integer)
2019-01-19 21:24:57.484 [http-nio-9091-exec-1] DEBUG c.e.demo.dao.UserMapper.getById : <== Total: 1
2019-01-19 21:24:57.516 [http-nio-9091-exec-1] INFO c.example.demo.config.log.LogAspect :
调用结束<-- com.example.demo.service.serviceimpl.UserServiceImpl#getUser 返回值:{"address":"4","birthday":"2019-01-17","id":2,"name":"321"} 耗时:478ms
<----------------------------