zoukankan      html  css  js  c++  java
  • springboot情操陶冶-web配置(三)

    承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用

    MVC简单例子

    直接编写一个Controller层的代码,返回格式为json

    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配置的过程

    DispatcherServletAutoConfiguration

    首先需要配置DispatcherServlet组件,分为几个步骤来看


    No.1 脑头注解了解下

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

    由以上的注解可得知,其需要在ServletWebServerFactoryAutoConfiguration类注入至bean工厂后方可继续,这就和前文关联起来了。


    No.2 DispatcherServletConfiguration内部类

    	@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集合中呢


    No.3 DispatcherServletRegistrationConfiguration内部类

    	@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来实现的,具体的本文不展开,不过如果用户想把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);
            }
        }
    }
    
    
  • 相关阅读:
    Exp8-Web综合
    Exp7-网络欺诈防范
    Exp6-MSF应用基础
    加密API学习
    Exp5 信息搜集与漏洞扫描
    Exp4-恶意代码分析
    Exp3-免杀原理
    Exp2-后门原理与实践
    leetcode 22括号生成 暴力法
    413 等差数列划分
  • 原文地址:https://www.cnblogs.com/question-sky/p/9585827.html
Copyright © 2011-2022 走看看