public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
Servlet是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理。
Servlet的任务有:
Servlet的创建:Servlet可以在第一次接收请求时被创建,也可以在在服务器启动时就被创建,这需要在web.xml的< servlet>中添加一条配置信息 < load-on-startup>5< /load-on-startup>,当值为0或者大于0时,表示容器在应用启动时就加载这个servlet,当是一个负数时或者没有指定时,则指示容器在该servlet被请求时才加载。
Servlet的生命周期方法:
在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,主要是为了让Servlet对象在处理客户端请求前可以完成一些初始化的工作,例如:建立数据库连接、获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用Servlet对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对行啊获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。
容器调用service()方法来处理客户端请求。要注意,在Servlet方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。在service方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destory()方法,以便让Servlet对象可以释放它所使用的资源,同时保存数据到持久存储设备中,例如将内存中的数据保存到数据库中,关闭数据库连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destory()方法。在Servlet容器调用destory()方法之前,如果还有其他线程正在service()方法中执行,容器将会等待这些线程执行完毕或等待服务器设定的超时时间到达。一旦Servlet对象的destory()方法被调用,容器不会再把其他的请求发送给该对象。如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端请求。在destory()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被Java垃圾收集器所回收。
LoginServlet
com.briup.estore.web.servlet.LoginServlet
LoginServlet
/login
该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。
返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。
public interface ServletConfig {
public String getServletName();//返回Servlet实例的名字
public ServletContext getServletContext();//返回Servlet上下文对象
public String getInitParameter(String name);//返回名称为name的初始化参数的值
public Enumeration getInitParameterNames();//返回Servlet所有初始化参数的名字和枚举集合
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{//GenericServlet抽象类定义了一个通用的、不依赖于具体协议的Servlet,简化子类的实现。
private transient ServletConfig config;//config为transient,不参与序列化
public GenericServlet() { }
public void destroy() {
}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
//对Servlet接口中init()方法的实现。其中调用了不带参数的的init()方法。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//不带参数的init()方法。通常我们在编写继承自GenericServlet的Servlet类时,只需重写该方法。
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
return config.getServletName();
}
}
public abstract class HttpServlet extends GenericServlet
implements java.io.Serializable
{
//绝大多数的网络应用中,都是客户端(浏览器)通过HTTP协议去访问服务器端的资源。
//而我们所编写的Servle也主要是应用于HTTP协议的请求和响应。该抽象类简化了开发应用于HTTP协议的Servlet。
......
//对GenericServlet类中service()方法的实现。
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
//首先进行显示类型转换
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);//调用下一个service方法
}
//在编写HTTPServlet子类时,通常不需要覆盖该方法,而只需要重写相应的doXXX()方法。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();//获取HTTP请求方法的类型
//HTTP1.1中定义了7种请求方法:Get、Post、Head、Put、Delete、Trace和Options
//然后根据请求方法的类型,调用相应的doXXX()方法
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req, resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req, resp);
} else {
//不支持的请求方法,提示错误
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
......}
Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过Servlet接口中的init()、service()和destory()方法来表示。
注:如果需要让Servlet容器在启动时自动加载Servlet,可以在web.xml文件中配置。
Servlet规范中定义,默认情况下(Servlet不是在分布式的环境中部署),Servlet容器对声明的每一个Servlet,只创建一个实例。如果有多个客户端请求同时访问这个Servlet,Servlet容器如何处理多个请求呢?答案是采用多线程,Servlet容器维护一个线程池来服务请求。当容器接收到一个访问Servlet的请求,调度者线程从线程池中选取一个工作线程,将请求传递给该线程,然后由这个线程执行Servlet的service()方法。
变量的线程安全
因为Servlet是单实例多线程模型,多个线程共享一个Servlet实例,因此对于实例变量的访问是非线程安全的。
建议:在Servlet中尽可能的使用局部变量,应该只使用只读的实例变量和静态变量。如果非得使用共享的实例变量或静态变量,在修改共享变量时应该注意线程同步。
属性的线程安全
在Servlet中,可以访问保存在ServletContext、HttpSession和ServletRequest对象中的属性。那么这三种不同范围的对象,属性访问是否是线程安全的呢?
ServletContext:该对象被Web应用程序的所有Servlet共享,多线程环境下肯定是非线程安全的。
HttpSession:HttpSession对象只能在同属于一个Session的请求线程中共享。对于同一个Session,我们可能会认为在同一时刻只有一个用户请求,因此,Session对象的属性访问是线程安全的。但是,如果用户打开多个同属于一个进程的浏览器窗口,在这些窗口中的访问请求同属于一个Session,对于多个线程的并发修改显然不是线程安全的。
ServletRequest:因为Servlet容器对它所接收到的每一个请求,都创建一个新的ServletRequest对象,所以ServletRequest对象只在一个线程中被访问,因此对ServletRequest的属性访问是线程安全的。但是,如果在Servlet中创建了自己的线程,那么对ServletRequest的属性访问的线程安全性就得自己去保证。此外,如果作死的将当前请求的Servlet通过HttpSession或者ServletContext共享,那当然也是非线程安全的。
1、重定向和转发的区别
JSP是一种建立在Servlet规范提供的功能之上的动态网页技术,它通过在网页文件中嵌入脚本代码,用于产生动态内容。
JSP文件在用户第一次请求时,会被编译成Servlet,然后由这个Servlet处理用户请求,所以JSP也可以看成是运行时的Servlet。
JSP和Servlet的区别与联系?
JSP在本质上就是SERVLET,但是两者的创建方式不一样.Servlet完全是JAVA程序代码构成,擅长于流程控制和事务处理,通过Servlet来生成动态网页很不直观.JSP由HTML代码和JSP标签构成,可以方便地编写动态网页.因此在实际应用中采用Servlet来控制业务流程,而采用JSP来生成动态网页.在struts框架中,JSP位于MVC设计模式的视图层,而Servlet位于控制层.JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP是Java和HTML组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。
public interface Filter {
//用于完成Filter的初始化
public void init(FilterConfig filterConfig) throws ServletException;
//实现过滤功能
public void doFilter ( ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException, ServletException;
//用于销毁Filter前,完成某些资源的回收
public void destroy();
}
web.xml 中声明的每个 filter 在每个虚拟机中仅仅只有一个实例。
(1) 加载和实例化
Web 容器启动时,即会根据 web.xml 中声明的 filter 顺序依次实例化这些 filter。
(2) 初始化
Web 容器调用 init(FilterConfig) 来初始化过滤器。容器在调用该方法时,向过滤器传递 FilterConfig 对象,FilterConfig 的用法和 ServletConfig 类似。利用 FilterConfig 对象可以得到 ServletContext 对象,以及在 web.xml 中配置的过滤器的初始化参数。在这个方法中,可以抛出 ServletException 异常,通知容器该过滤器不能正常工作。此时的 Web 容器启动失败,整个应用程序不能够被访问。实例化和初始化的操作只会在容器启动时执行,而且只会执行一次。
(3) doFilter
doFilter 方法类似于 Servlet 接口的 service 方法。当客户端请求目标资源的时候,容器会筛选出符合 filter-mapping 中的 url-pattern 的 filter,并按照声明 filter-mapping 的顺序依次调用这些 filter 的 doFilter 方法。在这个链式调用过程中,可以调用 chain.doFilter(ServletRequest, ServletResponse) 将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用 RequestDispatcher 的 forward 和 include 方法,以及 HttpServletResponse 的 sendRedirect 方法将请求转向到其它资源。需要注意的是,这个方法的请求和响应参数的类型是 ServletRequest 和 ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
(4) 销毁
Web 容器调用 destroy 方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。
url-mapping的写法
匹配规则有三种:
如果想要以不同的方式拦截,我们需要在
的值进行覆盖。并且会拦截需要转发的请求)
include 的用法和dispacher 的用法相似,不在进行介绍。
error的用法是用来来接错误信息页面,我们可以在web.xml的页面中配置错误页面的信息,具体配置如下(在Mapping之外配置):
错误页面的信息配置完成之后,我们就可以像之前的request拦截一样,在
这时候,当我们要访问的页面出错误时,过滤器便会自动进行拦截,同时我们可以使用request.sendError(500,"错误信息");
来让页面产生错误。
注意:error的拦截只试用与错误页面,所谓的错误页面是在web.xml中配置的。
1、统一POST请求中文字符编码的过滤器
2、控制浏览器缓存页面中的静态资源的过滤器
有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。
3、使用Filter实现URL级别的权限认证
在实际开发中我们经常把一些执行敏感操作的servlet映射到一些特殊目录中,并用filter把这些特殊目录保护起来,限制只能拥有相应访问权限的用户才能访问这些目录下的资源。从而在我们系统中实现一种URL级别的权限功能。
4、实现用户自动登陆
首先,在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码。编写一个AutoLoginFilter,这个filter检查用户是否带有名称为user的cookie,如果有,则调用dao查询cookie的用户名和密码是否和数据库匹配,匹配则向session中存入user对象(即用户登陆标记),以实现程序完成自动登陆。
与开发 Servlet 不同的是,Filter 接口并没有相应的实现类可供继承,要开发过滤器,只能直接实现 Filer 接口。
此过滤器用来解决全站中文乱码问题:设置统一的字符编码集
public class CharacterEncodingFilter implements Filter {
private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-";
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}
public void destroy() {
}
}
当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用的启动和停止、用户Session的开始和结束等,通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。
Listener在当web容器启动的时候,去读取每个web应用的web.xml配置文件,当配置文件中配有filter和listener时,web容器实例化listener,listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个session时会调用它的sessionCreated()方法,当servlet容器关闭或者重新加载web应用时lister对象被销毁。
不同功能的Listener 需要实现不同的 Listener 接口,一个Listener也可以实现多个接口,这样就可以多种功能的监听器一起工作。常用监听器:
1)监听 Session、request、ServletContext 的创建于销毁,分别为
HttpSessionLister、ServletContextListener、ServletRequestListener
void contextInitialized(ServletContextEvent sce):创建Servletcontext时
void contextDestroyed(ServletContextEvent sce):销毁Servletcontext时
2)监听对象属性变化,分别为:
HttpSessionAttributeLister、ServletContextAttributeListener、ServletRequestAttributeListener
void attributeAdded(ServletContextAttributeEvent event):添加属性时;
void attributeReplaced(ServletContextAttributeEvent event):替换属性时;
void attributeRemoved(ServletContextAttributeEvent event):移除属性时;
3)与session中的绑定的对象相关的监听器(对象感知监听器)
1:HttpSessionBindingListener监听
⑴在需要监听的实体类实现HttpSessionBindingListener接口
⑵重写valueBound()方法,这方法是在当该实体类被放到Session中时,触发该方法
⑶重写valueUnbound()方法,这方法是在当该实体类从Session中被移除时,触发该方法
2:HttpSessionActivationListener监听
⑴在需要监听的实体类实现HttpSessionActivationListener接口
⑵重写sessionWillPassivate()方法,这方法是在当该实体类被序列化时,触发该方法
⑶重写sessionDidActivate()方法,这方法是在当该实体类被反序列化时,触发该方法
1、利用HttpSessionLister,统计当前在线人数。
public class OnLineCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
} else {
onLineCount++;
context.setAttribute("onLineCount", onLineCount);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
} else {
onLineCount--;
context.setAttribute("onLineCount", onLineCount);
}
}
}
2、自定义Session扫描器
当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。
package me.gacl.web.listener;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @ClassName: SessionScanerListener
* @Description: 自定义session扫描器
* @author: 孤傲苍狼
* @date: 2014-9-10 下午10:16:42
*
*/
public class SessionScanerListener implements HttpSessionListener,ServletContextListener {
/**
* @Field: list
* 定义一个集合存储服务器创建的HttpSession
* LinkedList不是一个线程安全的集合
*/
/**
* private List list = new LinkedList();
* 这样写涉及到线程安全问题,SessionScanerListener对象在内存中只有一个
* sessionCreated可能会被多个人同时调用,
* 当有多个人并发访问站点时,服务器同时为这些并发访问的人创建session
* 那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法
* sessionCreated方法的内部处理是往一个集合中添加创建好的session,那么在加session的时候就会
* 涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加session时,一定要保证集合是线程安全的才行
* 如何把一个集合做成线程安全的集合呢?
* 可以使用使用 Collections.synchronizedList(List list)方法将不是线程安全的list集合包装线程安全的list集合
*/
//使用 Collections.synchronizedList(List list)方法将LinkedList包装成一个线程安全的集合
private List list = Collections.synchronizedList(new LinkedList());
//定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
private Object lock = new Object();
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("session被创建了!!");
HttpSession session = se.getSession();
synchronized (lock){
/**
*将该操作加锁进行锁定,当有一个thread-1(线程1)在调用这段代码时,会先拿到lock这把锁,然后往集合中添加session,
*在添加session的这个过程中假设有另外一个thread-2(线程2)来访问了,thread-2可能是执行定时器任务的,
*当thread-2要调用run方法遍历list集合中的session时,结果发现遍历list集合中的session的那段代码被锁住了,
*而这把锁正在被往集合中添加session的那个thread-1占用着,因此thread-2只能等待thread-1操作完成之后才能够进行操作
*当thread-1添加完session之后,就把lock放开了,此时thread-2拿到lock,就可以执行遍历list集合中的session的那段代码了
*通过这把锁就保证了往集合中添加session和变量集合中的session这两步操作不能同时进行,必须按照先来后到的顺序来进行。
*/
list.add(session);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session被销毁了了!!");
}
/* Web应用启动时触发这个事件
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("web应用初始化");
//创建定时器
Timer timer = new Timer();
//每隔30秒就定时执行任务
timer.schedule(new MyTask(list,lock), 0, 1000*30);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("web应用关闭");
}
}
/**
* @ClassName: MyTask
* @Description:定时器要定时执行的任务
* @author: 孤傲苍狼
* @date: 2014-9-11 上午12:02:36
*
*/
class MyTask extends TimerTask {
//存储HttpSession的list集合
private List list;
//存储传递过来的锁
private Object lock;
public MyTask(List list,Object lock){
this.list = list;
this.lock = lock;
}
/* run方法指明了任务要做的事情
* @see java.util.TimerTask#run()
*/
@Override
public void run() {
//将该操作加锁进行锁定
synchronized (lock) {
System.out.println("定时器执行!!");
ListIterator it = list.listIterator();
/**
* 迭代list集合中的session,在迭代list集合中的session的过程中可能有别的用户来访问,
* 用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,
* 然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加的新的session进来了,
* 这样就导致了往list集合添加的新的session和遍历list集合中的session这两个操作无法达到同步
* 那么解决的办法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"这两段代码做成同步,
* 保证当有一个线程在访问"list.add(session)"这段代码时,另一个线程就不能访问"while(it.hasNext()){//迭代list集合}"这段代码
* 为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,
* 用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
* 当在执行往list集合添加的新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,
* 当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往往list集合添加的新的session
*/
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
/**
* 如果当前时间-session的最后访问时间>1000*15(15秒)
* session.getLastAccessedTime()获取session的最后访问时间
*/
if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
//手动销毁session
session.invalidate();
//移除集合中已经被销毁的session
it.remove();
}
}
}
}
}