文章目录
1. web项目的搭建
1. 创建web项目
2.修改web.xml版本
3.添加servlet、jsp依赖
4.servlet示例(使用注解)
5.配置tomcat
6.添加artifact
7.部署
8.启动tomcat、访问
9.打war包
10.部署到tomcat
将war包复制到tomcat的webapps目录下,在tomcat的bin目录下双击startup.bat运行项目
如果是linux双击startup.sh即可运行项目,
tomcat如果早就启动了,那么复制war包到webapps目录下后,war包会自动解压并运行
2.maven的项目搭建
1.创建项目
图解
2.tomcat启动方式
图解
idea打war包
3.改为使用maven启动
上面使用tomcat启动时,pom.xml中什么都没有写,现在改成使用maven启动,pom的xml中如下配置
<?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.zzhua</groupId>
<artifactId>tomcat-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--一定要设置打包方式为war-->
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置tomcat的运行插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 配置端口 -->
<port>8080</port>
<!-- 配置urlencoding -->
<uriEncoding>UTF-8</uriEncoding>
<!-- 配置项目的访问路径 -->
<path>/zzhua</path>
<!-- 配置tomcat上传war包的路径 -->
<!--<url>http://127.0.0.1:8080/manager/text</url>-->
<!-- tomcat的登陆名 -->
<!--<username>admin</username>-->
<!-- tomcat的登陆密码-->
<!--<password>admin</password>-->
<!-- 设置打包的名称 -->
<!--<path>/zzhua</path>-->
</configuration>
</plugin>
<!-- 配置jdk的编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<!-- 指定source和target的版本 -->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
图解
剩下的Facets和Artifacts这两个里面都是空的
idea打war包
5.三大组件
web.xml配置方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--配置自定义 Servlet-->
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.zzhua.servlet.UserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
<!--配置自定义过滤器 Filter-->
<filter>
<filter-name>userFilter1</filter-name>
<filter-class>com.zzhua.filter.UserFilter1</filter-class>
</filter>
<filter-mapping>
<filter-name>userFilter1</filter-name>
<url-pattern>/user</url-pattern>
</filter-mapping>
<!--配置自定义过滤器 Filter-->
<filter>
<filter-name>userFilter2</filter-name>
<filter-class>com.zzhua.filter.UserFilter2</filter-class>
</filter>
<filter-mapping>
<filter-name>userFilter2</filter-name>
<url-pattern>/user</url-pattern>
</filter-mapping>
<!--配置监听器-->
<listener>
<listener-class>com.zzhua.listener.UserListener</listener-class>
</listener>
</web-app>
1.Servlet
1.介绍
Servlet是用来处理请求的
HttpServlet继承GenericServlet,GenericServlet继承javax.servlet
2.Servlet接口
public interface Servlet {
public void init(ServletConfig config) throws ServletException; // 初始化方法
// ...可以实现上面这个方法,拿到tomcat传过来的servletConfig并保存到本servlet对象中,
// 然后在下面这个方法中的实现中获取保存的servletConfig
public ServletConfig getServletConfig(); // 获取servlet配置的方法
// 处理请求的方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy(); // 销毁的方法
}
3.ServletConfig
/**
* A servlet configuration object used by a servlet container to pass
* information to a servlet during initialization.
*/
public interface ServletConfig {
/**
* Returns the name of this servlet instance. The name may be provided via
* server administration, assigned in the web application deployment
* descriptor, or for an unregistered (and thus unnamed) servlet instance it
* will be the servlet's class name.
*
* @return the name of the servlet instance
*/
public String getServletName(); //================= 可以获取到servlet的名字
/**
* Returns a reference to the {@link ServletContext} in which the caller is
* executing.
*
* @return a {@link ServletContext} object, used by the caller to interact
* with its servlet container
* @see ServletContext
*/
public ServletContext getServletContext(); // ============= 可以获取到ServletContext
/**
* Returns a <code>String</code> containing the value of the named
* initialization parameter, or <code>null</code> if the parameter does not
* exist.
*
* @param name
* a <code>String</code> specifying the name of the
* initialization parameter
* @return a <code>String</code> containing the value of the initialization
* parameter
*/
public String getInitParameter(String name); // ============ 根据名字获取配置的初始化参数对应的值
/**
* Returns the names of the servlet's initialization parameters as an
* <code>Enumeration</code> of <code>String</code> objects, or an empty
* <code>Enumeration</code> if the servlet has no initialization parameters.
*
* @return an <code>Enumeration</code> of <code>String</code> objects
* containing the names of the servlet's initialization parameters
*/
public Enumeration<String> getInitParameterNames(); // ============ 获取到所有配置的初始化参数的名字
}
4.使用
public class UserServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("userServlet...init初始化");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// resp.setContentType("text/html;charset=utf-8"); // 或者如下设置,否则会中文乱码
resp.setHeader("content-type","text/plain;charset=UTF-8");
resp.getWriter().write("Hello, 你好!");
}
}
2.Filter
1.介绍
请求被Servlet处理之前,被Filter过滤器拦截,放行之后,请求才会被Servlet处理
如果配置了多个过滤器,那么将会按照顺序依次拦截
2.Filter接口
public interface Filter {
// 初始化方法, 可以从filterConfig中获取ServletContext
public void init(FilterConfig filterConfig) throws ServletException;
// 过滤的逻辑, 通过执行chain.do(request,response)才能放行请求
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain ) throws IOException, ServletException;
// 销毁的方法
public void destroy();
}
3.FilterConfig
/**
*
* A filter configuration object used by a servlet container to pass information
* to a filter during initialization.
*
* @see Filter
* @since Servlet 2.3
*/
public interface FilterConfig {
/**
* Get the name of the filter.
*
* @return The filter-name of this filter as defined in the deployment
* descriptor.
*/
public String getFilterName(); // ============= 获取到为该filter配置的名字
/**
* Returns a reference to the {@link ServletContext} in which the caller is
* executing.
*
* @return {@link ServletContext} object, used by the caller to interact
* with its servlet container
*
* @see ServletContext
*/
public ServletContext getServletContext(); // ========== 获取到servletContext
/**
* Returns a <code>String</code> containing the value of the named
* initialization parameter, or <code>null</code> if the parameter does not
* exist.
*
* @param name
* <code>String</code> specifying the name of the initialization
* parameter
*
* @return <code>String</code> containing the value of the initialization
* parameter
*/
public String getInitParameter(String name); // ============= 根据名字获取配置的初始化参数对应的值
/**
* Returns the names of the filter's initialization parameters as an
* <code>Enumeration</code> of <code>String</code> objects, or an empty
* <code>Enumeration</code> if the filter has no initialization parameters.
*
* @return <code>Enumeration</code> of <code>String</code> objects
* containing the names of the filter's initialization parameters
*/
public Enumeration<String> getInitParameterNames(); // ============ 获取到所有配置的初始化参数的名字
}
4.使用
public class UserFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("userFilter1 init...自定义过滤器userFilter1初始化");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("userFilter1 doFilter...自定义过滤器userFilter1执行");
// 放行, 不执行这一句将会拦截掉请求, 不会执行后面的filter和servlet,责任链模式
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("userFilter1 destroy...自定义过滤器userFilter1销毁");
}
}
5.源码分析
public final class ApplicationFilterChain implements FilterChain {
/**
* Filters. ApplicationFilterConfig封装了匹配当前请求的所有filter
* (注意这里并不是所有的filter,匹配的过程是tomcat自己做的)
* (并且执行完请求后,这个数据的每个元素都会release掉,即置为null)
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
/**
* The int which is used to maintain the current position
* in the filter chain.
*/
private int pos = 0; // 索引, 当前执行的filter在上面这个数组中的位置
/**
* The int which gives the current number of filters in the chain.
*/
private int n = 0; // 一共配置的filter的数量
/**
* The servlet instance to be executed by this chain.
*/
private Servlet servlet = null;
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
internalDoFilter(request,response); // 调用下面这个方法
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
// 0 -> 2 ,假设一共有两个Filter
// 第一轮: pos = 0, n = 2, 做判断 0 < 2, 好,这个时候pos + 1 所以此时pos = 1
// 假设此时的第一个Filter没有调用chain.doFilter(req,resp)方法,那么会执行下面的return;
// 就不会执行下面的servlet调用service方法了
// 假设第一轮中,调用了chain.doFilter,这个,又回到上面这个方法,接着又调用本方法, 注意此时是同一个FilterChain
// 此时 pos = 1, 所以 1 < 2, 依旧成立, 好,这个时候pos + 1 所以此时pos = 2
// 假设第二个过滤器如果又没调用chain.doFilter那么filter.doFilter(request, response, this);
// 这一句又断了,就直接执行return;
// 就不会执行下面的servlet调用service方法了
// 第二轮: 假设第二轮调用了,那么又会回到当前的方法2<2不成立, 那么就会执行下面的servlet调用service方法了
// 其实chain.doFilter可以这样理解,chain把过滤的具体功能交给filter来实现, 而filter做完自己的过滤功能后,
// 因为已经拿到了chain, 那么就有权决定,是否要把执行权交回给chain, 如果不交回, 那么chain就断了。
// =========这是责任链模式的另外一种用法========
// 思考: 假设其中某一个Filter, 它不是在最后一句调用的filterChain.doFilter
// 那么其实也实现了spring mvc的拦截器一样的功能,等servlet处理完了之后,
// 然后逆序调用filter的那句话后面的代码,
// 特别注意: 这个时候处理完的request和response已经让servlet处理过了,
// 所以filter可以继续接着对这个处理完成了的请求中的request和response对象处理
// 然后再返回给客户端。
// 假设很多个过滤器, 前面几个都通过了,但是被一个给拦住了,那么也会逆序回调过滤器dofilter代码之后面的代码
if (pos < n) { //
ApplicationFilterConfig filterConfig = filters[pos++]; // 根据索引找filter
try {
Filter filter = filterConfig.getFilter(); // 先获取到filter,如果之前创建过,直接返回,如果没有,
// 那么通过反射创建,并回调初始化方法,然后传个东西给
// 它。注意这里的filterConfig是
// ApplicationFilterConfig。
// 下面分析以下ApplicationFilterConfig
// ApplicationFilterChain中组合了配置的Filter,并都封装成了ApplicationFilterConfig数组,
// 并且ApplicationFilterConfig中封装了我们配置的filter的所有相关信息,创建filter也是由
// ApplicationFilterConfig这个对象创建的。所以tomcat读取web.xml中的filter时,创建的并不是
// Filter,而是ApplicationFilterConfig,由这个类来对filter的配置信息做一个封装。真正执行的时候
// 从ApplicationFilterConfig类中获取filter,如果之前创建过,那么直接返回,如果之前没有创建过,
// 那么通过反射创建Filter,创建好后。
// 调用Filter的init方法,并且把当前对象this,也就是ApplicationFilterConfig,传进去。因为
// ApplicationFilterConfig还实现了FilterCOnfig接口,它里面还封装了Context,而从Context里面
// 由能拿到ServletContext。
// 所以filter重写的init方法中的filterConfig实际上就是ApplicationFilterconfig
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return; // 这个return;语句是关键,如果中间的FilterChain断了的话,
// 那么会直接到这里来,下面调用servlet的方法也不会执行了
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
// 上面的过滤器执行完了,才轮到servlet去处理这个请求
// 注意:这里不一定执行完了, 可以看下上面的思考
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
}
3.监听器
1.介绍
当web应用启动,创建好ServletContext时,会回调配置的监听器的contextInitialized方法,并且将ServletContextEvent对象传递过来。
除了ServletContextListener接口,tomcat还会回调以下Listener(只要把以下listener配置给tomcat,使用xml或注解方式)
- HttpSessionListener、HttpSessionAttributeListener、HttpSessionIdListener、HttpSessionBindingListener
- ServletRequestListener、ServletRequestAttributeListener
- ServletContextListener、ServletContextAttributeListener
2.Listener接口
以ServletContextListener为例
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
// 通知web应用开始初始化,通知所有ServletContextListeners回调contextInitialized方法,
// (并且这个回调的contextInitialized方法的执行时机,任何的filter或者servlet之前)
// 1.可以从ServletContextEvent事件对象中取出ServletContext
// 2.这样又可以从servletContext中获取到配置的全局属性,即context-param配置的
// 3.也可以在此处获取到servletContext,然后使用它来注册servlet或者filter(添加返回的Dynamic配置)
public void contextInitialized(ServletContextEvent sce);
/**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param sce Information about the ServletContext that was destroyed
*/
// 容器销毁, 这个方法被执行, 并且执行时间是在 任何的filter或者servlet执行销毁方法之后
// 可以从ServletContextEvent事件对象中取出ServletContext
public void contextDestroyed(ServletContextEvent sce);
}
3.ServletContext
public interface ServletContext {
String TEMPDIR = "javax.servlet.context.tempdir";
String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
String getContextPath();
ServletContext getContext(String var1);
int getMajorVersion();
int getMinorVersion();
int getEffectiveMajorVersion();
int getEffectiveMinorVersion();
String getMimeType(String var1);
Set<String> getResourcePaths(String var1);
URL getResource(String var1) throws MalformedURLException;
InputStream getResourceAsStream(String var1);
RequestDispatcher getRequestDispatcher(String var1);
RequestDispatcher getNamedDispatcher(String var1);
/** @deprecated */
Servlet getServlet(String var1) throws ServletException;
/** @deprecated */
Enumeration<Servlet> getServlets();
/** @deprecated */
Enumeration<String> getServletNames();
void log(String var1);
/** @deprecated */
void log(Exception var1, String var2);
void log(String var1, Throwable var2);
String getRealPath(String var1);
String getServerInfo();
// 根据在web.xml配置的<context-param>标签中配置的key,拿配置的value
/* <context-param>
<param-name>name</param-name>
<param-value>zzhua173</param-value>
</context-param>
*/
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
boolean setInitParameter(String var1, String var2);
Object getAttribute(String var1);
Enumeration<String> getAttributeNames();
void setAttribute(String var1, Object var2);
void removeAttribute(String var1);
String getServletContextName();
Dynamic addServlet(String var1, String var2);
Dynamic addServlet(String var1, Servlet var2);
Dynamic addServlet(String var1, Class<? extends Servlet> var2);
<T extends Servlet> T createServlet(Class<T> var1) throws ServletException;
ServletRegistration getServletRegistration(String var1);
Map<String, ? extends ServletRegistration> getServletRegistrations();
javax.servlet.FilterRegistration.Dynamic addFilter(String var1, String var2);
javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Filter var2);
javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Class<? extends Filter> var2);
<T extends Filter> T createFilter(Class<T> var1) throws ServletException;
FilterRegistration getFilterRegistration(String var1);
Map<String, ? extends FilterRegistration> getFilterRegistrations();
SessionCookieConfig getSessionCookieConfig();
void setSessionTrackingModes(Set<SessionTrackingMode> var1);
Set<SessionTrackingMode> getDefaultSessionTrackingModes();
Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
void addListener(String var1);
<T extends EventListener> void addListener(T var1);
void addListener(Class<? extends EventListener> var1);
<T extends EventListener> T createListener(Class<T> var1) throws ServletException;
JspConfigDescriptor getJspConfigDescriptor();
ClassLoader getClassLoader();
void declareRoles(String... var1);
String getVirtualServerName();
}
4.使用
public class UserListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute("ctx", servletContext.getContextPath());
System.out.println("UserListener contextInitialized...自定义监听器初始化");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("UserListener contextDestroyed...自定义监听器销毁");
}
}
注解的配置方式
自servlet3.0开始, tomcat支持使用注解的方式代替web.xml配置的方式, 向web容器中注册Servlet,Filter,Listener.
如:@WebServlet,@WebFilter,@WebListener。
只要将继承自Servlet的非抽象类上加上@WebServlet的注解,启动tomcat后这个类将会被自动扫描到,并实例化成servlet来处理请求,同理filter和listener
1.@WebServlet
/**
* 自servlet3.0开始, tomcat支持使用注解的方式代替web.xml配置的方式, 向web容器中注册Servlet,Filter,Listener
* 只要把这个类打进war包里,tomcat会自动识别到这个类。因此可以猜测:tomcat会对war包里面的class逐个扫描,
* 检查每个类上是否有这个注解,如果有, 那么读取注解上的配置(和读取web.xml类似), 并向web容器中注册这个组件。
* 有点像SPI机制
*/
@WebServlet(
urlPatterns = {"/anno"} , // 不能丢“/”
initParams = { // 配置初始化参数
@WebInitParam(name = "name",value="zzhua"),
@WebInitParam(name = "age",value="25")
}
)
public class AnnoServlet implements Servlet {
private static ServletConfig SERVLET_CONFIG;
private static ServletContext SERVLET_CONTEXT = null;
public AnnoServlet() {
System.out.println(1);
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String initParamName = initParameterNames.nextElement();
System.out.println(initParamName + " - " + servletConfig.getInitParameter(initParamName));
}
SERVLET_CONTEXT = servletConfig.getServletContext();
AnnoServlet.SERVLET_CONFIG = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return AnnoServlet.SERVLET_CONFIG;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("我拿到了req和res");
}
@Override
public String getServletInfo() {
return "annoServlet";
}
@Override
public void destroy() {
}
}
2.@WebFilter
public class Demo1Filter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String initParamName = initParameterNames.nextElement();
System.out.println(initParamName + "-" + filterConfig.getInitParameter(initParamName));
}
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("经过demo1过滤器...");
}
@Override
public void destroy() {
}
}
3.@WebListener
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("请求对象被销毁");
RequestHolder.setRequest(null); // 清除该请求对象
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("当前线程: " + Thread.currentThread().getName());
HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
// 请求能这么存的前提是: tomcat是在本线程里面回调的此方法,并且也是在本线程里面处理该请求,证明确实是这样的
RequestHolder.setRequest(servletRequest); // 将tomcat当前线程上创建的request对象放入RequestHolder中
}
}
ServletContextListener配置tomcat组件方式
在tomcat中,配置ServletContextListener接口的监听器给tomcat(用上面的2种方式,web.xml方式或者@WebListener注解方式),tomcat会在创建完ServletContext时,回调配置的ServletContextListener监听器的contextInitialized方法,并且会回传ServletContextEvent事件对象,该事件对象中包装了ServletContext对象(即可以从contextInitialized方法中回传的ServletContextEvent对象参数中获取到ServletContext)。特别注意:此时,filter和servlet都还未初始化,所以此时我们可以往tomcat中添加listener、filter、servlet,(这里不能添加ServletContextListener接口的监听器,只能添加其它监听器接口的监听器,比如HttpSessionListener、ServletRequestListener等)
@WebListener
public class UserListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 从servletContextEvent中获取ServletContext
ServletContext servletContext = servletContextEvent.getServletContext();
// 使用ServletContext监听器注册了一个ServletListener
servletContext.addListener(new ServletRequestListener() {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
ServletRequest servletRequest = servletRequestEvent.getServletRequest();
System.out.println("==>请求被销毁");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("==>监听器listener");
}
});
// 使用ServletContext监听器注册了一个HttpSessionListener
servletContext.addListener(new HttpSessionListener() {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("session created...");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session destroyed...");
}
});
// ServletContext监听器注册了一个servlet
ServletRegistration.Dynamic dynamicServlet = servletContext.addServlet("anonymousServlet",
new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("==>hello dynamicServlet");
}
@Override
public void init() throws ServletException {
System.out.println("init....anonymous servlet");
}
});
dynamicServlet.addMapping("/anonymous");
dynamicServlet.setLoadOnStartup(1);
// ServletContext监听器注册了一个filter
FilterRegistration.Dynamic dynamicFilter
= servletContext.addFilter("anonymousFilter",
new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("==>"+filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("==>拦截");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
});
dynamicFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class),
true,"/anonymous/*");
System.out.println("UserListener contextInitialized...自定义监听器初始化");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("UserListener contextDestroyed...自定义监听器销毁");
}
}
servlet 3.0之动态注册
先看下Registration的继承体系
动态注册使用
说实话,对servlet 3动态加载servlet的机制有些失望,本来期望着可以在运行时完成对servlet的注册和销毁,但现状是,只能在webapp启动在初始化时进行完成注册,可能是为了安全考虑吧。
在Servlet3.0中可以动态注册Servlet,Filter,Listener,每个组件注册都提供三个方法,很细心。
下面谈谈动态注册Servlet,但不要希望太高,只能在初始化时进行注册。在运行时为了安全原因,无法完成注册。在初始化情况下的注册Servlet组件有两种方法:
- 1.实现ServletContextListener接口,在contextInitialized方法中完成注册.
- 2.在jar文件中放入实现ServletContainerInitializer接口的初始化器
在ServletContext类中对应注册API为:
/**
* 添加Servlet
*/
public ServletRegistration.Dynamic addServlet(String servletName, String className);
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet>
servletClass);
/**
* 添加Filter
*/
public FilterRegistration.Dynamic addFilter(String filterName, String className);
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass);
/**
* 添加Listener
*/
public void addListener(String className);
public <T extends EventListener> void addListener(T t);
public void addListener(Class<? extends EventListener> listenerClass);
先说在ServletContextListener监听器中完成注册。
public void contextInitialized(ServletContextEvent sce) {
ServletContext sc = sce.getServletContext();
// Register Servlet
ServletRegistration sr = sc.addServlet("DynamicServlet",
"web.servlet.dynamicregistration_war.TestServlet");
sr.setInitParameter("servletInitName", "servletInitValue");
sr.addMapping("/*");
// Register Filter
FilterRegistration fr = sc.addFilter("DynamicFilter",
"web.servlet.dynamicregistration_war.TestFilter");
fr.setInitParameter("filterInitName", "filterInitValue");
fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST),
true, "DynamicServlet");
// Register ServletRequestListener
sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener");
}
再说说在jar文件中的servlet组件注册,
需要在jar包含META-INF/services/javax.servlet.ServletContainerInitializer文件,文件内容为已经实现ServletContainerInitializer接口的类:
com.learn.servlet3.jardync.CustomServletContainerInitializer
该实现部分代码:
@HandlesTypes({ JarWelcomeServlet.class })
public class CustomServletContainerInitializer implements ServletContainerInitializer {
private static final Log log = LogFactory .getLog(CustomServletContainerInitializer.class);
private static final String JAR_HELLO_URL = "/jarhello";
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
log.info("CustomServletContainerInitializer is loaded here...");
log.info("now ready to add servlet : " + JarWelcomeServlet.class.getName());
ServletRegistration.Dynamic servlet = servletContext.addServlet(
JarWelcomeServlet.class.getSimpleName(),
JarWelcomeServlet.class
);
servlet.addMapping(JAR_HELLO_URL);
log.info("now ready to add filter : " + JarWelcomeFilter.class.getName());
FilterRegistration.Dynamic filter = servletContext.addFilter(
JarWelcomeFilter.class.getSimpleName(),
JarWelcomeFilter.class);
EnumSet<DispatcherType> dispatcherTypes = EnumSet .allOf(DispatcherType.class);
dispatcherTypes.add(DispatcherType.REQUEST);
dispatcherTypes.add(DispatcherType.FORWARD);
filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);
log.info("now ready to add listener : " + JarWelcomeListener.class.getName());
servletContext.addListener(JarWelcomeListener.class);
}
}
其中@HandlesTypes注解表示 CustomServletContainerInitializer 可以处理的类,在 onStartup方法中,可以通过 <Set> c获取得到。
jar文件中不但可以包含需要自定义注册的servlet,也可以包含应用注解的servlet,具体怎么做,视具体环境而定。把处理某类事物的servlet组件打包成jar文件,有利于部署和传输,功能不要了,直接去除掉jar即可,方便至极!
ServletContainerInitializer
SCI规范
规范
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filters等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的==META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer==的文件,文件内容指定具体的**ServletContainerInitializer实现类**,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作
(注意:在springboot中这样做是没有效果的)。
伴随着ServletContainerInitializer一起使用的还有**@HandlesTypes**注解,通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入(这个感兴趣的类从所有的类中找,搜索算法由tomcat负责)。
/**
*
* ServletContainerInitializers (SCIs) are registered via an entry in the
* file META-INF/services/javax.servlet.ServletContainerInitializer that must be
* included in the JAR file that contains the SCI implementation.
* <p>
* SCI processing is performed regardless of the setting of metadata-complete.
* SCI processing can be controlled per JAR file via fragment ordering. If
* absolute ordering is defined, then only the JARs included in the ordering
* will be processed for SCIs. To disable SCI processing completely, an empty
* absolute ordering may be defined.
* <p>
* SCIs register an interest in annotations (class, method or field) and/or
* types via the {@link javax.servlet.annotation.HandlesTypes} annotation which
* is added to the class.
*
* @since Servlet 3.0 3.0开始支持SCIs
*/
public interface ServletContainerInitializer {
/**
* ServletContainerInitializer的实现类上可以添加@HandleTypes,并且在这个注解里写上感兴趣的类,
* 则这个类的子类(子接口、子抽象类、子实现类,总之只会向下寻找,并且不包括当前类)都将添加到一个set集合中,
* 并将这个set集合传给ServletContainerInitializer的实现类的onStartUp方法作为第一个参数
* ServletContext作为第二个参数
*
* Receives notification during startup of a web application of the classes
* within the web application that matched the criteria defined via the
* {@link javax.servlet.annotation.HandlesTypes} annotation.
*
* @param c The (possibly null) set of classes that met the specified
* criteria
* @param ctx The ServletContext of the web application in which the
* classes were discovered
*
* @throws ServletException If an error occurs
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
tomcat启动
maven启动
执行顺序
请看【tomcat组件初始化顺序和请求执行顺序】这一节
意义
- 1.我们可以利用ServletContainerInitializer机制,使用完全基于代码的方式配置ServletContainer容器(注册三大组件),取代以前使用web.xml和注解这种两种注册组件的方式
- 2.因为ServletContainerInitializer是在jar包的META-INF/services下的javax.servlet.ServletContainerInitializer文件中的,我们可以添加或者移除这个jar包,从而实现功能的可插拔性(可插拔性指的是对ServletContext的配置的删减)。
- spring-web jar包下有:SpringServletContainerInitializer这个类,它实现了ServletContainerInitializer接口。
6.tomcat组件初始化顺序和请求执行顺序
初始化顺序
请求执行顺序
7.servlet的基本用法
1.请求转发Forward
转发给其它servlet
@WebServlet("/skip")
public class SkipServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
// 请求转发是服务器内部的行为,
req.getRequestDispatcher("forward").forward(req,resp); // 转发给下面这个servlet处理
}
}
@WebServlet("/forward")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello forward");
System.out.println("请求转发是服务内部的转发,让另外一个servlet接着处理...");
// 这个就是springmvc中InternalResourceViewResolver视图解析器的原理
// tomcat对WEB-INF下的jsp(包括其它在内的所有资源)是不能直接访问的,必须要通过请求转发,才能渲染出来被访问到
// (但请求转发意味着又会让tomcat决定走哪个Servlet(即:是走jsp呢?还是继续走DispacherServlet呢?)
// 如果转发请求的后缀是以jsp结尾,那么优先是根据转发请求的url找对应目录下的jsp处理请求并渲染出来,注意这个
// 优先是指在没有配置/*的前提下(springmvc的前端控制器的url-pattern须配置成/而非/*))
// 而对其它目录下的jsp试可以直接在浏览器输入url访问的。
req.getRequestDispatcher("/WEB-INF/forward.jsp").forward(req,resp);
}
}
转发给jsp
@WebServlet("/test01")
public class Test1Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getRequestDispatcher("/test1.jsp").forward(req,resp);
}
}
此处踩过坑一:在web.xml中配置spring-mvc的前端控制器时(即DispatcherServlet),把url-pattern设置成了/*,这样会导致所有的请求都给该DispatcherServlet处理了,哪怕是调用RequestDispatcher#forward(req,resp)方法转发的请求,也都被该前端控制器处理了,结果就是导致写的jsp根本渲染不出数据(只在浏览器上显示了jsp的源代码)。所以应该配置为"/“,而不是”/*"。显示为jsp源代码的原因就是,springmvc的InternalResourceViewResolver视图解析器组件能拿到这个文件,并且render出来。而并不是将请求转发给tomcat后,tomcat没有把请求交给该jsp(jsp本质上也是个Servlet)处理。所以tomcat在接收到我们需要转发请求的需求时,它把请求继续交给DispatcherServlet处理,但实际理应交给jsp来处理(这样才会走jsp渲染的逻辑),就是因为配置了/*,而把所有的请求都给了DispatcherServlet处理了。也就是说转发请求的时候,又会让tomcat决定此次转发请求将走哪个Servlet(即:是走jsp呢?还是继续走DispacherServlet呢?),并且如果转发请求的url是以jsp结尾,那么在springmvc如果配置的url-pattern是/而不是/*的情况下(如果配置的是/*,那么又会让DispatcherServlet去处理该转发请求),是会优先去找对应的jsp去处理请求并渲染出来。
转发给静态html
@WebServlet("/test02")
public class Test2Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/test002.html")
.forward(req,resp);
}
}
2.重定向Redirect
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect(req.getContextPath() + "/redirect.jsp"); // 需要加上contextPath
}
}
3.保存当前线程的请求对象
public class RequestHolder {
private static final ThreadLocal<HttpServletRequest> requestThreadLocal = new ThreadLocal<>();
public static void setRequest(HttpServletRequest request) {
requestThreadLocal.set(request);
}
public static HttpServletRequest getRequest() {
return requestThreadLocal.get();
}
}
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("请求对象被销毁");
RequestHolder.setRequest(null); // 清除该请求对象
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("当前线程: " + Thread.currentThread().getName());
HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
// 请求能这么存的前提是: tomcat是在本线程里面回调的此方法,并且也是在本线程里面处理该请求,证明确实是这样的
RequestHolder.setRequest(servletRequest); // 将tomcat当前线程上创建的request对象放入RequestHolder中
}
}
@WebServlet("/request")
public class RequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
System.out.println(session);
System.out.println("当前线程: " + Thread.currentThread().getName());
System.out.println("拿到的是同一个请求吗? " +( RequestHolder.getRequest() == req)); // true
}
}