• 22、spring注解学习(servlet3.0)——servlet3.0和SpringMVC注解启动


    一、servlet3.0规范

    1、新增的注解支持

      在servlet3.0之前的话,我们要添加Servlet、Filter、Listener都需要在web.xml中注册,而在servlet3.0添加了注解支持:

        @WebServlet: 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet,如: 

    @WebServlet(urlPatterns = {"/simple"}, asyncSupported = true, 
    loadOnStartup = -1, name = "SimpleServlet", displayName = "ss", 
    initParams = {@WebInitParam(name = "username", value = "tom")} 
    ) 
    public class SimpleServlet extends HttpServlet{ … }

        @WebFilter: 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器;

        @WebListener:该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现对应的监听器接口

        @WebInitParam:该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 <servlet> 和 <filter> 的 <init-param> 子标签。

    2、runtimes pluggability(运行时插件能力)

      在使用实现了servlet3.0规范的servlet容器中,Servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现,前提是ServletContainerInitializer的实现类必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer中,文件的内容就是ServletContainerInitializer实现类的全类名

    //容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
    //传入感兴趣的类型;
    @HandlesTypes(value={HelloService.class})
    public class MyServletContainerInitializer implements ServletContainerInitializer {
    
        /**
         * 应用启动的时候,会运行onStartup方法;
         * 
         * Set<Class<?>> arg0:感兴趣的类型的所有子类型;
         * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
         * 
         * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener)
         * 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
         *         必须在项目启动的时候来添加;
         *         1)、ServletContainerInitializer得到的ServletContext;
         *         2)、ServletContextListener得到的ServletContext;
         */
        @Override
            public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
                // TODO Auto-generated method stub
                System.out.println("感兴趣的类型:");
                for (Class<?> claz : arg0) {
                    System.out.println(claz);
                }
            
            //注册组件  ServletRegistration  
            ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet());
            //配置servlet的映射信息
            servlet.addMapping("/user");
            
            
            //注册Listener
            sc.addListener(UserListener.class);
            
            //注册Filter  FilterRegistration
            FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class);
            //配置Filter的映射信息
            filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
            
        }
    
    }

     使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充(比如注册三大组件),不需要额外的配置;

    二、SpringMVC注解启动

      在之前使用SpringMVC时,很多时候都是在web.xml中配置的方式来启动,而从SpringMVC 3.1开始就使用了servlet3.0的插件机制,可通过配置类的方式来启动SpringMVC

    1、SpringServletContainerInitializer

      在spring的web模块的jar包下存在META-INF/services/javax.servlet.ServletContainerInitializer,该文件中指定ServletContainerInitializer的实现类为SpringServletContainerInitializer,可知在web容启动时会加载这个类,来看看这个类:

    @HandlesTypes(WebApplicationInitializer.class)//容器启动的时候会将WebApplicationInitializer类型下面的子类(实现类,子接口等)传递过来
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        //webAppInitializerClasses就是WebApplicationInitializer类型
           @Override
        public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
                throws ServletException {
    
            List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
    
            if (webAppInitializerClasses != null) {
                for (Class<?> waiClass : webAppInitializerClasses) {
                    // 将webAppInitializerClasses集合中的非抽象,不是接口类型的class实例化并添加到initializer中
                    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                            WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                        try {
                            initializers.add((WebApplicationInitializer) waiClass.newInstance());
                        }
                        catch (Throwable ex) {
                            throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                        }
                    }
                }
            }
    
            if (initializers.isEmpty()) {
                servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
                return;
            }
    
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
         
            AnnotationAwareOrderComparator.sort(initializers);
         //遍历执行initializers集合中WebApplicationInitializer.onStartup(servletContext)方法
            for (WebApplicationInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
    
    }    

    接下来看看SpringServletContainerInitializer使用@HandlesTypes引入的WebApplicationInitializer接口(只定义了一个onStartup方法)的子类:

    2、AbstractContextLoaderInitializer

    public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    
        /** Logger available to subclasses */
        protected final Log logger = LogFactory.getLog(getClass());
    
      //该方法会在web容器启动时SpringServletContainerInitializer.onStartup中被调用
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
          //注册加载上下文的监听器
            registerContextLoaderListener(servletContext);
        }
    
        
        protected void registerContextLoaderListener(ServletContext servletContext) {
        //调用createRootApplicationContext()创建根容器,需要具体的实现类去实现该抽象方法获取根容器
            WebApplicationContext rootAppContext = createRootApplicationContext();
            if (rootAppContext != null) {
           //创建监听器,并将根容器传入
                ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
           //设置上下文初始化器
                listener.setContextInitializers(getRootApplicationContextInitializers());
           //添加监听器
                servletContext.addListener(listener);
            }
            else {
                logger.debug("No ContextLoaderListener registered, as " +
                        "createRootApplicationContext() did not return an application context");
            }
        }
    
        //抽象方法,子类必须实现
        protected abstract WebApplicationContext createRootApplicationContext();
       //默认返回空 子类可重写 
        protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
            return null;
        }
    
    }

    AbstractContextLoaderInitializer主要的功能:

      调用创建createRootApplicationContext()创建根容器,

      注册了监听器ContextLoaderListener(extends ContextLoader implements ServletContextListener)

    3、AbstractDispatcherServletInitializer:

    public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    
        /**
         * The default servlet name. Can be customized by overriding {@link #getServletName}.
         */
        public static final String DEFAULT_SERVLET_NAME = "dispatcher";
    
       //重写了AbstractContextLoaderInitializer.onStartup(ServletContext)
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
         //维持父类的实现
            super.onStartup(servletContext);
         //添加了注册DispatcherServlet的步骤
            registerDispatcherServlet(servletContext);
        }
    
       
        protected void registerDispatcherServlet(ServletContext servletContext) {
            String servletName = getServletName();
            Assert.hasLength(servletName, "getServletName() must not return empty or null");
         //调用createServletApplicationContext()创建web的ioc容器(管理Controller等springmvc的组件),需要子类去实现该抽象方法去获取web容器
            WebApplicationContext servletAppContext = createServletApplicationContext();
            Assert.notNull(servletAppContext,
                    "createServletApplicationContext() did not return an application " +
                    "context for servlet [" + servletName + "]");
         //创建了前端控制器DispatcherServlet
            FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
            dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
         //使用servletContext添加DispatcherServlet
            ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
            Assert.notNull(registration,
                    "Failed to register servlet with name '" + servletName + "'." +
                    "Check if there is another servlet registered under the same name.");
    
            registration.setLoadOnStartup(1);
         //具体的路径映射规则需要子类实现getServletMappings()
            registration.addMapping(getServletMappings());
            registration.setAsyncSupported(isAsyncSupported());
    
            Filter[] filters = getServletFilters();
            if (!ObjectUtils.isEmpty(filters)) {
                for (Filter filter : filters) {
                    registerServletFilter(servletContext, filter);
                }
            }
    
            customizeRegistration(registration);
        }
    
        
        protected String getServletName() {
            return DEFAULT_SERVLET_NAME;
        }
    
       
        protected abstract WebApplicationContext createServletApplicationContext();
    
       
        protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
            return new DispatcherServlet(servletAppContext);
        }
    
        
        protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
            return null;
        }
    
        
        protected abstract String[] getServletMappings();
    
        
        protected Filter[] getServletFilters() {
            return null;
        }
    
        
        protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
            String filterName = Conventions.getVariableName(filter);
            Dynamic registration = servletContext.addFilter(filterName, filter);
            if (registration == null) {
                int counter = -1;
                while (counter == -1 || registration == null) {
                    counter++;
                    registration = servletContext.addFilter(filterName + "#" + counter, filter);
                    Assert.isTrue(counter < 100,
                            "Failed to register filter '" + filter + "'." +
                            "Could the same Filter instance have been registered already?");
                }
            }
            registration.setAsyncSupported(isAsyncSupported());
            registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
            return registration;
        }
    
        private EnumSet<DispatcherType> getDispatcherTypes() {
            return (isAsyncSupported() ?
                    EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
                    EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
        }
    
       
        protected boolean isAsyncSupported() {
            return true;
        }
    
        protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        }
    
    }

    AbstractDispatcherServletInitializer 的主要功能:

      创建一个web的ioc容器:createServletApplicationContext();

      创建了DispatcherServlet:createDispatcherServlet();

      将创建的DispatcherServlet添加到ServletContext中,并设置路径映射等;

    4、AbstractAnnotationConfigDispatcherServletInitializer

    public abstract class AbstractAnnotationConfigDispatcherServletInitializer
            extends AbstractDispatcherServletInitializer {
       //实现了AbstractContextLoaderInitializer.createRootApplicationContext(),创建根容器
        @Override
        protected WebApplicationContext createRootApplicationContext() {
         //获取根容器的配置类
            Class<?>[] configClasses = getRootConfigClasses();
            if (!ObjectUtils.isEmpty(configClasses)) {
            //创建ioc容器
                AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
           //注册组件
                rootAppContext.register(configClasses);
                return rootAppContext;
            }
            else {
                return null;
            }
        }
    
       //实现了AbstractDispatcherServletInitializer.createServletApplicationContext(),创建web的ioc容器
        @Override
        protected WebApplicationContext createServletApplicationContext() {
         
            AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
         //获取web ioc容器的配置类
            Class<?>[] configClasses = getServletConfigClasses();
            if (!ObjectUtils.isEmpty(configClasses)) {
           //
                servletAppContext.register(configClasses);
            }
            return servletAppContext;
        }
    
       //抽象方法 子类实现 返回根容器的配置类
        protected abstract Class<?>[] getRootConfigClasses();
    
      //抽象方法 子类实现 返回web ioc容器的配置类   
        protected abstract Class<?>[] getServletConfigClasses();
    
    }

    AbstractAnnotationConfigDispatcherServletInitializer (注解方式配置的DispatcherServlet初始化器) 主要作用:

      创建根容器:createRootApplicationContext(),调用getRootConfigClasses()获取配置类

      创建web的ioc容器: createServletApplicationContext(),调用getServletConfigClasses()获取配置类

    5、以注解方式来启动SpringMVC:

      上面我们分析了三个抽象类的功能,最终需要我们需要继承AbstractAnnotationConfigDispatcherServletInitializer,实现对应抽象方法来指定DispatcherServlet的配置信息

    //web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
    public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        //获取根容器的配置类;(Spring的配置文件)   父容器;
        @Override
        protected Class<?>[] getRootConfigClasses() {
            // TODO Auto-generated method stub
            return new Class<?>[]{RootConfig.class};
        }
    
        //获取web容器的配置类(SpringMVC配置文件)  子容器;
        @Override
        protected Class<?>[] getServletConfigClasses() {
            // TODO Auto-generated method stub
            return new Class<?>[]{AppConfig.class};
        }
    
        //获取DispatcherServlet的映射信息
        //  /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
        //  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
        @Override
        protected String[] getServletMappings() {
            // TODO Auto-generated method stub
            return new String[]{"/"};
        }
    
    }

    3、定制SpringMVC

      在一个配置类中添加@EnableWebMvc注解,开启SpringMVC定制配置功能,类似于使用xml的<mvc:annotation-driven/>标签

    @Configuration
    @EnableWebMvc
    public class WebConfig {
    
    }

       实现WebMvcConfigurer配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)

    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
    
            // Override configuration methods...
    
    }

      当然有时候我们不需要配置所有的组件,没必要去实现WebMvcConfigurer所有方法,而WebMvcConfigurerAdapter实现了WebMvcConfigurer接口的所有方法(空实现),我们只要继承该类重写我们需要实现的方法即可

    @EnableWebMvc
    @Configuration
    public class AppConfig extends WebMvcConfigurerAdapter  {
    
        //定制
        
        //视图解析器
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            // TODO Auto-generated method stub
            //默认所有的页面都从 /WEB-INF/ xxx .jsp
            //registry.jsp();
            registry.jsp("/WEB-INF/views/", ".jsp");
        }
        
        //静态资源访问
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            // TODO Auto-generated method stub
            configurer.enable();
        }
        
        //拦截器
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // TODO Auto-generated method stub
            //super.addInterceptors(registry);
            registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
        }
    
    }

    注意:WebMvcConfigurer在5.0版本中已经被弃用了,spring的api文档有说明:as of 5.0 WebMvcConfigurer has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter   大概意思:从5.0开始,WebMvcConfigurer具有默认方法(从Java 8开始,接口可以有默认方法)并且可以直接实现而无需此适配器

    最后@EnableWebMvc做了什么:

         使用@Import(DelegatingWebMvcConfiguration.class)引入了DelegatingWebMvcConfiguration,是一个配置类,继承了WebMvcConfigurationSupport:

         WebMvcConfigurationSupport:使用@Bean为我们默认添加了很多springmvc组件,同时留下了一些空方法给子类重写来添加组件(模板方法模式)

         DelegatingWebMvcConfiguration:使用@Autowired(required = false)注入所有的WebMvcConfigurer的实现类,重写WebMvcConfigurationSupport添加组件的方法 ,实际上是依次调用WebMvcConfigurer对应的方法来添加组件

    所以其实可以不使用@EnableWebMvc,直接继承WebMvcConfigurationSupport,自己去实现对应的添加组件的方法也是可以的,当然还是推荐使用WebMvcConfigurer

    摘自:https://www.cnblogs.com/qzlcl/p/11074954.html

  • 相关阅读:
    JVM学习02:GC垃圾回收和内存分配
    JVM学习01:内存结构
    webservice学习01:wsdl文档结构
    项目总结21:项目总结21:input实现多图上传(FormData)(上传OSS并保存数据库)
    mysql学习3:mysql之my.cnf详解
    项目总结20:阿里云免费https证书申请
    5月9日——vue渲染过程中{{xxx}}显示
    5月8日——iOS中的3D Touch效果
    5月7日——采用第三方页面内容,但是顶部title使用自己的
    5月5日——更改手机状态栏的背景颜色
  • 原文地址:https://www.cnblogs.com/lyh233/p/12469559.html
Copyright © 2020-2023  润新知