你知道有哪些Spring MVC扩展点可以解析接口参数和处理返回值吗?

news2024/9/20 1:03:47

1.概述

Spring MVC 是一个灵活且强大的框架,它允许开发者在框架的基础上进行深度定制,以满足各种复杂的业务需求。HandlerMethodArgumentResolverHandlerMethodReturnValueHandlerSpring MVC 提供的两个重要扩展点,分别用于处理控制器方法的参数解析和返回值处理。本文将详细探讨这两个接口的作用、使用场景以及如何自定义实现。

关于Spring MVC的扩展点,我们之前已经总结过的两个扩展点:

谈谈@ControllerAdvice的使用及其实现原理:用于定义全局的异常处理、数据绑定、数据预处理等功能。

一文带你掌握SpringMVC扩展点RequestBodyAdvice和ResponseBodyAdvice如何使用及实现原理:RequestBodyAdvice允许开发者在处理 HTTP 请求体之前或之后插入自定义逻辑。它通过与 HttpMessageConverter 紧密集成,在请求体读取和转换的过程中提供了扩展点。了解其工作原理有助于在复杂的请求处理场景中实现更强大的功能,如日志记录、数据预处理加解密和签名验证等。ResponseBodyAdvice 是一个强大的工具,允许在 Spring MVC 中对响应数据进行集中处理和修改。通过自定义 ResponseBodyAdvice 实现类,可以实现响应数据的加密、格式转换、统一包装等多种功能,提升代码的可维护性和一致性。其实HandlerMethodArgumentResolverHandlerMethodReturnValueHandler实现的场景功能也是差不多的,都是对接口的参数解析和返回结果加工处理,但是今天这里就不再也对接口入参和返回结果加解密操作进行示例,感兴趣可以根据前面总结的文章提到的加解密需求功能用本文讲解的扩展点自行实现一波,那就真的学到了~~~

2.HandlerMethodArgumentResolver

HandlerMethodArgumentResolver 接口的主要作用是将 HTTP 请求中的参数解析为控制器方法的参数。Spring MVC 默认提供了多种 HandlerMethodArgumentResolver 的实现,如解析 @RequestParam@PathVariable@RequestBody 等注解的参数。

当一个请求到达时,Spring MVC 会遍历已注册的 HandlerMethodArgumentResolver,找到能够支持该方法参数的解析器,并调用其 resolveArgument 方法进行参数解析。定义如下:

public interface HandlerMethodArgumentResolver {

    /**
     * 是否支持解析该参数
     */
    boolean supportsParameter(MethodParameter parameter);

    /**
     * 解析该参数
     *
     */
    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

从源码来看这个接口定义很简单,#supportsParameter()是否支持解析该参数,如果支持就调用#resolveArgument(),话不多说直接来看实际开发中的应用场景,我们都知道一般在业务系统开发中,客户端访问服务端接口一般都需要走登录认证,把当前用户信息放到请求的上下文,以便后续获取当前登陆用户信息做逻辑处理

RequestUserHolder.getCurrentUser()

但是有时候想把登录信息当做方法参数进行传递,如下所示:

    @GetMapping("/user")
    public User getUserInfo(@RequestParam("userId") Long userId) {
        return userService.getUserInfo(userId);
    }

这是错误的示范,个人标识通过客户端传参,这意味着客户端想传谁的userId都行,这就导致了严重的数据安全问题。此时你可能在想有没有办法把userId作为方法参数,但是不再通过客户端传参,而是登录认证之后在请求上下文中获取。完全可以,通过HandlerMethodArgumentResolver就可以实现。

首先可以自定义一个注解标识@LoginUseruserInfo参数上,其作用不言而喻就是为了上面源码定义提到的方法#supportsParameter()满足解析参数条件。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
}

接下来就是实现HandlerMethodArgumentResolver完成参数解析,也就是通过在请求上下文中获取登陆信息对参数userId进行赋值操作:

@Slf4j
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {


    /**
     * 入参筛选
     *
     * @param methodParameter 参数集合
     * @return 格式化后的参数
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(LoginUser.class) && methodParameter.getParameterType().equals(UserSession.class);
    }

    /**
     * @param methodParameter       入参集合
     * @param modelAndViewContainer model 和 view
     * @param nativeWebRequest      web相关
     * @param webDataBinderFactory  入参解析
     * @return 包装对象
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) {
        return getCurrentUser(nativeWebRequest);
    }

    private UserSession getCurrentUser(NativeWebRequest webRequest) {
        // 这里是获取当前用户的逻辑
        // 1.你可以从请求信息中获取
        // HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        // ...

        // 2.也可以从登陆认证之后的上下文中获取
        // UserSession currentUser = RequestUserHolder.getCurrentUser();

        // 这里为了示例,就直接返回一个userSession进行模拟了
        UserSession session = new UserSession();
        session.setId(8L);
        session.setName("张三");
        session.setOrgId(6L);
        return session;
    }


}

当然,最后我们要实现 WebMvcConfigurer 接口的 #addArgumentResolvers() 方法,来增加这个自定义的处理器 LoginUserArgumentResolver

@Configuration
public class DefaultWebMvcConfig implements WebMvcConfigurer {


    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginUserArgumentResolver());
    }
}

通过上面参数解析配置之后,就可以通过参数解析赋值接口方法参数了。

   @GetMapping("/loginUser")
    public User getUser(@RequestParam("id") Long id, @LoginUser UserSession session) {
        Long userId = session.getId();
        User user = userService.getUser(userId);
        return user;
    }

postman调用接口:

从结果可以看出通过LoginUserArgumentResolver参数解析器获得userSession的userId为8,去查询数据库返回。

接口方法中我们虽然使用@RequestParam("id") Long id接收前端传参,后端也能正常接受到,但是我们并没有去使用它,如下所示:

3.HandlerMethodReturnValueHandler

Spring MVC中,当接口Controller方法执行完毕后,会遍历所有的HandlerMethodReturnValueHandler,找到第一个支持处理当前返回类型的HandlerMethodReturnValueHandler,然后调用其handleReturnValue方法处理返回值。定义如下所示:

public interface HandlerMethodReturnValueHandler {

	/**
	 * 是否支持处理返回值
	 */
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * 处理返回值
	 */
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

有时候,你可能需要统一处理控制器的返回值,比如将所有的返回值包装在一个标准的响应格式中。格式定义如下:

@Data
public class ResponseVO<T> implements Serializable {

    private Integer code;

    private String msg;

    private T data;

    public ResponseVO() {

    }

    public ResponseVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseVO(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public ResponseVO(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    private ResponseVO(ResponseStatusEnum resultStatus, T data) {
        this.code = resultStatus.getCode();
        this.msg = resultStatus.getMsg();
        this.data = data;
    }

    /**
     * 业务成功返回业务代码和描述信息
     */
    public static ResponseVO<Void> success() {
        return new ResponseVO<Void>(ResponseStatusEnum.SUCCESS, null);
    }

    /**
     * 业务成功返回业务代码,描述和返回的参数
     */
    public static <T> ResponseVO<T> success(T data) {
        return new ResponseVO<T>(ResponseStatusEnum.SUCCESS, data);
    }
 }

这时,可以自定义 HandlerMethodReturnValueHandler来实现:

public class ResponseReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return true;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true);
        HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
        servletResponse.getWriter().write(new ObjectMapper().writeValueAsString(ResponseVO.success(returnValue)));
    }
}

把自定义的处理器放到Spring MVC容器中:

@Configuration
public class DefaultWebMvcConfig implements WebMvcConfigurer {


    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginUserArgumentResolver());
    }
    
     @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.add(new ResponseReturnValueHandler());
    }
}

重启项目再次调用上面接口,你会发现并没有返回ResponseVO,而是直接返回了User。这就很奇怪了,为啥没生效呢???其实我们上面强调了Spring MVC在执行完接口方法之后会遍历所有的HandlerMethodReturnValueHandler找到第一个支持处理的处理器就返回了,通过debug我们发现,每次找到的都是RequestResponseBodyMethodProcessor,其实原因也很简单,现在项目中大部分采用前后端分离的架构,采用这种架构的项目,在返回数据时,几乎都是采用返回 json 格式的数据。而 Spring 中返回 json 格式的数据一般采用 @RestController 或者 @ResponseBody 注解,RequestResponseBodyMethodProcessor正是Spring MVC框架中默认的@ResponseBody的处理器,而我们自定义的处理器虽然加入了返回值处理器集合list中,但由于顺序比较靠后,遍历处理器集合时候先匹配上了RequestResponseBodyMethodProcessor处理器就返回了,自然没有我们自定义处理器的什么事了。

通过上面一说,你有没有体会出我们对返回值的处理加工都是建立在RequestResponseBodyMethodProcessor处理器基础上做一些改动,结果统一封装亦是如此,所以我们需要重新自定义下处理器:

public class ResponseReturnValueHandler implements HandlerMethodReturnValueHandler {
    // 其实定义一个RequestResponseBodyMethodProcessor内部变量,在它基础之上完成逻辑封装
    private HandlerMethodReturnValueHandler handler;

    public ResponseReturnValueHandler(HandlerMethodReturnValueHandler handler) {
        this.handler = handler;
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        // 和RequestResponseBodyMethodProcessor支持的一样,方便后续替换
        return handler.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        if (!(returnValue instanceof ResponseVO)) {
            returnValue = ResponseVO.success(returnValue);
        }
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}

下面就是将我们自定义的ResponseReturnValueHandler来代替RequestResponseBodyMethodProcessor

@Configuration
public class RequestArgumentAndReturnValueConfig implements InitializingBean {
    @Resource
    RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    @Override
    public void afterPropertiesSet() throws Exception {
        // 获取处理器list
        List<HandlerMethodReturnValueHandler> originHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(originHandlers.size());
        // 遍历处理器list,替换掉RequestResponseBodyMethodProcessor
        for (HandlerMethodReturnValueHandler handler : originHandlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor) {
                newHandlers.add(new ResponseReturnValueHandler(handler));
            }else{
                newHandlers.add(handler);
            }
        }
        // 把新的处理器list放入Spring MVC
        requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
    }
}

重启项目再次执行上面的示例接口,输出如下:成功对返回结果进行了统一格式封装

{
    "code": 200,
    "msg": "OK",
    "data": {
        "id": 8,
        "userNo": "001",
        "gender": 0,
        "name": "张三",
        "birthday": "2024-08-07",
        "phone": "12234",
        "isDelete": 0,
        "createTime": "2024-07-03T16:09:12"
    }
}

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

4.实现原理

再来看看一次接口请求在Spring MVC的执行流程图:

都会进入核心控制器DispatcherServlet的方法#doDispatch()找到处理器适配器之后,执行:

				// Actually invoke the handler. 真正执行处理器controller
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

后续源码执行流程大概如下:

RequestMappingHandlerAdapter.invokeAndHandle(webRequest, mavContainer);
--ServletInvocableHandlerMethod.invokeAndHandle(webRequest, mavContainer);
---`Object returnValue = InvocableHandlerMethod.invokeForRequest(webRequest, mavContainer, providedArgs);`
---Object[] args = InvocableHandlerMethod.getMethodArgumentValues(request, mavContainer, providedArgs);

来到ServletInvocableHandlerMethod的方法#invokeAndHandle()

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

    // 执行入口    重点
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
      // 处理返回值   重点
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

接着来到InvocableHandlerMethod#invokeForRequest():

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

解析请求传输参数方法#getMethodArgumentValues()

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

  MethodParameter[] parameters = getMethodParameters();
  if (ObjectUtils.isEmpty(parameters)) {
    return EMPTY_ARGS;
  }

  Object[] args = new Object[parameters.length];
  // 遍历接口方法参数
  for (int i = 0; i < parameters.length; i++) {
    MethodParameter parameter = parameters[i];
    parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    args[i] = findProvidedArgument(parameter, providedArgs);
    if (args[i] != null) {
      continue;
    }
    // 重点 判断参数是否有对应的解析器
    if (!this.resolvers.supportsParameter(parameter)) {
      throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    }
    try {
      // 重点 重点 重点 找到对应参数解析器解析参数
      args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    }
    catch (Exception ex) {
      // Leave stack trace for later, exception may actually be resolved and handled...
      if (logger.isDebugEnabled()) {
        String exMsg = ex.getMessage();
        if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
          logger.debug(formatArgumentError(parameter, exMsg));
        }
      }
      throw ex;
    }
  }
  return args;
}

这里的this.resolversHandlerMethodArgumentResolverComposite,它也实现了HandlerMethodArgumentResolver,核心代码如下:

@Override
	public boolean supportsParameter(MethodParameter parameter) {
    // 是否有解析当前参数的解析器
		return getArgumentResolver(parameter) != null;
	}

	/**
	 * Iterate over registered
	 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
	 * and invoke the one that supports it.
	 * @throws IllegalArgumentException if no suitable argument resolver is found
	 */
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
    // 解析参数
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}

	/**
	 * Find a registered {@link HandlerMethodArgumentResolver} that supports
	 * the given method parameter.
	 */
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 先从本地缓存中查找,没有的话再遍历
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
      // 遍历解析器list
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
        // 匹配上支持的
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
          // 放入缓存
					this.argumentResolverCache.put(parameter, result);
          // 中断遍历,返回解析器
					break;
				}
			}
		}
		return result;
	}

上面就是关于HandlerMethodArgumentResolver的实现,下面我们再来看看HandlerMethodReturnValueHandler是咋实现的。其实和上面流程差不多的,还是来到ServletInvocableHandlerMethod的方法#invokeAndHandle()中执行的返回值处理方法this.returnValueHandlers.handleReturnValue();这里的this.returnValueHandlersHandlerMethodReturnValueHandlerComposite,它也实现了HandlerMethodReturnValueHandler,核心代码如下:

/**
	 * Whether the given {@linkplain MethodParameter method return type} is supported by any registered
	 * {@link HandlerMethodReturnValueHandler}.
	 */
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
    // 是否支持对结果进行处理
		return getReturnValueHandler(returnType) != null;
	}

	@Nullable
	private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
    // 遍历处理器
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

	/**
	 * Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
	 * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
	 */
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 查找处理器
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
    // 处理返回值
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

可以看出在实现原理和流程上,HandlerMethodArgumentResolverHandlerMethodReturnValueHandler套路几乎是一样的。

5.总结

HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 是 Spring MVC 框架中非常重要的扩展点,它们允许开发者根据业务需求对参数解析和返回值处理进行深度定制。通过合理使用这些扩展点,你可以在不改变核心框架的情况下,灵活地实现各种复杂的功能需求。希望本文对你理解和使用这两个接口有所帮助。

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

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

相关文章

SLF4J 警告 - SLF4J: Class path contains multiple SLF4J bindings.

SLF4J 警告是因为类路径中存在多个 SLF4J 绑定。SLF4J 是一个抽象的日志接口&#xff0c;它可以与不同的日志实现&#xff08;如 Logback 或 SLF4J Simple&#xff09;一起使用。这个警告表明在你的项目中&#xff0c;SLF4J 找到了多个实现&#xff0c;导致它不知道该使用哪一个…

python如何判断回文

打开JUPTER NOTEBOOK&#xff0c;新建一个PYTHON文档。 n input("Please input string: ") print(n) 我们首先让用户输入要进行判断的字符串&#xff0c;然后打印出来查看一下。 n input("Please input string: ") is_palidrome n[::-1] if n is_palid…

Windows IPv6漏洞CVE-2024-38063

2024年8月&#xff0c;微软发现Windows10、Windows11、Windows Server2008~Server2022系统里&#xff0c;有个TCP/IP栈的远程代码执行漏洞&#xff0c;它通过目标系统的445端口&#xff0c;走IPv6协议&#xff0c;向目标系统发生特制的TCP包&#xff0c;执行任意代码&#xff0…

MySQL索引(三)

MySQL索引(三) 文章目录 MySQL索引(三)为什么建索引&#xff1f;怎么建立索引为什么不是说索引越多越好什么时候不用索引更好 索引怎么优化索引失效如何解决索引失效 学习网站&#xff1a;https://xiaolincoding.com/ 为什么建索引&#xff1f; 1.索引大大减少了MySQL需要扫描…

io进程中进程的创建,回收,退出

目录 一丶什么是进程 1.概念 2.特点 3 进程段 4.进程分类 5.进程状态 6.进程状态切换图 7.进程相关命令 8.优先级调度 二丶进程函数接口 1.创建进程fork() 2.进程回收wait() 3.结束进程exit() 4.获取进程号getpid(),getppid() 5.exec函数族 6.守护进程 特点&a…

AI辅助创作全攻略:如何高效利用人工智能撰写各类作品文字

在数字化时代的浪潮中人工智能&#xff08;AI&#xff09;已经渗透到咱们生活的方方面面&#xff0c;其中就包含文学创作领域。辅助创作不仅可以加强写作效率还能激发创作灵感宽创作视野。 那么怎么样高效利用人工智能撰写各类作品文字呢&#xff1f;本文将为您详细解析这一全攻…

软件测试 | 测试用例

测试用例&#xff08;Test Case&#xff09;是为了实施测试而向被测试的系统提供的一组集合&#xff0c;这组集合包含&#xff1a;测试环境&#xff0c;测试步骤&#xff0c;测试数据&#xff0c;预期结果等要素。 设计测试用例原则⼀&#xff1a; 测试用例中⼀个必需部分是对…

进程间通信:采用有名管道,创建两个发送接收端,父进程写入管道1和管道2,子进程读取管道2和管道1.

作业1&#xff1a;有名管道&#xff0c;创建两个发送接收端&#xff0c;父进程写入管道1和管道2&#xff0c;子进程读取管道2和管道1. 右进程 #include <myhead.h> int main(int argc, const char *argv[]) {pid_t pidfork();if(pid>0)//父进程&#xff0c;将数据发…

PHP软件下载-安装-环境配置

.1.下载 下载地址如下 windows.php.net - /downloads/releases/ 安装包如下. .2.安装 可以在D盘或者E盘的根目录创建一个自定义目录。注意文件夹目录中不能包含中文&#xff0c;不能包含空格等特殊字符。 版本说明&#xff1a; (1)ts表示非线程安全版本。这个安装包还指明了…

easypoi实现ftl转doc文档(循环填充数据)

1.pom文件 java <dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId></dependency><!-- EasyPoi依赖 --><dependency><groupId>cn.afterturn</groupId><artifactId>easypo…

【生日视频制作】室内告白表白祝福布置霓虹灯AE模板修改文字软件生成器教程特效素材【AE模板】

室内告白表白祝福布置霓虹灯生日视频制作教程AE模板改字生成器 怎么如何做的【生日视频制作】室内告白表白祝福布置霓虹灯AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 安装AE软件下载AE模板把AE模板导入AE软件修改图片或文字渲染出视频

select epoll搭建并发式服务器

select 在C语言中&#xff0c;使用select函数可以创建一个并发式服务器。select是一个系统调用&#xff0c;它允许服务器同时监视多个文件描述符&#xff08;如套接字&#xff09;&#xff0c;以便知道哪个文件描述符准备好了进行读取或写入操作。这使得服务器能够同时处理多个…

Python简介、发展史

Python简介、发展史 本文目录&#xff1a; 零、时光宝盒 一、Python简介 二、Python设计者 三、Python发展史 四、Python语言的编程语言特性 五、Python现状 六、Python的未来 零、时光宝盒 我家所在的楼是3栋楼连接在一起的建筑&#xff0c;也就是3栋楼楼顶建筑上互通。…

学习笔记——后端项目中的相关技术 【随时更新】

文章目录 1. Session 共享1.0 cookie和session的工作流1.1 Cookie范围1.2 为什么要共享&#xff1f;1.3 如何共享存储1.4 session共享实现 1. Session 共享 1.0 cookie和session的工作流 在 Web 开发中&#xff0c;Cookie 和 Session 是非常常见的&#xff0c;尤其是在处理用…

使用 Java 在 Android 平台上通过 socket 实现进程间通信

引言 Socket是位于应用层和传输层之间的一个抽象层&#xff0c;把TCP/IP层复杂的操作抽象为几个简单的接口&#xff0c;供应用层调用以实现进程在网络中通信。 Socket分为流式套接字和数据包套接字&#xff0c;分别对应网络传输控制层的TCP协议和UDP协议。TCP协议是一种面向连…

Qt之界面优化

目录 前言 QSS基础知识 选择器 样式属性 控件样式⽰例 绘图 接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言 Qt 仿照CSS的模式,引⼊了QSS,来对Qt中的控件做出样式上的设定,从⽽允许程序猿写出界⾯更好看 的代码.当然,由于Q…

【面试题系列Vue05】跟其他人不太一样的 Vue生命周期总结

既然要讲 生命周期&#xff0c;那渲染过程得先了解下。 数据绑定 Vue 文本插值 在 Vue 中&#xff0c;最基础的模板语法是数据绑定&#xff0c;例如&#xff1a; <div>{{ data }}</div>这里绑定了一个 msg 的变量&#xff0c;开发者在 Vue 实例 data 中绑定该变…

DataWhale AI夏令营-《李宏毅深度学习教程》笔记-task2

DataWhale AI夏令营-《李宏毅深度学习教程》笔记 第四章 卷积神经网络卷积神经网络构成一个通俗的例子 第四章 卷积神经网络 这部分看书的时候感觉云里雾里的&#xff0c;好在之前学过一些卷积神经网络、全链接、感受野的概念&#xff0c;我就用自己的理解阐述一篇笔记吧&…

idea插件开发(一)合并检查

一、引言 由于代码合并冲突的时候&#xff0c;代码丢失的情况频发&#xff0c;作者研究idea的VFS虚拟文件系统和Git4ide源码&#xff0c;创作idea插件检测代码合并丢失 可以区分主动删除与被动丢失&#xff0c;比如本地或者删除一段代码&#xff0c;合并之后不会被认为是丢失…

华为云 x 容联云|828企业节,助推中国数智产业实力再升级

2024年8月27日&#xff0c;华为第三届828 B2B企业节在2024中国国际大数据产业博览会上正式开幕。 828 B2B企业节是全国首个基于数字化赋能的企业节&#xff0c;由华为联合上万家生态伙伴共同发起&#xff0c;旨在为广大企业尤其是中小企业搭建数字化创新发展平台&#xff0c;融…