Qunar大讲堂
1.web项目结构
1.1 web项目结构
2.servlet
2.1 servlet
2.2 listener
2.3 filter
2.4 加载过程
2.5 路径映射,转发与重定向
2.6 cookie
一、Web 工程项目结构
二、servlet
1.Servlet / GenericServlet / HttpServlet
2.Listener
Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。
目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是:
- 4 个 EventListeners 类型的:由某个事件触发;当这些Listener的属性被修改时,这些事件将被触发
ServletContextAttributeListener
监听对ServletContext属性的操作,比如增加、删除、修改属性
ServletRequestAttributeListener
ServletRequestListener
HttpSessionAttributeListener
- 2 个 LifecycleListeners 类型的:由生命周期的不同状态触发
ServletContextListener 创建StandardContext时contextInitialized方法被调用
当创建ServletContext时,激发contextInitialized(ServletContextEvent sce)方法;当销毁ServletContext时,激发contextDestroyed(ServletContextEvent sce)方法。
HttpSessionListener 创建Session是sessionCreated方法被调用
监听HttpSession的操作。当创建一个Session时,激发session Created(HttpSessionEvent se)方法;当销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se)方法。
Listener是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。
Listener Demo
下面我们开发一个具体的例子,这个监听器能够统计在线的人数(一次“在线”定义为Session创建到被销毁的时段)。在ServletContext初始化和销毁时,在服务器控制台打印对应的信息。当ServletContext里的属性增加、改变、删除时,在服务器控制台打印对应的信息。
要获得以上的功能,监听器必须实现以下3个接口:
- HttpSessionListener
ServletContextListener
ServletContextAttributeListener
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @author wanlong.ma
*
*/
public class OnlineUserListener implements HttpSessionListener,
ServletContextListener, ServletContextAttributeListener {
// 当前在线人数计数器
private long onlineUserCount = 0;
public long getOnlineUserCount() {
return onlineUserCount;
}
@Override
public void attributeAdded(ServletContextAttributeEvent arg0) {
}
@Override
public void attributeRemoved(ServletContextAttributeEvent arg0) {
}
@Override
public void attributeReplaced(ServletContextAttributeEvent attributeEvent) {
System.err.println("...attributeReplaced...");
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
// 当Session被创建时调用,在线人数+1,并调用本地toUpdateCount方法设置一个onlineUserCount属性,以便被获取
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
onlineUserCount ++;
toUpdateCount(httpSessionEvent);
}
// 当Session被销毁时调用,在线人数-1;机制同上
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
onlineUserCount --;
toUpdateCount(httpSessionEvent);
}
private void toUpdateCount(HttpSessionEvent httpSessionEvent){
httpSessionEvent.getSession().setAttribute("onlineUserCount", onlineUserCount);
}
}
配置web.xml:
<listener>
<listener-class>com.ee.listener.OnlineUserListener</listener-class>
</listener>
jsp中通过request.getSession().getAttribute获取我们设置的attribute.
3.Filter
-
概述
Filter是一种可以改变进入的请求(Request)和返回的响应(Response)的Header和内容的Java组件。
Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。
由上图可以看出:在Request进入Servlet之前Filter对其进行预处理,在Response离开Servlet之后也会被Filter进行处理,要注意这个顺序前后是相反的:
web.xml中元素执行的顺序listener -> filter -> struts拦截器 -> servlet
优点
过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题过滤器的作用描述
☑ 在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest 。
☑ 根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
☑ 在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。
☑ 根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。Filter接口
1.如何驱动
在 web 应用程序启动时,web 服务器将根据 web.xml 文件中的配置信息来创建每个注册的 Filter 实例对象,并将其保存在服务器的内存中
2.方法介绍
init() init 方法在 Filter 生命周期中仅执行一次,web 容器在调用 init 方法时
destory() 在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
doFilter() Filter 链的执行
- Filter示例:实现编码过滤器和请求url日志记录过滤器
web.xml配置
<!-- 编码过滤器 -->
<filter>
<filter-name>setCharacterEncoding</filter-name>
<filter-class>com.company.strutstudy.web.servletstudy.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>setCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 请求url日志记录过滤器 -->
<filter>
<filter-name>logfilter</filter-name>
<filter-class>com.company.strutstudy.web.servletstudy.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
编码拦截器实现
public class EncodingFilter implements Filter {
private String encoding;
private Map<String, String> params = new HashMap<String, String>();
// 项目结束时就已经进行销毁
public void destroy() {
System.out.println("end do the encoding filter!");
params=null;
encoding=null;
}
// Filter处理方法:对request和response进行处理
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
//UtilTimerStack.push("EncodingFilter_doFilter:");
System.out.println("before encoding " + encoding + " filter!");
req.setCharacterEncoding(encoding);
// resp.setCharacterEncoding(encoding);
// resp.setContentType("text/html;charset="+encoding);
chain.doFilter(req, resp);
System.out.println("after encoding " + encoding + " filter!");
System.err.println("----------------------------------------");
//UtilTimerStack.pop("EncodingFilter_doFilter:");
}
// 项目启动时就已经进行读取
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the encoding filter!");
encoding = config.getInitParameter("encoding");
for (Enumeration e = config.getInitParameterNames(); e
.hasMoreElements();) {
String name = (String) e.nextElement();
String value = config.getInitParameter(name);
params.put(name, value);
}
}
}
日志拦截器实现
public class LogFilter implements Filter {
FilterConfig config;
public void destroy() {
this.config = null;
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
// 获取ServletContext 对象,用于记录日志
ServletContext context = this.config.getServletContext();
System.out.println("before the log filter!");
// 将请求转换成HttpServletRequest 请求
HttpServletRequest hreq = (HttpServletRequest) req;
// 记录日志
System.out.println("Log Filter已经截获到用户的请求的地址:"+hreq.getServletPath() );
try {
// Filter 只是链式处理,这里将请求转发给下一个Filter,请求依然转发到目的地址
chain.doFilter(req, res);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("after the log filter!");
}
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the log filter!");
this.config = config;
}
}
4.加载过程
- context-param 初始化键值对
- listener 有多个listener时,以在web.xml中的注册顺序加载
- filter 有多个filter时,以web.xml中<url-pattern>的顺序加载
- servlet 同一时间web任期中只有一个servlet实体,servlet并发需要自己做
servlet并发处理
当请求来临时,servlet容器会初始化对应的servlet。如果多个请求同时访问的是同一个servlet,Servlet容器会创建多个线程同时调用servlet的service()方法来处理这些请求,而不是多个servlet实例。
如果给service方法设置了synchronized关键字,servlet容器则是序列化请求依次通过service方法。
但如果servlet实现了SingleThreadModel接口(此时,这个servlet只能一次处理一个请求),那么servlet容器会根据请求的数量创建多个servlet的实例(每个servlet实例相当于一个线程),并调用servlet的service方法来处理请求。
5.路径映射、转发与重定向
url-pattern类型
全路径映射:如/aaa/bbb
路径映射:以/开头、/*结尾
扩展映射:以×.开头
默认映射:/匹配顺序
精确路径匹配 -> 最长路径匹配 -> 扩展匹配 -> Default ServletRequestDispatcher接口
RequestDispatcher接口,定义一个对象,从客户端接收请求,然后将它发给服务器的可用资源(例如Servlet、CGI、HTML文件、JSP文件)。Servlet引擎创建request dispatcher对象,用于封装由一个特定的URL定义的服务器资源。这个接口是专用于封装Servlet的,但是一个Servlet引擎可以创建request dispatcher对象用于封装任何类型的资源。request dispatcher对象是由Servlet引擎建立的,而不是由Servlet开发者建立的。
RequestDispatcher接口中定义了两个方法:forward方法(页面跳转)和include方法(页面包含)。
获取RequestDispatcher对象的方法:
ServletContext.getRequestDispatcher (参数只能写绝对路径,以“/”开头)
ServletRequest.getRequestDispatcher (参数可以是绝对路径,也可以是相对路径)
如:
request.getRequestDispacther("/test.jsp")
-
转发(forward)与重定向(sendredirect)
都是跳转到另一个路径,不过二者有很大的区别。
简单地说,请求转发是当前servlet处理完Request之后,将服务资源(就是由上面的RequestDispatcher接口来负责的)转发给下一个servlet接着处理,是在同一个请求里面完成的,二者共享的是同一个request,整个过程都是在服务端完成的。
重定向并不转发这次请求的服务资源,而是直接向客户端返回响应,响应告诉客户端你必须要再发送一个请求,去访问重定向的页面,紧接着客户端收到这个请求后,立刻发出一个新的请求,去请求重定向的页面,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。
6.cookie
使用cookie来保存登录信息。
会话:用户开一个浏览器,访问一个网站,只要不关闭该浏览器,不管该用户点击多少个超链接,访问多少资源,直到用户关闭浏览器,整个这个过程我们称为一次会话。
为什么要使用cookie?
1.记录用户的事件
2.浏览历史记录
3.用户名和密码的记录cookie特性
1.cookie是在服务端创建的;
2.cookie是保存在浏览器这端的;
3.cookie的生命周期可以通过cookie.setMaxAge(Int value)设置
value为负时,表示不保存cookie;
value为正时,表示最大生存秒数;
value为0时,表示在客户端删除这个cookie.
如果不设置,默认为-1,关闭浏览器就destroy了;
4.cookie可以被多个浏览器共享;
5.一个WEB应用可以保存多个cookie
6.cookie存放的时候是以明文方式存放的,因此安全比较低,我们可以通过加密后保存,可以用md5(不可逆)算法加密(想起了base64),经过md5加密后存放到数据库,用户输入密码后加密然后再和数据库里面的匹配。
7.Servlet本身支不支持并发处理响应?
同一时间,在同一个应用的Web容器中,一个Servlet只存在一个实例,但是Servlet可以接受并发的请求。这是因为Servlet是无状态的(Servlet中没有保存状态的可变变量),因此一般情况下Servlet中不会发生资源争用,每次请求都会调用Servlet的service方法,在一个新的线程中去处理请求,Servlet容器会自动使用线程池等技术来支持系统的运行。
但是如果继承的Servlet定义了可争用的资源(可变变量等),需要自己做并发控制。
当客户端第一次请求某个Servlet时,Servlet 容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。