>  기사  >  Java  >  Java SpringBoot 프로젝트에서 작업 로깅을 우아하게 구현하는 방법은 무엇입니까?

Java SpringBoot 프로젝트에서 작업 로깅을 우아하게 구현하는 방법은 무엇입니까?

WBOY
WBOY앞으로
2023-04-22 12:55:171065검색

    1. AOP란 무엇인가요?

    AOP(Aspect-Oriented 프로그래밍), AOP라고 하면 Spring 프레임워크를 연구한 거의 모든 사람이 이것이 Spring의 세 가지 핵심 아이디어(IOC: Inversion of Control, DI: 종속성 주입, AOP: Aspect Oriented) 중 하나라는 것을 알고 있습니다. 프로그램 작성). 비즈니스와 관련이 없지만 비즈니스 모듈에서 일반적으로 호출하는 로직이나 책임(트랜잭션 처리, 로그 관리, 권한 제어 등)을 캡슐화하여 시스템 내 코드 중복을 줄이고 비용을 줄일 수 있습니다. 모듈 간의 결합은 향후 확장성과 유지 관리에 도움이 됩니다.

    2. AOP는 무슨 일을 했나요?

    간단히 말하면 AOP는 크게 3가지 일을 한다.

    • 1 어디를 잘라야 할지, 즉 로깅 같은 비즈니스 코드가 아닌 코드가 실행되는 곳이다.

    • 2. 비즈니스 코드가 실행되기 전이나 후에 중단할 시기.

    • 3. 권한 확인, 로그인 등 전환 후 해야 할 일

    사진으로 이해 가능:

    Java SpringBoot 프로젝트에서 작업 로깅을 우아하게 구현하는 방법은 무엇입니까?

    사진의 핵심 용어 설명:

    • Pointcut: cut point, 비즈니스 코드를 잘라낼 위치 결정 중간 (즉, 절단면에 짜여져 있음). 포인트컷은 실행 모드와 주석 모드로 구분됩니다. 실행 모드: 경로 표현식을 사용하여 측면에 짜여진 클래스를 지정할 수 있습니다. 주석 모드: 측면에 짜여진 주석 수정 코드를 지정할 수 있습니다.

    • Advice: 처리(처리 타이밍 및 콘텐츠 처리 포함). 콘텐츠를 처리한다는 것은 권한 확인, 로그 기록 등의 작업을 수행하는 것을 의미합니다. 처리 시점이란 처리 내용이 실행되는 시기를 말하며, 전처리(즉, 비즈니스 코드 실행 전), 후처리(비즈니스 코드 실행 후) 등으로 구분됩니다.

    • Aspect: Aspect, 즉 Pointcut 및 Advice입니다.

    • 조인트 포인트: 조인트 포인트는 프로그램 실행 포인트입니다. 예를 들어 메서드 실행이나 예외 처리 등이 있습니다. Spring AOP에서 조인 포인트는 항상 메소드 실행을 나타냅니다.

    • Weaving: Weaving은 동적 프록시를 통해 대상 객체 방식으로 콘텐츠를 처리하는 과정입니다.

    3. 구현 단계

    (1) @Log 주석 사용자 정의 (2) 관점 클래스 생성, @Log 주석이 달린 메서드를 가로채는 컷 포인트 설정, 전달된 매개 변수를 가로채서 로깅 수행 (3) 변경 @ Log는 인터페이스에 표시됩니다

    구체적인 구현 단계는 다음과 같습니다.

    1. AOP 종속성 추가

    코드는 다음과 같습니다(예).

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

    2. 일반적으로 주석은 로그 유형 포인트컷 표현식에 사용됩니다. 먼저 로그 주석을 생성합니다. 스프링 컨테이너가 이 주석으로 메서드를 스캔하면 성능이 향상됩니다.

    코드는 다음과 같습니다(예제).

    @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.Aspect 선언

    Aspect 클래스를 선언하고 이를 Spring 컨테이너에 넘겨 관리합니다.

    코드는 다음과 같습니다(예):

    @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. 인터페이스에 표시합니다.

    작업 로그를 기록해야 하는 인터페이스에 사용자 정의 주석을 표시합니다. 코드는 다음과 같습니다(예).

    	@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. 구현 효과

    해당 작업이 수행되면 로그가 기록되며, 일부 기본 정보가 데이터 테이블에 기록되어 저장됩니다.

    위 내용은 Java SpringBoot 프로젝트에서 작업 로깅을 우아하게 구현하는 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제