Spring注解驱动开发之web

Easter79
• 阅读 576

前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余

一、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 中 子标签。

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, "/*");
        
    }

}

 Spring注解驱动开发之web

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

二、SpringMVC注解启动

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

    Spring注解驱动开发之web

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方法)的子类: 

Spring注解驱动开发之web

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
0、Spring 注解驱动开发
0、Spring注解驱动开发0.1简介《Spring注解驱动开发》是一套帮助我们深入了解Spring原理机制的教程;现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k