1. Tomcat
Tomcat是一个Web服务器(同时也是Servlet容器),通过它我们可以很方便地接收和返回到请求(如果不用Tomcat,那我们需要自己写Socket来接收和返回请求)
2. Servlet基础
Servlet API 包含以下4个Java包:
1.javax.servlet 其中包含定义servlet和servlet容器之间契约的类和接口。
2.javax.servlet.http 其中包含定义HTTP Servlet 和Servlet容器之间的关系。
3.javax.servlet.annotation 其中包含标注servlet,Filter,Listener的标注。它还为被标注元件定义元数据。
4.javax.servlet.descriptor,其中包含提供程序化登录Web应用程序的配置信息的类型。
2.1 Servlet 的使用方法
Servlet技术的核心是Servlet,它是所有Servlet类必须直接或者间接实现的一个接口。在编写实现Servlet的Servlet类时,直接实现它。在扩展实现这个这个接口的类时,间接实现它。
2.2 Servlet 的工作原理
Servlet接口定义了Servlet与servlet容器之间的契约。这个契约是:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法。但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例。
用户请求致使Servlet容器调用Servlet的Service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。ServletRequest对象和ServletResponse对象都是由Servlet容器(例如TomCat)封装好的,并不需要程序员去实现,程序员可以直接使用这两个对象。
ServletRequest中封装了当前的Http请求,因此,开发人员不必解析和操作原始的Http数据。ServletResponse表示当前用户的Http响应,程序员只需直接操作ServletResponse对象就能把响应轻松的发回给用户。
对于每一个应用程序,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。
2.3 Servlet 的生命周期
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
其中,init( ),service( ),destroy( )是Servlet生命周期的方法。代表了Servlet从“出生”到“工作”再到“死亡 ”的过程。Servlet容器(例如TomCat)会根据下面的规则来调用这三个方法:
1.init( ),当Servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来,但是这个方法在后续请求中不会在被Servlet容器调用,就像人只能“出生”一次一样。我们可以利用init( )方法来执行相应的初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig对象进来从而对Servlet对象进行初始化。
2.service( )方法,每当请求Servlet时,Servlet容器就会调用这个方法。就像人一样,需要不停的接受老板的指令并且“工作”。第一次请求时,Servlet容器会先调用init( )方法初始化一个Servlet对象出来,然后会调用它的service( )方法进行工作,但在后续的请求中,Servlet容器只会调用service方法了。
3.destory,当要销毁Servlet时,Servlet容器就会调用这个方法,就如人一样,到时期了就得死亡。在卸载应用程序或者关闭Servlet容器时,就会发生这种情况,一般在这个方法中会写一些清除代码。
getServletInfo( ),这个方法会返回Servlet的一段描述,可以返回一段字符串。
getServletConfig( ),这个方法会返回由Servlet容器传给init( )方法的ServletConfig对象。
2.4 ServletRequset接口
Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法。其中,ServletRequest对象内封装了关于这个请求的许多详细信息。
public interface ServletRequest {
int getContentLength();//返回请求主体的字节数
String getContentType();//返回主体的MIME类型
String getParameter(String var1);//返回请求参数的值
2.5 ServletResponse接口
javax.servlet.ServletResponse接口表示一个Servlet响应,在调用Servlet的Service( )方法前,Servlet容器会先创建一个ServletResponse对象,并把它作为第二个参数传给Service( )方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。
public interface ServletResponse {
String getCharacterEncoding();
String getContentType();
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentType(String var1);
void setBufferSize(int var1);
int getBufferSize();
void flushBuffer() throws IOException;
void resetBuffer();
boolean isCommitted();
void reset();
void setLocale(Locale var1);
Locale getLocale();
}
其中的getWriter方法,它返回了一个可以向客户端发送文本的的Java.io.PrintWriter对象。默认情况下,PrintWriter对象使用ISO-8859-1编码(该编码在输入中文时会发生乱码)。
在向客户端发送响应时,大多数都是使用该对象向客户端发送HTML。
还有一个方法也可以用来向浏览器发送数据,它就是getOutputStream,从名字就可以看出这是一个二进制流对象,因此这个方法是用来发送二进制数据的。
在发送任何HTML之前,应该先调用setContentType()方法,设置响应的内容类型,并将“text/html”作为一个参数传入,这是在告诉浏览器响应的内容类型为HTML,需要以HTML的方法解释响应内容而不是普通的文本,或者也可以加上“charset=UTF-8”改变响应的编码方式以防止发生中文乱码现象。
2.6 ServletConfig接口
当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init( )方式传入一个ServletConfig对象。可以通过@WebServlet或者部署描述符传给一个Servlet的配置信息,以这种方式传递的每一条信息都称作初始化参数
2.7 ServletContext对象
ServletContext对象表示Servlet应用程序。每个Web应用程序都只有一个ServletContext对象。在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。
通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。
有了ServletContext,就可以共享通过应用程序中的所有资源访问到的信息,并且可以动态注册Web对象。前者将对象保存在ServletContext中的一个内部Map中。保存在ServletContext中的对象被称作属性。
ServletContext中的下列方法负责处理属性:
Object getAttribute(String var1);
Enumeration<String> getAttributeNames();
void setAttribute(String var1, Object var2);
void removeAttribute(String var1);
2.8 GenericServlet抽象类
- 为Servlet接口中的所有方法提供了默认的实现
- 提供方法来包装ServletConfig中的方法。
- 将init( )方法中的ServletConfig参数赋给了一个类级变量,从而可以调用getServletConfig来获取
2.9 HttpServlet抽象类
HttpServlet抽象类覆盖了GenericServlet抽象类中的Service( )方法,并且添加了一个自己独有的Service(HttpServletRequest request,HttpServletResponse方法。
HttpServlet有两个特性是GenericServlet所不具备的:
- 不用覆盖service方法,而是覆盖doGet或者doPost方法。在少数情况,还会覆盖其他的5个方法;
- 使用的是HttpServletRequest和HttpServletResponse对象
HttpServletRequest表示Http环境中的Servlet请求。它扩展于javax.servlet.ServletRequest接口,并添加了几个方法;可以通过该对象分别获得HTTP请求的请求行,请求头和请求体
HttpServletResponse接口,它继承自ServletResponse接口,专门用来封装HTTP响应消息。 由于HTTP请求消息分为状态行,响应消息头,响应消息体三部分,因此,HttpServletResponse接口中定义了向客户端发送响应状态码,响应消息头,响应消息体的方法。
解决post提交方式的乱码:request.setCharacterEncoding(“UTF-8”);
解决get提交的方式的乱码:parameter = newString(parameter.getbytes(“iso8859-1”),“utf-8”);
2.10 使用部署描述符
web.xml,或者通过@WebServlet注解
- 可以包含@WebServlet中没有的元素,比如 load-on-startup,在应用启动时加载Servlet,而不是第一次调用时
- 如果需要修改配置值,如Servlet路径,不需要重新编译Servlet类
- 可以将初始参数传给一个Servlet,并且不需要重新编译Servlet类就可以对它们进行编辑
- 部署描述符允许覆盖Servlet注解中指定的值
3. Session管理
HTTP无状态,默认情况下Web服务器不知道一个HTTP请求是来自初次用户还是之前已经访问过的用户,因此服务端需要能够管理用户的session,4种方法:网址重写、隐藏域、cookie、HTTPSession
3.1 URL rewriting
在我们学习session的时候,我们知道:当服务器创建session出来后,会把session的id号,以cookie的形式回写给客户端,这样,只要客户端的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户端浏览器带session id过来了,就会检索这个id,然后找到服务器中与之对应的session为之服务。
但是,cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。
经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。
3.2 隐藏域
与网址重写类似,但不是将值添加到url后面,而是将它们放在html表单的隐藏域中。
- 只有当页面包含表单或者可以在页面中添加表单时,才适合使用
- 胜过网址重写技术的地方在于:可以将更多的字符传到服务器,不需要字符编码
- 与网址重写一样的缺点:当要传递的信息不需要跨越多个页面时,才适合使用
3.3 cookie
自动地在web服务器和浏览器之间来回传递的一小块信息。cookie是作为HTTP标头嵌入的,传输它的过程由HTTP协议处理。可以根据需要设置cookie的有效期限。当浏览器再次发出对同一个资源或者对同一台服务器中的不同资源的请求时,会把从web浏览器处收到的cookie再传回去。
不足:用户可以修改浏览器设置拒接接收cookie
3.4 HTTPSession对象
当用户第一次访问Servlet时,服务器端会给用户创建一个独立的Session,并且生成一SessionID,这个SessionID在响应浏览器的时候会被装进cookie中,从而被保存到浏览器中;当用户再一次访问Servlet时,请求中会携带着cookie中的SessionID去访问服务器会根据这个SessionID去查看是否有对应的Session对象,有就拿出来使用,没有就创建一个Session(相当于用户第一次访问)
HttpSession中的值是保存在内存中的,因此只能将尽可能小的对象放在里面,且数量不能太多。添加到Httpsession中的值不一定是string,可以为任意java对象,实现了Serializable接口即可。
由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
4. JSP(Java Server Pages)
Servlet需要一行一行的把HTML语句给用Writer输出,早期简单的网页还能应付得住,但是随着互联网的不断发展,网站的内容和功能越来越强大,一个普通的HTML文件可能就达到好几百行,如果在采用使用Servlet去一行一行的输出HTML代码的话,将会非常的繁琐并且浪费大量的时间,且在当时,出现了PHP这种可以内嵌到HTML文件的动态语言,使得制作动态网页变得异常的简单和轻松,因此大量的程序员转上了PHP语言的道路,JAVA的份额急剧减小,当时JAVA的开发者Sun公司为了解决这个问题,也开发出了自己的动态网页生成技术,使得同样可以在HTML文件里内嵌JAVA代码,这就是现在的JSP技术。
以下是JSP生命周期中所走过的几个阶段:
- 编译阶段:servlet容器编译servlet源文件,生成servlet类
- 初始化阶段:加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法
- 执行阶段:调用与JSP对应的servlet实例的服务方法
- 销毁阶段:调用与JSP对应的servlet实例的销毁方法,然后销毁servlet实例
5. 监听器
为了能够在应用程序中进行事件驱动编程,Servlet API提供了一整套事件类和监听器接口。所有事件类均源自java.util.Event,并且监听器在以下三个不同级别中均可用:ServletContext、HttpSession、ServletRequest
- ServletContextListener 对Servlet Context生命周期事件做出相应的监听器
- ServletContextAttributeListener 添加、删除、替换某个Servlet Context属性时采取相应动作的监听器
- HttpSessionListener 创建、移除、替换Servlet上下文属性时被调用的监听器
- HttpSessionAttributeListener 添加、删除、替换某个session属性时被调用的监听器
- HttpSessionActivationListener 打开和关闭某个HttpSession时调用
- HttpSessionBingdingListener 其实例被保存为可以实现这个接口的HttpSession属性,当它在HttpSession中被添加或者删除时,实现这个类的实例会收到通知
- ServletRequestListener 对ServletRequest的创建和删除做出响应的监听器
- ServletRequestAttributeListener ServletRequest中添加、删除、替换掉某个属性时会调用
- AsyncListener 用于异步操作的监听器
让监听器生效,采用@WebListener标注实现类,或者在部署描述符中使用listener元素
5.1 Servlet Context监听器
package javax.servlet;
import java.util.EventListener;
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent var1);
void contextDestroyed(ServletContextEvent var1);
}
Spring容器是如何借用ServletContextListener这个接口来实例化的?
当Servlet容器启动时,ServletContext对象被初始化,然后Servlet容器调用web.xml中注册的监听器的
public void contextInitialized(ServletContextEvent event)
方法,而在监听器中,调用了
this.initWebApplicationContext(event.getServletContext())
方法,在这个方法中实例化了Spring IOC容器。即ApplicationContext对象。
因此,当ServletContext创建时我们可以创建applicationContext对象,当ServletContext销毁时,我们可以销毁applicationContext对象。这样applicationContext就和ServletContext“共生死了”。
属性变化监听:
public interface ServletContextAttributeListener extends EventListener {
default void attributeAdded(ServletContextAttributeEvent event) {
}
default void attributeRemoved(ServletContextAttributeEvent event) {
}
default void attributeReplaced(ServletContextAttributeEvent event) {
}
}
5.2 Session监听器
public interface HttpSessionListener extends EventListener {
default void sessionCreated(HttpSessionEvent se) {
}
default void sessionDestroyed(HttpSessionEvent se) {
}
}
当HttpSession被创建或销毁时调用,示例 HttpSession计数器
当HttpSession中的属性有变化时,调HttpSessionAttributeListener
HttpSessionActivationListener
HttpSessionBindingListener
当listener绑定到HttpSession或者取消绑定时,都会收到通知。
public interface HttpSessionBindingListener extends EventListener {
default void valueBound(HttpSessionBindingEvent event) {
}
default void valueUnbound(HttpSessionBindingEvent event) {
}
}
前者为数据绑定(解释:所谓对session进行数据绑定,就是调用session.setAttribute()把HttpSessionBindingListener保存进session中。),
后者为取消绑定(解释:valueUnbound的触发条件是以下三种情况:
- 执行session.invalidate()时
- session超时,自动销毁时
- 执行session.setAttribute(“onlineUserListener”, “其他对象”);
或session.removeAttribute(“onlineUserListener”),将listener从session中删除时
5.3 ServletRequest监听器
ServletRequestListener对ServletRequest的创建和销毁做出响应。
在Servlet容器中是通过池来重用ServletRequest的,创建花费的时间相当于从池中获取它的时间,销毁时间则相当于它返回到池中的时间
public interface ServletRequestListener extends EventListener {
default void requestDestroyed(ServletRequestEvent sre) {
}
default void requestInitialized(ServletRequestEvent sre) {
}
}
一个HTTP大约花费的时间:
ServletRequestAttributeListener在ServletRequest中有属性变化时会调用
6. 过滤器
Filter是指拦截请求,并对传给被请求资源的ServletRequest或ServletResponse进行处理的一个对象。可用于登录、加解密、会话检查、图片转换等,过滤器可配置为拦截一个或多个资源。
Filter生命周期
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
doFilter中的最后一行代码应该是 filterChain.doFilter,不调用的话程序会在这个地方停止
让过滤器生效,采用@WebFilter标注实现类,或者在部署描述符中使用filter元素,指定要拦截的URL模式
应用程序通常是多用户的,可以通过多个线程访问一个过滤器实例,必须谨慎处理多线程问题
7. 异步处理
首先,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时 Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。如此一来, Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
7.1 启用异步
@WebServlet(asyncSupported=true)
@WebFilter(asyncSupported=true)
<!-- servlet -->
<servlet>
<servlet-name>asynServlet</servlet-name>
<servlet-class>com.servlet.AsynServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
<!-- Filter -->
<filter>
<filter-name>asynFilter</filter-name>
<filter-class>com.filter.AsynFilter</filter-class>
<async-supported>true</async-supported>
</filter>
7.2 使用异步
- ServletRequest中调用startAsync方法,得到一个AsyncContext返回
- AsyncContext中调用setTimeout方法,没有set会采用容器的默认时间,若在规定时间内未完成,将会抛出异常
- 调用asyncContext.start方法,传递一个执行长时间任务的Runnable
- 任务完成时,通过Runnable调用asyncContext.complete或者dispatch方法
@WebServlet(value="/asyn-servlet",asyncSupported=true)
public class ServletAsyn extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Servlet is start");
//1.获得异步上下文对象
AsyncContext ac = request.startAsync();
//2.启动一个耗时的子线程
ThreadTask tt = new ThreadTask(ac);
//3.可设置异步超时对象,需在启动异步上下文对象前设置
/*
* 设置超时后,在超时时间内子线程没有结束,主线程则会停止等待,继续往下执行
*/
ac.setTimeout(3000);
//4.开启异步上下文对象
ac.start(tt);
//主线程结束向客户端发送消息
System.out.println("Servlet is end");
response.setContentType("text/html;charset=utf-8");
response.getWriter().append("信息已发送到邮箱");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
public class ThreadTask implements Runnable{
private AsyncContext ac; //定义一个异步上下文
public ThreadTask(AsyncContext ac) {
super();
this.ac = ac;
}
@Override
public void run() {
/*
* 服务端异步典型应用是注册时向邮箱发送验证码
*/
try {
//进行异步的一些处理
HttpServletRequest requst = (HttpServletRequest) ac.getRequest();
HttpSession session = requst.getSession();
System.out.println("asyn-task start" + new Date());
for(int i=5;i>0; i--) {
System.out.println(i);
Thread.sleep(1000);
}
//将结果放到session等方式
session.setAttribute("message", "This is the result of asyn");
System.out.println("asyn-task end" + new Date());
//通知主线程已经处理完成
/*
* 除了使用 ac.complete() 方法通知主线程已经处理外
* 还可以使用 ac.dispatch() 方法重定向到一个页面
*/
ac.dispatch("/show.jsp");
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.3 异步监听器
public interface AsyncListener extends EventListener {
void onComplete(AsyncEvent var1) throws IOException;
void onTimeout(AsyncEvent var1) throws IOException;
void onError(AsyncEvent var1) throws IOException;
void onStartAsync(AsyncEvent var1) throws IOException;
}
AsyncListener的实现类没有用@WebListener进行注解,需要在AsyncContext中手动注册:
asyncContext.addListener(new MyAsyncListener());
8. 动态注册和容器初始化
8.1 动态注册
/**
* 添加Servlet
*/
<T extends Servlet> T createServlet(Class<T> var1) throws ServletException;
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
*/
<T extends Filter> T createFilter(Class<T> var1) throws ServletException;
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
*/
<T extends EventListener> T createListener(Class<T> var1) throws ServletException;
public void addListener(String className);
public <T extends EventListener> void addListener(T t);
public void addListener(Class<? extends EventListener> listenerClass);
例:Servlet myServlet = createServlet(MyServlet.class);
8.2 初始化
Servlet容器初始化的核心是ServletContainerInitializer
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
实现ServletContainerInitializer的类必须用 @HandleTypes注解进行标注,以便声明初始化程序可以处理这些类型的类。