在最新的SpringMVC中,一个web项目中无需传统的web.xml文件,这是怎么实现的呢?其实这并不是SpringMVC的功劳,而是servlet3规范以及web容器对这个规范的支持。
简单使用
配置
引入依赖:
... ....
<!-- 指定servlet版本为3.0 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 指定作用域为provided,避免与tomcat冲突 -->
<scope>provided</scope>
</dependency>
... ...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 忽略无xml的错误 -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
自定义Servlet
package com.morris.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello");
resp.getWriter().write("hello servlet 3.0");
}
}
自定义ServletContainerInitializer
ServletContainerInitializer有一个onStartup方法,这个方法会在web容器启动时被调用,可以使用这个方法向容器中注入Servlet、Listener、Filter。
package com.morris.servlet;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import java.util.Set;
public class MyServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("MyServletContainerInitializer");
ServletRegistration.Dynamic servlet = servletContext.addServlet("helloServlet", HelloServlet.class);
servlet.addMapping("/hello");
}
}
配置ServletContainerInitializer
,在resources目录下新建META-INF/services/javax.servlet.ServletContainerInitializer,文件内容为自定义的ServletContainerInitializer的全限定名:
com.morris.servlet.MyServletContainerInitializer
容器在启动时会扫描所有jar中的META-INF/services/javax.servlet.ServletContainerInitializer。
发布至tomcat
servlet3.0需要tomcat7及其以上版本,这里使用内置的Tomcat。
引入Tomcat的依赖:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.23</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.16</version>
</dependency>
使用代码方式启动Tomcat:
package com.morris;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;
import javax.servlet.ServletException;
import java.io.File;
public class Application {
public static void main(String[] args) throws LifecycleException, ServletException {
Tomcat tomcat = new Tomcat(); // 创建Tomcat容器
tomcat.setPort(8080); // 端口号设置
String basePath = System.getProperty("user.dir");
tomcat.setBaseDir(basePath);
//改变文件读取路径,从resources目录下去取文件
StandardContext ctx = (StandardContext) tomcat.addWebapp("/", basePath + File.separator + "src" + File.separator + "main" + File.separator + "resources");
// 禁止重新载入
ctx.setReloadable(false);
// class文件读取地址
File additionWebInfClasses = new File(basePath, "target/classes");
// 创建WebRoot
WebResourceRoot resources = new StandardRoot(ctx);
// tomcat内部读取Class执行
resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/"));
ctx.setResources(resources);
tomcat.start();
// 异步等待请求执行
tomcat.getServer().await();
}
}
注入组件的方法
注解
加了@WebServlet、@WebFilter、@WebListener的类会自动扫描并加入到容器中,无需配置。
@WebServlet
package com.morris.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("user");
resp.getWriter().write("hello morris");
}
}
类似web.xml中:
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.morris.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
@WebFilter
package com.morris.servlet;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("myFilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("myFilter doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("myFilter destroy");
}
}
类似web.xml中:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.morris.servlet.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
@WebListener
package com.morris.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("myListener contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("myListener contextDestroyed");
}
}
类似web.xml中:
<listener>
<listener-class>com.morris.servlet.MyListener</listener-class>
</listener>
ServletContext注入
public class MyServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("MyServletContainerInitializer");
// 手动添加servlet
ServletRegistration.Dynamic servlet = servletContext.addServlet("helloServlet", HelloServlet.class);
servlet.addMapping("/hello");
// 手动添加filter
FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// 手动添加listener
servletContext.addListener(MyListener.class);
}
}
可以通过两种方法获得ServletContext,也就是可以在这两个地方注入ServletContext(必须在项目启动的时候注入):
- 实现ServletContainerInitializer接口得到ServletContext
。 - 实现ServletContextListener接口得到ServletContext
,也就是Listener。
HandlesTypes
容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递到实现了ServletContainerInitializer接口的onStartup()方法的参数Set<Class<?>> set中。
假设现在HelloService的实现类或子接口如下:
在MyServletContainerInitializer启动类上面增加@HandlesTypes注解指定HelloService.class:
@HandlesTypes(HelloService.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
set.forEach(System.out::println);
}
}
运行结果如下:
interface com.morris.service.HelloServiceExt
class com.morris.service.HelloServiceImpl
class com.morris.service.AbstractHelloService
可见HelloService的实现类或子接口都加入到了Set集合中。