前言
诚如各位所知,Servlet3.0是一次Java EE规范的一次重要升级。支持到可以全部采用注解驱动,大大简化了配置web.xml的麻烦。现在启动一个web容器并不强制依赖于web.xml部署描述符了。
然后我们印象深刻的是,之前我们在使用Spring MVC的时候,DispatcherServlet是必须要要在web.xml里配置,现在没有了这个,我们该怎么办呢?
本文主要以全注解驱动整合Spring MVC(注意:非Spring Boot环境,否则内部细节都看不到了)抛出问题,从而从内部原理方面去了解里面的门道。
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)
准备工作
准备一个Spring MVC的maven工程
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.fsx</groupId> <artifactId>demo-war</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> <version>1.18.4</version> </dependency> <!-- 记录log日志 logback-core并不需要显示导入--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- Spring MVC自动数据封装依赖的包 否则可能出现下面的错误,若使用@RequestBody的时候 --> <!-- Content type 'application/json' not supported 当然还有其余配置,原理了解--> <!-- 此处需要导入databind包即可, jackson-annotations、jackson-core都不需要显示自己的导入了--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency> </dependencies> <build> <plugins> <!-- 该插件是为了没有web.xml情况下,打war包。编译不要报错 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <!-- 编译环境在1.8编译 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <compilerVersion>${java.version}</compilerVersion> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> </plugins> <!-- directory:属性指定资源文件放置的目录。 includes:包含哪些配置文件(.class文件不用写) filtering:如果设置为false的话,则表示上文的filters配置失效;如果设置为true,则会根据${env}.properties里面的键值对来 填充includes指定文件里的${xxxx}占位符(若不做环境区分,一般就是false即可) --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> <include>**/*.tld</include> </includes> <filtering>false</filtering> </resource> </resources> </build> </project>
然后个logback.xml一个最小配置:
<configuration scan="true" scanPeriod="60 seconds" debug="false"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
备注:运行本war包的web容器为:tomcat-8.0(最高支持到了Servlet3.1~)
写一个最基本的Servlet,然后就可以访问了http://localhost:8080/demowar_war/hello
:
/** * @author fangshixiang * @description * @date 2019-02-16 22:04 */ @WebServlet(urlPatterns = "/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello servlet..."); } }
我们发现,比之前采用web.xml配置的方式,省事太多了,可谓非常方便。
ServletContainerInitializer
之前web容器要整合其余模块,都是通过web.xml来的。那么现在注解驱动的话,怎么做呢?
这就是Servlet3.0给我们提供的特别特别重要的一个类ServletContainerInitializer来整个其它模块组件。通过读Servlet3.0的官方文档如下:
大致可以看出如下意思,它有如下能力:
Shared libraries(共享库) / runtimes pluggability(运行时插件能力)
1.Servlet容器启动会扫描,当前应用里面每一个jar包ServletContainerInitializer的实现
2.coder可以自己提供ServletContainerInitializer的实现类;然后自己书写逻辑。但是,但是,但是要记住,一定要必须绑定在,META-INF/services/javax.servlet.ServletContainerInitializer这个文件里,文件内容为就是ServletContainerInitializer实现类的全类名;
这样web容器在启动的时候,就会执行该接口的实现方法,从而我们就可以书写我们自己的模块初始化的一些逻辑。
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来; @HandlesTypes(value = {HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 应用启动的时候,会运行onStartup方法; * <p> * Set<Class<?>> c:感兴趣的类型的所有子类型; * ServletContext ctx:代表当前Web应用的ServletContext;一个Web应用一个ServletContext; * <p> */ @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { //这里的c会把所有我们感兴趣的类型都拿到 System.out.println("感兴趣的类型:"); for (Class<?> claz : c) { System.out.println(claz); } //==========================编码形式注册三大组件============================ 注册组件 ServletRegistration //ServletRegistration.Dynamic servlet = ctx.addServlet("userServlet", new UserServlet()); 配置servlet的映射信息 //servlet.addMapping("/user"); // 注册Listener //ctx.addListener(UserListener.class); // 注册Filter FilterRegistration //FilterRegistration.Dynamic filter = ctx.addFilter("userFilter", UserFilter.class); 配置Filter的映射信息 //filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); } }
启动容器,我们会看到:
把我们关心的接口子类型(包含子接口、抽象类、实现类)都放进来了。但是需要注意:不包含自己哦~
使用ServletContext注册Web组件(Servlet、Filter、Listener)
使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
必须在项目启动的时候来添加(为了安全考虑,若已经启动完成再添加,是无效的)
1)、ServletContainerInitializer得到的ServletContext;
2)、ServletContextListener得到的ServletContext;
/不拦截.jsp。而/*都会拦截
DispatcherServlet映射:/
Filter映射:/*
Spring MVC拦截器的映射:/**