zoukankan      html  css  js  c++  java
  • SpringMVC零xml配置原理

    前言:

    我们使用springmvc时,每次都要去配置web.xml,spring-mvc.xml,甚至和spring整合时候,还要配置spring.xml。用起来比较麻烦,用过springboot的朋友应该知道,springboot中使用springmvc时候就不会去指定xml。那这样的操作是怎么实现的呢,下面我们就来探究下springmvc零配置xml原理。

    不使用web.xml

    1.实现WebApplicationInitializer接口

    如果我们想实现不使用web.xml来配置需要的内容(注:此情况下web.xml可不配置内容,但不能删除),关键在于实现WebApplicationInitializer。然后重写接口中的onStartup()方法。

    然后在这个重写的方法内完成相应的配置,代码如下所示。

    import com.evan.config.AppConfig;
    import org.springframework.web.WebApplicationInitializer;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
     
    import javax.servlet.ServletContext;
    import javax.servlet.ServletRegistration;
     
    //https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/web.html#mvc-servlet
    public class MyWebApplicationInitializer implements WebApplicationInitializer {
      
        @Override
        public void onStartup(ServletContext servletCxt) {
            //初始化spring容器  以注解的方式
            AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
            //将配置类注册进spring容器
            ac.register(AppConfig.class);
            DispatcherServlet servlet = new DispatcherServlet(ac);
            ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("*.do");
        }
    }

    不使用web.xml原因详解

    tomcat在启动时候,就会调用到这个接口。

    那么为什么tomcat启动后就会调用到这个接口呢?下面请听我慢慢介绍。

    原因:实际上是因为servlet3.0的新规范,间接实现了ServletContainerInitializer接口。

    Tomcat也遵守了这套规范,去找实现了ServletContainerInitializer接口的类,调用它的onStartUp方法。

    springMVC遵守新规范,并且实现了SPI(相当于也实现了ServletContainerInitializer接口)。

    那么什么是SPI机制呢?

    SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。

    了解了上面这些后,我们接着去看一下spring-web的jar包。在这里我们可以看到如SPI机制所描述的一样,springMVC自己实现了这种SPI。

     然后我们继续去看一下文件的内容,这里我们可以看到里面是一个类的全类名,接着我们点进去看一下这个类的详细内容。

    这里我们可以看到这个类,实现了ServletContainerInitializer接口。已经逐渐验证了我们的结论。

    接着再看,上面有一个@HandlesTypes注解。

    那么这个注解有什么用呢?

    作用:这个注解可以配置一个类,更准确来说是一个接口。假如现在我们有一个A接口,里面有一个test()方法,然后分别有B、C、D实现了A接口,

    然后我们将A接口配置到@HandlesTypes(A.class)注解中。servlet将会扫描所有实现了A接口的类,即扫描到BCD,然后把他们

    组装成一个set作为参数传入到重写的onStartup()方法中。

    然后会在onStartUp()方法中遍历每一个A的实现类的test()方法,这样就达到了在onStartup()中,调用这些方法的目的。

    那么servlet为什么要这么做呢?

    这就就不得不说这种成熟的经典框架中的设计思想多么优秀了。如果我们自己实现SPI的话,如果有10个类,都实现了

    ServletContainerInitializer接口,我们需要为每一个接口都去写一个javax.servlet.ServletContainerInitializer文件,然后在文件中,写上我们实现了这个接口的类的全限定名。如果有100个我们要写一百次,这样会十分麻烦,而且这样的设计在程序中是绝不允许的,所以我们要把这些操作抽象出来。从而这样设计来达到目的。

    不使用spring-mvc.xml

    想不使用spring-mvc.xml更加简单,只需要通过配置类实现WebMvcConfigurer接口,或继承WebMvcConfigurationSupport类。

    两者没有很大的区别,但是实现WebMvcConfigurer接口接口的话,要在配置类上加上@EnableWebMvc注解。

    在里面我们可以做例如消息转换器,视图解析器,参数处理器等等一系列的配置。

    import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.validation.MessageCodesResolver;
    import org.springframework.validation.Validator;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.config.annotation.*;
     
    import java.util.List;
     
    @Configuration
    @ComponentScan("com")
    @EnableWebMvc  // <annotation:driver>
    //https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/web.html#mvc-config
    public class AppConfig implements WebMvcConfigurer {
     
        //这接口里面的方法贯穿了所有spring mvc的配置
     
    //    @Bean
     
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
     
        }
     
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
     
        }
     
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
     
        }
     
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
     
        }
     
        @Override
        public void addFormatters(FormatterRegistry registry) {
     
        }
     
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
     
        }
     
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
     
        }
     
        @Override
        public void addCorsMappings(CorsRegistry registry) {
     
        }
     
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
     
        }
     
        /**
         * 在这里配置视图解析器
         *
         * @param registry
         */
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/page/", ".html");
        }
     
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
     
        }
     
        @Override
        public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
     
        }
     
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //        for (HttpMessageConverter<?> converter : converters) {
    //            System.out.println(converter);
    //        }
            //每个消息转换器都会跑一遍
            //new一个新的解析器 添加到converters中。一般是在spring-mvc.xml中配置
            FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
            converters.add(fastJsonHttpMessageConverter);
        }
     
        //剔除掉不想用的解析器
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
     
        }
     
        @Override
        public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
     
        }
     
        @Override
        public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
     
        }
     
        @Override
        public Validator getValidator() {
            return null;
        }
     
        @Override
        public MessageCodesResolver getMessageCodesResolver() {
            return null;
        }
     
     
    //    @Bean
    //    public TestController user(){
    //         return  new TestController();
    //    }
     
     
    }

    真正的不使用web.xml

    之前的不使用web.xml的话,只是不在web.xml中配置内容,但并不能删除web.xml,不然会报错。因为之前是以web资源的形式去启动项目,tomcat会去检测是否存在web.xml。所以如果删除的话是会报错的。

    但是我们都知道,springboot中是没有web.xml的,也没有报错,

    那是怎么实现的呢,答案是通过内嵌的tomcat。

    spring是以一个启动类,main方法作为程序入口,我们模拟一下。

    在代码中,指定了一个资源目录是当前项目,获取配置属性指定为一个临时目录。

    这样的话就不再需要web.xml了,springboot也是通过这种方式实现了零xml。

    但是,这种方式有个问题,就是无法访问静态资源,那么springboot是怎么实现静态资源访问的呢,我们后续会专门写一篇文章解释,在此先记录一下。

    下面代码实现,通过这个main方法即可启动web项目。

    <!--使用maven tomcat插件时,当前依赖需要注释掉,不然会产生冲突。-->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-core</artifactId>
                <version>8.5.31</version>
            </dependency>

    代码

    import org.apache.catalina.Context;
    import org.apache.catalina.LifecycleListener;
    import org.apache.catalina.startup.Tomcat;
     
    /**
     * @ClassName App
     * @Description
     * @Author EvanWang
     * @Version 1.0.0
     * @Date 2019/11/19 11:25
     */
    public class App {
        public static void main(String[] args) throws Exception {
            //内嵌tomcat
            Tomcat tomcat = new Tomcat();
            tomcat.setPort(80);
            //现在找不到静态资源,如果指向我们当前项目,则可以访问到静态资源
            Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
            //addContext和addWebapp的区别:只会去初始化一个 context的资源目录(项目) 并不会加载 web的生命周期
            // Tomcat的文件夹webapps,目录内是我们的项目
            // 有两种方式启动项目:1.war  2.文件夹
            //tomcat.addWebapp("/","C:\Program Files\pro\evan-project\spring-mvc\src\main\webapp");
            //手动添加生命周期监听器
            context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
            tomcat.start();
            //挂起
            tomcat.getServer().await();
     
            //传一个xml文件进去
    //        ClassPathXmlApplicationContext
    //                classPathXmlApplicationContext = new ClassPathXmlApplicationContext();
     
        }
    }

    版权声明:本文为CSDN博主「Evan Wang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_41378597/article/details/103173322



  • 相关阅读:
    疑问
    Android之Adapter用法总结-(转)
    Android之Adapter用法总结(转)
    ListView中每个item条目在被单击选中时能够高亮显示
    使用简单的python语句编写爬虫 定时拿取信息并存入txt
    Python实现简单的爬虫获取某刀网的更新数据
    scrapy-redis(七):部署scrapy
    Scrapy-redis改造scrapy实现分布式多进程爬取
    Scrapy-redis实现分布式爬取的过程与原理
    https://blog.csdn.net/u012150179/article/details/38091411
  • 原文地址:https://www.cnblogs.com/lusaisai/p/13121668.html
Copyright © 2011-2022 走看看