SpringMVC源码:视图解析器

news2024/9/29 17:22:06

参考资料:

《SpringMVC源码解析系列》

《SpringMVC源码分析》

《Spring MVC源码》

        写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

前文:

《SpringMVC源码:DispatcherServlet初始化流程》

《SpringMVC源码:HandlerMapping加载1》

《SpringMVC源码:HandlerMapping加载2》

《SpringMVC源码:getHandler、getHandlerAdapter过程》

《SpringMVC源码:参数解析、方法调用与返回值处理》

        前文我们已经介绍了,DispatcherServlet初始化时创建的handlermapping与HandlerAdapter组件,本文我们介绍另一个重要组件视图解析器ViewResolver。

目录

​一、视图的基本介绍

        1、initViewResolvers

        2、View与ViewResolver

        3、使用场景

二、视图解析器

        1、BeanNameViewResolver

        1.1、使用样例

       1.2、resolveViewName

        2、ContentNegotiatingViewResolver

        2.1、resolveViewName

        2.2、getMediaTypes

        2.3、getCandidateViews 

        3、AbstractCachingViewResolver

        3.1、resolveViewName

        3.2、UrlBasedViewResolver#createView

         3.3、buildView


​一、视图的基本介绍

        1、initViewResolvers

        过程和HandlerMapping的初始化很类似,一样是解析容器中的ViewResolver类型的Bean。

	private void initViewResolvers(ApplicationContext context) {
		this.viewResolvers = null;
		if (this.detectAllViewResolvers) {
			Map<String, ViewResolver> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.viewResolvers);
			}
		}
		else {
			try {
				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
				this.viewResolvers = Collections.singletonList(vr);
			}
			catch (NoSuchBeanDefinitionException ex) {
			}
		}
		if (this.viewResolvers == null) {
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

        2、View与ViewResolver

        View接口定义了视图的基本行为,其中最重要的就是渲染方法render,我们的视图实现类可以通过重写该方法实现我们所要的渲染结果,这一点我们下文将会展示。

        ViewResolver接口负责对处理器适配器获取到的对象进行解析获取对应的视图最终呈现给浏览器进行渲染。

public interface View {
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
	String getContentType();
	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

}

public interface ViewResolver {
	View resolveViewName(String viewName, Locale locale) throws Exception;
}

        ViewResolver组件是根据String类型的视图名和对应的Locale(语言环境 国际化相关)解析出View对象,而View对象则是用来将数据填充进来,并解析成对应的html(也可能是其他类型)类型的文件渲染给前台。View对象是根据不同的类型使用某些模块来渲染的,比如针对jsp类型使用JstlView进行渲染。

         可以看出ViewResolver主要有四种实现类,包括:

  • BeanNameViewResolver: 根据名字获取对应的视图对象
  • ContentNegituatingViewResolver: 根据请求的MediaTypes(context-type)来获取最佳的View对象
  • AbstractCachingViewResolver: 可以缓存解析过的视图对象的基类,亮点是视图解析后的缓存,实现了相关固定化的功能,提供变化的抽象功能交由子类实现,我们常使用的jsp相关的视图InternalResourceViewResolver,以及freeMarket模板语言的FreeMarkerViewResolver解析器。
  • ViewResolverComposite: 是包含如上各个ViewResolver的组合类

        3、使用场景

        在render方法中,判断ModelAndView对象是否拥有已经处理好了的视图对象,如果未包含的话就调用resolveViewName方法,遍历所有的视图处理器,找出支持解析的那一个并完成解析返回视图对象。

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		View view;
		if (mv.isReference()) {
            // We need to resolve the view name
			view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
		}
		else {
            // No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
		}
        // 其余代码
	}

	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

二、视图解析器

        1、BeanNameViewResolver

        1.1、使用样例

        要使用BeanNameViewResolver则需要实现自定义的View接口实现类的render方法,比如下面我们顶一个一个handler与View。


// 自定义的View对象
public class HelloView implements View {
    // 设置该视图的media类型
    @Override
    public String getContentType() {
        return "text/html";
    }
     //设置渲染方法
    @Override  
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("Welcome to hello View:"+new Date());
    }
}


@Controller
@RequestMapping(value = "/beanNameView")
public class BeanNameViewController {
    @RequestMapping(value = "hello", method = RequestMethod.GET)
    public String toHello(HttpServletRequest request){
        System.out.println("使用BeanNameViewResolver解析器 获取解析视图");
        //返回时String 类型的View 对象
        return "helloView";
    }
}

         另外进行视图处理器注册

<!--视图解析 -->
<!BeanNameViewResolver视图解析器 根据controller类的方法返回的String
 类型的viewName  作为bean的name从spring中获取 View对象调用其的render()方法 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
 
</bean>
<!--配置view对象-->
 <bean id="helloView" class="com.text.HelloView"/>

       1.2、resolveViewName

        当调用beanNameView/hello时,将会返回视图对象helloView,随后视图解析器BeanNameViewResolver就会进行解析,我们来看下源码。

    //BeanNameViewResolver.java
	@Override
	public View resolveViewName(String viewName, Locale locale) throws BeansException {
        // 获取容器
		ApplicationContext context = getApplicationContext();
		// 检查当前容器内有无
        if (!context.containsBean(viewName)) {
			if (logger.isDebugEnabled()) {
				logger.debug("No matching bean found for view name '" + viewName + "'");
			}
			return null;
		}
        // 判断该bean是否是视图类型
		if (!context.isTypeMatch(viewName, View.class)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found matching bean for view name '" + viewName +
						"' - to be ignored since it does not implement View");
			}
			return null;
		}
        // 返回视图对象
		return context.getBean(viewName, View.class);
	}

        可以非常明确的看出这里是根据返回的ViewName从spring环境中获取View对象返回。

        2、ContentNegotiatingViewResolver

        2.1、resolveViewName

        我们直接从源码看起,ContentNegotiatingViewResolver首先获取请求的属性,并解析其中的MediaType,获取符合如上mediaType的所有视图view对象,最后根据条件获取到最佳匹配的View对象返回。

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
        //从请求上下文中获取ServletRequestAttributes对象(封装过的ThreadLocal,其中保存了每次请求的HttpServletRequest对象)
        //只有是该HttpServletRequest 才能从其中获取到对应的mediaType类型
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        //根据HttpServletRequest解析其中的对应的media_type 和我们在xml中的配置有关
        // 比如配置favorParameter 为true则才可以使用xxxx?format=xml的形式 下面单独讲解
        //这一步获取到了请求的mediaType(可使用的)
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
            //获取符合如上mediaType的所有视图view对象 获取视图对象可以从spring注册的ViewResolver 组件解析
            //也可以使用defaultViews配置的默认视图对象(先使用ViewResolver在使用defaultViews)
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
            //根据条件获取到最佳匹配的View对象返回
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}
		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("No acceptable view found; returning null");
			return null;
		}
	}

        2.2、getMediaTypes

        这里主要是两个操作,一个是根据请求解析出其可接受mediaType,另一个是获取请求本身带有的meidaType属性信息,两者兼容并按照优先级排序 。

        主要起作用的是ContentNegotiationManager(这个类中的一些参数也是我们在xml配置的,我们用的不是该类本身而是其对应的FactoryBean对象)和ContentNegotiationStrategy类(按照url后缀、参数、请求头等策略)

	protected List<MediaType> getMediaTypes(HttpServletRequest request) {
		try {
			ServletWebRequest webRequest = new ServletWebRequest(request);
            //解析请求可以接受的mediaType
			List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
			acceptableMediaTypes = (!acceptableMediaTypes.isEmpty() ? acceptableMediaTypes :
					Collections.singletonList(MediaType.ALL));
            //获取请求本身的mediaType
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
            //对上述解析出来的mediaType 进行兼容处理
			Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
			for (MediaType acceptable : acceptableMediaTypes) {
				for (MediaType producible : producibleMediaTypes) {
					if (acceptable.isCompatibleWith(producible)) {
						compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
					}
				}
			}
            //获取到的最终mediaType按照优先级排序
			List<MediaType> selectedMediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
			MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
			if (logger.isDebugEnabled()) {
				logger.debug("Requested media types are " + selectedMediaTypes + " based on Accept header types " +
						"and producible media types " + producibleMediaTypes + ")");
			}
			return selectedMediaTypes;
		}
		catch (HttpMediaTypeNotAcceptableException ex) {
			return null;
		}
	}

        在该方法中ContentNegotiationStrategy代表不同的解析策略 

	@Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
				continue;
			}
			return mediaTypes;
		}
		return Collections.emptyList();
	}

        2.3、getCandidateViews 

        先交由注册在spring中的ViewResolver去解析对应的view,如果没有在从默认的views对象defauleViews集合中获取视图。

	private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<View>();
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				candidateViews.add(view);
			}
			for (MediaType requestedMediaType : requestedMediaTypes) {
				List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
				for (String extension : extensions) {
					String viewNameWithExtension = viewName + '.' + extension;
					view = viewResolver.resolveViewName(viewNameWithExtension, locale);
					if (view != null) {
						candidateViews.add(view);
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}

        3、AbstractCachingViewResolver

        3.1、resolveViewName

        该方法可以对解析过的视图对象进行缓存,下面我们看下其实现原理

	private static final View UNRESOLVED_VIEW = new View() {
		@Override
		public String getContentType() {
			return null;
		}
		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
		}
	};

    // 默认1024
	private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;

	public boolean isCache() {
		return (this.cacheLimit > 0);
	}

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
        //判断是否对解析出来的View对象(视图对象) 进行缓存
        //判断逻辑是其cacheLimit(允许缓存的视图对象个数)默认是1024 默认是走缓存 可以更改为不走缓存
		if (!isCache()) {
            //创建View
			return createView(viewName, locale);
		}
		else {
            //先尝试从缓存中获取
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
                        // 缓存没有则进行创建工作
						view = createView(viewName, locale);
						// view 为空则返回默认的视图
                        if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
                        //创建出来的View对象放入缓存中 并返回View对象
                        //下次获取直接从缓存中获取
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) {
								logger.trace("Cached view [" + cacheKey + "]");
							}
						}
					}
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

        该先判断是否走缓存,不走缓存直接创建View对象,否则走缓存,先从缓存中获取,缓存中没有则执行创建View对象并放入缓存操作,真正创建View对象的方法为 createView()。

        该类使用了两个Map作为缓存,第一个map是支持并发获取的ConcurrentHashMap,第二个map是能提供对于缓存溢出时候的清楚工作(LinkedHashMap的removeEldestEntry方法)。

        3.2、UrlBasedViewResolver#createView

        在createView()方法中主要是针对转发、重定向、普通请求进行View的实例化和初始化

	protected boolean canHandle(String viewName, Locale locale) {
		String[] viewNames = getViewNames();
		return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
	}

	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
        //检查该视图解析器是否可以解析该viewName对应的视图
        //可以通过配置viewNames让其与viewName进行匹配判断是否能够解析
		if (!canHandle(viewName, locale)) {
			return null;
		}
        //对于重定向请求的处理
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            //创建对应的View对象
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			view.setHosts(getRedirectHosts());
            //对实例化后的View进行属性初始化 调用BeanFactory的initializeBean()方法
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}
        //对于转发请求的处理
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            //获取重定向的url
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            //创建对应的View对象
			return new InternalResourceView(forwardUrl);
		}
        // 其他情况 普通请求 调用父类createView 最终调用子类的loadView()方法
		return super.createView(viewName, locale);
	}

         3.3、UrlBasedViewResolver#buildView

        上文中的createView方法会调用父类AbstractCachingViewResolver中的实现,但内部是调用了loadview方法,而子类UrlBasedViewResolver对其进行了重写。

    //AbstractCachingViewResolver.java
	protected View createView(String viewName, Locale locale) throws Exception {
		return loadView(viewName, locale);
	}

    //UrlBasedViewResolver.java
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
        //调用buildView创建视图对象
		AbstractUrlBasedView view = buildView(viewName);
        //对实例化后的View进行属性初始化
		View result = applyLifecycleMethods(viewName, view);
		return (view.checkResource(locale) ? result : null);
	}

         该方法获取viewClass通过反射形式进行实例化,并设置url、contextType、requestContextAttribute 等属性。

	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        //获取View对应的class (UrlBasedViewResolver子类中每一个类型都有只处理一个View对象)
        //比如FreeMarkerViewResolver 的viewClass 为FreeMarkerView
        //InternalResourceViewResolver的viewClass为JstlView
        //获取view class 并进行实例化
		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
        //设置view的url
		view.setUrl(getPrefix() + viewName + getSuffix());
        //设置view的contentType
		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}
        //设置view的requestContextAttribute 用于持有request对象的beanName
		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());
        //是否支持view使用PathVariables(url中的参数)
		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
        //是否支持view可以使用spring容器中的bean实例
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
        //是否配置view可以使用spring容器中的那些bean实例
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

        到这里视图解析器基本介绍完了,下文我们会介绍DispatcherServlet是如何使用视图解析器工作的。

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

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

相关文章

APP线上产品的日志埋点方案

运营运维系列文章 APP线上产品的日志埋点方案 APP日志埋点前言什么是埋点&#xff1f;埋点方案设计事件模型埋点事件上报日志存储平台1. 亚马逊云S32. Kibana博客创建时间&#xff1a;2023.03.08 博客更新时间&#xff1a;2023.03.09 以Android studio build7.0.0&#xff0c…

git stash 暂存减少分支误操作的神器

背景 有时不小心在master或者develop分支上开发了代码&#xff0c;正要提交时才发现自己选错分支了&#xff0c;以前的笨方法是把要提交的代码&#xff0c;一个个记录下来&#xff0c;都保存另外一个文件中去&#xff0c;然后再切换到特性分支中&#xff0c;一个个覆盖到具体的…

问到ThreadLocal,看这一篇就够了|金三银四系列

ThreadLocal 原理和常见问题详解&#xff0c;用来复习准没错&#xff01;点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达ThreadLocal 是什么&#xff1f;ThreadLocal 是线程本地变量。当使用 ThreadLocal 维护变量时&#xff0c;ThreadLo…

高效学 C++|组合类的构造函数

设计好MyString类后&#xff0c;就可以像使用普通类型一样使用它了。例如&#xff0c;类的对象可以像普通的变量一样作为另一个类的数据成员。【例1】 MyString类的对象作为CStudent类的数据成员。1. //MyString类的定义省略 2. //注意&#xff1a;保留其构造函数、析构函数、…

LeetCode——2379. 得到 K 个黑块的最少涂色次数

一、题目 给你一个长度为 n 下标从 0 开始的字符串 blocks &#xff0c;blocks[i] 要么是 ‘W’ 要么是 ‘B’ &#xff0c;表示第 i 块的颜色。字符 ‘W’ 和 ‘B’ 分别表示白色和黑色。 给你一个整数 k &#xff0c;表示想要 连续 黑色块的数目。 每一次操作中&#xff0…

Java 中的拆箱和装箱

在 Java 中&#xff0c;每个基本数据类型都对应了一个包装类型&#xff0c;比如&#xff1a;int 的包装类型是 Integer&#xff0c;double 的包装类型是 Double…那么&#xff0c;基本数据类型和包装类型有什么区别呢&#xff1f; 大概有以下几点区别&#xff1a; 成员变量的…

详解Vue安装与配置(2023)

文章目录一、官网下载node.js二、安装Node.js三、环境配置四、idea导入vue项目五、IDEA添加Vue.js插件一、官网下载node.js Vue是前端开发框架。搭建框架&#xff0c;首先要搭建环境。搭建Vue的环境工具&#xff1a;node.js&#xff08;JavaScript的运行环境&#xff09;&…

【最重要的 G 代码命令列表】

【最重要的 G 代码命令列表】1. 什么是G代码&#xff1f;2. 如何阅读G代码命令&#xff1f;3. 最重要/最常见的 G 代码命令3.1 G00 – 快速定位3.2 G01 – 线性插值3.3 G02 – 顺时针圆形插值3.4 G00、G01、G02 示例 – 手动 G 代码编程3.4 G03 – 逆时针圆形插补3.5 G20/ G21 …

【Unity游戏破解】外挂原理分析

文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cpp dumper例子…

yolov5双目检测车辆识别(2023年+单目+双目+python源码+毕业设计)

行人识别yolov5和v7对比yolo车距源码:yolov5双目检测车辆识别(2023年单目双目python源码毕业设计)上盒岛APP&#xff0c;开线上盲盒商店http://www.hedaoapp.com/yunPC/goodsDetails?pid4132 为了提高传统遗传算法(genetic algorithm, GA)IGA优化BP网络迭代时间过长以及精度偏…

ArrayList与LinkedList的区别 以及 链表理解

list接口中ArrayList、LinkedList都不是线程安全&#xff0c;Vector是线程安全 1、数据结构不同 ArrayList是Array(动态数组)的数据结构&#xff0c;LinkedList是Link(链表)双向链表的数据结构。 2、空间灵活性 ArrayList最好指定初始容量 LinkedList是比ArrayList灵活的&a…

Noah-MP陆面过程模型建模

【方式】&#xff1a;直播永久回放长期答疑群辅助全套资料【目标】&#xff1a;了解陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用&#xff1b;熟悉模型的发展历程&#xff0c;常见模型及各自特点&#xff1b;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP…

用Python优雅地求解阿基米德分牛问题

文章目录题目大意sympy求解结果题目大意 问 太阳神有一牛群&#xff0c;由白、黑、花、棕四种颜色的公、母牛组成&#xff0c;其间关系如下&#xff0c;求每种牛的个数。 公牛中&#xff0c;白牛多于棕牛&#xff0c;二者之差为黑牛的1213\frac{1}{2}\frac{1}{3}21​31​&…

【Redis】搭建分片集群

目录 集群结构 准备实例和配置 启动 创建集群 测试 集群结构 分片集群需要的节点数量较多&#xff0c;这里我们搭建一个最小的分片集群&#xff0c;包含3个master节点&#xff0c;每个 master包含一个slave节点&#xff0c;结构如下&#xff1a; 这里我们会在同一台虚…

超详细CentOS7 NAT模式(无图形化界面即最小安装)网络配置

在此附上CentOS7&#xff08;无图形化界面最小安装&#xff09;安装教程 超详细VMware CentOS7&#xff08;无图形化界面最小安装&#xff09;安装教程 打开VMware—>点击编辑---->选择虚拟网络编辑器 打开虚拟网络编辑器后如下图所示&#xff1a; 从下图中我们看到标…

由Deep InfoMax开始对比学习

作者&#xff1a;KON 来源&#xff1a;投稿 编辑&#xff1a;学姐 作者介绍&#xff1a;Kon 擅长是自然语言处理、推荐系统&#xff0c;爱好是cv&#xff1b;著有cv相关专利一篇&#xff0c;西安交通大学软件专业本硕。 1.前言 本次给大家带来的是发表在「ICLR2019」上的一篇…

10Wqps评论中台,如何架构?B站是这么做的!!!

说在前面 在尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常遇到一个 非常、非常高频的一个面试题&#xff0c;但是很不好回答&#xff0c;类似如下&#xff1a; 千万级数据&#xff0c;如何做系统架构&#xff1f;亿级数据&#xff0c;如何做系统架构&#xff1…

阿里云服务器使用教程:使用xshell、xFtp工具连接阿里云服务器(Centos7)并修改Centos7的yum源为阿里镜像源

目录 1、下载并安装xshell、xFtp 2、远程连接阿里云服务器 3、 修改Centos7的yum源为阿里镜像源 1、下载并安装xshell、xFtp XShell可以在Windows界面下来访问远端不同系统下的服务器&#xff0c;从而比较好的达到远程控制终端的目的。它支持 RLOGIN、SFTP、SERIAL、TELNET、…

STM32中断分组配置NVIC_PriorityGroup,移植操作系统需需注意NVIC_PriorityGroup_4

一、先说明中断分组的由来中断优先级分组表&#xff1a;优先级分组抢占优先级响应优先级bit[7:4] 分配情况备注NVIC_PriorityGroup_0取值&#xff1a;0取值&#xff1a;0~150:40bit抢占优先级、4bit响应优先级NVIC_PriorityGroup_1取值&#xff1a;0~1取值&#xff1a;0~71:31b…

关于热力图展示大量数据点耗时导致浏览器崩溃问题及解决方案

目录 问题描述 问题分析 解决方案 问题描述&#xff1a; Web前端在地图上加载空间数据库里存储的地块中心点时因为数据点太多从而导致页面崩溃。Mybatis查询大量数据时&#xff0c;耗时时间更长是主要原因。8万多条数据&#xff0c;数据库查询最慢0.6s, Mybatis查询结果需要…