jsp & servlet 精华一页纸
servlet/jsp 本质上是一种java编程协议/规范, 存在于web容器中,由web容器解析,管理其生命周期。
1、容器
一个典型简单容器的实现:
实现一个Java程序Application
使用Socket监听一个端口
解析url/解析协议
发送数据给客户端
容器处理请求的典型过程:
I、客户请求
II、Web容器接受到请求,把相关信息封装成两个对象 HttpServletRequest/HttpServletResponse
III、根据请求的URL找到具体的servlet,并为这个请求创建一个线程,把请求和响应对象传递给这个servlet线程
IV、容器调用 servlet提供的service方法(doGet、doPost)
V、servlet把处理结果设置到 响应对象中去
VI、线程结束,容器把响应翻译并返回给客户端
2、典型应用
I、部署结构
web-app
WEB-INF
web.xml
classes
html/jsp
II、web.xml
两个部分:处理的servlet | 拦截的URL路径
servletA
xxx.xxx.ServletA
servletA
/
首先定义 servlet 名称,并且映射到具体的代码路径
其次定义 servlet 名称,到url响应的服务名
III、Servelt实现
public class ServletA extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String c = request.getParameter("xxx"); -- 获取参数
MyObject o = new MyObject-- Model 部分
request.setAttribute("styles", o); -- View 部分
RequestDispatcher view = request.getRequestDispatcher("result.jsp");
view.forward(request, response);-- 转发请求
}
}
注意标红的这段代码, 这是很多 第三方MVC 框架(比如 Spring MVC)的核心基础, 从这里开始是第一步,基于此不断构建了一个庞大的框架体系。
3、Servlet详解
继承体系
servlet接口 -> genericservlet抽象类 -> HttpServlet
ServletRequest(接口) - HttpServletReqeust(接口)
ServletResponse(接口) - HttpServletResponse
生命周期- httpServlet 接口方法
启动 - init: servlet创建后,服务前执行
服务 - service doGet/doPost:应用覆盖执行
销毁 - destory:释放资源
数据交互
客户端请求数据 - HttpRequest : 参数 getParameter | 头部 getHeader | 缓存 getCookies ...
返回客户端数据 - HttpResponse: 头部 setHeader | 输出流 getOutputStream ...
系统全局参数 - servletConfig | servletContext
监听与全局参数
全局参数 与 的区别
是整个webApp 级别的参数,给所有Servlet使用
是Servlet级别的参数,给当前Servlet的 多个实例使用
另外,纠正网上 以讹传讹的一个错误,init-param并不是只能在 init方法中使用,而是从 init 方法的生命周期之后才能使用,具体说来就是 构造函数里不能使用,直到容器调用init方法开始才能使用
监听
web.xml
com.example.MyServletContextListener
因为监听类扩展的接口 ServletContextListener ,可以获取全局对象 ServletContext,因而可以做一些全局的准备工作,把对应的结果缓存到全局对象中;典型的就是数据库链接
几个注意点:
a、请求幂等性的问题, 非查询性的请求处理时要注意检查,防止重复执行
b、请求重定向与请求分派的区别:
请求分派在服务端发生,分派是由服务器的一个组件传递给另一个组件
重定向在客户端发生。服务器发送一个 301 状态的响应给浏览器,并在头部有一个location的参数,包含新的URL,浏览器根据新的URL重新请求,在客户端浏览器能够看到变化
4、会话管理
http协议是无状态协议 ,如何缓存状态,如何跟踪用户的访问
思考一下, 要跟踪一个客户, 首先需要标识一个客户; 其次这个标识在交互中要一直携带,如何携带
I、生成这个标识
方法一: 本地 自己生成 - 比如非浏览器的http请求
方法二:服务端 在初次访问时生成一个唯一标识 request.getSession(); session.getId();
II、携带这个标识
方法一:客户端在发送请求的 cookie中携带这个标识; 服务端在发送相应时 set-Cookie (除了设置标识外,cookie 可以存储更多的数据)-- 这部分工作是容器在处理
方法二:通过 URL 重写,response.encodeURL("SessionTest.do") -- 原理就是http请求携带一个 参数jsessionid
方法三:通过表单 hidden 域,这个已不推荐使用
会话的生命周期
创建 : 创建会话 getSession
变化 :增加属性 setAttribute | 删除属性 removeAttribute
销毁 :销毁会话 Invalidate
迁移 :如果是分布式商用容器(比如weblogic/websphere),会把一台设备上的会话钝化,然后迁移到另一台设备上
更多关于 session 的讨论,参见本人写的另一片文章 《Session 一页纸精华》
5、JSP详解 - java裸编码
jsp本质是由容器转化成Servlet来处理,目的是减少写 html 脚本的工作量。如果使用的是tomcat,可以到work目录下查找对应jsp的class文件
引子- 既然是java代码, 那按照java代码的过程和元素,描述一下 jsp的使用
a、引入 包 -- page 指令
<%@ page import="java.util.*,java.io.*" %> 多个包用逗号分隔
b、定义Servlet类
-- 因为 类是自动生成的,所以这一步是容器生成的,跳过
c、定义域、属性、全局变量 -- 声明
在 _jspService之前执行
<%!
int count = 0;
%>
d、使用表达式片段 - scriptlet
因为所有的jsp最终都翻译成 _jspService 方法调用, 所以一般在 scriptlet中不能定义方法,如果非要定义的话,可以在声明里面定义,因为这部分内容不在方法内
<%
out.println(new Counter().getCount());
%>
表达式 -- 容器会调用表达式返回值(必须有返回值),通过out输出
<%=new Counter().getCount()%>
e、注释
<%-- --%>
最常用的例子,表格取数据迭代
<% for(int i = 0 ; i < 5; i++){ %>
<%=i%>
<% } %>
最终在 html页面中,就可以灵活插入各个java代码执行的片段,把其中的html变成 outputStream的输出
生命周期
创建 : 容器接受请求,翻译成 servlet的源代码,然后加载,实例化,最终变成一个Servlet
处理 : 执行service方法
销毁 : 容器执行 _jspDestroy方法
部署与全局(隐式)参数
部署和Servlet一样,只是Servlet-class 换成了 /testjsp.jsp
同Servlet一样, jsp 也拥有Servlet所有的全局参数, request | session | config | application(对应ServletContext) | out 等等对应Servlet各个部分
6、JSP进阶 - JavaBean + EL表达式
从上面table的例子可以看到,直接Java编程是非常low的事情,逻辑混乱,而且不利于解耦,前端和业务人员无法界定。所以JSP又演进了一步,不写Java的Scriptlet代码
I、使用JavaBean绑定
name:
ID#:
-- 因为JavaBean属性和表单名称
--一致,使用了通配符
II、再加上 EL 表达式
${xxx.xxx} -- 左边是隐式(param,scope,cookie..)对象、map或者bean,右边是属性
${xxx["xxx"} --可以适用于map,数组,或者bean等对象
[] 方式可以嵌套表达式,括号中的内容如果没有使用引号,容器会进行计算,否则会可能成键值等
比如request里面有 map和array两个对象
request.setAttribute("map", map);
request.setAttribute("array", array);
EL:${map[array[0]]}
tag标签 - 可以通过表达式使用 java代码中的静态方法
a、提供java静态方法
b、在WEB-INF中提供 *.tld 文件,标注方法和参数
c、在jsp中引用静态方法
<%@ taglib prefix="mine" uri="DiceFunctions" %> --mine只是一个别名而已
${mine:rollIt()}
可以设置模版值,替换另一个文件的表达式,以达到模板化的目的。
EL表达式和JavaBean绑定只解决了取值问题,虽然动态标记可以解决一些逻辑控制问题,但都是固化的模式,并没有能灵活使用。怎么解决Scriptlet代码在页面中的控制问题?
答案是 JSTL - 具体参加本人博客 《标签模版技术 JSTL 一页纸精华》
7、过滤器
过滤器是和Servlet平级的另一种处理过程,在请求到达处理Servlet之前经过一些列过滤,这里体现了 面向切面的思想,虽然没有采用AOP的技术。
I、典型例子
a、声明过滤器
在web.xml(DD)中定义,定义方法和Servlet类似
FilterA
com.example.web.FilterA
BeerRequest
/ -- 也可以指定servlet SessionTest
REQUEST -- 可以指定拦截的资源类型,REQUEST 客户端请求,INCLUDE通过include调用分派的请求,
FORWARD 通过forward调用分派来的请求,ERROR 错误处理器调用的请求
b、编写过滤器
public class FilterA implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
处理代码
chain.doFilter(req, resp);
处理代码
}
}
II、生命周期
启动 : init 提供FilterConfig对象
服务 : doFilter()中处理,提供三个对象 ServletRequest/ServletRequest/FilterChain
销毁 : destroy 方法
III、过滤器的概念栈
a、调用第一个过滤器a的doFilter方法,直到 chain.doFilter方法
b、调用第n个过滤器n的doFilter方法,直到 chain.doFilter方法
c、调用真实Servlet的 service 方法,执行完返回
d、容器把控制器返回第n个过滤器的doFilter方法,chain.doFilter之后的内容
e、容器把控制器返回第1个过滤器的doFilter方法,chain.doFilter之后的内容
f、容器完成整个流程响应
这里,有一个最有名的应用,struts2 - 基于过滤器拦截, 进行的流程处理,而没有使用servlet这种经典流程