基于 servlet3.0以及以上版本
注册Servlet的多种方式
web.xml
metadata-complete
metadata-complete=true ,表示只使用 web.xml,不读取annotation 以及Web fragment。
编写Servlet
public class XmlServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("<h1> XML servlet </h1>");
}
}
注册Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_9527" version="3.1" metadata-complete="false">
<!-- metadata-complete=true ,表示只使用 web.xml,不读取annotation 以及Web fragment-->
<display-name>servlet3.x</display-name>
<servlet>
<servlet-name>xmlServlet</servlet-name>
<servlet-class>cn.jhs.XmlServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>xmlServlet</servlet-name>
<url-pattern>/xml</url-pattern>
</servlet-mapping>
</web-app>
结果
Annotation
servlet3.0之后支持@WebServlet,@WebFilter,@WebListener
,通过注解的方式来实现servlet/filter/listener的注册。
编写Servlet2
@WebServlet(name = "annoServlet",value = "/anno")
public class AnnoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("<h1> ANNO servlet </h1>");
}
}
结果
无需在web.xml
(注册)。
若存在web.xml 且metadata-complete="true",则会使得注解不生效
.
SPI - javax.servlet.ServletContainerInitializer
定义接口 Page 以及 实现类
public interface Page {
String getPageInfo(); //具体的处理逻辑
String getPath(); //定义path,类似 urlmapping-pattern
}
///----//
public class PageOne implements Page {
@Override
public String getPageInfo() {
return "page one info";
}
@Override
public String getPath() {
return "/pageOne";
}
}
///----//
public class PageTwo implements Page {
@Override
public String getPageInfo() {
return "model two info";
}
@Override
public String getPath() {
return "/pageTwo";
}
}
定义Controller
public class PageController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
Page page = resolvePage(req);
if (page != null) {
resp.getWriter().write("Page info: " + page.getPageInfo() + "<br/>");
}
}
//根据路径,来确定具体的Page
private Page resolvePage(HttpServletRequest req) {
String path = req.getRequestURI().substring(req.getContextPath().length());
Object obj = req.getServletContext().getAttribute("pages");
if (obj instanceof List) {
Optional<Page> first = ((List<Page>) obj).stream()
.filter(p -> path.equals(p.getPath()))
.findFirst();
if (first.isPresent()) {
return first.get();
}
}
return null;
}
}
该servlet没有带有注解,也没有在web.xml中注册!
自定义ServletContainerInitializer
通过自定义ServletContainerInitializer
,实现servlet的动态注册
//实现类必须使用: javax.servlet.annotation.HandlesTypes 注解
@HandlesTypes({Page.class}) //classload查找所有实现Page.class的类
public class MyInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> pageClasses, ServletContext ctx) throws ServletException {
List<Page> pages = new ArrayList<>();
if (pageClasses != null) {
for (Class<?> pageClass : pageClasses) {
if (!pageClass.isInterface() && !Modifier.isAbstract(pageClass.getModifiers())) {
try {
Page page = (Page) pageClass.newInstance();
pages.add(page);
} catch (Throwable ex) {
throw new ServletException( "Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (pages.size() > 0) {
ctx.setAttribute("pages", pages);
ServletRegistration.Dynamic servletRegistration = ctx.addServlet("pageController", PageController.class);
pages.forEach(p -> {
//为 PageServlet.class 添加mappings ,多个
servletRegistration.addMapping(p.getPath());
});
}
}
}
SPI
新建META-INF/services/javax.servlet.ServletContainerInitializer
文件,内容为:
cn.jhs.framework.MyInitializer
结果
web fragment
为了给开发人员更好的可插拔性和更少的配置,在这个版本(Servlet 3.0)的规范中,我们引入了web模块部署描述符片段(web fragment)的概念。web fragment是web.xml的部分或全部
,可以在一个类库或框架 jar包的META-INF/web-fragment.xml
。
web fragment是web应用的一个逻辑分区,描述符的顶级元素必须是web-fragment
且对应的描述符文件必须被称为web-fragment.xml
.
定义fragment.jar
1. 定义servlet
public class HiServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("<h2> hi:" + LocalDateTime.now() + " <h2>");
}
}
2.创建META-INF/fragment.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
<servlet>
<servlet-name>HiServlet</servlet-name>
<servlet-class>cn.jhs.HiServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HiServlet</servlet-name>
<url-pattern>/hi</url-pattern>
</servlet-mapping>
</web-fragment>
3.打包
fragment.jar的maven坐标为:
<groupId>cn.jhs</groupId>
<artifactId>servlet3x_web_fragment</artifactId>
<version>1.0-SNAPSHOT</version>
执行mvn install
引入fragment.jar
pom依赖中增加fragment.jar
的maven坐标即可。
执行
Listener动态添加
@WebListener
public class ServletContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletRegistration.Dynamic dynamic = sce.getServletContext().addServlet("innerServlet", new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("<h1> inner servlet </h1>");
}
});
dynamic.addMapping("/inner");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
执行顺序
web.xml和web-fragment.xml 注册顺序
由于规范允许应用配置由多个配置文件组成(web.xml 和 web-fragment.xml)的资源,从应用中多个不同位置发现和加载,顺序问题必须被解决。
web-fragment.xml
有一个顶级元素<name></name>
属性,且在一个web-fragment.xml中仅能有一个<name>
元素。
绝对顺序
在web.xml中的<absolute-ordering>
元素。在一个web.xml中仅能有一个<absolute-ordering>元素
。
<absolute-ordering>
<name>fragment1</name>
<name>fragment3</name>
<others/>
</absolute-ordering>
web.xml和WEB-INF/classes
必须在列在absolute-ordering元素中的所有web-fragment之前处理.- 按照
<absolute-ordering><name>...</name></absolute-ordering>
中<name>
绝对顺序执行- 当遍历
<absolute-ordering>
子元素,遇到多个子元素具有相同<name>元素,只需考虑首次出现的。
- 当遍历
<absolute-ordering>
可以包含零个或一个<others />
元素。- 如果
没有<others/>元素
,没有在<name />
明确提到的web-fragment必须被忽略。
- 如果
相对顺序
在web-fragment.xml
中的<ordering>
元素,一个web-fragment.xml只能有一个<ordering>元素。
<name>fragment1</name>
<ordering>
<after><name>fragment2</name></after>
<before><others/></before>
</ordering>
对于一个应用Listener、Servlet和Filter的调用顺序是很重要的!
Filter执行顺序
匹配请求的Filter链的顺序是它们在web.xml中声明的顺序。
如果是注解的Filter,按照 filter-name 自然顺序执行。
filter-name 不要带有如
- *
之类的符号, 最好是英文字母+数字组合。
Servlet匹配优先级
精准匹配
一个url匹配到多个Servlet时,按照最精准匹配
的方式选择Servlet.
<servlet-mapping>
<servlet-name>xmlServlet</servlet-name>
<url-pattern>/p/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>annoServlet</servlet-name>
<url-pattern>/p/anno</url-pattern>
</servlet-mapping>
当访问$IP:PORT/CONTEXT_PATH$/p/anno
时,执行的是annoServlet
逻辑。
Servlet 的
load-on-startup
元素表示的顺序实例化。
Listener执行顺序
在Servlet3.0,Listener以它们在web.xml中声明的顺序调用,如下所示:
javax.servlet.ServletContextListener
实现的contextInitialized方法以声明时顺序调用,contextDestroyed 以相反顺序调用。javax.servlet.ServletRequestListener
实现的requestInitialized 以声明时顺序调用,requestDestroyed 方法以相反顺序调用。javax.servlet.http.HttpSessionListener
实现的sessionCreated方法以声明时顺序调用,sessionDestroyed 方法以相反顺序调用。- 其他任何Listener接口的调用顺序是未指定的。