spring aop:通过自定义注解实现java埋点

spring aop:通过自定义注解实现java埋点

提出问题

近期在实习中负责了一个日志系统,主要功能为每个项目收集到用户的操作日志,将这些日志发送到一个专门收集统计日志的项目对这些日志进行管理与统计

分析问题

在这个系统当中目前据我认为有两个相对于的难点,第一个是用户操作日志的收集,第二个是当项目与用户量增多时的并发问题的处理。针对于这两个问题首先用户操作日志的收集可以利用spring的aop,我们自定义一个注解,我们需要收集哪些操作时我们给这个方法打上这个注解,然后进入到我们编写的切面中,在切面中进行用户操作的解析。本文主要解决用过aop切面完成对用户操作进行收集的功能

解决问题

如果要详细了解如何进行切面编程请去查看https://blog.csdn.net/qq_36761831/article/details/90299680
下面我们来介绍一下如何通过spring的aop来实现我们的业务需要,首先我们自定义一个注解Mylog

  /**
*
* 操作日志注解
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    /**
     * 记录操作描述
     */
    String remark() default "";
    /**
     * 是否打日志 默认打
     */
    boolean isLog() default true;
}

注解中我们定义两个属性一个是remark属性,我们用来记录当前方法的主要功能描述,isLog属性我们用于判断是否需要记录该操作日志,如果还有其他的需要,可以自行添加其余的属性。

下面我们建立一个切面来拦截这个注解进行处理,首先我们定义拦截点

@Pointcut("@annotation(com.jtexplorer.aop.annotation.MyLog)")
private void sendLog() {
}

定义完了拦截点之后,根据我们的业务需求在方法在拦截的方法执行之前,我们首先设置一些默认值

@Before("sendLog()")
public void before(JoinPoint joinPoint) {
    //初始化
    //requestMap = new HashMap<>();
    try {
        requestMap.put("usloDbStartTime", TimeTools.transformDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
        startTime = System.currentTimeMillis();
    } catch (ParseException e) {
        e.printStackTrace();
    }
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    MyLog myLog = signature.getMethod().getAnnotation(MyLog.class);
    if (myLog != null) {
        if (myLog.isLog()) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //封装域名、url
            requestMap.put("usloDomain", request.getServerName());
            requestMap.put("usloUrl", request.getServletPath());
            //请求类型
            if(request.getMethod().equals("POST")){
                requestMap.put("usloType","1");
            }else if(request.getMethod().equals("GET")){
                requestMap.put("usloType","2");
            }else if(request.getMethod().equals("PUT")){
                requestMap.put("usloType","3");
            }else if(request.getMethod().equals("DELETE")){
                requestMap.put("usloType","4");
            }
            String projectName = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogProjectName");
            if(StringUtil.isNotEmpty(projectName)){
                requestMap.put("usloItemName", projectName);
            }
            String projectId = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogProjectId");
            if(StringUtil.isNotEmpty(projectId)){
                requestMap.put("usloItemId", projectId);
            }
            //方法描述
            try {
                //获得http还是https
                String networkProtocol = request.getScheme();
                //获得端口号
                int port = request.getServerPort();
                //获得服务器ip和机器名
                InetAddress ia = InetAddress.getLocalHost();
                String hotsAddress = ia.getHostAddress();
                String hostName = ia.getHostName();
                requestMap.put("usloPor", String.valueOf(port));
                //服务器ip
                requestMap.put("usloServerAddress", hotsAddress);
                //机器名称
                requestMap.put("usloHostName", hostName);
                if (networkProtocol.equals("http")) {
                    requestMap.put("usloIdentification", String.valueOf(1));
                } else if (networkProtocol.equals("https")) {
                    requestMap.put("usloIdentification", String.valueOf(2));
                }
                String methodDescription = getControllerMethodDescription(joinPoint);
                //方法描述
                requestMap.put("usloDescription", methodDescription);
                // String ipAddress =  InetAddress.getLocalHost().toString().substring(InetAddress.getLocalHost().toString().lastIndexOf("/") + 1);
                String requestIp = IPUtil.getIpFromRequest(request);
                //访问者ip
                requestMap.put("usloVisitAddress", requestIp);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //拼接请求参数
            StringBuffer params = new StringBuffer();
            params.append("{");
            Enumeration<?> enumeration = request.getParameterNames();
            while (enumeration.hasMoreElements()) {
                String paramName = enumeration.nextElement().toString();
                params.append("\"");
                params.append(paramName + "\":\"" + request.getParameter(paramName) + "\",");
            }
            params.append("}");
            //请求数据
            requestMap.put("usloRequestData", params.toString());
            //响应数据
            requestMap.put("usloMethodName", joinPoint.getSignature().getName());
        }
    }
}
 

/**
* 获得注解中的描述信息
*
* @param joinPoint
* @return
* @throws ClassNotFoundException
*/
public static String getControllerMethodDescription(JoinPoint joinPoint) throws ClassNotFoundException {
    String targetName = joinPoint.getTarget().getClass().getName();
    String methodName = joinPoint.getSignature().getName();
    Object[] arguments = joinPoint.getArgs();
    Class targetClass = Class.forName(targetName);
    Method[] methods = targetClass.getMethods();
    String description = "";
    for (Method method : methods) {
        if (method.getName().equals(methodName)) {
            Class[] clazzs = method.getParameterTypes();
            if (clazzs.length == arguments.length) {
                description = method.getAnnotation(MyLog.class).remark();
                break;
            }
        }
    }
    return description;
}

@before执行完成之后根据执行流程会进入到@Around注解中去,@Around注解环绕了方法执行前和执行后,根据我们此次的业务需求,在此方法里只需要我们注解所标注的方法的返回数据

@Around("sendLog()")
public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable {
    Object result = null;
    requestMap = new HashMap<>();
    result = joinPoint.proceed();
    if (result != null) {
        //提交日志需要
        requestMap.put("usloResponseData", result.toString());
    }
    return result;
}``

当@Around执行结束之后,我们的切面就回去执行@After注解的方法在该方法执行时说明我们的方法一定是响应结束了,在这里面我们记录方法的结束时间,用于求总的响应时间

@After("sendLog()")
public void after(JoinPoint joinPoint) {
    try {
        requestMap.put("usloDbEndTime", TimeTools.transformDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
    } catch (ParseException e) {
        e.printStackTrace();
    }
    endTime = System.currentTimeMillis();
    //响应所耗费时间
    requestMap.put("usloRequestTime", String.valueOf(endTime-startTime));
}

最后我们只剩下@AfterReturning注解与@AfterThrowing注解,其实从名字上我们也可以看出来这两个注解的方法是用来干什么的,@AfterReturning就是在正常响应之后会进入到这个注解之中根据我们的业务需求主要用来记录我们的响应状态码

@AfterReturning("sendLog()")
public void afterReturning(JoinPoint joinPoint) {
    try {
        HttpServletResponse httpServletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        int status = httpServletResponse.getStatus();
        //状态码 200 500
        requestMap.put("usloAction", String.valueOf(status));
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Employee employee = SessionUtil.getLoginEmp(httpServletRequest.getSession());
        if (employee != null) {
            requestMap.put("usloUserId", employee.getEmpId().toString());
            requestMap.put("usloUserName", employee.getEmpName());
        }
        if (requestMap.get("usloAction").equals("200")) {
            requestMap.put("usloErrorContent", "");
        }
        String url = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogIP");
        sendLogByThreadPool(url);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@AfterThrowing注解则是当代码出现错误的时候会被拦截到改注解之中,用于处理当后台代码报错时我们应存储什么操作日志,根据实际的业务需求进行变动在这里我们只需要记录状态码和错误原因

@AfterThrowing(value = "execution(* com.*.controller..*(..))", throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
    throwable.printStackTrace();
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    throwable.printStackTrace(pw);
    String[] errors = sw.toString().split("\r\n");
    int i = 0;
    StringBuffer sb = new StringBuffer();
    for(String error : errors){
        if(i<10){
            sb.append(errors[i] + "\r\n");
        }else{
            break;
        }
        if(i == 0){
            requestMap.put("usloErrorName",errors[i]);
        }
        i++;
    }
    requestMap.put("usloErrorContent", sb.toString());
    requestMap.put("usloAction", "500");
    String url = PropertiesUtil.GetValueByKey(Thread.currentThread().getContextClassLoader().getResource(FILE_PATH).getPath(), "sendLogIP");
    sendLogByThreadPool(url);
}

至此我们通过aop切面编程完成对用户操作日志的收集就完成了。

总结

本文主要使用了spring的aop编写自定义注解来实现了用户操作日志的收集工作。aop中注解的执行顺序为@before->@around->@after->@afterReturning当出现异常时执行顺序为:@before->@around->@after->@afterThrowing。before我们一般用于初始值的赋值,具体的业务逻辑我们一般编写在后面的过程之中。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要定义一个自定义注解 `@RequiresPermissions`,用于标识需要授权访问的方法,例如: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiresPermissions { String[] value(); // 权限值 } ``` 然后,我们需要实现一个切面,用于拦截被 `@RequiresPermissions` 标识的方法,并进行权限校验,例如: ```java @Component @Aspect public class PermissionCheckAspect { @Autowired private AuthService authService; @Around("@annotation(requiresPermissions)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions) throws Throwable { // 获取当前用户 User user = authService.getCurrentUser(); if (user == null) { throw new UnauthorizedException("用户未登录"); } // 获取当前用户的权限列表 List<String> permissions = authService.getUserPermissions(user); // 校验权限 for (String permission : requiresPermissions.value()) { if (!permissions.contains(permission)) { throw new ForbiddenException("没有访问权限:" + permission); } } // 执行目标方法 return joinPoint.proceed(); } } ``` 在切面中,我们首先通过 `AuthService` 获取当前用户及其权限列表,然后校验当前用户是否拥有被 `@RequiresPermissions` 标识的方法所需的所有权限,如果没有则抛出 `ForbiddenException` 异常,如果有则继续执行目标方法。 最后,我们需要在 Spring 配置文件中启用 AOP 自动代理,并扫描切面所在的包,例如: ```xml <aop:aspectj-autoproxy /> <context:component-scan base-package="com.example.aspect" /> ``` 这样,我们就通过 Spring AOP自定义注解模拟实现了类似 Shiro 权限校验的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值