AOP的定义
AOP通过 预编译方式 和 运行期动态代理 实现,在不修改源代码的情况下,给程序动态统一添加功能的一种技术,简称AOP,一句话总结:在不改变原有代码的条件下,对功能进行扩展
公式:AOP=切入点表达式 + 通知方法
AOP的一些概念
- 1.连接点:在执行正常的业务过程中满足了切入点表达式时进入切面的点.(织入)多个
- 2.通知: 在切面中执行的具体的业务 (方法)
(1)前置通知(@Before): 目标方法执行之前执行
(2)后置通知(@After):在目标方法运行结束之后运行(无论方法正常结束还是异常结束)
(3)异常通知(@AfterThrowing): 目标方法执行之后抛出异常时执行
(4)返回通知(@AfterReturning):在目标方法正常返回之后运行
说明:上面的四大通知类型不能控制目标方法是否执行,一般适用上面的四大通知类型,都是用来记录程序的执行状态.
(5)环绕通知(@Around):在目标方法执行前后都要执行的通知方法.控制目标方法是否执行,并且环绕通知的功能最为强大 - 3.切入点:能够进入切面的一个判断 (if判断 一个)
切入点表达式说明:
1)bean(bean的id) 类名首字母小写 匹配一个类
2).within(包名.类名) 按包路径匹配类 匹配多个类
上述表达式时粗粒度的控制,按类匹配
3)execution(返回值类型 包名.类名.方法名[参数列表])
4)@annotation(包名.注解名) 按注解进行拦截
使用AOP记录日志操作
- 1、自定义注解
/**
* 标记需要做业务日志的方法
@Target 指定注解作用范围(比如说:该注解是作用在类上,还是方法,或者是属性上等等)
@Retention 指定注解的生命周期(也就是注解的保留时间,是在编译器有效,还是运行时有效等等)
@Documented 是一个标记注解,里面没有任何属性,用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。(不常用,可选项)
@Inherited 也是一个标记注解,,没有定义属性,作用是为了表示该注解可以被继承(比如说:你自定义了一个A注解,并且使用了@Inherited修饰,然后在paren类使用了A注解,那么paren的所有子类都默认继承了注解A)。【不常用】
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface BussinessLog {
/**
* 业务的名称,例如:"修改菜单"
*/
String value() default "";
/**
* 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"
*/
String key() default "id";
/**
* 字典(用于查找key的中文名称和字段的中文名称)
*/
String dict() default Dict.SYSTEM_DICT;
/**
* request参数默认为json
* @return
*/
String type() default ParaType.JSON;
}
- 2、AOP相关配置类
/**
* 日志记录
*/
/**
* 遇到的问题:
* 1、AOP没有生效:因为切面是个bean,需要注入到容器,springboot默认扫描启动类所在的包下的bean,需要在启动类加上注解 @ComponentScan
*
* 2、启动服务报错:error Type referred to is not an annotation type, @Pointcut("@annotation(OptLogAnnotation)") 改成 @Pointcut("@annotation(com.dfq.aop.annotation.OptLogAnnotation)")
*
* 3、class.getMethod(msig.getName()) 报错, 因为实际执行的方法有参数 改成 clas.getMethod(msig.getName(), msig.getParameterTypes())
*
* 4、获取请求参数方法 point.getArgs()
*
* 5、ProceedingJoinPoint is only supported for around advice报错原因: ProceedingJoinPoint只能用 @Around
*/
@Aspect
@Component
public class LogAop {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final String POINT_CUT ="execution(public * net.ruixin.dao.*.*(..))";
//定义切点 @Pointcut
//在注解的位置切入代码
@Pointcut(value = "@annotation(net.ruixin.service.plat.log.aop.BussinessLog)")
public void cutService() {
}
@Pointcut(POINT_CUT)
public void cutDao() {
}
//切面 配置通知
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先执行业务
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日志记录出错!", e);
}
return result;
}
private void handle(ProceedingJoinPoint point) throws Exception {
//从切面织入点处通过反射机制获取织入点处的方法
Signature sig = point.getSignature();
MethodSignature msig;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
//获取切入点所在的方法
String methodName = currentMethod.getName();
//如果当前用户未登录,不做日志
ShiroUser user = ShiroKit.getUser();
if (null == user) {
return;
}
//获取拦截方法的参数
String className = point.getTarget().getClass().getName();
//获取当前用户操作信息
// Object[] params = point.getArgs();
//获取操作名称
BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
String bussinessName = annotation.value();
String key = annotation.key();
String dictClass = annotation.dict();
String paraType = annotation.type();
//如果涉及到修改,比对变化
String msg;
if (Objects.equals(paraType, ParaType.ATTRIBUTE)) {
if (bussinessName.contains("修改")) {
Object obj1 = LogObjectHolder.me().get();
Map<String, String> obj2 = HttpKit.getRequestParameters();
msg = Contrast.contrastObj(dictClass, key, obj1, obj2);
} else {
Map<String, String> parameters = HttpKit.getRequestParameters();
AbstractDictMap dictMap = DictMapFactory.createDictMap(dictClass);
msg = Contrast.parseMutiKey(dictMap, key, parameters);
}
} else {
//如何使用json数据,需要特殊转换处理
Map<String, String> parameters = HttpKit.getRequestParameters();
msg = "日志名称:"+bussinessName + "; 操作数据: " +parameters.toString();
}
// 保存操作日志
LogManager.me().executeLog(LogTaskFactory.bussinessLog(user, bussinessName, className, methodName, msg));
}
}
- 3、使用自定义注解
@ResponseBody
@RequestMapping(value = "/delSysOrgan")
@BussinessLog(value = "机构删除")
public AjaxReturn delSysOrgan(Long id, Long newOrganId) {
organService.delSysOrgan(id, newOrganId);
return success();
}
- 4、日志实体类
@Table(name = "SYS_LOG_OPERATION")
@Entity
@DynamicInsert
@DynamicUpdate
public class OperationLog extends BaseDomain {
@Id
@SequenceGenerator(name = "seq_sys_log", sequenceName = "SEQ_SYS_LOG_LOGIN", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_sys_log")
private Long id;
/**
* 日志类型
*/
@Column(name = "LOG_TYPE")
private String logType;
/**
* 日志名称
*/
@Column(name = "LOG_NAME")
private String logName;
/**
* 用户id
*/
@Column(name = "USER_ID")
private Long userId;
/**
* 用户名
*/
@Column(name = "USER_NAME")
private String userName;
/**
* 类名称
*/
@Column(name = "CLASS_NAME")
private String className;
/**
* 方法名称
*/
@Column(name = "METHOD")
private String method;
/**
* 创建时间
*/
@Column(name = "CREATE_TIME")
private Date createTime;
/**
* 是否成功
*/
@Column(name = "SUCCESS")
private String success;
}