自定义注解+自定义springAOP环绕增强实现与使用

通过一个模拟情景来进行讲解:项目打算增加一个日志审计功能,对所有的功能接口做一个事件记录,保存相关的请求地址、接口地址、用户账号信息、用户代理信息等。

我们对该功能做一个大致的想法:通过自定义的注解作为aop的开启条件,在该注解修饰的方法/接口下切入,通过AOP的环绕通知特性,环绕前置获取到请求信息作为记录,环绕后置请求成功后将事件访问持久化保存下来。

1. 自定义声明注解

自定义注解用来作为事件的启用,具体的注释已经放在代码上了,仅仅是一个注解的声明,功能的实现大多还是在AOP中实现的。

// 注解的生命周期  3种:source:源码上 class:字节码上 runtime:运行时 source < calss < runtime
@Retention(RetentionPolicy.RUNTIME)
// 注解可用范围:类 、 方法、 参数...... 详细范围可见ElementType中的枚举类
@Target({ElementType.METHOD})
// 元注解 文档显示
@Documented
// 注解自动继承
// 父子类上,如果父类上标注的 某个注解上带@Inherited的注解 ,那么子类也可以拥有该注解。
// 如果接口上 某个注解上带@Inherited的注解,实现类则不会拥有该接口的这个注解
@Inherited
// @interface 注解的声明方式
public @interface AuditRecord {


    /**
     * 注解的方法声明,在使用注解时必须传入值 可以用default "" 就无需传值了。
     * 如果只有一个可以在注解上省略 value="**" 多个必须指定字段名和值的对应关系 例:@XXX(value="**", value2="***")
     *
     * @return
     */
    String value();
//    String value() default "";

}

2. 自定义AOP切面类

在通过指定的切点进入切面生成代理对象,对注释所修饰的方法上进行环绕通知,在请求前获取相关请求信息,请求完成后将事件记录到数据库中持久化。几乎每行代码都有注释,有问题评论区讨论。

// 声明切面
@Aspect
// 配置注解
@Component
// 日志
@Slf4j
public class AuditRecordAspect {

    /**
     * 注解@Pointcut:声明一个切点。@Pointcut包含多个表达式,@annotation是其中一种
     * 注解@annotation:当执行的方法上拥有指定的注解时生效。
     * 理解为当某个方法上拥有@AuditRecord注解时,会进入controllerAspect()方法
     */
    @Pointcut("@annotation(com.example.web.config.AuditRecord)")
    private void controllerAspect() {
    }
    @Resource
    private UserService userService;
    
    /**
     * 注解@Around("controllerAspect()")指的是在该方法之前通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @ResponseBody
    @Around("controllerAspect()")
    public Object doRecord(ProceedingJoinPoint joinPoint) throws Throwable {
		// 	环绕前通知,接口请求前触发 也在before通知前触发
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        // 1. getDeclaredAnnotations和getAnnotations方法指的是获取修饰在 类/方法 上的注解
        // 2. getDeclaredAnnotations得到的是当前成员所有的注解,不包括继承的。而getAnnotations得到的是包括继承的所有注解。
        // 3. getAnnotation(XXX.class) 指的是获取XXX类型的注解,如果没有返回的是null
        AuditRecord annotation = method.getAnnotation(AuditRecord.class);

		// 获取注释参数
        String eventName = auditRecord.eventName();
        // 通过请求获取客户端ip地址
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        String clientIP = getIpAdrress(request);
        // 获取当前用户登录的认证对象(项目中Security定制化的登录认证对象)
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        Map<String, Object> userAuthentication = null;
        if (authentication != null) {
            userAuthentication = new HashMap<>();
            userAuthentication.put("name", authentication.getName());
            userAuthentication.put("principal", authentication);
            // 业务通过id查询出登录的账户信息
            User user = userService.selectByPrimaryKey(authentication.getId());
            userAuthentication.put("details", user);
        }

        HttpServletRequest httpServletRequest = HttpContextUtil.getHttpServletRequest();
        String eventSource = httpServletRequest.getHeader("Referer");
        if (StrUtil.isBlank(eventSource)) {
            eventSource = httpServletRequest.getHeader("origin");
        }
		// 请求参数名称数组
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
		// 请求参数value数组
        Object[] args = joinPoint.getArgs();
        cn.hutool.json.JSONArray arrayValues = JSONUtil.parseArray(JSONUtil.toJsonStr(args));
        cn.hutool.json.JSONObject argMap = JSONUtil.createObj();

        for (int i = 0; i < argNames.length; i++) {
            argMap.put(argNames[i], arrayValues.get(i));
        }


        String requestUri = httpServletRequest.getRequestURI();
        String queryString = httpServletRequest.getQueryString();

        Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();

        JSONObject requestParameters = new JSONObject();
        requestParameters.put("queryString", queryString);
        requestParameters.put("parameterMap", parameterMap);
        requestParameters.put("args", argMap);
		
		// 执行接口功能,返回response 
        Object result = joinPoint.proceed();

		// 	环绕后通知,接口请求成功后触发  也在after前触发  用来记录事件
        ActionEvent actionEvent = new ActionEvent()
                .setRequestId(IdUtil.fastUUID())
                .setRequestUri(requestUri)
                .setEventName(eventName)
                .setEventTime(LocalDateTime.now())
                .setEventSource(eventSource)
                .setSourceIpAddress(clientIP)
                .setRequestParameters(requestParameters)
                .setUserAgent(ServletUtil.getHeaderIgnoreCase(getHttpServletRequest(), "User-Agent"))

		// 记录接口请求结果
        if (result instanceof ResponseMessage) {
            actionEvent.setResponseBody(JSONUtil.toBean(JSONUtil.toJsonStr(result), JSONObject.class));
            if (!((ResponseMessage<?>) result).getSuccess()) {
                actionEvent.setErrorCode(String.valueOf(((ResponseMessage<?>) result).getCode()));
                actionEvent.setErrorMessage(((ResponseMessage<?>) result).getMsg());
            }
        }
        // 对象持久化
        ThreadUtil.execute(() -> {
            actionEventService.insertSelective(actionEvent);
        });
        return result;
    }
    
     /**
     * 获取Ip地址方法
     *
     * @param request
     * @return
     */
    private static String getIpAdrress(HttpServletRequest request) {

        String iPAddress = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(iPAddress) && !"unKnown".equalsIgnoreCase(iPAddress)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = iPAddress.indexOf(",");
            if (index != -1) {
                return iPAddress.substring(0, index);
            } else {
                return iPAddress;
            }
        }
        iPAddress = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(iPAddress) && !"unKnown".equalsIgnoreCase(iPAddress)) {
            return iPAddress;
        }
        if (StringUtils.isBlank(iPAddress) || "unknown".equalsIgnoreCase(iPAddress)) {
            iPAddress = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(iPAddress) || "unknown".equalsIgnoreCase(iPAddress)) {
            iPAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(iPAddress) || "unknown".equalsIgnoreCase(iPAddress)) {
            iPAddress = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(iPAddress) || "unknown".equalsIgnoreCase(iPAddress)) {
            iPAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(iPAddress) || "unknown".equalsIgnoreCase(iPAddress)) {
            iPAddress = request.getRemoteAddr();
        }
        if("127.0.0.1".equals(iPAddress) || "0:0:0:0:0:0:0:1".equals(iPAddress)){
            //根据网卡取本机配置的IP
            InetAddress inet=null;
            try {
                inet = InetAddress.getLocalHost();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            iPAddress= inet.getHostAddress();
        }

        return iPAddress;
    }
 }

你可能感兴趣的:(spring,java,前端,spring)