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


N0.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等请求


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();
            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
 **/
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);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值