当前位置: 首页 > news >正文

springboot系列--web相关知识探索三

一、前言

web相关知识探索二中研究了请求是如何映射到具体接口(方法)中的,本次文章主要研究请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。本次主要研究注解方式以及Servlet API方式。

二、 注解方式

接口参数绑定主要涉及的一下注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@CookieValue、@RequestBody、@MatrixVariable

其中@MatrixVariable矩阵变量注解实际开发中基本不用,跳过研究。主要研究一下springmvc是如何将请求中的参数绑定到下面一个个注解修饰的参数中的。

一、测试用例 

@RestController
public class MvcTestController {/**** @param id* @param name* @param pv 这个map可以接受url上的所有值,*           key是id;value:url路径上的值*           key是name;value:* @param userAgent* @param header* @param age* @param list* @param params* @param _ga* @param cookie* @return*/@GetMapping("/demo/{id}/test/{name}")public Map<String,Object> test(@PathVariable("id") Integer id,@PathVariable("name") String name,@PathVariable Map<String,String> pv,@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> header,@RequestParam("age") Integer age,@RequestParam("list") List<String> list,@RequestParam Map<String,String> params,@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie){Map<String,Object> map = new HashMap<>();map.put("id",id);map.put("name",name);map.put("pv",pv);map.put("userAgent",userAgent);map.put("headers",header);map.put("age",age);map.put("list",list);map.put("params",params);map.put("_ga",_ga);return map;}@GetMapping("/test/servlet")public Map testServlet(HttpServletRequest request){Map<String,Object> map = new HashMap<>();map.put("request",request);return map;}@PostMapping("/test/method")public Map testPostMethod(@RequestBody String content){Map<String,Object> map = new HashMap<>();map.put("content",content);return map;}
}

首先,请求进来统一是有前端控制器(中央处理器),也即是DispatcherServlet这个类进行处理。然后中央处理器就会去调用处理器映射器,也即是handlerMapping,handlerMapping通过请求方式以及请求路径找到匹配的handler(也就是我们常说的接口,也是具体的处理方法),之后DispatcherServlet调用处理器适配器,也就是handlerAdatper根据具体的handler规则去执行对应的handler。

二·、原理

get请求:http://localhost:8080/demo/1/test/zhangsan,进入到上一章研究到的,通过handlerMapping获取到具体的handler地方。

1、HandlerMapping中找到能处理请求的Handler(Controller.method())

2、然后为当前Handler 找一个适配器 HandlerAdapter,这个适配器一般就是RequestMappingHandlerAdapter

3、适配器执行目标方法并确定方法参数的每一个值

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 这里就是上一张研究到获取合适的handler的地方mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}
// 这里就是获取到合适的处理器适配器的地方,源码在下方HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 这里是利用处理器适配器执行具体的handler     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}
}

一、找到合适的处理器适配器 

// 找到合适的HandlerAdapter 
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {// 遍历所有的处理器适配器,直到找到合适的 ,总共会有4种   if (this.handlerAdapters != null) {Iterator var2 = this.handlerAdapters.iterator();while(var2.hasNext()) {HandlerAdapter adapter = (HandlerAdapter)var2.next();// 这里的handler刚好就是HandlerMethod 对象,具体可以查看上一张获取handler。if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}// 判断是否支持当前handler,public final boolean supports(Object handler) {return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);}

 

0--支持方法上标注@RequestMapping、@GetMpping等注解的处理器适配器

1--支持函数式编程的

 二、利用处理器适配器执行具体的handler

从mv = ha.handle(processedRequest, response, mappedHandler.getHandler());方法进入

 

 一、参数解析器--HandlerMethodArgumentResolver

1、参数解析器主要是确定将要执行的目标方法的每一个参数的值是什么

2、SpringMVC目标方法能写多少种参数类型。取决于参数解析器

例如:目标方法(controller中的方法,也即是具体接口)使用了@RequestParam注解,springmvc就会使用RequestParamMethodArgumentResolvers这个参数解析器进行解析,,其他注解同理,会用其他解析器解析。

// 这是参数解析器接口,具体实现有27种,
public interface HandlerMethodArgumentResolver {// 当前解析器是否支持解析这种参数boolean supportsParameter(MethodParameter var1);// 支持就调用 resolveArgument@NullableObject resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
二、返回值处理器--HandlerMethodReturnValueHandler

返回值处理器主要就是处理返回值类型的,有多少种返回值处理器就能处理多少种返回值类型。这期主要研究参数解析器,返回值处理器另外再研究。

public interface HandlerMethodReturnValueHandler {boolean supportsReturnType(MethodParameter var1);void handleReturnValue(@Nullable Object var1, MethodParameter var2, ModelAndViewContainer var3, NativeWebRequest var4) throws Exception;
}
 三、将参数解析器、返回值处理器包装到执行handler对象中
@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndView var15;try {WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {// invocableMethod这个对象就是执行具体的handler的,把参数解析器包装进去
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}
// invocableMethod这个对象就是执行具体的handler的,把返回值处理器包装进去if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);Object result;if (asyncManager.hasConcurrentResult()) {result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(this.logger, (traceOn) -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});// 执行具体的handler,也就是controller中的接口方法invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);if (asyncManager.isConcurrentHandlingStarted()) {result = null;return (ModelAndView)result;}var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);} finally {webRequest.requestCompleted();}return var15;}

四、真正执行目标方法

 真正执行目标方法是在这个类ServletInvocableHandlerMethod里面的invokeAndHandle方法里的       

Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);这个方法

真正执行目标方法有三个重要的步骤:

1、将目标方法中的参数与请求进来的参数进行绑定以及判断是否能否绑定等等。也即是对请求参数进行解析,这里就需要用到参数解析器了

2、通过反射调用目标方法进行执行

3、将目标方法执行后的数据绑定,也就是对返回值进行处理,这里就用到返回值处理器了

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {//这里是真正执行目标方法,执行这个方法,就会到controller里面的接口方法,下图可见Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);this.setResponseStatus(webRequest);if (returnValue == null) {if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {this.disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(this.getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception var6) {if (logger.isTraceEnabled()) {logger.trace(this.formatErrorForReturnValue(returnValue), var6);}throw var6;}}

// 执行目标方法源码       
@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 这里就是将请求参数与目标方法中的参数进行绑定Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 通过反射执行目标方法return this.doInvoke(args);}

一、将请求参数与目标方法上的参数进行绑定

1、判断是否支持解析当前参数,如果有一个不支持就会报错

// 这段代码主要就是确定  controllerr中具体接口方法上的参数的值。也就是把请求参数的值与目标方法参数进行绑定protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 这里就是获取controllerr中具体接口方法上的参数、以及对应的类型,参数的位置等等,也就是获取参数声明信息MethodParameter[] parameters = this.getMethodParameters();// 如果目标方法没有参数,也就不需要绑定,直接返回if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;} else {// 创建一个数组,长度是目标方法参数的个数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) {// 判断当前解析器是否支持当前接口方法中的这个参数类型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 var10) {if (logger.isDebugEnabled()) {String exMsg = var10.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw var10;}}}return args;}}// 判断解析器是否支持当前参数类型的解析,解析器上面有讲过,有27中解析器public boolean supportsParameter(MethodParameter parameter) {return this.getArgumentResolver(parameter) != null;}@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// this.argumentResolverCache刚进来的时候是空的HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);if (result == null) {// 第一次是空的,会进到这里,然后	挨个遍历27个解析器Iterator var3 = this.argumentResolvers.iterator();while(var3.hasNext()) {HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();// 这里判断是否支持,具体源码在下方if (resolver.supportsParameter(parameter)) {result = resolver;// 解析出来后放入本地缓存中,下次进来就不用再判断了this.argumentResolverCache.put(parameter, resolver);break;}}}return result;}// 判断解析器是否支持当前参数解析,这里只拿PathVariable注解解析器源码举例public boolean supportsParameter(MethodParameter parameter) {// 判断当前参数是否使用了PathVariable注解,没使用就不支持解析if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;// 到了这里就说明当前参数一定是PathVariable注解修饰的,然后判断是不是map,如果不是map那么就能够支持} else if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {return true;} else {// 到这里就说明参数是map类型的,然后还需要做其他判断,也就是PathVariable注解中的value一定要有值,如果没有值,就说明这个注解的map想要全部接受url// 上的参数,这种就需要用另外一个解析器进行处理了,如果有值就说明url上的参数是map类型的,名字是PathVariable上的value属性的值。然后会将这个mapp参数值映射到// 这个参数上PathVariable pathVariable = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);return pathVariable != null && StringUtils.hasText(pathVariable.value());}}

2、通过了参数解析判断后,将进行真正的参数解析。 

   // 这里主要是进行参数解析这个方法是在HandlerMethodArgumentResolverComposite这个类下@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 拿到所有参数解析器        HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");} else {// 调用参数解析器,解析参数的方法return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}}// 这里是拿到这个参数对应的解析器,例如是@PathVariable注解修饰的参数,那就会拿到@PathVariable的参数解析器@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// 这里首先会从缓存中获取,由于之前在做判断是否支持这种类型的参数时,有做过缓存处理,所以这里可以直接获取到这个参数类型对应的参数解析器HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);if (result == null) {Iterator var3 = this.argumentResolvers.iterator();while(var3.hasNext()) {HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, resolver);break;}}}return result;}// 真正开始解析参数,这个方法是在AbstractNamedValueMethodArgumentResolver这个类里面,应该是一个公共方法,类似于模板方法的公共方法,处理公共逻辑@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 这里将注解信息封装成一个对象,例如注解里标识这个参数以什么样的名字绑定,是否是必须得,以及默认值。例如:@PathVariable(value = "id",required = false)这里面的属性NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();// 这里就是从封装对象解析出接口方法要绑定的名字,例如这个注解@PathVariable("id"),解析出来就是id这个名字Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");} else {// 这里开始将url上对应的值与id这个名字进行绑定,也即是对id进行赋值,这里将参数解析绑定后,下面逻辑可以不用看了Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);} else if (namedValueInfo.required && !nestedParameter.isOptional()) {this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);} catch (ConversionNotSupportedException var11) {throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());} catch (TypeMismatchException var12) {throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());}if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}}this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}}// 获取参数注解信息private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {// 先从缓存中获取NamedValueInfo namedValueInfo = (NamedValueInfo)this.namedValueInfoCache.get(parameter);if (namedValueInfo == null) {// 这个方法是从注解中获取注解属性信息namedValueInfo = this.createNamedValueInfo(parameter);// 这里其实就是重新更新一下namedValueInfo值中的defaultValue属性。namedValueInfo = this.updateNamedValueInfo(parameter, namedValueInfo);this.namedValueInfoCache.put(parameter, namedValueInfo);}return namedValueInfo;}// 从注解中获取注解属性信息,拿PathVariable注解举例protected AbstractNamedValueMethodArgumentResolver.NamedValueInfo createNamedValueInfo(MethodParameter parameter) {// 这里获取的是一个注解PathVariable ann = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);Assert.state(ann != null, "No PathVariable annotation");// 这里会把注解传入这个对象,里面会把属性保存到这个对象中return new PathVariableNamedValueInfo(ann);}// 把属性保存到这个对象中private static class PathVariableNamedValueInfo extends AbstractNamedValueMethodArgumentResolver.NamedValueInfo {public PathVariableNamedValueInfo(PathVariable annotation) {// 父类NamedValueInfosuper(annotation.name(), annotation.required(), "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n");}}protected static class NamedValueInfo {private final String name;private final boolean required;@Nullableprivate final String defaultValue;public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {this.name = name;this.required = required;this.defaultValue = defaultValue;}}// 这里是对id进行赋值的源码,name:就是接口方法中使用注解属性的值,也就是@PathVariable("id")里面的id@Nullableprotected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {/***HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值为org.springframework.web.servlet.HandlerMapping.uriTemplateVariables*request请求信息里面在org.springframework.web.servlet.HandlerMapping.uriTemplateVariables这个key下,保存了url上的请求参数*从请求中获取到数据后,保存在map当中,其中key为:注解属性的id,value为请求携带的值,就拿@PathVariable("id"),这个注解来说吧,key-id,value-请求请来的值。(@GetMapping("/demo/{id}/test/{name}")是从这里面取到的id、name为key)这里是由于请求一进来会有一个UrlPathHelper类里面的方法将url里面的路径变量全部解析出来*然后提前保存到请求域当中。所以uriTemplateVars是请求域中的值,但是并未绑定到接口参数上,*/Map<String, String> uriTemplateVars = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0);// 这里就是从请求域中的值里面获取到id对应的value        return uriTemplateVars != null ? uriTemplateVars.get(name) : null;}

 

三、Servlet API方式 

当我们的接口参数是WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId这些类型时,springmvc会有专门的处理器进行处理。也就是ServletRequestMethodArgumentResolver 这个处理器。主要源码如下。

@Overridepublic boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);}

一、判断是否支持HttpservletRequest类型参数

 

 二、解析出参数

相关文章:

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…...

AI冲击下的编程职业未来:你缺的不是技术,而是跨学科思维!

随着AIGC技术&#xff08;如ChatGPT、MidJourney、Claude等大语言模型&#xff09;的不断进化&#xff0c;AI辅助编程工具迅速普及&#xff0c;程序员的工作方式正在经历前所未有的转型。代码自动补全、智能化代码生成等功能大幅提升了工作效率&#xff0c;但与此同时&#xff…...

是否是 2 的幂次方

给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;tr…...

音视频入门

一个视频&#xff0c;一秒内普遍大于等于25帧。 入门知识&#xff1a; 1.帧&#xff0c;一张画面就是一帧。一个视频就是由许许多多帧组成的。 帧率&#xff0c;单位时间内帧的数量。单位&#xff1a;帧/秒 或 fps。 分类&#xff1a;I帧&#xff0c;P帧&#xff0c;B帧 I…...

C++随心记 续一

C中的模板 在其它语言中如Java或者C#中可能叫做泛型&#xff0c;在C中为模板&#xff0c;泛型的限制通常比模板多。模板可以解决多次的代码重复问题&#xff0c;如以下场景 #include <iostream> #include <string>void print(int value) {std::cout << val…...

消息中间件:RabbitMQ

消息中间件&#xff1a;RabbitMQ 前言安装Window安装Linux安装 管理页面什么是RabbitMQ&#xff1f;入门基本概念简单队列工作队列&#xff08;Work Queues&#xff09;发布/订阅&#xff08;Publish/Subscribe&#xff09;临时队列 路由&#xff08;Routing&#xff09;主题&a…...

sql-labs:42~65

less42&#xff08;单引号闭合、报错回显&#xff09; login_useradmin login_password123 and if(11,sleep(2),1) # # 单引号闭合 ​ login_useradmin login_password123and updatexml(1,concat(0x7e,database(),0x7e),1)# # 报错回显…...

KaTeX.js渲染数学公式

什么是KaTeX.js ? KaTeX 是一个集成速度快且功能丰富的数学公式渲染库&#xff0c;专为 Web 设计。它由 Khan Academy 开发&#xff0c;提供接近印刷品质的数学公式展示&#xff0c;同时保持与浏览器的高效互动性。KaTeX 特点包括快速渲染速度、高质量的输出、独立运行、跨平…...

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…...

H.264编解码工具 - FFmpeg

一、简介 FFmpeg是一款用于处理多媒体数据的开源软件,可以完成音频、视频和多媒体流的编解码、转码、解码、录制、流媒体播放等功能。它提供了丰富的命令行工具和库函数,适用于各种平台和操作系统。 FFmpeg支持多种常见的音视频格式,包括MP3、WAV、FLAC、MP4、AVI、MKV等。它…...

60 序列到序列学习(seq2seq)_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录一、理论知识比喻机器翻译Seq2seq编码器-解码器细节训练衡量生成序列的好坏的BLEU(值越大越好)总结 二、代码编码器解码器损失函数训练预测预测序列的评估小结练习 一、理论知识 比喻 seq2seq就像RNN的转录工作一样&#xff0c;非常形象的比…...

elementPlus的tree组件点击后有白色背景

在使用elementPlus的tree组件时&#xff0c;需要对它进行样式的重写&#xff0c;下面是相关代码 <script setup> import { ref } from vue const data [{label: Level one 1,children: [{label: Level two 1-1,children: [{label: Level three 1-1-1}]}]},{label: Leve…...

【Git】Git在Unity中使用时的问题记录

个人向笔记。 &#xff08;为什么没截图&#xff0c;因为公司电脑没法截图&#xff01;&#xff09; 1 前言 主要记录在使用Git协同开发时的各种问题&#xff0c;方便以后查阅。 2 记录 2.1 合并冲突 git pull下来后直接给合并了&#xff0c;麻了。若不想直接合并应该先把分…...

python学习记录6

&#xff08;1&#xff09;循环嵌套 可以将一个循环语句所属的语句块也可以是一个完整的一个循环语句&#xff0c;一般嵌套不应该超过3层。 嵌套可以是while-while、for-for,也可以是while-for。 基本图形输出&#xff1a;正方形&#xff0c;直角三角形 #输入一个数字n&…...

MongoDB 的基本使用

目录 数据库的创建和删除 创建数据库 查看数据库 删除数据库 集合的创建和删除 显示创建 查看 删除集合 隐式创建 文档的插入和查询 单个文档的插入 insertOne insertMany 查询 嵌入式文档 查询数组 查询数组元素 为数组元素指定多个条件 通过对数组元素使…...

数据揭秘:分类与预测技术在商业洞察中的应用与实践

分类与预测&#xff1a;数据挖掘中的关键任务 在数据挖掘的广阔天地中&#xff0c;分类与预测就像是一对互补的探险家&#xff0c;它们携手深入数据的丛 林&#xff0c;揭示隐藏的宝藏。 一、分类&#xff1a;数据的归类大师 分类是一种将数据点按照特定的属性或特征划分到不…...

学MybatisPlus

1.设置MySql的数据库 spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicodetrue&characterEncodingUTF-8&autoReconnecttrue&serverTimezoneAsia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123 logging:l…...

如何使用工具删除 iPhone 上的图片背景

在 iPhone 上删除背景图像变得简单易行。感谢最近 iOS 更新中引入的新功能。如今&#xff0c;iOS 用户现在可以毫不费力地删除背景&#xff0c;而无需复杂的应用程序。在这篇文章中&#xff0c;您将学习如何使用各种方法去除 iPhone 上的背景。这可确保您可以选择最适合您偏好的…...

软件工程-数据流图

数据流图(Data Flow Diagram&#xff0c;DFD)是一种图形化技术&#xff0c;它描绘信息流和数据从输入移动到输出的过程中所经受的变换。 数据流图的设计原则 数据守恒原则&#xff0c;对于任何一个加工来说&#xff0c;其所有输出数据流中的数据必须能从该加工的输入数据流中…...

链式前向星(最通俗易懂的讲解)

链式前向新&#xff1a;用于存储图的 边集 数组 前言 当我们存储图的时候&#xff0c;往往会使用 邻接矩阵 或是 邻接表。 邻接矩阵 好写&#xff0c;但太浪费空间&#xff0c;节点一多就存不下&#xff1b; 邻接表 效率高&#xff0c;但涉及指 &#xff0c;不好写容易出错…...

【C++设计模式】(四)创建型模式:简单工厂模式,工厂方法模式,抽象工厂模式

文章目录 &#xff08;四&#xff09;创建型模式&#xff1a;简单工厂模式&#xff0c;工厂方法模式&#xff0c;抽象工厂模式简单工厂模式工厂方法模式抽象工厂模式 &#xff08;四&#xff09;创建型模式&#xff1a;简单工厂模式&#xff0c;工厂方法模式&#xff0c;抽象工…...

浅析Golang的Context

文章目录 1. 简介2. 常见用法2.1 控制goroutine的生命周期&#xff08;cancel&#xff09;2.2 传递超时&#xff08;Timeout&#xff09;信息2.3 传递截止时间&#xff08;Deadline&#xff09;2.4 传递请求范围内的全局数据 &#xff08;value&#xff09; 3 特点3.1 上下文的…...

生日礼物C++代码

#include<bits/stdc.h> using namespace std; string s; int a,b; int main(){cout<<" 生日之地"<<\n;cout<<" 1.开始游戏"<<" 2.不想开始"<<\n;cin>>a;if(a1||a2){if(a2)cout<<…...

使用python基于DeepLabv3实现对图片进行语义分割

DeepLabv3 介绍 DeepLabv3 是一种先进的语义分割模型&#xff0c;由 Google Research 团队提出。它在 DeepLab 系列模型的基础上进行了改进&#xff0c;旨在提高图像中像素级分类的准确性。以下是 DeepLabv3 的详细介绍&#xff1a; 概述DeepLabv3 是 DeepLab 系列中的第三代…...

【漏洞复现】泛微OA E-Office do_excel.php 任意文件写入漏洞

》》》产品描述《《《 泛微0-0fice是一款标准化的协同 OA办公软件&#xff0c;泛微协同办公产品系列成员之一,实行通用化产品设计&#xff0c;充分贴合企业管理需求&#xff0c;本着简洁易用、高效智能的原则&#xff0c;为企业快速打造移动化、无纸化、数字化的办公平台。 》》…...

算法(食物链)

240. 食物链 题目 动物王国中有三类动物 A,B,C&#x1d434;,&#x1d435;,&#x1d436;&#xff0c;这三类动物的食物链构成了有趣的环形。 A&#x1d434; 吃 B&#x1d435;&#xff0c;B&#x1d435; 吃 C&#x1d436;&#xff0c;C&#x1d436; 吃 A&#x1d434;。…...

ubuntu20.04系统安装zookeeper简单教程

Ubuntu系统中安装和配置Zookeeper的完整指南 Apache Zookeeper是一个开源的分布式协调服务&#xff0c;广泛用于分布式应用程序中管理配置、提供命名服务、分布式同步以及组服务等。在本教程中&#xff0c;我们将详细介绍如何在Ubuntu系统中安装Zookeeper&#xff0c;并进行相关…...

.NET Core 高性能并发编程

一、高性能大并发架构设计 .NET Core 是一个高性能、可扩展的开发框架&#xff0c;可以用于构建各种类型的应用程序&#xff0c;包括高性能大并发应用程序。为了设计和开发高性能大并发 .NET Core 应用程序&#xff0c;需要考虑以下几个方面&#xff1a; 1. 异步编程 异步编程…...

B 私域模式升级:开源技术助力传统经销体系转型

一、引言 1.1 研究背景 随着市场竞争加剧&#xff0c;传统经销代理体系面临挑战。同时&#xff0c;开源技术发展迅速&#xff0c;为 B 私域升级带来新机遇。在当今数字化时代&#xff0c;企业面临着日益激烈的市场竞争。传统的经销代理体系由于管理效率低下、渠道局限、库存压…...

vue之vuex的使用及举例

Vuex是专门为Vue.js设计的集中式状态管理架构&#xff0c;它允许你将所有的组件共享状态存储在一个单独的地方&#xff0c;即“store”&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。以下是Vuex的基本使用方法&#xff1a; 一、安装Vuex 对于Vue 2项目&…...