原理简析

1. 背景知识:org.springframework.web.ServletContainerInitializer接口

在基于注解的servlet开发中,ServletContainerInitializer接口用于代替web.xml。它只有一个方法:onStartup,可以在其中注册servlet、拦截器(Filter)、监听器(Listener)这三大组件。另外,ServletContainerInitializer还可以使用@HandlesTypes在onStartup方法的参数列表中注入感兴趣的类。servlet容器启动时,会扫描每个jar包的项目根目录下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,执行这个文件中指定的ServletContainerInitializer接口的实现类的onStartup方法。

2. org.springframework.web包提供的ServletContainerInitializer实现类

org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer文件指定了ServletContainerInitializer接口的实现类:SpringServletContainerInitializer,首先来看一下这个类的spring源码:

package org.springframework.web;

@HandlesTypes(WebApplicationInitializer.class)  //(1)
public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); 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) waiClass.newInstance()); //(2)
}
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); //(3)
}
} }

(1) 通过@HandlesType注解在onStartup方法的参数列表中注入感兴趣的类,即WebApplicationInitializer;

(2) 将WebApplicationInitializer的每个实现类,都新建一个实例,并放入initializers列表中;

(3) 遍历initializers列表,对每个WebApplicationInitializer实例执行其onStartup方法。

那么问题来了:WebApplicationInitializer有哪些实现类,是用来干什么的?

3. WebApplicationInitializer的实现类及其功能

WebApplicationInitializer的实现类有很多,重点看一下AbstractAnnotationConfigDispatcherServletInitializer

package org.springframework.web.servlet.support;

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer { @Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
} @Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
} @Nullable
protected abstract Class<?>[] getRootConfigClasses(); @Nullable
protected abstract Class<?>[] getServletConfigClasses(); }

这个类提供了两个方法的实现,以及两个抽象方法供子类继承

(1) createRootApplicationContext:创建根容器;

(2) createServletApplicationContext:创建servlet容器;

(3) getRootConfigClasses:抽象类,用于注册根容器的配置类,相当于spring.xml;

(4) getServletConfigClasses:抽象的类,用于注册servlet容器的配置类,相当于springmvc.xml;

另外,它还从AbstractDispatcherServletInitializer类继承了getServletMappings方法,用于注册servlet的映射。

因此,我们可以自定义一个WebApplicationInitializer的实现类,继承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器启动时,会创建spring根容器和servlet容器,代替web.xml配置文件。同时,我们可以看到,在基于注解的springmvc开发中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,这与本文开头提到的基于注解的servlet开发有些区别。

4. 根容器和servlet容器

根容器用于管理@Service、@Repository等业务逻辑层和数据库交互层组件;

servlet容器用于管理@Controller、视图解析器、拦截器等跟页面处理有关的组件。

使用步骤

0. 导包或添加依赖:spring-web、spring-webmvc

1. 编写数据库访问层、业务逻辑层、控制层等组件,这个跟基于配置文件的springmvc没有区别;

2. 编写根容器和servlet容器的配置类,这里不需要添加@Configuration注解;

3. 自定义WebApplicationInitializer,继承AbstractAnnotationConfigDispatcherServletInitializer;

4. 在3的实现类中注册根容器和servlet容器的配置类,以及servlet映射;

5. 在servlet容器中中注册视图解析器、拦截器等组件,用法是使servlet容器配置类实现WebMvcConfigurer接口,

然后选择相应的方法进行注册,详见示例demo。

示例Demo

pom文件

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6-SNAPSHOT</version>
</dependency>

业务逻辑层组件

package cn.monolog.annabelle.springmvc.service;

import org.springframework.stereotype.Service;

/**
* 业务逻辑层组件
* created on 2019-05-04
*/
@Service
public class BusinessService { public String resolve(String source) {
return "hello " + source;
}
}

控制层组件

package cn.monolog.annabelle.springmvc.controller;

import cn.monolog.annabelle.springmvc.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; /**
* 控制层组件
* created on 2019-05-04
*/
@Controller
@RequestMapping(value = "/business")
public class BusinessController { //从容器中自动装配组件
@Autowired
private BusinessService businessService; @GetMapping(value = "/resolve")
@ResponseBody
public String resolve(String source) {
String result = this.businessService.resolve(source);
return result;
} }

自定义springmvc拦截器

package cn.monolog.annabelle.springmvc.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 自定义的springmvc拦截器
* created on 2019-05-12
*/
public class CustomedInterceptor implements HandlerInterceptor { /**
* 重写preHandle方法
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle正在执行...");
return true;
} /**
* 重写postHandle方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle正在执行...");
} /**
* 重写afterCompletion方法
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion正在执行...");
}
}

根容器的配置类,用于管理数据库访问层、业务逻辑层等组件,相当于spring.xml

package cn.monolog.annabelle.springmvc.config;

import org.springframework.context.annotation.ComponentScan;

/**
* 根容器配置类
* created on 2019-05-04
*/
@Configuration
@ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.service"})
public class RootConfig {
}

servlet容器的配置类,用于管理控制层、视图解析器等组件,相当于springmvc.xml

可以在其中配置视图解析器、静态资源解析器、拦截器等组件

package cn.monolog.annabelle.springmvc.config;

import cn.monolog.annabelle.springmvc.interceptor.CustomedInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*; /**
* servlet容器配置类
* @EnableWebMvc相当于配置文件中的<mvc:annotation-drivern />
* created on 2019-05-04
*/
@Configuration
@ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.controller"})
@EnableWebMvc
public class ServletConfig implements WebMvcConfigurer { /**
* 注册视图解析器
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
} /**
* 注册静态资源解析器
* 将springmvc处理不了的请求交给tomcat
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
} /**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomedInterceptor());
}
}

自定义的WebApplicationInitializer,用于注册根容器、servlet容器、servlet映射、拦截器、监听器等,相当于web.xml

package cn.monolog.annabelle.springmvc.initializer;

import cn.monolog.annabelle.springmvc.config.RootConfig;
import cn.monolog.annabelle.springmvc.config.ServletConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /**
* 自定义web容器启动器
* created on 2019-05-04
*/
public class CustomerServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /**
* 注册根容器配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
} /**
* 注册servlet容器配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ServletConfig.class};
} /**
* 注册servlet的映射
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

测试代码

<html>
<head>
<title>homepage</title>
<style type="text/css">
a {
color: blueviolet;
font-size: 20px;
}
</style>
</head>
<body>
<a href="/annabelle/business/resolve?source=violet">start...</a>
</body>
</html>

基于注解的springmvc开发的更多相关文章

  1. SpringMVC札集(03)——基于注解的SpringMVC入门完整详细示例

    自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onL ...

  2. Spring 基于注解零配置开发

    本文是转载文章,感觉比较好,如有侵权,请联系本人,我将及时删除. 原文网址:< Spring 基于注解零配置开发 > 一:搜索Bean 再也不用在XML文件里写什么配置信息了. Sprin ...

  3. 基于注解的SpringMVC简单介绍

    SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet负责转发每一个Request请 ...

  4. SpringMVC学习总结(四)——基于注解的SpringMVC简单介绍

    SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是 DispatcherServlet,DispatcherServlet负责转发每一个Request ...

  5. 【转载】基于注解的SpringMVC简单介绍

    SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet负责转发每一个Request请 ...

  6. Spring基于注解及SpringMVC

    1.使用注解 (1)组件扫描 指定一个包路径,Spring会自动扫描该包 及其子包所有组件类,当发现组件类定义前有 特定的注解标记时,就将该组件纳入到Spring 容器.等价于原有XML配置中的< ...

  7. 基于注解的SpringMVC

    相比传统的继承Controller体系中某些类的方式,SpringMVC的注解具有以下优点: 1.Controller不再需要继承某个特定类,只是简单的POJO. 2.请求映射的配置非常方便灵活. 3 ...

  8. 基于注解的SpringMVC整合JPA

    转载位置:http://www.blogjava.net/sxyx2008/archive/2010/11/02/336768.html 实体类 Department package com.sj.b ...

  9. spring in action 第五章基于注解搭建SpringMvc环境

    request的生命历程

随机推荐

  1. 索尼展示基于MicroLED技术的16K显示屏:约780吋

    尽管 8K 彩电刚刚在消费级市场崭露头角,更极致的 16K 却已不慌不忙地登场了. 在日前于拉斯维加斯举办的 NAB 2019 展会上,索尼就秀出了旗下的 16K 显示设备,它目前正在日本横滨的资生堂 ...

  2. zabbix修改默认字体

    在zabbixweb界面里有时候显示界面为框框 所以修改默认字体 现在windows里  随便找个字体: win+R  搜fonts 然后随便复制了一个字体,传到zabbix服务器里 cp calib ...

  3. windows2008R2下安装sqlserver2008R2时,点setup.exe应用程序无法打开错误代码0xc0150004

    windows2008R2下安装sqlserver2008R2时,点setup.exe应用程序无法打开错误代码0xc0150004 问题截图: 网上查的答案都是需要安装.net framework 3 ...

  4. [USACO12FEB]牛券Cow Coupons(堆,贪心)

    [USACO12FEB]牛券Cow Coupons(堆,贪心) 题目描述 Farmer John needs new cows! There are N cows for sale (1 <= ...

  5. java一键搭建新项目(地址)

    构建地址:   https://start.spring.io 文档地址:https://www.cnblogs.com/ityouknow/p/5662753.html

  6. [COGS 755]山海经:线段树

    网上似乎这道题的题解很少?写一个吧 我跟这道题的渊源追溯到了上个学期刚刚学线段树的那一天... 当时线段树专题前边的题都是一些板子就不一会就水过了,然后就看到了最后一题的它:山海经 那一个上午,我竭尽 ...

  7. create-react-app踩坑记

    前言 哇,不的不说这个react 这个脚手架create-react-app脚确实有很多问题,哈哈,下面来看看吧有哪些坑: 引用sass或者less 记得16年还是几年是不支持sass,和less的, ...

  8. websocket和通信

    最近默默的在学websocket,推荐的当然是阮一峰的博客了,其中也学到了不少  可以去看看咯 http://www.ruanyifeng.com/blog/2017/05/websocket.htm ...

  9. Vue全局混入

    混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式.混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项. 数据对象合并 数据对象在 ...

  10. Python条件控制与循环

    条件控制语句:if 循环语句:while.for 其他语句:continue.break.pass 1.if语句 # ================================ a = 1 if ...