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;
}