前言

此文章是对SpringMVC注解开发的demo配置以及通过Debug对启动流程做一个大概的分析。

介绍

通过SpringMVC注解驱动开发,我们就无需使用web.xml、springmvc配置文件对spring mvc进行配置。
SpringMVC注解驱动开发是基于Servlet3.0新特性的基础上实现的,准确来说是基于servlet3.0的ServletContainerInitializer接口实现的。

接口和类介绍:
public interface ServletContainerInitializer {//servlet3.0的新接口,tomcat会在启动时调用该接口的实现类方法。public void onStartup(Set<Class<?>> c, ServletContext ctx)throws ServletException;
}
//ServletContainerInitializer 接口的实现类,在spring的web包下,如果是基于springmvc注解驱开发,
//tomcat容器会在启动时加载工程时调用该类的onStartup方法。//真正处理springmvc启动的接口用该注解HandlesTypes定义。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {/**** @param webAppInitializerClasses  该参数就是真正进行初始化的实现类对象集合,就是上面HandlesTypes注解的value值*                                  WebApplicationInitializer接口的所有实现类对象集合。* @param servletContext   servlet上下文* @throws ServletException*/@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {//定义初始化对象的集合List<WebApplicationInitializer> initializers = new LinkedList<>();if (webAppInitializerClasses != null) {//遍历webAppInitializerClassesfor (Class<?> waiClass : webAppInitializerClasses) {//这里做出一些判断、防止一些servlet容器为我们提供无效的处理类// 首先该类不能是接口,并且不能是抽象类,且必须是WebApplicationInitializer接口的实现类if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {//符合上面判断进入这里。//为符合规则的处理类(WebApplicationInitializer实现类)创建对象并添加到集合List中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) {//遍历处理对象,执行处理方法onStartupinitializer.onStartup(servletContext);}}}

在spring-web包的META-INF.services下有一个javax.servlet.ServletContainerInitializer文件,文件的内容是org.springframework.web.SpringServletContainerInitializer ,这个是使用SPI机制创建的SpringServletContainerInitializer 对象。SPI机制详情查看相关文章。

//该接口是springmvc初始化的处理的顶级接口。
//类结构如下图
public interface WebApplicationInitializer {void onStartup(ServletContext servletContext ) throws ServletException;
}


spring为该接口的提供的都是抽象类,所以我们要使用注解驱动的springmvc,就要实现自己的实现类。我们可以通过继承AbstractDispatcherServletInitializer或者AbstractAnnotationConfigDispatcherServletInitializer抽象类实现自己的启动类。

这里就通过继承AbstractDispatcherServletInitializer实现吧。

所以要配置一个注解驱动的springmvc项目环境,基本有以下几步:
第一步:引入相关Jar包。
第二步:编写spring配置类,用于配置spring IOC父容器。
第三步:编写springmvc配置类,用于配置spring mvc子容器。
第四步:编写AbstractDispatcherServletInitializer实现类,完成对springmvc的初始化。

springmvc注解驱动环境搭建

第一步:引入jar包
 <dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>5.1.9.RELEASE</version></dependency><!--必须3.0以上版本--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.1</version><scope>provided</scope></dependency>
第二步:编写spring配置文件和springmvc配置文件

/*** @author YeHaocong* @decription spring配置文件 用于创建IOC父容器*/@Configuration
//扫描com.spring.springmvcdemo包及其子包,但是排除Controller注解,这是为了实现父子容器,表现层的Bean保存在子容器WEB IOC容器
//其他Bean保存在父容器中,子容器可以访问父容器的bean,父容器不能访问子容器的bean。
@ComponentScan(basePackages = "com.spring.springmvcdemo",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
public class SpringConfig {}/*** @author YeHaocong* @decription springMVC配置文件,用于配置Bean到子容器中。*/@Configuration
//扫描web包及其子包
@ComponentScan(basePackages = "com.spring.springmvcdemo.web")
public class SpringMVCConfig {//配置一个视图解析器到springmvc子容器中。@Beanpublic ViewResolver viewResolver(){InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/pages/");viewResolver.setSuffix(".jsp");return viewResolver;}
}
第三步:编写springmvc初始化类

这里选择继承AbstractDispatcherServletInitializer。

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {/*** DispatcherServlet   servlet名称*/public static final String DEFAULT_SERVLET_NAME = "dispatcher";//初始化方法@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {//先调用父类的onStartup方法super.onStartup(servletContext);//该类的onStartup方法主要是注册一个DispatcherServlet到servletContext中,用于完成对请求的分发。registerDispatcherServlet(servletContext);}/*** 注册一个DispatcherServlet*/protected void registerDispatcherServlet(ServletContext servletContext) {//获取servlet名称 dispatcherString servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return empty or null");WebApplicationContext servletAppContext = createServletApplicationContext();Assert.notNull(servletAppContext,"createServletApplicationContext() did not return an application " +"context for servlet [" + servletName + "]");//创建一个dispatcherServletFrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());//注册到servletContext上下文中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);//添加mapping 前缀,就是类似项目名称,端口号localhost:8080/前缀/真正的requestmappingregistration.addMapping(getServletMappings());//设置是否支持异步registration.setAsyncSupported(isAsyncSupported());//获取过滤器Filter[] filters = getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {//把过滤器注册到servletContext上下文中registerServletFilter(servletContext, filter);}}//自定义注册customizeRegistration(registration);}/*** */protected String getServletName() {return DEFAULT_SERVLET_NAME;}/*** 创建一个web容器*/protected abstract WebApplicationContext createServletApplicationContext();/****/protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {return new DispatcherServlet(servletAppContext);}/*** */@Nullableprotected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {return null;}/*** */protected abstract String[] getServletMappings();/*** 获取过滤器,返回空,如果要在springmvc启动时注册过滤器可以覆盖该方法*/@Nullableprotected Filter[] getServletFilters() {return null;}/*** 注册过滤器到servletContext上下文中*/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) {}}

我的实现类:

public class WebAnnotationInitializer extends AbstractDispatcherServletInitializer {//创建一个Springmvc子容器。@Overrideprotected WebApplicationContext createServletApplicationContext() {//创建容器对象AnnotationConfigWebApplicationContext servletContext = new AnnotationConfigWebApplicationContext();//使用SpringMVCConfig配置类对容器进行初始化。servletContext.register(SpringMVCConfig.class);//返回return servletContext;}//设置项目访问url的前缀,比如如下设置,访问hello接口就要 http://localhost:8080/springmvc/hello//如果不想用前缀,就使用   “/”就好。@Overrideprotected String[] getServletMappings() {return new String[]{"/springmvc/*"};}//创建spring IOC的父容器/根容器  @Overrideprotected WebApplicationContext createRootApplicationContext() {//创建AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();//传入SpringConfig配置类对容器进行初始化rootContext.register(SpringConfig.class);//返回return rootContext;}/*** 在springmvc初始化时添加一些过滤器* @return*/@Overrideprotected Filter[] getServletFilters() {//添加一个编码过滤器CharacterEncodingFilter filter = new CharacterEncodingFilter();filter.setEncoding("UTF-8");return new Filter[]{filter};}
}

最后一步:编写controller类用于测试:

/*** @author YeHaocong* @decription controller*/@Controller
public class SpringMVCDemoController {@RequestMapping("/hello")public String springMVCDemo(){System.out.println("进入此方法");//返回视图return "springmvc";}
}

jsp所在位置

添加web容器,我这里是tomcat8.5。

启动tomcat。运行结果如下:

该项目的详细启动流程如下:

  1. tomcat启动时调用从SpringServletContainerInitializer的onStartup方法。找到我们自定义的初始化类。

    从上图可以看出是使用了我们的自定义初始化类WebAnnotationInitializer 。

  2. 为我们的初始化类创建对象,并且调用它的onStartup()方法。

  3. 由于我们没有复写父类AbstractDispatcherServletInitializer 的onStartup方法,所以实际上是调用AbstractDispatcherServletInitializer 的onStartup方法。

  4. AbstractDispatcherServletInitializer 的onStartup方法首先调用了它的父类AbstractContextLoaderInitializer的onStartup方法。

    该方法主要是注册一个上下文的监听器和创建IOC父容器。

  5. 创建IOC父容器。

    因为我们的自定义初始化类中复写了该方法,所以实际调用的是我们的自定义初始化类的方法。

  6. 调用完父类的onStartup方法,就返回到AbstractDispatcherServletInitializer的onStartup方法。

    注册一个DispatcherServlet到servlet上下文中。


注册springmvc子容器,因为我们的自定义初始化类中复写了该方法,所以实际调用的是我们的自定义初始化类的方法。

  1. 创建DispatcherServlet并注册到servletContext中。

  2. 添加Mapping的前缀。

    因为我们的自定义初始化类中复写了该方法,所以实际调用的是我们的自定义初始化类的方法。

  3. 获取Filter并添加到servletContext中

    因为我们的自定义初始化类中复写了该方法,所以实际调用的是我们的自定义初始化类的方法。

  4. 执行一些自定义的注册,由于我们demo没有复写该具体方法,所以不做任何事。

    至此,基本初始化完毕,可以接收请求了。

SpringMVC注解驱动开发相关推荐

  1. 0、Spring 注解驱动开发

    0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...

  2. 【视频分享】尚硅谷Java视频教程_Spring注解驱动开发视频教程

    <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了S ...

  3. SPRING注解驱动开发-雷神课程超详细笔记

    SPRING注解驱动开发-雷神课程超详细笔记 时间:2021-03-21 2022-04-06更新:最近翻起一年多前写的笔记复习,还是收获颇多,很多当时无法理解的知识现在慢慢能理解了,可能是工作一年的 ...

  4. spring注解驱动开发-10 Servlet3.0

    Spring AOP实现 前言 servlet3.0简介 ServletContainerInitializer shared libraries(共享库) / runtimes pluggabili ...

  5. 【Spring注解驱动开发】使用@Scope注解设置组件的作用域

    写在前面 Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象.如果每次 ...

  6. Spring注解驱动开发第26讲——总有人让我给他讲讲@EnableAspectJAutoProxy注解

    @EnableAspectJAutoProxy注解 在配置类上添加@EnableAspectJAutoProxy注解,便能够开启注解版的AOP功能.也就是说,如果要使注解版的AOP功能起作用的话,那么 ...

  7. Spring注解驱动开发学习总结8:自动装配注解@Autowire、@Resource、@Inject

    Spring注解驱动开发学习总结8:自动装配注解@Autowire.@Resource.@Inject 1.自动装配@Autowire.@Resource.@Inject 1.1 构建bookDao ...

  8. 【Spring注解驱动开发】二狗子让我给他讲讲@EnableAspectJAutoProxy注解

    写在前面 最近,二狗子入职了新公司,新入职的那几天确实有点飘.不过慢慢的,他发现他身边的人各个身怀绝技啊,有Spring源码的贡献者,有Dubbo源码的贡献者,有MyBatis源码的贡献者,还有研究A ...

  9. spring注解驱动开发-5 Spring AOP实现

    Spring AOP实现 前言 AOP案例实现 1.编写目标类 2.编写切面类 3.编写配置类 4.编写测试类 end... 前言 AOP为Aspect Oriented Programming的缩写 ...

最新文章

  1. windows xp开机音乐7秒_(老旧电脑的福音)win 10 ltsb2015开机只要7秒
  2. 这样写的博客才有更多的人愿意看
  3. shell一段脚本的一点经验(实时文件流读写)
  4. HDU 2152 选课时间(题目已修改,注意读题) (母函数)
  5. Are We Ready for SDN? Implementation Challenges for Software-Defined Networks
  6. mySQL教程 第1章 数据库设计
  7. 国内创业活动平台,创业赛事报名网站
  8. 无限分级 层次输出 demo
  9. 关于sql多条件查询,但又不确定值的个数的方法。
  10. Spring JDBC 框架中, 绑定 SQL 参数的另一种选择:具名参数(named parameter)
  11. x390开机键_【ThinkPadX390评测】ThinkPad X390 4G版全球首测:全时在线超长续航的便携商务利器(全文)_ThinkPad X390_笔记本评测-中关村在线...
  12. vijos-伊甸园日历游戏
  13. 千帆竞发待东风――乱弹网络游戏广告
  14. leaflet 常用方法总结
  15. 分页插件之--kaminari
  16. 湖大计算机学院博士后李晓灿,李蕊-湖大信息科学与工程学院
  17. Android加载网络图片学习过程
  18. windows函数(system)
  19. 深入探访支付宝双11十年路,技术凿穿焦虑与想象极限 | CYZONE特写 1
  20. 关于 xx客 的词的解释

热门文章

  1. 安卓学习笔记26:菜单
  2. 英语学习笔记2019-10-25
  3. 【BZOJ1443】游戏Game,博弈+二分图匹配
  4. 语言程序设计赵山林电子版_【特别策划】崇州“老市长”赵抃系列之一:做官要像江水保持清白...
  5. python函数封装总结_python 笔记 之 函数封装成类
  6. 2017.5.6 表达式的值 思考记录
  7. 【英语学习】【WOTD】avuncular 释义/词源/示例
  8. Pentium 4处理器架构/微架构/流水线 (7) - NetBurst前端详解 - 分支预测
  9. barbuttonitem 文字换行_ios开发 常用代码整理
  10. css 字体倾斜_css笔记3(文本,列表,背景声明)