练习项目后端代码解析切面篇(Aspect)

news2024/11/27 21:40:08

前言

之前注解篇时我说,通常情况下一个自定义注解一般对应一个切面,虽然项目里的切面和注解个数相同,但是好像有一个名字看起来并不对应,无所谓,先看了再说。
在这里插入图片描述

ExceptionLogAspect切面

我在里面做了具体注释,所以看起来比较长,但我觉得作者的代码思路还是很清晰的,就是里面一下就涉及了三个自写工具类的使用,会让我暂时少了一些具体逻辑的理解。

如果不想看代码里的注释可以先看看我的理解。

我们先来看这个类的类图吧

在这里插入图片描述

这个类被两个注解修饰,@Component@Aspect一起使用,可以方便地实现Spring AOP的功能,同时确保切面类被Spring容器管理。

@Component:这个注解是Spring的一个核心注解,用于指示一个类是Spring容器管理的组件。当一个类被标记为@Component时,Spring会自动检测并注册它,使得它可以在应用程序的其他部分中被注入和使用。

@Aspect:这个注解是Spring AOP(面向切面编程)的一部分,用于指示一个类是一个切面。切面是一个关注点(Cross-Cutting Concerns)模块化的类,它包含了一系列的通知(Advice),这些通知在特定的连接点(Join Points)上执行。

这个类有四个方法
handlelog(JoinPoint,Exception) :根据传入的参数,返回一个填充好数据的 ExceptionLog的对象,ExceptionLog的属性部分我也放在图里面了。

logAfterThrowing(JoinPoint,Exception):用于指定在目标方法抛出异常后执行的通知。它被一个注解 @AfterThrowing(value = “logPointcut()”, throwing = “e”) 修饰,value属性指定了切点,throwing属性指定了异常对象,它可以在通知方法中作为参数使用。

logPointcut():它被 @Pointcut(“execution(* com.rawchen.controller….(…))”)修饰,表示切入点的声明,里面的参数表示拦截的范围。

最后实现的功能

一旦com.rawchen.controller包下的某个类的某个方法抛出异常,ExceptionLogAspect切面中的logAfterThrowing通知方法就会被触发。该方法上面一行的注解会找到切入点对象和异常信息传到方法里,然后开始执行逻辑。

@AfterThrowing(value = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
    ExceptionLog log = handleLog(joinPoint, e);
    exceptionLogService.saveExceptionLog(log);
}

ExceptionLog log = handleLog(joinPoint, e) 调用handleLog方法来创建一个新的ExceptionLog对象,并填充其属性,包括异常发生的URI、方法、IP地址、用户代理、描述信息、错误堆栈跟踪以及请求参数。

exceptionLogService.saveExceptionLog(log) 调用ExceptionLogService的saveExceptionLog方法来保存异常日志。这个方法将异常日志信息保存到数据库或其他存储介质中。
ExceptionLogService是一个服务层接口,它定义了保存异常日志的方法,这个接口在ExceptionLogAspect类中通过@Autowired注解注入了实现类。

完整代码注释

package com.rawchen.aspect;

import com.rawchen.annotation.OperationLogger;
import com.rawchen.annotation.VisitLogger;
import com.rawchen.entity.ExceptionLog;
import com.rawchen.service.ExceptionLogService;
import com.rawchen.util.AopUtils;
import com.rawchen.util.IpAddressUtils;
import com.rawchen.util.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.rawchen.util.JacksonUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @Description: AOP记录异常日志
 * @Date: 2020-12-03
 */
@Component
@Aspect
public class ExceptionLogAspect {
	//@Autowired:这是一个Spring的注解,用于自动注入ExceptionLogService的实例。
	//Spring会自动查找合适的组件并将其注入到当前类中。
	@Autowired
	ExceptionLogService exceptionLogService;

	/**
	 * 配置切入点
	 * logPointcut():它的作用是作为切点的标识符。
	 * 在这个方法上标注了@Pointcut注解,因此它会被Spring AOP识别为切点定义。
	 */
	 //@Pointcut("execution(* com.rawchen.controller..*.*(..))"):这是一个切点注解,用于定义哪些方法会被拦截。
	 //在这个例子中,execution(* com.rawchen.controller..*.*(..))
	 //表示拦截com.rawchen.controller包及其子包下的所有方法。
	@Pointcut("execution(* com.rawchen.controller..*.*(..))")
	public void logPointcut() {
	}
	
	/**
	* 该方法它接收JoinPoint和Exception对象作为参数这个方法可用于创建一个异常日志对象,并将为其填充为合适的值后存储。
	*/
	//@AfterThrowing(value = "logPointcut()", throwing = "e"):这是一个通知注解,
	//用于指定在目标方法抛出异常后执行的通知。value属性指定了切点,throwing属性指定了异常对象,
	//它可以在通知方法中作为参数使用。
	@AfterThrowing(value = "logPointcut()", throwing = "e")
	public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
		ExceptionLog log = handleLog(joinPoint, e);
		exceptionLogService.saveExceptionLog(log);
	}

	/**
	 * 设置ExceptionLog对象属性
	 * 
	 * @return 填充好数据的ExceptionLog对象
	 */
	private ExceptionLog handleLog(JoinPoint joinPoint, Exception e) {
	     //获取了当前的ServletRequestAttributes对象,这个对象包含了当前HTTP请求的详细信息。
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		//获取当前的HttpServletRequest对象,这个对象代表了当前的HTTP请求。
		HttpServletRequest request = attributes.getRequest();
		//通过request对象的方法获取一些具体信息
		//获取请求的URI(统一资源标识符)
		String uri = request.getRequestURI();
		//获取请求方法
		String method = request.getMethod();
		//通过作者自己写的工具类,来获取ip地址
		String ip = IpAddressUtils.getIpAddress(request);
		//获取请求的User-Agent头,这个头通常包含了客户端浏览器的信息。
		String userAgent = request.getHeader("User-Agent");
		//todo 使用swagger后,可以直接使用注解上的内容作为 ExceptionLog 的 description
		//通过类里私有方法获取注解上的描述内容存储在字符串中
		String description = getDescriptionFromAnnotations(joinPoint);
		//使用作者自己写的StringUtils工具类的方法来获取异常的堆栈跟踪信息。
		String error = StringUtils.getStackTrace(e);
		//创建一个新的ExceptionLog对象,并使用前面获取的信息来填充它的属性。
		ExceptionLog log = new ExceptionLog(uri, method, description, error, ip, userAgent);
		//调用AopUtils工具类的方法来获取请求的参数。
		Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);
		//将请求参数转换为JSON字符串,并截取前2000个字符,
		//然后将其设置为ExceptionLog对象的param属性,只截取2000个字符是为了限制日志的长度,避免日志过长。
		log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));
		return log;
	}
	
	/**
	* 如果抛出方法被自定义注解修饰,那就得到OperationLogger注解里或者VisitLog描述操作的那段字符串
	*/

	private String getDescriptionFromAnnotations(JoinPoint joinPoint) {
		String description = "";
		//这行代码从JoinPoint对象中获取方法签名,并将其转换为Method对象,这样就可以访问方法上的注解了。
		Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
		OperationLogger operationLogger = method.getAnnotation(OperationLogger.class);
		if (operationLogger != null) {
			description = operationLogger.value();
			return description;
		}
		VisitLogger visitLogger = method.getAnnotation(VisitLogger.class);
		if (visitLogger != null) {
			description = visitLogger.behavior();
			return description;
		}
		return description;
	}
}

OperationLogAspect

还是先看类图

在这里插入图片描述
注解已经解释过一遍了,而且这三个方法与前面的ExceptionLogAspect切面相差不大,同时一样在图里附上OperationLog类便于理解。

logPointcut() :被 @Pointcut(“@annotation(operationLogger)”) 注解修饰,表示切入点的声明,它将会匹配所有带有OperationLogger注解的方法。

logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger):被 @Around(“logPointcut(operationLogger)”) 注解修饰,表示该切入点的方法被环绕通知,环绕通知是一种动态拦截方法执行的通知,它允许你完全控制方法的执行流程。

环绕通知的logAround方法可以执行以下操作:
执行被环绕的方法:Object result = joinPoint.proceed(); 这行代码会执行原始的方法调用,并将返回值存储在result变量中。
在方法执行前后添加额外的逻辑:在这个例子中,它记录了方法执行的时间,并创建了一个操作日志对象。
返回方法的执行结果:return result; 这行代码将原始方法的执行结果返回给调用者。

handleLog(ProceedingJoinPoint joinPoint, OperationLogger operationLogger, int times):通过调用工具类进行信息解析,返回一个填充好数据属性的OperationLog对象,供服务层接口进行日志保存。

最后实现的功能

一旦被OperationLogger注解修饰的方法被调用,然后切入点进行匹配,传参到logAround方法,进行一个环绕通知来记录这个方法的执行时间,最后通过handleLog方法与其他信息一同被记录到日志当中。也是服务层接口通过注解注入实体类,服务层接口方法负责日志保存。

 @Around("logPointcut(operationLogger)")
    public Object logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {
        // 设置当前时间
        currentTime.set(System.currentTimeMillis());
        // 执行被环绕的方法
        Object result = joinPoint.proceed();
        // 计算方法执行时间
        int times = (int) (System.currentTimeMillis() - currentTime.get());
        // 清除当前时间
        currentTime.remove();
        // 创建操作日志对象
        OperationLog operationLog = handleLog(joinPoint, operationLogger, times);
        // 保存操作日志
        operationLogService.saveOperationLog(operationLog);
        // 返回方法的执行结果
        return result;
    }

完整代码注释

@Component
@Aspect
public class OperationLogAspect {
    // 注入操作日志服务
    @Autowired
    OperationLogService operationLogService;

    // 线程局部变量,用于记录当前时间
    ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 配置切入点,用于匹配带有OperationLogger注解的方法
     */
    @Pointcut("@annotation(operationLogger)")
    public void logPointcut(OperationLogger operationLogger) {
    }

    /**
     * 配置环绕通知,用于记录操作日志
     *
     * @param joinPoint 方法执行的连接点
     * @param operationLogger 方法上的OperationLogger注解
     * @return 方法的执行结果
     * @throws Throwable 方法执行中可能抛出的异常
     */
    @Around("logPointcut(operationLogger)")
    public Object logAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {
        // 设置当前时间
        currentTime.set(System.currentTimeMillis());
        // 执行被环绕的方法
        Object result = joinPoint.proceed();
        // 计算方法执行时间
        int times = (int) (System.currentTimeMillis() - currentTime.get());
        // 清除当前时间
        currentTime.remove();
        // 创建操作日志对象
        OperationLog operationLog = handleLog(joinPoint, operationLogger, times);
        // 保存操作日志
        operationLogService.saveOperationLog(operationLog);
        // 返回方法的执行结果
        return result;
    }

    /**
     * 获取HttpServletRequest请求对象,并设置OperationLog对象属性
     *
     * @param operationLogger 方法上的OperationLogger注解
     * @param times 方法执行时间
     * @return 操作日志对象
     */
    private OperationLog handleLog(ProceedingJoinPoint joinPoint, OperationLogger operationLogger, int times) {
        // 获取当前请求的ServletRequestAttributes
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 从ServletRequestAttributes中获取HttpServletRequest对象
        HttpServletRequest request = attributes.getRequest();
        // 获取当前用户名,通过JWT工具类解析
        String username = JwtUtils.getTokenBody(request.getHeader("Authorization")).getSubject();
        // 获取请求的URI
        String uri = request.getRequestURI();
        // 获取请求的HTTP方法
        String method = request.getMethod();
        // 获取OperationLogger注解的描述信息
        String description = operationLogger.value();
        // 获取请求的IP地址
        String ip = IpAddressUtils.getIpAddress(request);
        // 获取请求的User-Agent头
        String userAgent = request.getHeader("User-Agent");
        // 创建新的操作日志对象
        OperationLog log = new OperationLog(username, uri, method, description, ip, times, userAgent);
        // 通过工具类,获取请求参数,并将其转换为JSON字符串,然后截取前2000个字符作为日志参数,避免日志过长
        Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);
        log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));
        // 返回操作日志对象
        return log;
    }
}

VisitLogAspect切面

类图

在这里插入图片描述

这里面的logAround环绕通知方法,logPointcut方法以及handleLog的方法与上面两个切面类也是大同小异,现在主要看一下类图里前面三个方法。
checkIdentification(HttpServletRequest request) :校验访客标识码,通过获取请求里面的校验码,有校验码就与数据库里的进行比对,比对成功后保存在Redis中,比对不成功就签发一个新的标识码;加入请求里根本没有校验码,也是签发一个新的。

saveUUID(HttpServletRequest request) :签发校验码,根据时间戳、ip、userAgent生成UUID,并进行数据库与Redis的保存操作。

至于这个judgeBehavior(String behavior, String content, Map<String, Object> requestParams, Object result)方法,它里面只是简单的字符串与注解里的参数进行匹配,再根据请求信息完成日志数据,所以说我放一张VisitLogger的注解使用次数反而更加好理解。
在这里插入图片描述

最后实现功能

  1. 访问日志记录:当一个方法被VisitLogger注解修饰时,该方法执行前后会被VisitLogAspect切面拦截,并在方法执行前后执行日志记录逻辑。
  2. 访客标识码验证:在方法执行前,切面会检查请求头中是否包含identification字段,这是访客的唯一标识码。如果未包含,切面会生成一个新的访客标识码并保存到数据库和Redis中。
  3. 行为判断和日志内容设置:根据VisitLogger注解的行为和内容描述,切面会判断并设置访问日志的备注和内容字段。
  4. 访问日志保存:在方法执行后,切面会创建一个VisitLog对象,并填充其属性,包括访客标识码、请求URI、HTTP方法、行为描述、内容描述、IP地址、执行时间、User-Agent头等。然后,它会将这个访问日志对象保存到服务层进行持久化。
  5. Redis使用:切面使用Redis服务来存储和验证访客标识码。它将访客标识码保存到Redis中的一个集合中,并在验证访客标识码时检查这个集合中是否包含该标识码。
  6. 数据库访问:切面会访问数据库来验证访客标识码是否已存在于数据库中,以及是否需要生成新的访客标识码。

UUID的作用

UUID(Universally Unique Identifier,通用唯一识别码)是一种用于生成唯一标识符的机制。

在这个项目里,UUID被用于生成访客的唯一标识码。当请求头中没有identification字段时,切面会生成一个新的UUID,并将其保存到数据库和Redis中。之后,在每次请求中,切面都会检查请求头中的identification字段,以验证访客标识码是否有效。

这种机制可以防止重复访问和刷访问量等恶意行为。

完整代码注释

@Component
@Aspect
public class VisitLogAspect {
    // 注入访问日志服务
    @Autowired
    VisitLogService visitLogService;
    // 注入访客服务
    @Autowired
    VisitorService visitorService;
    // 注入Redis服务
    @Autowired
    RedisService redisService;

    // 线程局部变量,用于记录当前时间
    ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 配置切入点,用于匹配带有VisitLogger注解的方法
     */
    @Pointcut("@annotation(visitLogger)")
    public void logPointcut(VisitLogger visitLogger) {
    }

    /**
     * 配置环绕通知,用于记录访问日志
     *
     * @param joinPoint 方法执行的连接点
     * @param visitLogger 方法上的VisitLogger注解
     * @return 方法的执行结果
     * @throws Throwable 方法执行中可能抛出的异常
     */
    @Around("logPointcut(visitLogger)")
    public Object logAround(ProceedingJoinPoint joinPoint, VisitLogger visitLogger) throws Throwable {
        // 设置当前时间
        currentTime.set(System.currentTimeMillis());
        // 执行被环绕的方法
        Object result = joinPoint.proceed();
        // 计算方法执行时间
        int times = (int) (System.currentTimeMillis() - currentTime.get());
        // 清除当前时间
        currentTime.remove();
        // 获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 校验访客标识码
        String identification = checkIdentification(request);
        // 记录访问日志
        VisitLog visitLog = handleLog(joinPoint, visitLogger, request, result, times, identification);
        // 保存访问日志
        visitLogService.saveVisitLog(visitLog);
        // 返回方法的执行结果
        return result;
    }

    /**
     * 校验访客标识码
     *
     * @param request
     * @return
     */
    private String checkIdentification(HttpServletRequest request) {
        // 获取请求头中的访客标识码
        String identification = request.getHeader("identification");
        // 如果请求头中没有访客标识码,则签发一个新的访客标识码
        if (identification == null) {
            // 签发新的访客标识码并保存到数据库和Redis
            identification = saveUUID(request);
        } else {
            // 校验Redis中是否存在访客标识码
            boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, identification);
            // 如果Redis中不存在访客标识码,则检查数据库中是否存在
            if (!redisHas) {
                // 检查数据库中是否存在访客标识码
                boolean mysqlHas = visitorService.hasUUID(identification);
                // 如果数据库中存在,则保存至Redis
                if (mysqlHas) {
                    redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, identification);
                } else {
                    // 如果数据库中不存在,则签发一个新的访客标识码
                    identification = saveUUID(request);
                }
            }
        }
        return identification;
    }

   /**
	 * 签发UUID,并保存至数据库和Redis
	 *
	 * @param request
	 * @return
	 */
	private String saveUUID(HttpServletRequest request) {
		//获取响应对象
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		//获取当前时间戳,精确到小时,防刷访客数据
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		String timestamp = Long.toString(calendar.getTimeInMillis() / 1000);
		//获取访问者基本信息
		String ip = IpAddressUtils.getIpAddress(request);
		String userAgent = request.getHeader("User-Agent");
		//根据时间戳、ip、userAgent生成UUID
		String nameUUID = timestamp + ip + userAgent;
		String uuid = UUID.nameUUIDFromBytes(nameUUID.getBytes()).toString();
		//添加访客标识码UUID至响应头
		response.addHeader("identification", uuid);
		//暴露自定义header供页面资源使用
		response.addHeader("Access-Control-Expose-Headers", "identification");
		//校验Redis中是否存在uuid
		boolean redisHas = redisService.hasValueInSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
		if (!redisHas) {
			//保存至Redis
			redisService.saveValueToSet(RedisKeyConfig.IDENTIFICATION_SET, uuid);
			//保存至数据库
			Visitor visitor = new Visitor(uuid, ip, userAgent);
			visitorService.saveVisitor(visitor);
		}
		return uuid;
	}




	/**
	 * 设置VisitLogger对象属性
	 *
	 * @param joinPoint
	 * @param visitLogger
	 * @param result
	 * @param times
	 * @return 填充好的日志对象
	 */
	private VisitLog handleLog(ProceedingJoinPoint joinPoint, VisitLogger visitLogger, HttpServletRequest request, Object result,
	                           int times, String identification) {
	    // 获取请求的URI
	    String uri = request.getRequestURI();
	    // 获取请求的HTTP方法
	    String method = request.getMethod();
	    // 获取VisitLogger注解的行为描述
	    String behavior = visitLogger.behavior();
	    // 获取VisitLogger注解的内容描述
	    String content = visitLogger.content();
	    // 获取请求的IP地址
	    String ip = IpAddressUtils.getIpAddress(request);
	    // 获取请求的User-Agent头
	    String userAgent = request.getHeader("User-Agent");
	    // 获取请求参数
	    Map<String, Object> requestParams = AopUtils.getRequestParams(joinPoint);
	    // 根据访问行为,设置对应的访问内容或备注
	    Map<String, String> map = judgeBehavior(behavior, content, requestParams, result);
	    // 创建新的访问日志对象
	    VisitLog log = new VisitLog(identification, uri, method, behavior, map.get("content"), map.get("remark"), ip, times, userAgent);
	    // 设置日志的参数
	    log.setParam(StringUtils.substring(JacksonUtils.writeValueAsString(requestParams), 0, 2000));
	    // 返回访问日志对象
	    return log;
	}

	/**
	 * 根据访问行为,设置对应的访问内容或备注
	 *
	 * @param behavior
	 * @param content
	 * @param requestParams
	 * @param result
	 * @return
	 */
	private Map<String, String> judgeBehavior(String behavior, String content, Map<String, Object> requestParams, Object result) {
		Map<String, String> map = new HashMap<>();
		String remark = "";
		if (behavior.equals("访问页面") && (content.equals("首页") || content.equals("动态"))) {
			int pageNum = (int) requestParams.get("pageNum");
			remark = "第" + pageNum + "页";
		} else if (behavior.equals("查看博客")) {
			Result res = (Result) result;
			if (res.getCode() == 200) {
				BlogDetail blog = (BlogDetail) res.getData();
				String title = blog.getTitle();
				content = title;
				remark = "文章标题:" + title;
			}
		} else if (behavior.equals("搜索博客")) {
			Result res = (Result) result;
			if (res.getCode() == 200) {
				String query = (String) requestParams.get("query");
				content = query;
				remark = "搜索内容:" + query;
			}
		} else if (behavior.equals("查看分类")) {
			String categoryName = (String) requestParams.get("categoryName");
			int pageNum = (int) requestParams.get("pageNum");
			content = categoryName;
			remark = "分类名称:" + categoryName + ",第" + pageNum + "页";
		} else if (behavior.equals("查看标签")) {
			String tagName = (String) requestParams.get("tagName");
			int pageNum = (int) requestParams.get("pageNum");
			content = tagName;
			remark = "标签名称:" + tagName + ",第" + pageNum + "页";
		} else if (behavior.equals("点击友链")) {
			String nickname = (String) requestParams.get("nickname");
			content = nickname;
			remark = "友链名称:" + nickname;
		}
		map.put("remark", remark);
		map.put("content", content);
		return map;
	}
}

后续

一扯到这个切面,里面的工具类,服务层接口,redis配置类全部一下子冒出来了,还好这个项目比较小,总算是看完了,现在我很难想象以后工作时真要看项目代码时,那得是件多煎熬的事啊,下一篇看看项目里的工具类代码咋写的。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1650405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 线程池 ( Thread Pool )的简单介绍

想象一下&#xff0c;你正指挥着一支超级英雄团队&#xff0c;面对蜂拥而至的敌人&#xff08;任务&#xff09;&#xff0c;不是每次都召唤新英雄&#xff08;创建线程&#xff09;&#xff0c;而是精心调配现有成员&#xff0c;高效应对。这就是Java线程池的魔力&#xff0c;…

口感与风味的完善结合:精酿啤酒的多样风格

啤酒的世界是丰富多彩的&#xff0c;不同的啤酒有着各自与众不同的口感和风味。而Fendi club啤酒&#xff0c;作为精酿啤酒的代表&#xff0c;以其多样化的风格和卓着的口感&#xff0c;吸引了无数啤酒爱好者的目光。 Fendi club啤酒的多样风格&#xff0c;首先体现在其原料的选…

【教学类-50-14】20240505“数一数”图片样式12:数一数(12个“人物”图案)

作品展示 背景需求&#xff1a; 前文做了“”材料”图片的数一数学具&#xff0c;效果不错&#xff0c; https://blog.csdn.net/reasonsummer/article/details/138466325https://blog.csdn.net/reasonsummer/article/details/138466325 为了让图案内容更丰富&#xff0c;我又…

2024-05-07 商业分析-如何在社会层面做一个更好的工具人-记录

摘要: 2024-05-07 商业分析-如何成为一个靠谱的工具人 如何在社会层面做一个更好的工具人 那么今天讲的这个主题呢&#xff0c;对吧&#xff1f;你们一看啊&#xff0c;就觉得这个就不应该我讲是吧啊&#xff0c;但是呢这个逻辑呢我还得跟你们讲一下啊&#xff0c;就是如何成为…

java面向对象实现文字格斗游戏

面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;是一种程序设计思想&#xff0c;它利用“对象”来封装状态和行为&#xff0c;使得代码更易于维护和扩展。 下面我们使用java中的面向对象编程&#xff0c;来实现一个文字格斗的游戏联系&#xff01; 实…

Day21 代码随想录打卡|字符串篇---右旋转字符串

题目&#xff08;卡码网 T55&#xff09;&#xff1a; 字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k&#xff0c;请编写一个函数&#xff0c;将字符串中的后面 k 个字符移到字符串的前面&#xff0c;实现字符串的右旋转…

京东生产环境十万并发秒杀系统三高架构

文章目录 三高——高并发、高可用、高可扩展用数据库乐观锁解决超卖阿里巴巴&#xff1a;为了提升数据库性能&#xff0c;对数据库的源码级别做了改造——在DB内部实现内存队列&#xff0c;一次性接收很多的请求&#xff0c;一次性更新。京东&#xff1a;redis&#xff0c;mq&a…

微软开发新模型;YouTube 推出新AI功能;可折叠iPhone 或发布?

微软或开发新模型与 Google、OpenAI 竞争 The Information 报道&#xff0c;微软正在训练一种新的 AI 大模型「MAI-1」&#xff0c;规模上足以与 Google、Anthropic 乃至 OpenAI 的先进模型抗衡。 据报道&#xff0c;这个 MAI-1 模型由微软聘请的 Inflection 前 CEO Mustafa S…

【Linux】Linux线程

一、Linux线程的概念 1.什么是线程 1.一个进程的一个执行线路叫做线程&#xff0c;线程的一个进程内部的控制序列。 2.一个进程至少有一个执行线程 3.线程在进程内部&#xff0c;本质是在进程地址空间内运行 4.操作系统将进程虚拟地址空间的资源分配给每个执行流&#xff0…

每日OJ题_DFS解决FloodFill⑤_力扣417. 太平洋大西洋水流问题

目录 力扣417. 太平洋大西洋水流问题 解析代码 力扣417. 太平洋大西洋水流问题 417. 太平洋大西洋水流问题 难度 中等 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下…

题目----力扣--移除链表元素

题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 输入&…

2024年4月17日华为春招实习试题【三题】-题目+题解+在线评测,2024.4.17,华为机试

2024年4月17日华为春招实习试题【三题】-题目题解在线评测 &#x1f52e;题目一描述&#xff1a;扑克牌消消乐输入描述输出描述样例一样例二Limitation解题思路一&#xff1a;模拟&#xff0c;遇到连续3张相同牌号的卡牌&#xff0c;直接删除解题思路二&#xff1a;栈解题思路三…

【备战软考(嵌入式系统设计师)】08 - 多媒体技术信息安全

多媒体技术 这内容比较杂&#xff0c;而且跟咱嵌入式的关系不大&#xff0c;但是软考里会考一些&#xff0c;下面我就结合我已经刷过的一千多道往年真题概括总结一下常考的知识点。 媒体分类 首先媒体分为五类&#xff1a; 感觉媒体&#xff0c;让人直接感觉得到的媒体&…

热敏电阻怎么进行性能测试?并以LabVIEW为例进行说明

过程也可用于执行热敏电阻测量。RTD和热敏电阻遵循非常相似的功能原理&#xff0c;测量步骤与下面提供的步骤相同。有关热敏电阻的更多信息&#xff0c;请参阅本文档。 查找设备引脚排列 在连接任何信号之前&#xff0c;请找到您的设备引脚排列。 打开NI MAX并展开设备和接口。…

【Java笔记】多线程:一些有关中断的理解

文章目录 线程中断的作用线程的等待状态WAITINGTIMED_WAITING 线程从等待中恢复 java.lang.Thread中断实现相关方法中断标识interrupted 一些小练习Thread.interrupt() 只唤醒线程并修改中断标识sleep() 清除中断状态标识 Reference 线程中断的作用 线程中断可以使一个线程从等…

关于2024年上半年软考考试批次安排的通告

按照《2024年计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试工作安排及有关事项的通知》&#xff08;计考办〔2024〕1号&#xff09;文件精神&#xff0c;结合各地机位实际&#xff0c;现将2024年上半年计算机软件资格考试有关安排通告如下&#xff1a; 一、考…

鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断

关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多&#xff0c;比如中断控制器&#xff0c;中断源&#xff0c;中断向量&#xff0c;中断共享&#xff0c;中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念…

GORM 与 MySQL(一)

GORM 操作 Mysql 数据库&#xff08;一&#xff09; 温馨提示&#xff1a;以下关于 GORM 的使用&#xff0c;是基于 Gin 框架的基础上&#xff0c;如果之前没有了解过 Gin 可能略微困难。 GORM 介绍 GORM 是 Golang 的一个 orm 框架。简单说&#xff0c;ORM 就是通过实例对象…

京东工业优选商品详情API接口:解锁高效工业采购新体验

京东工业优选的商品详情API接口&#xff0c;允许开发者通过程序化的方式&#xff0c;快速获取平台上的商品详细信息。这些详细信息包括但不限于商品名称、价格、规格、库存、图片、评价等&#xff0c;为企业提供全方位的商品信息查询服务。 二、API接口的主要功能 实时查询&a…

如何查看慢查询

4.2 如何查看慢查询 知道了以上内容之后&#xff0c;那么咱们如何去查看慢查询日志列表呢&#xff1a; slowlog len&#xff1a;查询慢查询日志长度slowlog get [n]&#xff1a;读取n条慢查询日志slowlog reset&#xff1a;清空慢查询列表 5、服务器端优化-命令及安全配置 安…