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

做建筑材料哪个网站好一点/十大外贸平台

做建筑材料哪个网站好一点,十大外贸平台,佛山市平台购物网站制作公司,韩国做色情网站违法不a、SpringMVC 在启动过程中主要做了什么事情&#xff1f; SpringMVC在启动过程中是什么时候解析web.xml文件的&#xff0c;又是什么时候初始化9大内置对象的&#xff1f; <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xml…

a、SpringMVC 在启动过程中主要做了什么事情?

SpringMVC在启动过程中是什么时候解析web.xml文件的,又是什么时候初始化9大内置对象的?

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"><!--Spring配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--Spring监听器--><listener><listener-class> org.springframework.web.context.ContextLoaderListener </listener-class></listener><!--SpringMVC前端控制器--><servlet><servlet-name> SpringMVC </servlet-name><servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class><!-- 配置springMVC需要加载的配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/springMVC.xml</param-value></init-param><load-on-startup>1</load-on-startup><async-supported>true</async-supported></servlet><servlet-mapping><servlet-name>SpringMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

b、父子容器的概念

在Spring MVC中,有两个容器:父容器(Root WebApplicationContext)和子容器(Servlet WebApplicationContext)。

父容器(Root WebApplicationContext)

主要负责加载应用程序中的共享bean和配置。它通常包含以下内容:
  1. 数据源和事务管理器:用于处理数据库操作和事务管理。
  2. 持久化层(DAO)和业务层(Service)的bean:用于处理数据访问和业务逻辑。
  3. 安全配置:用于配置安全相关的bean,如认证和授权配置。
  4. 缓存配置:用于配置缓存相关的bean,如缓存管理器和缓存注解处理器。
  5. 其他共享bean:例如,公共工具类、全局异常处理器等。

子容器(Servlet WebApplicationContext)

主要负责加载与Web应用程序相关的bean和配置。它通常包含以下内容:
  1. 控制器(Controller)和视图解析器(ViewResolver):用于处理请求和生成响应。
  2. Web相关的bean:例如,处理文件上传的MultipartResolver、处理静态资源的ResourceHandler等。
  3. Web安全配置:用于配置Web安全相关的bean,如Spring Security配置。
  4. 其他与Web应用程序相关的bean:例如,国际化资源处理器、消息转换器等。

Spring本身的ApplicationContext属于父容器(Root WebApplicationContext),它通常包含整个应用程序范围内的bean和配置。这包括父容器中的共享bean,以及子容器中的bean。在Spring MVC中,通常使用ContextLoaderListener来加载父容器的ApplicationContext。

总结起来,父容器(Root WebApplicationContext)包含应用程序范围内的共享bean和配置,子容器(Servlet WebApplicationContext)包含与Web应用程序相关的bean和配置,而Spring本身的ApplicationContext属于父容器,包含整个应用程序的bean和配置。
Spring 父子容器是指 Spring 容器的层次结构,其中一个容器作为另一个容器的父容器。父容器可以提供资源和 bean 给子容器,子容器可以覆盖或扩展父容器的 bean 定义。

这种父子关系在 Spring Web 应用中非常常见。通常情况下,在 Web 应用中会有两个 Spring 容器:Root WebApplicationContext 和 Servlet WebApplicationContext。

c、启动过程中两个关键类

ContextLoaderListener和DispatcherServlet

Tomcat启动时候会先创建servlet容器,然后解析web.xml。
1、首先加载conf/web.xml文件,然后加载web应用程序中的WEB-INF/web.xml文件。
2、按照以下顺序读取和处理web.xml中的元素:context-param -> listener -> filter -> servlet。

context-param元素用于向ServletContext提供键值对,即应用程序上下文信息,可以写在任意位置。
servlet元素用于定义servlet的名字和类,servlet-mapping元素用于指定访问servlet的URL,servlet-mapping必须出现在servlet之后,servlet的初始化顺序按照load-on-startup元素指定的值,如果值为正数或零,则按照从小到大的顺序初始化,如果值为负数或未定义,则在第一次请求时初始化。

先解析context-param,然后创建ServletContext对象,并将context-param转换为键值对交给ServletContext,然后再解析listener-class,并创建监听器实例。如果监听器类实现了ServletContextListener接口,那么它的contextInitialized(ServletContextEvent sce)方法和contextDestroyed(ServletContextEvent sce)方法会在ServletContext对象创建和销毁时被调用,这两个方法的参数sce可以通过getServletContext()方法获取ServletContext对象。

1、ContextLoaderListener

ContextLoaderListener实现了Servlet规范中的javax.servlet.ServletContextListener,WEB容器在启动过程中即ServletContext对象创建时会回调ServletContextListener.contextInitialized(), 同理在销毁的时候会回调contextDestroyed(ServletContextEvent sce)

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener(WebApplicationContext context) {super(context);}@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}
} 

调用contextInitialized()

创建WebApplicationContext,设置必要的参数,如配置文件位置contextConfigLocation。
调用refresh()。在这个过程中,XmlWebApplicationContext 会解析 XML 配置文件,加载 bean 定义,并创建并初始化所有的 bean。将这个容器当作父容器存到servletContext里边。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {/*** 首先通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个String类型的静态变量获取一个父容器* 父容器作为全局变量存储在application对象中,如果存在则有且只能有一个* 如果在初始化父容器时发现已经存在则直接抛出异常* 这个值在下面的代码中会放入application中*/if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// context代表父容器,已经有值了// xml会在这里创建if (this.context == null) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {if (cwac.getParent() == null) {ApplicationContext parent = loadParentContext(servletContext); // nullcwac.setParent(parent);}// 创建spring容器,调用refresh()方法完成父容器的初始化configureAndRefreshWebApplicationContext(cwac, servletContext);}}// 将父容器放入到了servletContext中//在servlet域中设置根容器(在子容器就可以直接拿到了)servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException | Error ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}
}

2、DispatcherServlet

当创建完成父容器之后,就开始创建子容器。解析servlet标签,即DispatcherServlet。
1、在web.xml文件中配置dispatcherServlet的servlet-name,servlet-class,init-param和load-on-startup等信息。
2、会根据load-on-startup的值,按照从小到大的顺序初始化servlet实例,并调用它们的init()方法

先看一下DispatcherServlet的继承关系:
在这里插入图片描述
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup

public void onStartup(ServletContext servletContext) throws ServletException {super.onStartup(servletContext); // 创建ContextLoaderListenerregisterDispatcherServlet(servletContext); // 创建DispatcherServlet
}protected void registerDispatcherServlet(ServletContext servletContext) {String servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return null or empty");WebApplicationContext servletAppContext = createServletApplicationContext(); // 子容器Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");// 创建DispatcherServletFrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // nullServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);if (registration == null) {throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +"Check if there is another servlet registered under the same name.");}registration.setLoadOnStartup(1);registration.addMapping(getServletMappings()); // 处理哪些映射registration.setAsyncSupported(isAsyncSupported()); // 默认为true,支持异步Filter[] filters = getServletFilters(); // 过滤器if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {// 添加filter到servlet容器中registerServletFilter(servletContext, filter);}}customizeRegistration(registration);
}protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {return new DispatcherServlet(servletAppContext);
}

1. HttpServletBean#init()

WEB容器启动后调用Servlet的init()方法进行初始化,此方法的实现实在父类HttpServletBean中:
org.springframework.web.servlet.HttpServletBean#init

@Override
public final void init() throws ServletException {// 解析 init-param 并封装只 pvs 中(xml)PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {// 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);// 修改servlet状态,并将pvs里边的值赋值给servletbw.setPropertyValues(pvs, true);}// 初始化Servlet,创建Spring容器initServletBean();
}

2. FrameworkServlet#initServletBean()

protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {// 对子容器进行初始化this.webApplicationContext = initWebApplicationContext();initFrameworkServlet(); // NOP}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}
}

3. FrameworkServlet#initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {// 从ServletContext中获取父容器WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext; // 子容器if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext); // 父子间建立关系}// 调用refresh()方法对子容器进行初始化configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) { // 不会进入wac = findWebApplicationContext();}if (wac == null) { // 不会进入wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) { // refreshEventReceived=true不会进入synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {String attrName = getServletContextAttributeName();// 将子容器也放入到ServletContext中getServletContext().setAttribute(attrName, wac);}return wac;
}

4. FrameworkServlet#configureAndRefreshWebApplicationContext()

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());// 添加了一个监听子容器刷新的事件wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}postProcessWebApplicationContext(wac); // NOPapplyInitializers(wac);// invoke refresh methodwac.refresh();
}

在子容器初始化之前添加了一个监听容器刷新的事件ContextRefreshListener,当容器刷新完成后将会调用ContextRefreshListener.onApplicationEvent()方法。发布容器刷新事件ContextRefreshedEvent,最终会再刷新事件处理器中调用FrameworkServlet.onApplicationEvent()。

5. FrameworkServlet.onApplicationEvent()

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {FrameworkServlet.this.onApplicationEvent(event);}
}

6. FrameworkServlet#onApplicationEvent()

public void onApplicationEvent(ContextRefreshedEvent event) {this.refreshEventReceived = true;synchronized (this.onRefreshMonitor) {onRefresh(event.getApplicationContext());}
}

7. DispatcherServlet#onRefresh()

protected void onRefresh(ApplicationContext context) {initStrategies(context);
}protected void initStrategies(ApplicationContext context) {// 初始化用于处理文件上传的MultipartResolver,即:从IOC中获取名称为“multipartResolver”的beaninitMultipartResolver(context); // 初始化用于国际化的LocaleResolver(从IOC中获取名称为“localeResolver”的bean)initLocaleResolver(context);// 初始化用于主题风格的ThemeResolver(从IOC中获取名称为“themeResolver”的bean)initThemeResolver(context);// 初始化用于处理Request请求及流转的HandlerMapping,根据变量”detectAllHandlerMappings“,有如下3种获取方式://   方式1:获取IOC中所有类型为HandlerMapping的bean集合//   方式2:获取名称为“handlerMapping”的bean//   方式3:获取DispatcherServlet.properties文件中配置的HandlerMapping的bean列表initHandlerMappings(context);// 初始化用于进行请求处理的HandlerAdapter,根据变量”detectAllHandlerAdapters“,有如下3种获取方式://   方式1:获取IOC中所有类型为HandlerAdapter的bean集合//   方式2:获取名称为“handlerAdapter”的bean//   方式3:获取DispatcherServlet.properties文件中配置的HandlerAdapter的bean列表initHandlerAdapters(context);// 初始化用于对异常的类型进行处理并生成ModelAndView的HandlerExceptionResolver,根据变量”detectAllHandlerExceptionResolvers“,有如下3种获取方式://   方式1:获取IOC中所有类型为HandlerExceptionResolver的bean集合//   方式2:获取名称为“handlerExceptionResolver”的bean//   方式3:获取DispatcherServlet.properties文件中配置的HandlerExceptionResolver的bean列表initHandlerExceptionResolvers(context);// 初始化用于获取“逻辑视图名称”的RequestToViewNameTranslator(从IOC中获取名称为“viewNameTranslator”的bean)initRequestToViewNameTranslator(context);// 初始化用于创建View对象的ViewResolver,根据变量”viewResolver“,有如下3种获取方式://   方式1:获取IOC中所有类型为ViewResolver的bean集合//   方式2:获取名称为“viewResolver”的bean//   方式3:获取DispatcherServlet.properties文件中配置的ViewResolver的bean列表initViewResolvers(context);// 初始化用于管理FlashMap的FlashMapManager(从IOC中获取名称为“flashMapManager”的bean)initFlashMapManager(context);
}

8、总结

如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中;
然后会加载DispatcherServlet,因为DispatcherServlet实质是一个Servlet,所以会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

3、DispatcherServlet.properties

DispatcherServlet中默认的组件

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

4、SpringMVC中的9大内置组件初始化

4.1 初始化文件解析器initMultipartResolver()

初始化multipart解析器的, MultipartResolver是一个接口,在Web开发中,multipart解析器主要用于处理HTTP请求中的multipart/form-data类型的数据,这种数据类型通常用于文件上传。

SpringMVC提供了两种multipart解析器:

1、CommonsMultipartResolver:基于Apache Commons FileUpload库实现的multipart解析器。
2、StandardServletMultipartResolver:基于Servlet 3.0规范实现的multipart解析器。

在Spring MVC中,如果你定义了一个名为"multipartResolver"的bean,那么Spring MVC就会使用你定义的这个bean作为multipart解析器。否则,Spring MVC就不会处理multipart/form-data类型的请求。
LOCALE_RESOLVER_BEAN_NAME = “localeResolver”;

1、一种是我们自定义上传文件解析器

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置最大上传文件为5MB --><property name="maxUploadSize" value="5242880"/>
</bean>或者--------------@Bean
public CommonsMultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setMaxUploadSize(5242880);return multipartResolver;
}

2、默认文件上传解析器

如果我们没有自定义上传文件的bean,那么就会利用spi去配置文件里边取值,然后加载到子容器中。

// 通过PropertiesLoaderUtils工具类加载DispatcherServlet.properties
//DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
//spi
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

在这里插入图片描述

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

4.2 初始化国际化应用initLocaleResolver()

初始化 DispatcherServlet 中的 LocaleResolver。在Spring MVC中,LocaleResolver是一个接口,它定义了如何解析客户端的地区信息。地区信息通常被用于国际化应用,可以根据不同的地区显示不同的信息。
该方法的主要逻辑如下:
首先,尝试从Spring上下文中获取名为localeResolver的bean,如果获取到了,就使用该bean作为地区解析器。
如果没有获取到,那么就会使用getDefaultStrategy方法来获取一个默认的地区解析器。具体的实现类是在DispatcherServlet.properties中配置的和上边的一样也是利用SPI技术。

在Spring MVC中,有几个预定义的LocaleResolver的实现:

  1. AcceptHeaderLocaleResolver: 根据HTTP的Accept-Language头来解析地区信息。
  2. FixedLocaleResolver: 提供固定的地区信息,无论请求是什么。
  3. CookieLocaleResolver: 将地区信息保存在浏览器的Cookie中。
  4. SessionLocaleResolver: 将地区信息保存在HTTP Session中。
  5. 如果我们没有自己配置LocaleResolver的Bean,Spring
    MVC将默认使用AcceptHeaderLocaleResolver。
private void initLocaleResolver(ApplicationContext context) {try {this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);catch (NoSuchBeanDefinitionException ex) {this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);}
}

4.3 初始化主题initThemeResolver()

初始化 ThemeResolver 的方法。ThemeResolver 是一个接口,定义了如何解析应用程序的主题的规则。主题是Spring MVC中用于改变视图层外观的一种机制,例如颜色、CSS样式等。在Spring MVC中,可以为每个用户设置不同的主题,或者为所有用户设置一个统一的主题。
具体查找和上边一样,查找的是themeResolverbean,默认实现的是FixedThemeResolver,保存到themeResolver 。

4.4 初始化HandlerMapping()

初始化处理器映射(Handler Mapping),这里也是和上边一样如果我们自己定义HandlerMapping,就用我们自己定义的,我们可以定义多个,如果没有自定义用默认的,默认有三个实现。

4.4.1 自定义实现HandlerMapping

实现HandlerMapping接口或者继承AbstractHandlerMapping。创建一个CustomHandlerMapping类,它继承自AbstractHandlerMapping。
然后,在Spring MVC配置中注册这个自定义的HandlerMapping。当一个请求的URL路径为"/custom"时,CustomHandlerMapping就会返回CustomHandler作为处理器。
自定义的HandlerMapping与Spring MVC默认的HandlerMapping(比如RequestMappingHandlerMapping)是可以共存的(需要手动注入了,因为有默认的了,就不加载默认的了)。当一个请求到来时,Spring MVC会按照HandlerMapping的顺序来查找处理器。你可以通过实现Ordered接口或者使用@Order注解来调整HandlerMapping的顺序。

public class CustomHandlerMapping extends AbstractHandlerMapping {//保存映射关系Map<String, Object> pathHandlers = new HashMap<>();public CustomHandlerMapping() {// 添加一些自定义的映射// 此处仅作示例,一般我们不会在构造函数中添加映射// 更常见的是在配置类中或者通过其他方式动态添加映射this.pathHandlers.put("/custom", new CustomHandler());}@Overrideprotected Object getHandlerInternal(HttpServletRequest request) throws Exception {// 根据请求的URL路径查找对应的处理器String path = request.getServletPath();return this.pathHandlers.get(path);}
}class CustomHandler {// 这里可以添加自定义的处理器方法
}

4.4.2 默认实现BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping:这个处理器映射使用 Spring bean 的名称作为 URL
来映射请求。例如,一个名为 “/test” 的 bean 将会处理来自 “/test” URL
的请求。这种方式在实际开发中用的比较少,因为它不够灵活且易于混淆。

BeanNameUrlHandlerMapping的寻找流程:
1、找出Spring容器中所有的beanName
2、判断beanName是不是以“/”开头
3、如果是,则把它当作一个Handler,并把beanName作为key,bean对象作为value存入handlerMap中,handlerMap就是一个Map

4.4.3 默认实现SimpleUrlHandlerMapping

SimpleUrlHandlerMapping:这个处理器映射允许我们显式地指定 URL与处理器之间的映射关系。然后将handlerMapping都保存到handlerMappings集合中。

4.4.4 默认实现 RequestMappingHandlerMapping

RequestMappingHandlerMapping:这个是最常用的处理器映射。它会扫描 Spring容器中的所有控制器(Controller),找出带有 @RequestMapping 注解的方法,然后根据注解的参数(例如,HTTP方法、URL 等)来建立请求与方法之间的映射。在处理 HTTP 请求时,它会找到与请求参数匹配的那个方法来处理请求。

RequestMappingHandlerMapping查找handler流程

默认得处理器映射(Handler Mapping)是spring创建的,是经过了bean得生命周期得,也就是通过getBean() -> createBean()那一套流程得,RequestMappingHandlerMapping类图:

在这里插入图片描述
RequestMappingHandlerMapping实现了InitializingBean,在初始化时候会执行afterPropertiesSet方法然后调用initHandlerMethods方法,这个方法会去解析所有的handler。
在 Spring MVC 中,RequestMappingHandlerMapping 是负责处理标有 @RequestMapping 注解的 Controller 的。它的工作流程如下:
1、当 Spring 容器启动的时候,RequestMappingHandlerMapping 会进行初始化,在初始化的过程中,afterPropertiesSet 方法会被调用。这个方法会进一步调用 initHandlerMethods 方法。

2、在 initHandlerMethods 方法中,会获取Spring 容器中所有的Bean。对于获取到的每一个 Bean,RequestMappingHandlerMapping 都会检查它是否标有 @Controller 或者 @RequestMapping 注解。只有标有这些注解的 Bean 才会被认为是一个有效的 Controller。

3、如果一个 Bean 被认为是一个有效的 Controller,那么 RequestMappingHandlerMapping 会进一步检查这个 Controller 中的所有方法。对于每一个方法,如果它上面有 @RequestMapping 注解,那么 RequestMappingHandlerMapping 会根据这个注解以及它所在的 Controller 的信息,创建一个 RequestMappingInfo 对象。

4、RequestMappingInfo 对象包含了请求的 URL、请求的方法、请求的参数等信息。这个对象会被用来与进来的 HTTP 请求进行匹配,以决定哪一个方法应该被用来处理这个请求。

5、最后将保存k-v,将path-List保存到pathLookup map中,为什么是list,因为相同的路径有get、post区分。将 RequestMappingInfo-headlMethod保存到registry map中。

创建handlerMethod
当解析到带有@RequestMapping注解的方法时,会创建一个HandlerMethod对象,该对象包含了方法的相关信息,如所属的类、方法名、参数列表等在这里插入图片描述
构建RequestMappingInfo:
代表了一个请求路径和请求方法的映射关系
在这里插入图片描述

4.5 初始化handler适配器initHandlerAdapters()

处理器适配器(HandlerAdapter)负责调用处理器(Handler)的处理方法,最后的方法都封装成了handler, 不同的handler有不同的handlerAdapter来负责调用。
首先从应用程序上下文中获取所有类型为HandlerAdapter的bean。如果找不到任何HandlerAdapter的bean,那么它会使用一组默认的处理器适配器。这组默认的处理器适配器包括RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter、HandlerFunctionAdapter,并将其保存到handlerAdapters集合中。

适配逻辑:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {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");
}

4.5.1 自定义HandlerAdapter

当我们自定义了handlerMapping就需要自定义HandlerAdapter来对他的handler进行匹配,当然,也可以利用默认的。

4.5.2 HttpRequestHandlerAdapter

HttpRequestHandlerAdapter:这个类是用于处理实现了HttpRequestHandler接口的类,这个接口只有一个方法handleRequest,用于直接处理请求和响应。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并将返回值封装成一个ModelAndView对象。

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);return null;
}

4.5.3 SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter:这个类是用于处理实现了Controller接口的类,这个接口也只有一个方法handleRequest,用于返回一个ModelAndView对象。它会将HttpServletRequest和HttpServletResponse对象作为参数传递给handleRequest方法,并直接返回其返回值。

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);
}

4.5.4 HandlerFunctionAdapter

HandlerFunctionAdapter:这个类是用于处理实现了HandlerFunction接口的类,这个接口是一个函数式接口,用于定义一个函数,接受一个ServerRequest对象并返回一个Mono对象。它会将HttpServletRequest和HttpServletResponse对象转换成ServerRequest和ServerResponse对象,并调用apply方法来执行函数,并将返回值转换成一个ModelAndView对象。

HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);

上面这几个接收的直接就是Requeset对象,不用SpringMVC做额外的解析,所以比较简单,比较复杂的是RequestMappingHandlerAdapter,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。

4.5.5 RequestMappingHandlerAdapter

RequestMappingHandlerAdapter:这个类是用于处理带有@RequestMapping注解的方法。它会根据请求的参数,头部,属性等来解析方法的参数,并调用反射来执行方法。
此处比较复杂,后续单独说明

4.6 初始化异常initHandlerExceptionResolvers()

负责将在处理器处理过程中抛出的异常转换为对应的模型-视图结果。
在initHandlerExceptionResolvers()方法中,获取所有类型为HandlerExceptionResolver的Bean。如果找不到任何HandlerExceptionResolver的Bean,那么它会使用一组默认的处理器异常解析器。
这里的默认是会取读取DispatcherServlet.properties

在这里插入图片描述

private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {HandlerExceptionResolver her =context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found.if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

4.6.0 DispatcherServlet.properties

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

4.6.1 ExceptionHandlerExceptionResolver

用于处理通过@ExceptionHandler注解处理的方法抛出的异常。

4.6.2 ResponseStatusExceptionResolver

用于处理通过@ResponseStatus注解处理的异常

4.6.3 DefaultHandlerExceptionResolver

用于处理Spring MVC自己抛出的一些特定的异常

4.7 initRequestToViewNameTranslator

initRequestToViewNameTranslator方法用于初始化RequestToViewNameTranslator的,RequestToViewNameTranslator是一个接口,用于根据请求解析出一个默认的视图名称,当ModelAndView对象不为null,但是没有指定视图时,就会使用这个接口来获取视图名称。
寻找bean名字为viewNameTranslator试图解析器,没有的话用DefaultRequestToViewNameTranslator,并存放到viewNameTranslator。
工作原理是:它会去掉请求URL的前缀和后缀,然后将剩下的部分作为视图名称。例如,如果我们设置prefix为"/app/“,suffix为”.html",stripExtension为false,那么请求的URL"/app/home.html"就会被解析为"home.html"。如果我们设置stripExtension为true,那么就会被解析为"home"。

4.8 初始化视图解析器initViewResolvers()

默认是寻找所有类型为ViewResolver的bean,将他们作为试图解析器,默认的试图解析器是InternalResourceViewResolver。它们是用于将控制器返回的逻辑视图名解析为具体的视图对象,这个对象可以是一个 JSP、一个 HTML 页面、一个 PDF 视图、一个 Thymeleaf 模板等。

4.9 初始化重定向initFlashMapManager()

默认的是SessionFlashMapManager。
FlashMap是Spring MVC提供的一种临时存储数据的方式,它用于存储一次请求的数据,并在下一次请求中使用。FlashMap通常用于重定向请求时传递数据,比如,当你在处理POST请求后重定向到一个GET请求时,你可能希望重定向的GET请求能够访问POST请求处理的一些结果数据,这就可以使用FlashMap来实现。例如可以这样存储Flash属性:

@RequestMapping(method = RequestMethod.POST)
public String handlePostData(@ModelAttribute("data") Data data, RedirectAttributes redirectAttrs) {// 处理POST请求...// 将一些数据存储在FlashMap中,以便在重定向后的GET请求中使用redirectAttrs.addFlashAttribute("message", "123");// 重定向到GET请求return "redirect:/get-data";
}

然后在重定向后的GET请求中提取Flash属性:

@RequestMapping(method = RequestMethod.GET)
public String displayData(Model model) {// 如果存在Flash属性,它们会自动添加到Model中if (model.containsAttribute("message")) {System.out.println("Message-FlashMap: " + model.getAttribute("message"));}// 处理GET请求...
}

相关文章:

SpringMVC源码分析(一)启动流程分析

a、SpringMVC 在启动过程中主要做了什么事情&#xff1f; SpringMVC在启动过程中是什么时候解析web.xml文件的&#xff0c;又是什么时候初始化9大内置对象的&#xff1f; <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xml…...

ARM 10.12

设置按键中断&#xff0c;按键1按下&#xff0c;LED亮&#xff0c;再按一次&#xff0c;灭 按键2按下&#xff0c;蜂鸣器响。再按一次&#xff0c;不响 按键3按下&#xff0c;风扇转&#xff0c;再按一次&#xff0c;风扇停 src/key.c #include"key.h"//按键3的配…...

vue-rouer 路由

安装/配置: //进入项目目录:(在搭建项目的时候安装了) cnpm install vue-router --save旧版路由 需要自己配置 //项目中载入,一般在main.js中载入:import VueRouter from vue-routerVue.use(VueRouter)let router new VueRouter({}) //其中配置路径和地址//在Vue中引入:n…...

元数据的前世今生

什么是元数据 元数据(Metadata)是描述数据的数据。它是一组信息,用于描述数据的特征、属性、结构和内容,以便更好地管理、理解、组织和使用数据。让人们能够清楚拥有什么数据、代表什么、源自何处、如何在系统中移动,以及哪些人可以使用源数据,如何使用。 元数据通常包…...

Python实现简易过滤删除数字的方法

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 如果想从一个含有数字&#xff0c;汉字&#xff0c;字母的列表中滤除仅含有数字的字符&#xff0c; 当然可以采取正则表达式来完成&#xff0c;但是有点太麻烦了…...

软件测试定位bug方法+定位案例(详解)

1、问题bug定位技巧 首先&#xff0c;作为开发也好&#xff0c;测试也好&#xff0c;定位问题有一个总的思路&#xff0c;而这个思路是和数据的走向一致的。 大致是这样&#xff1a; 用户层面问题 -> Web页面/软件界面 -> 中间件 -> 后端服务 -> 代码 -> 数据…...

【算法练习Day21】组合剪枝

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 组合剪枝总结&#xff1a; …...

NPM相关命令

临时使用 npm --registry https://registry.npm.taobao.org install 包名2.永久设置为淘宝镜像 npm config set registry https://registry.npm.taobao.org3.换回国外官方源 npm config set registry https://registry.npmjs.org4.查看使用的源地址 npm config get registr…...

Kubernetes 集群部署 Prometheus 和 Grafana

Kubernetes 集群部署 Prometheus 和 Grafana 文章目录 Kubernetes 集群部署 Prometheus 和 Grafana一.部署 node-exporter1.node-exporter 安装2.部署 node-exporter 二.部署Prometheus1.Prometheus 安装和配置&#xff08;1&#xff09;创建 sa 账号&#xff0c;对 sa 做 rbac…...

【算法-动态规划】零钱兑换 II-力扣 518

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

Hadoop3教程(六):HDFS中的DataNode

文章目录 &#xff08;63&#xff09;DataNode工作机制&#xff08;64&#xff09;数据完整性&#xff08;65&#xff09;掉线时限参数设置参考文献 &#xff08;63&#xff09;DataNode工作机制 DataNode内部存储了一个又一个Block&#xff0c;每个block由数据和数据元数据组…...

Macos音乐制作:Ableton Live 11 Suite for Mac中文版

Ableton Live 11是一款数字音频工作站软件&#xff0c;用于音乐制作、录音、混音和现场演出。它由Ableton公司开发&#xff0c;是一款极其流行的音乐制作软件之一。 以下是Ableton Live 11的一些主要特点和功能&#xff1a; Comping功能&#xff1a;Live 11增加了Comping功能…...

ThinkPHP5小语种学习平台

有需要请加文章底部Q哦 可远程调试 ThinkPHP5小语种学习平台 一 介绍 此小语种学习平台基于ThinkPHP5框架开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。平台角色分为学生&#xff0c;教师和管理员三种。学生注册登录后可观看学习视频&#xff0c;收藏视频&#xf…...

升级包版本之后Reflections反射包在springboot jar环境下扫描不到class排查过程记录

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d; 一位上进心十足的【Java ToB端大厂…...

Excel 函数大全应用,包含各类常用函数

Excel 函数大全应用&#xff0c;各类函数应用与案例实操。 AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作&#xff0c; Power BI 商业智能 68集&#xff0c; 数据库Mysql8.0 54集 数据库Oracle21C 142集&#xff0c; Office 2021实战&#xff0c; Python 数据分析&#xff0…...

深入浅出的介绍一下虚拟机VMware Workstation——part3(VMware快照)

虚拟机VMware使用 前言快照的原理快照的使用 前言 可以先查看之前的2篇博文&#xff0c;学习基础的虚拟机使用 深入浅出的介绍一下虚拟机VMware Workstation——part1 深入浅出的介绍一下虚拟机VMware Workstation——part2(详细安装与使用) 由于我们使用虚拟机的初衷就是用来…...

《Python基础教程》专栏总结篇

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…...

JavaScript 事件

HTML 事件是发生在 HTML 元素上的事情。 当在 HTML 页面中使用 JavaScript 时&#xff0c; JavaScript 可以触发这些事件。 HTML 事件 HTML 事件可以是浏览器行为&#xff0c;也可以是用户行为。 以下是 HTML 事件的实例&#xff1a; HTML 页面完成加载HTML input 字段改变…...

轻松学会这招,给大量视频批量添加滚动字幕不求人

想要给大量视频批量添加滚动字幕不求人吗&#xff1f;下面就教你一个简单的方法。首先你需要下载并安装一款名为“固乔剪辑助手”的软件&#xff0c;这是一款非常专业的视频剪辑软件&#xff0c;它可以帮助你快速地给大量视频添加滚动字幕。 打开固乔剪辑助手软件后&#xff0c…...

哪个文字转语音配音软件最好用?

现在TTS技术不断发展&#xff0c;文字转语音技术已经越来越成熟&#xff0c;声音听着拟人度非常高&#xff0c;现在好用的软件也不在少数。很多手机里面都有自带的朗读功能&#xff0c;如果觉得声音不够&#xff0c;也可以自己下载软件使用。给大家分享一下我一直使用的一款文字…...

多关键词高亮显示

引入关键词文件&#xff0c;符合有条件的背景色高亮显示&#xff0c;也可取消。 <div id"testHtml"><p>写入的文本</p><p>关键词</p></div> var str 多个关键词&#xff0c;关键词文件&#xff0c;关键词 var strL str.replac…...

浅谈 33 台 iPad 发展史;OpenAI“悄悄”修改了企业核心价值观丨 RTE 开发者日报 Vol.67

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 **观点 **」、「有意思的 数据 」、「有思考…...

Mysql之备份(Mysqldump)

本篇文章旨在介绍Mysql的备份&#xff0c;借助mysqldump命令。 1.准备数据 准备一个数据库d1&#xff0c;表t1 表结构如下&#xff1a; mysql> desc t1; ------------------------------------------------------- | Field | Type | Null | Key | Default | Extra …...

算法leetcode|84. 柱状图中最大的矩形(rust重拳出击)

文章目录 84. 柱状图中最大的矩形&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 84. 柱状图中最大的矩形&#xff1a; 给定 n 个非负整…...

Java中通过List中的stream流去匹配相同的字段去赋值,避免for循环去查询数据库进行赋值操作

List<EquipmentDeviceMessage> equipmentDeviceMessageInfo greenThinkTanksInfoPlanMapper.getEquipmentDeviceMessageInfo(phone, startDate, endDate); List<BladeUserVo> userList bladexsqlMapper.getUserList();Q&#xff1a;上面两个列表怎么使用流&#…...

开源酒店预订订房小程序源码系统+多元商户 前端+后端完整搭建教程 可二次开发

大家好啊&#xff0c;罗峰今天来给大家分享一款酒店预订订房小程序源码系统&#xff0c;这款系统进行了全新的升级&#xff0c;从原来的单门店升级成了多门店&#xff0c;可以自由切换账号&#xff0c;统一管理。功能强大。以下是部分代码截图&#xff1a; 酒店预订订房小程序源…...

Leetcode 2906. Construct Product Matrix

Leetcode 2906. Construct Product Matrix 1. 解题思路2. 代码实现 题目链接&#xff1a;2906. Construct Product Matrix 1. 解题思路 这道题其实算是一道数论题。 本来其实python的pow内置函数已经帮我们基本处理了所有的问题了&#xff0c;但是这里稍微做了一点复杂化操…...

【Leetcode Sheet】Weekly Practice 11

Leetcode Test 2731 移动机器人(10.10) 有一些机器人分布在一条无限长的数轴上&#xff0c;他们初始坐标用一个下标从 0 开始的整数数组 nums 表示。当你给机器人下达命令时&#xff0c;它们以每秒钟一单位的速度开始移动。 给你一个字符串 s &#xff0c;每个字符按顺序分别…...

本地PHP搭建简单Imagewheel私人云图床,在外远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言、Linux &#x1f325;️每日语录&#xff1a;追逐影子的人&#xff0c;自己就是影子。 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 1.前言 云存储在前几年风头无两&#xff0c;云存…...

Python图像处理进阶:Pillow库的中级应用

在上一篇文章中&#xff0c;我们介绍了Python的Pillow库&#xff0c;了解了如何使用Pillow进行一些基础的图像操作。今天&#xff0c;我们将深入探讨Pillow库的中级功能&#xff0c;包括颜色空间转换&#xff0c;直方图&#xff0c;像素操作和绘制。 一、颜色空间转换 在图像…...