springboot情操陶冶-web配置(三)

承接前文springboot情操陶冶-web配置(二),本文將在前文的基礎上分析下mvc的相關應用css

MVC簡單例子

直接編寫一個Controller層的代碼,返回格式爲jsonhtml

package com.example.demo.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * @author nanco
 * -------------
 * -------------
 * @create 2018/9/4
 **/
@Controller
@RequestMapping("/boot")
@ResponseBody
public class DemoController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public Map<String, String> helloWorld() {
        Map<String, String> result = new HashMap<>();
        result.put("springboot", "hello world");
        return result;
    }
}

運行以後,客戶端工具HTTP訪問連接http://127.0.0.1:9001/demoWeb/boot/hello即可獲得如下的簡單結果前端

{"springboot":"hello world"}

源碼剖析

咱們都知道springmvc最核心的組件即是DispatcherServlet,其本質是個Servlet組件,也包含了處理前端請求的邏輯,具體的可參照SpringMVC源碼情操陶冶-DispatcherServlet。本文則講解Springboot建立DispatcherServlet以及MVC配置的過程java

DispatcherServletAutoConfiguration

首先須要配置DispatcherServlet組件,分爲幾個步驟來看jquery


No.1 腦頭註解瞭解下web

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
}

由以上的註解可得知,其須要在ServletWebServerFactoryAutoConfiguration類注入至bean工廠後方可繼續,這就和前文關聯起來了。spring


No.2 DispatcherServletConfiguration內部類json

@Configuration
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration {
		// 引入了spring.mvc爲開頭的配置
		private final WebMvcProperties webMvcProperties;

		private final ServerProperties serverProperties;

		public DispatcherServletConfiguration(WebMvcProperties webMvcProperties,
				ServerProperties serverProperties) {
			this.webMvcProperties = webMvcProperties;
			this.serverProperties = serverProperties;
		}

		// 直接建立DispatcherServlet並注入至bean工廠
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet() {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			// 對應spring.mvc.dispatch-options-request
			dispatcherServlet.setDispatchOptionsRequest(
					this.webMvcProperties.isDispatchOptionsRequest());
			// 對應spring.mvc.dispatch-trace-request
			dispatcherServlet.setDispatchTraceRequest(
					this.webMvcProperties.isDispatchTraceRequest());
			// 對應spring.mvc.throw-exception-if-no-handler-found
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(
					this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
			return dispatcherServlet;
		}

		// 建立名爲multipartResolver的用於文件請求
		@Bean
		@ConditionalOnBean(MultipartResolver.class)
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
			// Detect if the user has created a MultipartResolver but named it incorrectly
			return resolver;
		}

		// 獲取server.servlet.path代表DispatcherServlet的攔截路徑
		@Bean
		public DispatcherServletPathProvider mainDispatcherServletPathProvider() {
			return () -> DispatcherServletConfiguration.this.serverProperties.getServlet()
					.getPath();
		}

	}

很簡單,就是建立了DispatcherServlet,那麼如何被注入至tomcat的servlet集合中呢tomcat


No.3 DispatcherServletRegistrationConfiguration內部類springboot

@Configuration
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		private final ServerProperties serverProperties;

		private final WebMvcProperties webMvcProperties;

		private final MultipartConfigElement multipartConfig;

		public DispatcherServletRegistrationConfiguration(
				ServerProperties serverProperties, WebMvcProperties webMvcProperties,
				ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
			this.serverProperties = serverProperties;
			this.webMvcProperties = webMvcProperties;
			this.multipartConfig = multipartConfigProvider.getIfAvailable();
		}

		// 對DispatcherServlet注入至tomcat等容器中
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
				DispatcherServlet dispatcherServlet) {
			// 同server.servlet.path,默認爲/
			ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
					dispatcherServlet,
					this.serverProperties.getServlet().getServletMapping());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			// 讀取spring.mvc.servlet.load-on-startup,默認爲-1
			registration.setLoadOnStartup(
					this.webMvcProperties.getServlet().getLoadOnStartup());
			if (this.multipartConfig != null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			return registration;
		}
	}

由上述代碼得知,將servlet注入至tomcat容器是經過ServletContextInitializer接口的實現類ServletRegistrationBean<T extends Servlet>來實現的,具體的本文不展開,不過若是用戶想把Servlet或者Filter注入至tomcat,則經常使用此Bean來操做便可

WebMvcAutoConfiguration

DispatcherServlet組件建立並注入至web容器後,接下來即是對mvc的相關配置,筆者也按幾個步驟來分析


No.1 腦袋註解看一下

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}

此配置也是根據上文中的DispatcherServletAutoConfiguration注入至bean工廠後再生效。


No.2 Filter集合

1.HiddenHttpMethodFilter-隱性傳播PUT/DELETE/PATCH請求

@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		// 默認對post請求的包讀取_method參數指定的方法,而後再做轉換
		return new OrderedHiddenHttpMethodFilter();
	}

隱性的經過methodParam參數來傳播PUT/DELETE/PATCH請求,默認參數名爲_method,也可用戶自行配置

2.HttpPutFormContentFilter-顯性響應PUT/DELETE/PATCH請求

// spring.mvc.formcontent.putfilter.enabled不指定或者值不爲false則生效
	@Bean
	@ConditionalOnMissingBean(HttpPutFormContentFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
	public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
	    // 直接對PUT/DELETE/PATCH請求進行響應,其order值大於OrderedHiddenHttpMethodFilter
		return new OrderedHttpPutFormContentFilter();
	}

其通常與上述的OrderedHiddenHttpMethodFilter搭配使用,其order值大於前者因此排在後面響應PUT等請求。 舒適提示:此處只是註冊了filter到bean工廠,並無被注入至tomcat等web容器中,用戶若是想支持上述的請求方法,能夠考慮經過ServletRegistrationBean/FilterRegistrationBean來進行注入


No.3 EnableWebMvcConfiguration內部類,其類同*@EnableWebMvc註解,類同咱們經常使用spring配置的mvc:annotation-driven*。因爲代碼過多,就挑選幾個來看

@Configuration
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
		// 註冊RequestMappingHandlerAdapter組件
		@Bean
		@Override
		public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
			RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
			adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null
					|| this.mvcProperties.isIgnoreDefaultModelOnRedirect());
			return adapter;
		}

		// 註冊RequestMappingHanlderMapping組件
		@Bean
		@Primary
		@Override
		public RequestMappingHandlerMapping requestMappingHandlerMapping() {
			// Must be @Primary for MvcUriComponentsBuilder to work
			return super.requestMappingHandlerMapping();
		}
		
		// 校驗器組件
		@Bean
		@Override
		public Validator mvcValidator() {
			if (!ClassUtils.isPresent("javax.validation.Validator",
					getClass().getClassLoader())) {
				return super.mvcValidator();
			}
			return ValidatorAdapter.get(getApplicationContext(), getValidator());
		}
		
		// 異常處理組件
		@Override
		protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
			if (this.mvcRegistrations != null && this.mvcRegistrations
					.getExceptionHandlerExceptionResolver() != null) {
				return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
			}
			return super.createExceptionHandlerExceptionResolver();
		}
	}

主要是用來註冊響應前端請求的插件集合,具體的怎麼整合可見筆者置頂的spring文章,裏面有提,就不在此處展開了 舒適提示:筆者此處提醒下此類是DelegatingWebMvcConfiguration的實現類,其自己也被註解*@Configuration修飾,其內部的**setConfigurers()**方法有助於集結全部實現了WebMvcConfigurer*接口的集合,因此用戶可經過實現此接口來擴展mvc的相關配置

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


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

No.4 WebMvcAutoConfigurationAdapter內部類(WebMvcConfigurer接口實現類)-在上述的MVC組件的基礎上新增其餘的組件,包含視圖組件、消息處理器組件等。 限於代碼過長,筆者此處也挑選幾個來看

// 消息處理器集合配置
		@Override
		public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
			converters.addAll(this.messageConverters.getConverters());
		}
		
		// 對路徑請求的配置
		@Override
		public void configurePathMatch(PathMatchConfigurer configurer) {
			// 對應spring.mvc.pathmatch.use-suffix-pattern,默認爲false
			configurer.setUseSuffixPatternMatch(
					this.mvcProperties.getPathmatch().isUseSuffixPattern());
			// 對應spring.mvc.patchmatch.use-registered-suffix-pattern,默認爲false
			configurer.setUseRegisteredSuffixPatternMatch(
					this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
		}
		
		// 建立jsp視圖解析器
		@Bean
		@ConditionalOnMissingBean
		public InternalResourceViewResolver defaultViewResolver() {
			InternalResourceViewResolver resolver = new InternalResourceViewResolver();
			// 對應spring.mvc.view.prefix,默認爲空		resolver.setPrefix(this.mvcProperties.getView().getPrefix());
			// 對應spring.mvc.view.suffix,默認爲空
			resolver.setSuffix(this.mvcProperties.getView().getSuffix());
			return resolver;
		}
		
		// 靜態文件訪問配置
		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			// 對應spring.resource.add-mappings,默認爲true
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache()
					.getCachecontrol().toHttpCacheControl();
                        // 此處的靜態資源映射主要針對前端的一些文件,好比jquery/css/html等等
			if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry
						.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod))
						.setCacheControl(cacheControl));
			}
			// 對應spring.mvc.static-path-pattern,默認爲/**
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(
						registry.addResourceHandler(staticPathPattern)
								// 對應spring.resources.static-locations
								.addResourceLocations(getResourceLocations(
										this.resourceProperties.getStaticLocations()))
								.setCachePeriod(getSeconds(cachePeriod))
								.setCacheControl(cacheControl));
			}
		}

		// 歡迎界面配置,通常可在static或者項目根目錄下配置index.html界面便可
		@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(
				ApplicationContext applicationContext) {
			return new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext),
					applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
		}

小結

本文主要講解了mvc的springboot自動配置過程,讀者主要關注DispatcherServlet組件和消息處理等組件的bean工廠配置便可。若是用戶也想自定義去擴展mvc的相關配置,可自行去實現WebMvcConfigurer接口便可,樣例以下

package com.example.demo.web.config;

import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author nanco
 * -------------
 * -------------
 * @create 2018/9/5
 **/
@Configuration
public class BootWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

    }
}

本文也講述了若是用戶想擴展相應的Filter或者Servlet,可以使用FilterRegistrationBean/ServletRegistrationBean,樣例以下

package com.example.demo.web.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author nanco
 * -------------
 * -------------
 * @create 2018/9/5
 **/
@Configuration
public class ServletFilterBeans {

    // only intercept /simple/
    @Bean("simpleServlet")
    public ServletRegistrationBean<Servlet> simpleServlet() {
        return new ServletRegistrationBean<>(new SimpleServlet(), "/simple/");
    }

    // intercept /simple、/simple/、/simple/ha etc.
    @Bean("simpleFilter")
    public FilterRegistrationBean<Filter> simpleFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean<>();
        bean.setFilter(new SimpleFilter());
        bean.addUrlPatterns("/simple/*");
        return bean;
    }

    private static class SimpleServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("doService path: " + req.getRequestURI());
            super.doGet(req, resp);
        }
    }

    private static class SimpleFilter extends OncePerRequestFilter {

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            System.out.println("filter path: " + request.getRequestURI());
            filterChain.doFilter(request, response);
        }
    }
}
相關文章
相關標籤/搜索