SpringMVC系列-4 参数解析器

news2024/11/21 0:14:16

背景:

本文作为SpringMVC系列的第四篇,介绍参数解析器。本文讨论的参数解析表示从HTTP消息中解析出JAVA对象或流对象并传参给Controller接口的过程。
本文内容包括介绍参数解析器工作原理、常见的参数解析器、自定义参数解析器等三部分。其中,原理部分会结合源码进行说明。

1.工作原理

说明:本文重点在于说明参数解析器的工作原理和使用方式,为避免文章过于冗长,会刻意省略对异步请求和文件上传部分的分支逻辑,读者可在理解主线逻辑后自定阅读该部分源码。
源码介绍时,会忽略所有的日志打印以及与主线逻辑无关的try-catch-finnally块

1.1 解析过程

SpringMVC系列-2 HTTP请求调用链 中介绍过:收到http请求后,进入DispatcherServlet的dispatcherServlet方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//...
	
	// 1.preHandle
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// 2.call handler
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	// 3.postHandle
	mappedHandler.applyPostHandle(processedRequest, response, mv);

	//...
}

在执行HandlerInterceptor的preHandle和postHandle之间,会通过ha.handle(processedRequest, response, mappedHandler.getHandler())反射调用Controller接口,跟踪该调用链进入:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	//	⚠️1.调用controller接口
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	
	//	⚠️2.处理返回值
	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;
	}
}

invokeAndHandle方法从逻辑上可以分为两个部分:(1)调用controller接口并获取返回值;(2)处理返回值。
本文关注第一部分:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
其中:webRequest包装了HTTP请求的request和response对象,mavContainer是MVC对象,providedArgs传入的是null.
跟进invokeForRequest方法:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	return doInvoke(args);
}

包括两个步骤:(1)调用getMethodArgumentValues获取参数;(2)将步骤(1)获取的参数传递给doInvoke,通过反射调用Controller接口并返回结果。
跟进getMethodArgumentValues方法:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,	Object... providedArgs) throws Exception {
	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return new Object[0];
	}

	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) {
			throw ex;
		}
	}
	return args;
}

由于入参providedArgs为null, 因此上述逻辑可以简化为:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,	Object... providedArgs) throws Exception {
	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return new Object[0];
	}

	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		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) {
			throw ex;
		}
	}
	return args;
}

上述解析参数的逻辑可以分为两步:(1)获取目标接口的参数数组,并判断是否为空(参数为空即不需要处理参数);(2)遍历参数数组,根据参数解析器对每个参数对象依此进行处理,如果没有匹配的参数处理器,则抛出IllegalStateException异常。

这里的参数解析器this.resolvers类型是HandlerMethodArgumentResolverComposite,是一个组合模型,内部维持了一个HandlerMethodArgumentResolver数组:

private final List<HandlerMethodArgumentResolver> argumentResolvers

参数解析最终都会派发给argumentResolvers的各个元素,派发原则是选择第一个满足匹配规则的参数解析器

在后续SpringMVC源码介绍过程中,会发现框架大量使用了组合设计模式;大部分采取匹配+处理的组合手段完成。

因此HandlerMethodArgumentResolver接口需要有两个接口:

public interface HandlerMethodArgumentResolver {
	boolean supportsParameter(MethodParameter parameter);

	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

supportsParameter方法用于判断该HandlerMethodArgumentResolver是否与参数匹配,resolveArgument方法用于解析参数并返回解析结果。

1.2 初始化过程

ServletInvocableHandlerMethods的resolvers属性
解析过程的核心在于参数解析器,即ServletInvocableHandlerMethod对象中的HandlerMethodArgumentResolverComposite resolvers属性,而每次HTTP调用都会生成一个ServletInvocableHandlerMethod对象,因此关注该对象的resolvers属性如何被初始化即可:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		// ...
		
		ServletInvocableHandlerMethod invocableMethod = new ServletInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		
		// ...
		
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		
		// ...
	}
}

如上所示:ServletInvocableHandlerMethod对象的resolvers属性来自RequestMappingHandlerAdapter对象的this.argumentResolvers属性。

RequestMappingHandlerAdapter的argumentResolvers属性
继续跟踪RequestMappingHandlerAdapter的this.argumentResolvers属性初始化过程,需要注意的是RequestMappingHandlerAdapter是全局Bean对象,因此可以从头梳理一下该对象关于argumentResolvers属性的初始化过程。
【1】实例化阶段

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcValidator") Validator validator) {

	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
	adapter.setCustomArgumentResolvers(getArgumentResolvers());
	
	// ...
	return adapter;
}

createRequestMappingHandlerAdapter()返回RequestMappingHandlerAdapter实例后,将getArgumentResolvers()获取的自定义参数解析器设置到this.customArgumentResolvers属性中。
看一下getArgumentResolvers()逻辑:

protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
	this.argumentResolvers = new ArrayList<>();
	addArgumentResolvers(this.argumentResolvers);
	return this.argumentResolvers;
}

@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
	this.configurers.addArgumentResolvers(argumentResolvers);
}

argumentResolvers数据来自于this.configurers,看一下这个属性的初始化过程:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

configurers来自于IOC容器中WebMvcConfigurer类型的对象。

因此用户可自定义WebMvcConfigurer对象并将其注入到IOC中,在自定义的WebMvcConfigurer类中通过复写addArgumentResolvers方法可实现自定义参数解析器的添加。

【2】初始化阶段
RequestMappingHandlerAdapter实现了InitializingBean接口,在Bean的初始化阶段中,会调用其afterPropertiesSet()钩子函数:


public void afterPropertiesSet() {
	// ...
	if (this.argumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	// ...
}

可以看出所有的参数解析器来自有getDefaultArgumentResolvers()方法:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
	List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

	// Annotation-based argument resolution
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
	resolvers.add(new RequestParamMapMethodArgumentResolver());
	resolvers.add(new PathVariableMethodArgumentResolver());
	resolvers.add(new PathVariableMapMethodArgumentResolver());
	resolvers.add(new MatrixVariableMethodArgumentResolver());
	resolvers.add(new MatrixVariableMapMethodArgumentResolver());
	resolvers.add(new ServletModelAttributeMethodProcessor(false));
	resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new RequestHeaderMapMethodArgumentResolver());
	resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
	resolvers.add(new SessionAttributeMethodArgumentResolver());
	resolvers.add(new RequestAttributeMethodArgumentResolver());

	// Type-based argument resolution
	resolvers.add(new ServletRequestMethodArgumentResolver());
	resolvers.add(new ServletResponseMethodArgumentResolver());
	resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
	resolvers.add(new RedirectAttributesMethodArgumentResolver());
	resolvers.add(new ModelMethodProcessor());
	resolvers.add(new MapMethodProcessor());
	resolvers.add(new ErrorsMethodArgumentResolver());
	resolvers.add(new SessionStatusMethodArgumentResolver());
	resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

	// Custom arguments
	if (getCustomArgumentResolvers() != null) {
		resolvers.addAll(getCustomArgumentResolvers());
	}

	// Catch-all
	resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
	resolvers.add(new ServletModelAttributeMethodProcessor(true));

	return resolvers;
}

这里包含了框架内置的参数解析器以及自定义参数解析器,需要注意一下几点:
(1)getCustomArgumentResolvers()来自于【1】实例化阶段中设置的自定义参数解析器;
(2)自定义参数解析器的顺序比较靠后,需要避免被其他参数解析器拦截,supportsParameter方法可以根据参数类型进行匹配。
(3)首位端各存在一个RequestParamMethodArgumentResolver类型的参数解析器,区别是内部useDefaultResolution属性前者是false, 后者是true.

2.常见的参数解析器

2.1 解析器分类

Debug是阅读源码的一个很有效的方式。

Debug
Spring框架默认的消息解析器超过20个, 红框圈选的是常见的参数解析器(本章节重点对着一部分进行介绍),如下所示:在这里插入图片描述
上述26个参数解析器按照匹配条件类型可以分为:
【1】注解类型
根据Controller接口中参数是否拥有指定注解确定使用的参数解析器:
@RequestParam -> RequestParamMethodArgumentResolver, RequestParamMapMethodArgumentResolver
@PathVariable -> PathVariableMethodArgumentResolver, PathVariableMapMethodArgumentResolver
@MatrixVariable -> MatrixVariableMethodArgumentResolver, MatrixVariableMapMethodArgumentResolver
@ModelAttribute -> ModelAttributeMethodProcessor
@RequestBody -> RequestResponseBodyMethodProcessor
@RequestPart -> RequestPartMethodArgumentResolver
@RequestHeader -> RequestHeaderMethodArgumentResolver, RequestHeaderMapMethodArgumentResolver
@CookieValue -> ServletCookieValueMethodArgumentResolver
@Value -> ExpressionValueMethodArgumentResolver
@SessionAttribute -> SessionAttributeMethodArgumentResolver
@RequestAttribute -> RequestAttributeMethodArgumentResolver
上述参数解析器根据是否有对应注解(以及满足特定条件)确定是否匹配参数。

【2】参数类型
根据Controller接口中参数类型确定使用的参数解析器。
ServletResponse及其子类, OutputStream及其子类, Writer及其子类 -> ServletResponseMethodArgumentResolver
HttpEntity, RequestEntity -> HttpEntityMethodProcessor
RedirectAttributes及其子类 -> RedirectAttributesMethodArgumentResolver
Model及其子类 -> ModelMethodProcessor
Errors及其子类 -> ErrorsMethodArgumentResolver
SessionStatus -> SessionStatusMethodArgumentResolver
UriComponentsBuilder, ServletUriComponentsBuilder -> UriComponentsBuilderMethodArgumentResolver

部分参数解析器的supportsParameter方法条件比较复杂,如下所示:
ServletRequestMethodArgumentResolver:
支持的类型较多,如下所示:

@Override
public 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);
}

MapMethodProcessor:

public boolean supportsParameter(MethodParameter parameter) {
	return Map.class.isAssignableFrom(parameter.getParameterType()) &&
			parameter.getParameterAnnotations().length == 0;
}

参数不能有注解,且参数为Map类型.

ServletModelAttributeMethodProcessor:

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
			(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}

该解析器在实例化时设置的annotationNotRequired为true,因此可以简化为:

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(ModelAttribute.class) ||
			!BeanUtils.isSimpleProperty(parameter.getParameterType());
	}

表示:参数有@ModelAttribute注解或者BeanUtils.isSimpleProperty(parameter.getParameterType())返回false;

public static boolean isSimpleProperty(Class<?> type) {
	// 如果类型是数据调用isSimpleValueType判读数组的元素
    return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());
}

public static boolean isSimpleValueType(Class<?> type) {
    return Void.class != type && Void.TYPE != type 
    && 	(ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);
}

即:参数类型(或参数数组的元素类型)是Void或Void.TYPE或者均不是(Enum,Number,CharSequence,Locale,…)类型的才会匹配。

2.2 常用注解及消息解析器

略(待补充)

3.使用方式

案例从请求url中解析出name和age、从HTTP请求头中解析出token、并获取当前服务器时间,用于构造User对象,并传参给Controller接口。
定义参数类:

@Data
public class User {
    private String name;

    private Integer age;

    private String token;

    private LocalDateTime time;
}

定义Controller接口:

@GetMapping("/test")
public Object queryByType(User user) {
   return user;
}

自定义参数解析器:

public class MyArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(User.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        User user = new User();
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        user.setName(request.getParameter("name"));
        user.setAge(Integer.parseInt(request.getParameter("age")));
        user.setToken(request.getHeader("token"));
        user.setTime(LocalDateTime.now());
        return user;
    }
}

将参数解析器注册到容器中:

@Configuration
public class BaseWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new MyArgumentResolver());
    }
}

使用postman调用测试结果如下:
在这里插入图片描述

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

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

相关文章

css实现不规则图片文字环绕效果

依旧,先上效果图,可以看见,文字环绕这个椭圆形的图片, 依旧是遵循开源精神,代码就直接放下面了 (点个赞或者给个评论啥的吧,我就发现我的文章全是光看不点赞,不评论的的) <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&quo…

【重拾C语言】六、批量数据组织(二)线性表——分类与检索(主元排序、冒泡排序、插入排序、顺序检索、对半检索)

目录 前言 六、批量数据组织——数组 6.4 线性表——分类与检索 6.4.1 主元排序 6.4.2 冒泡排序 6.4.3 插入排序 6.4.4 顺序检索&#xff08;线性搜索&#xff09; 6.4.5 对半检索&#xff08;二分查找&#xff09; 算法比较 前言 线性表是一种常见的数据结构&#xf…

Linux网络编程系列之TCP协议编程

一、什么是TCP协议 TCP&#xff08;Transmission Control Protocol&#xff09;协议是一种面向连接的、可靠的、基于字节流的传输控制协议&#xff0c;属于传输层。TCP协议可以通过错误检测、重传丢失的数据包、流量控制、拥塞控制等方式来实现可靠传输&#xff0c;同时也具有较…

string和const char*参数类型选择的合理性对比

在编程中&#xff0c;我们经常需要处理字符串类型的参数。在C中&#xff0c;有两种常见的表示字符串的参数类型&#xff0c;即string和const char*。本文将对比这两种参数类型的特点&#xff0c;分析其在不同情况下的合理性&#xff0c;以便程序员能够根据实际需求做出正确的选…

超赞极简奶油风装修攻略~速来抄作业

如果您想将极简奶油风应用于自家装修&#xff0c;以下是小编的一些优化建议&#x1f3e0;✨&#xff1a;色彩选择&#x1f3a8;&#xff1a;主色调应选择简洁、柔和的颜色&#xff0c;如白色☁、米色☕、淡灰色&#x1f32b;等。在这些基础颜色中适度添加1-2个饱和度较高的活力…

接收机灵敏度和动态范围定义

一、接收机灵敏度 灵敏度是来自天线的最小信号电平的特定值&#xff0c;在该特定值处接收器可以提供足够的输出信噪比&#xff08;SNR&#xff09;。最小可辨别信号&#xff08;MDS&#xff09;是0dB射频信噪比&#xff08;RFSNR&#xff09;的信号电平。MDS通常以dBm表示。 图…

【kubernetes】kubernetes中的应用配置(ConfigMap和Secret)

目录 1 为什么需要ConfigMap和Secret2 k8s中给容器传递配置的方式3 ConfigMap的基本使用4 ConfigMap的实践5 Secret的基本使用6 ConfigMap和Secret的对比 1 为什么需要ConfigMap和Secret 应用程序启动过程中通常需要传递参数&#xff0c;当参数较多时会将参数保存到配置文件中…

Parse [5/10/2020 7:05:04 PM] with format [yyyy-MM-dd] error!

项目场景&#xff1a; 对日期格式转化报错&#xff1a; Parse [5/10/2020 7:05:04 PM] with format [yyyy-MM-dd] error! 问题描述 例如&#xff1a;数据日期格式无法强行转化为常见格式 String releaseDate"5/10/2020 7:05:04 PM";String format DateUtil.format…

C++设计模式-适配器(Adapter)

目录 C设计模式-适配器&#xff08;Adapter&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-适配器&#xff08;Adapter&#xff09; 一、意图 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工…

Python: 库decimal()用于浮点数相加

from decimal import Decimal a1.1 b2.2 print(Decimal(2.2)Decimal(1.1))结果为&#xff1a;3.3 Pyhton中浮点数是不能直接相加的。 可以看到结果并不对。 因此需要用到decimal 可以看到ac时不计算的结果是正确的。 因此在python中&#xff0c;计算浮点数时&#xff0c;一部…

SpringCloud学习一

单体应用存在的问题 随着业务的发展&#xff0c;开发变得越来越复杂。 修改、新增某个功能&#xff0c;需要对整个系统进行测试、重新部署。 一个模块出现问题&#xff0c;很可能导致整个系统崩溃。 多个开发团队同时对数据进行管理&#xff0c;容易产生安全漏洞。 各个模块…

王道考研操作系统——文件管理

磁盘的基础知识 .txt用记事本这个应用程序打开&#xff0c;文件最重要的属性就是文件名了 保护信息&#xff1a;操作系统对系统当中的各个用户进行了分组&#xff0c;不同分组的用户对文件的操作权限是不一样的 文件的逻辑结构就是文件内部的数据/记录应该被怎么组织起来&…

【C++深入浅出】类和对象下篇

一. 前言 老样子&#xff0c;先来回顾一下上期的内容&#xff1a;上期我们着重学了C类中的六大默认成员函数&#xff0c;并自己动手实现了一个日期类&#xff0c;相信各位对C中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦&#xff0c;终于要结束咯&#xff0c;吧…

Java编程题(完数)

题目 一个正整数的因子是所有可以整除它的正整数。而一个数如果恰好等于除它本身外的因子之和&#xff0c;这个数就称为完数。例如61&#xff0b;2&#xff0b;3(6的因子是1,2,3)。 现在&#xff0c;你要写一个程序&#xff0c;读入两个正整数n和m&#xff08;1<n<m<…

《Spring安全配置》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【Python】简记操作:Centos安装、卸载、升级Python运行环境

目录 安装 1、选择合适自己的python版本 2、选择合适的目录进行指定版本源码下载 3、解压编译安装 解压 编译安装&#xff0c;完成即可执行python相关命令 测试是否已成功安装python 4、设置python的全局环境变量&#xff08;/etc/profile&#xff09; 设置环境变量 校…

RPC分布式网络通信框架项目

文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化&#xff0c;相比较于json&#xff0c;有哪些优点&#xff1f;环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…

【RabbitMQ 实战】08 集群原理剖析

上一节&#xff0c;我们用docker-compose搭建了一个RabbitMQ集群&#xff0c;这一节我们来分析一下集群的原理 一、基础概念 1.1 元数据 前面我们有介绍到 RabbitMQ 内部有各种基础构件&#xff0c;包括队列、交换器、绑定、虚拟主机等&#xff0c;他们组成了 AMQP 协议消息…

次方计数的拆贡献法(考虑组合意义)+限定类问题善用值域与位置进行ds:1006T3

对于多次方的计数问题可以考虑拆贡献。 题目问 ∣ S ∣ 3 |S|^3 ∣S∣3&#xff0c; ∣ S ∣ |S| ∣S∣ 表示选的点数。相当于在 ∣ S ∣ |S| ∣S∣ 中选了3次&#xff0c;也就是选了3个可相同的点。 先考虑3个不相同点的贡献&#xff0c;对应任意3个点&#xff0c;必然会对…

Go Gin Gorm Casbin权限管理实现 - 2. 使用Gorm存储Casbin权限配置以及`增删改查`

文章目录 0. 背景1. 准备工作2. 权限配置以及增删改查2.1 策略和组使用规范2.2 用户以及组关系的增删改查2.2.1 获取所有用户以及关联的角色2.2.2 角色组中添加用户2.2.3 角色组中删除用户 2.3 角色组权限的增删改查2.3.1 获取所有角色组权限2.3.2 创建角色组权限2.3.3 修改角色…