Heim  >  Artikel  >  Java  >  Wie implementiert man die Vorgangsprotokollierung elegant in einem Java SpringBoot-Projekt?

Wie implementiert man die Vorgangsprotokollierung elegant in einem Java SpringBoot-Projekt?

WBOY
WBOYnach vorne
2023-04-22 12:55:171065Durchsuche

    1. Was ist AOP?

    AOP (Aspektorientierte Programmierung) Apropos AOP: Fast jeder, der das Spring-Framework studiert hat, weiß, dass es eine der drei Kernideen von Spring ist (IOC: Inversion of Control, DI: Dependency Injection, AOP: Aspect Oriented). Programmierung). Es kann Logik oder Verantwortlichkeiten (z. B. Transaktionsverarbeitung, Protokollverwaltung, Berechtigungskontrolle usw.) kapseln, die nicht mit dem Geschäft zusammenhängen, aber üblicherweise von den Geschäftsmodulen aufgerufen werden, um die Duplizierung von Code im System zu reduzieren und zu reduzieren die Kopplung zwischen Modulen und trägt zur zukünftigen Skalierbarkeit und Wartbarkeit bei.

    2. Was hat AOP gemacht?

    Einfach ausgedrückt macht AOP hauptsächlich drei Dinge:

    • 1 Wo eingeschnitten werden soll, das heißt, in welchem ​​Geschäftscode Nicht-Geschäftscode wie die Protokollierung ausgeführt wird.

    • 2. Wann einzuschneiden ist, vor oder nach der Ausführung des Geschäftscodes.

    • 3. Was nach dem Einschalten zu tun ist, z. B. Berechtigungsüberprüfung, Protokollierung usw.

    kann mit einem Bild verstanden werden:

    Wie implementiert man die Vorgangsprotokollierung elegant in einem Java SpringBoot-Projekt?

    Erklärung eines Kernbegriffs auf dem Bild:

    • Punktschnitt: Schnittpunkt, entscheiden Sie, wo in den Geschäftscode geschnitten werden soll Mitte (d. h. in die Schnittfläche eingewebt). Pointcuts sind in Ausführungsmodus und Anmerkungsmodus unterteilt. Ausführungsmodus: Sie können Pfadausdrücke verwenden, um anzugeben, welche Klassen in Aspekte eingebunden werden. Annotationsmodus: Sie können angeben, welcher annotationsmodifizierte Code in Aspekte eingebunden wird.

    • Hinweis: Verarbeitung, einschließlich Verarbeitungszeitpunkt und Verarbeitungsinhalt. Das Verarbeiten von Inhalten bedeutet, etwas zu tun, beispielsweise Berechtigungen zu überprüfen und Protokolle aufzuzeichnen. Der Verarbeitungszeitpunkt bezieht sich auf den Zeitpunkt der Ausführung des Verarbeitungsinhalts, der in Vorverarbeitung (dh vor der Ausführung des Geschäftscodes), Nachverarbeitung (nach der Ausführung des Geschäftscodes) usw. unterteilt wird.

    • Aspekt: Aspekt, nämlich Pointcut und Advice.

    • Joint Point: Joint Point ist ein Punkt der Programmausführung. Beispielsweise die Ausführung einer Methode oder die Behandlung einer Ausnahme. In Spring AOP stellt ein Join-Punkt immer eine Methodenausführung dar.

    • Weaving: Weaving ist der Prozess der Inhaltsverarbeitung in der Zielobjektmethode durch dynamischen Proxy.

    3. Implementierungsschritte

    (1) Passen Sie eine Annotation @Log an (2) Erstellen Sie eine Aspektklasse, legen Sie den Schnittpunkt fest, um die mit @Log annotierte Methode abzufangen, fangen Sie die übergebenen Parameter ab und führen Sie die Protokollierung durch. (3) Ändern @ Protokoll ist auf der Schnittstelle markiert

    Die spezifischen Implementierungsschritte sind wie folgt:

    1. AOP-Abhängigkeit hinzufügen

    Der Code lautet wie folgt (Beispiel):

     <dependency>
       	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    2. Passen Sie eine Protokollanmerkung an

    Im Allgemeinen werden Annotationen für Protokolle verwendet. Geben Sie Pointcut-Ausdrücke ein. Wir erstellen zunächst eine Protokollanmerkung. Wenn der Spring-Container die Methode mit dieser Annotation scannt, wird sie erweitert.

    Der Code lautet wie folgt (Beispiel):

    @Target({ ElementType.PARAMETER, ElementType.METHOD }) // 注解放置的目标位置,PARAMETER: 可用在参数上  METHOD:可用在方法级别上
    @Retention(RetentionPolicy.RUNTIME)    // 指明修饰的注解的生存周期  RUNTIME:运行级别保留
    @Documented
    public @interface Log {
    
        /**
         * 模块
         */
        String title() default "";
    
        /**
         * 功能
         */
        public BusinessType businessType() default BusinessType.OTHER;
    
        /**
         * 是否保存请求的参数
         */
        public boolean isSaveRequestData() default true;
    
        /**
         * 是否保存响应的参数
         */
        public boolean isSaveResponseData() default true;
    }

    3. Aspektdeklaration

    Deklarieren Sie eine Aspektklasse und übergeben Sie sie zur Verwaltung an den Spring-Container.

    Der Code lautet wie folgt (Beispiel):

    @Aspect
    @Component
    @Slf4j
    public class LogAspect {
        @Autowired
        private IXlOperLogService operLogService;
        /**
         * 处理完请求后执行
         * @param joinPoint 切点
         */
        @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
        public void doAfterReturnibng(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
            handleLog(joinPoint, controllerLog, null, jsonResult);
        }
        protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
            try {
                // 获取当前的用户
                JwtUser loginUser = SecurityUtils.getLoginUser();
    
                // 日志记录
                XlOperLog operLog = new XlOperLog();
                operLog.setStatus(0);
                // 请求的IP地址
                String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
                if ("0:0:0:0:0:0:0:1".equals(iP)) {
                    iP = "127.0.0.1";
                }
                operLog.setOperIp(iP);
                operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
                if (loginUser != null) {
                    operLog.setOperName(loginUser.getUsername());
                }
                if (e != null) {
                    operLog.setStatus(1);
                    operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
                }
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                operLog.setMethod(className + "." + methodName + "()");
                operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
                operLog.setOperTime(new Date());
                // 处理设置注解上的参数
                getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
                // 保存数据库
                operLogService.save(operLog);
    
            } catch (Exception exp) {
                log.error("异常信息:{}", exp.getMessage());
                exp.printStackTrace();
            }
        }
        /**
         * 获取注解中对方法的描述信息 用于Controller层注解
         * @param log 日志
         * @param operLog 操作日志
         * @throws Exception
         */
        public void getControllerMethodDescription(JoinPoint joinPoint, Log log, XlOperLog operLog, Object jsonResult) throws Exception {
            // 设置操作业务类型
            operLog.setBusinessType(log.businessType().ordinal());
            // 设置标题
            operLog.setTitle(log.title());
            // 是否需要保存request,参数和值
            if (log.isSaveRequestData()) {
                // 设置参数的信息
                setRequestValue(joinPoint, operLog);
            }
            // 是否需要保存response,参数和值
            if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
                operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
            }
        }
        /**
         * 获取请求的参数,放到log中
         * @param operLog 操作日志
         * @throws Exception 异常
         */
        private void setRequestValue(JoinPoint joinPoint, XlOperLog operLog) throws Exception {
            String requsetMethod = operLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
                String parsams = argsArrayToString(joinPoint.getArgs());
                operLog.setOperParam(StringUtils.substring(parsams,0,2000));
            } else {
                Map<?,?> paramsMap = (Map<?,?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                operLog.setOperParam(StringUtils.substring(paramsMap.toString(),0,2000));
            }
        }
        /**
         * 参数拼装
         */
        private String argsArrayToString(Object[] paramsArray) {
            String params = "";
            if (paramsArray != null && paramsArray.length > 0) {
                for (Object object : paramsArray) {
                    // 不为空 并且是不需要过滤的 对象
                    if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
                        Object jsonObj = JSON.toJSON(object);
                        params += jsonObj.toString() + " ";
                    }
                }
            }
            return params.trim();
        }
        /**
         * 判断是否需要过滤的对象。
         * @param object 对象信息。
         * @return 如果是需要过滤的对象,则返回true;否则返回false。
         */
        @SuppressWarnings("rawtypes")
        public boolean isFilterObject(final Object object) {
            Class<?> clazz = object.getClass();
            if (clazz.isArray()) {
                return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
            } else if (Collection.class.isAssignableFrom(clazz)) {
                Collection collection = (Collection) object;
                for (Object value : collection) {
                    return value instanceof MultipartFile;
                }
            } else if (Map.class.isAssignableFrom(clazz)) {
                Map map = (Map) object;
                for (Object value : map.entrySet()) {
                    Map.Entry entry = (Map.Entry) value;
                    return entry.getValue() instanceof MultipartFile;
                }
            }
            return object instanceof MultipartFile || object instanceof HttpServletRequest
                    || object instanceof HttpServletResponse || object instanceof BindingResult;
        }
    }

    4. Markieren Sie ihn auf der Schnittstelle.

    Markieren Sie die benutzerdefinierte Anmerkung auf der Schnittstelle, die zum Aufzeichnen des Betriebsprotokolls benötigt wird. Der Code lautet wie folgt (Beispiel):

    	@Log(title = "代码生成", businessType = BusinessType.GENCODE)
        @ApiOperation(value = "批量生成代码")
        @GetMapping("/download/batch")
        public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
            String[] tableNames = Convert.toStrArray(tables);
            byte[] data = genTableService.downloadCode(tableNames);
            genCode(response, data);
        }

    5. Implementierungseffekt

    Protokolle werden aufgezeichnet, wenn relevante Vorgänge ausgeführt werden, und einige grundlegende Informationen werden aufgezeichnet und in der Datentabelle gespeichert.

    Wie implementiert man die Vorgangsprotokollierung elegant in einem Java SpringBoot-Projekt?

    Das obige ist der detaillierte Inhalt vonWie implementiert man die Vorgangsprotokollierung elegant in einem Java SpringBoot-Projekt?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen