4) 回顾SpringMVC组件初始化原理

在之前的文章中,主要涉及Springboot的启动原理以及Jar包方式运行的理解。
接下来进入Springboot应用之前,先回顾一下Servlet与SpringMVC相关知识。
Spring MVC时序图这边文章中有我辛苦整理的SpringMVC相关的时序图,参照着更加方便理解。

Servlet3.0

Servlet3.0之前,需要在Web.xml中配置三大组件:Servlet、Filter、Listener,此外包括核心的DispatchServlet也需要在web.xml中注册。而Servlet3.0之后,可以直接通过注解进行快速搭建。
关于Servlet、Filter、Listener这些如何使用就不再赘述,下面主要去理解Servlet的原理。

ServletContainerInitializer(SCI)

为了支持可以不使用web.xml,提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。

SPI(Service Provider Interface):Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。Java提供ServiceLoader来实现SPI,它内部会new一个LazyIterator类,来扫描所引用Jar包中META_INF/services/目录下的配置文件,解析并加载对应的类

SPI机制我们就可以在不修改Jar包或框架的时候为Api提供新实现。
优点
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点
1.ServiceLoader算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
2.获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
3.多个并发多线程使用ServiceLoader类的实例是不安全的。

public final class ServiceLoader<S> implements Iterable<S> {
    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";
    // 被加载的类或接口
    private final Class<S> service;
    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;
    // 上下文对象
    private final AccessControlContext acc;
    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;
    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;
        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取目录下所有的类
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }
        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}
HandlesTypes

SCI必须配合@HandlesTypes注解,容器启动的时候会将这个注解指定的类型下面的实现类传递过来,并且在OnStart方法中作为参数调用。

public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {}

onStart方法的第一个参数是一个class类型的set,对应的就是HandlesTypes注解中设置的类型(确切的说是子类或者子接口)
onStart方法的第二个参数s是ServletContext,则可以在启动的时候,通过ServletContext注册web组件(Servlet,Filter,Listener)
对于我们自己提供的Servlet,可以通过@WebServlet,@WebFilter,@WebListener直接使用,而对于阿里或者其他第三方提供的三大组件,就可以通过这个参数来实现引用

//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
//传入感兴趣的类型;
@HandlesTypes(value={JamesService.class})
public class JamesServletContainerInitializer implements ServletContainerInitializer{
   /**
    * tomcat启动时加载应用的时候,会运行onStartup方法;
    * 
    * Set<Class<?>> arg0:感兴趣的类型的所有子类型(对实现了JamesService接口相关的);
    * ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
    * 
    * 使用ServletContext注册Web组件(Servlet、Filter、Listener),一般在WebApplicationInitializer里实现。
    */
   public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
      System.out.println("感兴趣的类型:");
      for (Class<?> claz : arg0) {
         System.out.println(claz);//当传进来后,可以根据自己需要利用反射来创建对象等操作
         
         try {
            if(claz.getName().contains("JamesServiceImpl")) {
               System.out.println(claz.newInstance());
            }
            
         } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      } 
      //这里也可以做,但是一般都在WebApplicationInitializer中注册。
      //注册servlet组件
      javax.servlet.ServletRegistration.Dynamic servlet = arg1.addServlet("orderServlet", new OrderServlet());
      //配置servlet的映射信息(路径请求)
      servlet.addMapping("/orderTest");
      //注册监听器Listener
      arg1.addListener(OrderListener.class);    
      //注册Filter
      javax.servlet.FilterRegistration.Dynamic filter = arg1.addFilter("orderFilter", OrderFilter.class);
      //添加Filter的映射信息,可以指定专门来拦截哪个servlet
      filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
   }
}

SpringServletContainerInitializer与WebApplicationInitializer

在spring-web的jar中,提供了META-INF/services/javax.servlet.ServletContainerInitializer文件,内容是org.springframework.web.SpringServletContainerInitializer类,所以我们使用SpringMVC,负责对容器启动时相关组件进行初始化的就是SpringServletContainerInitializer类。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        //调用反射进行实例化加载进来的类
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(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);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

通过上述源码发现,SpringServletContainerInitializer主要处理的是通过HandlesTypes注解传进来的WebApplicationInitializer接口的实现类。

WebApplicationInitializer

综上所述,SpringMVC中对容器启动时进行组件初始化的实际类是WebApplicationInitializer的实现类。
WebApplicationInitializer所有实现类都会被HandlesTypes注解传递到SpringServletContainerInitializer中,进行实例化,并调用onStartup()方法。

   public class MyWebAppInitializer implements WebApplicationInitializer {
  
      @Override
      public void onStartup(ServletContext container) {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext =
          new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);
  
        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));
  
        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext =
          new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(DispatcherConfig.class);
  
        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher =
          container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
      }
  
   }

这段代码是Javadoc上提供的,在onStartup()方法中创建了root ApplicationContext,并交由ContextLoaderListener管理生命周期,另外还创建了Servlet ApplicationContext,来创建DispatcherServlet,最终都交由ServletContext进行管理。
WebApplicationInitializer的实现类:

  • AbstractContextLoaderInitializer:主要是创建根容器rootAppContext ,并且添加Listener。
public void onStartup(ServletContext servletContext) throws ServletException {
    this.registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
    //createRootApplicationContext由子类实现
    WebApplicationContext rootAppContext = this.createRootApplicationContext();
    if (rootAppContext != null) {
        //创建ContextLoaderListener
        ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
        listener.setContextInitializers(this.getRootApplicationContextInitializers());
        //添加Servlet的Listener
        servletContext.addListener(listener);
    } else {
        this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
    }
}
  • AbstractDispatcherServletInitializer:继承自AbstractContextLoaderInitializer,所以也会调用registerContextLoaderListener方法。
    同时自己也提供了registerDispatcherServlet方法,来对DispatcherServlet初始化,并且设置servletMapping还有Filter等。
public void onStartup(ServletContext servletContext) throws ServletException {
    //调用AbstractContextLoaderInitializer的onStartup()方法,创建rootApplicationContext
    super.onStartup(servletContext);
    this.registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = this.getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");
    //createServletApplicationContext()由子类实现
    WebApplicationContext servletAppContext = this.createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    //FrameworkServlet是DispatcherServlet的父类
    FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
    //添加DispatcherServlet
    Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);//将dispatcherServlet加到servletContext中。
    if (registration == null) {
        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
    } else {
        registration.setLoadOnStartup(1);
        registration.addMapping(this.getServletMappings());
        registration.setAsyncSupported(this.isAsyncSupported());
        Filter[] filters = this.getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            Filter[] var7 = filters;
            int var8 = filters.length;

            for(int var9 = 0; var9 < var8; ++var9) {
                Filter filter = var7[var9];
                //注册Filter
                this.registerServletFilter(servletContext, filter);
            }
        }

        this.customizeRegistration(registration);
    }
}
  • AbstractAnnotationConfigDispatcherServletInitializer:继承自AbstractDispatcherServletInitializer,主要就是提供了createRootApplicationContext()和createServletApplicationContext()(模板方法)。
protected WebApplicationContext createRootApplicationContext() {
    Class<?>[] configClasses = this.getRootConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(configClasses);
        return context;
    } else {
        return null;
    }
}
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    Class<?>[] configClasses = this.getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}

这里需要解释一下Root容器和Servlet容器的区别:
Servlet容器会包括Controllers,viewResolver视图解析器,以及HandlerMapping(web相关组件);
Root容器则包含Services和Repositories也就是对服务层和数据源DAO层以及事物控制层。

image.png

自定义WebApplicationInitializer,都是实现AbstractAnnotationConfigDispatcherServletInitializer,提供root以及Servlet的配置文件。

//web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
public class JamesWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
   //获取根容器的配置类;(Spring的配置文件)   父容器;
   @Override
   protected Class<?>[] getRootConfigClasses() {
      //指定配置类(配置文件)位置
      return new Class<?>[]{JamesRootConfig.class} ;
   }
   //获取web容器的配置类(SpringMVC配置文件)  子容器;
   @Override
   protected Class<?>[] getServletConfigClasses() {
      return new Class<?>[]{JamesAppConfig.class} ;
   }
   //获取DispatcherServlet的映射信息
   //  /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
   //  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;在tomcat的web.xml中配置了jspServlet,这个servlet会专门过滤jsp路径,且这个mapping 的优先级比我们定义的ServletMapping要高。同理在tomcat的web.xml中还有一个Servletmapping专门处理静态资源的,DefaultServlet,在APPConfig中配置了

   @Override
   protected String[] getServletMappings() {
      // TODO Auto-generated method stub
      return new String[]{"/"};
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容