Servlet & JSP & Filter & Listener
Servlet
Servlet 是运行在服务器端的一个对象,web 服务器(如 Tomcat)会根据用户的访问链接将请求路由到不同的 Servlet 中并由其处理与返回响应。路由配置一般在 web.xml 中,当然也可以使用注解的形式。
特点
- 单例
- Servlet 在请求时实例化与初始化,web 服务创建时默认不创建 Servlet 对象,不过可以配置自启动(
load-on-startup
)
基本原理
下面是 HTML & servlet & web.xml 的一个示例
HTML(注意 action 和 method 的取值):
...
<form action="login" method="post">
账号: <input type="text" name="name"> <br>
密码: <input type="password" name="password"> <br>
<input type="submit" value="登录">
</form>
...
web.xml(注意 Servlet 和 url 路径的映射关系):
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>LoginServlet</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
Servlet:
public class LoginServlet extends HttpServlet{
// @Override // 当前函数的作用见下
// public void service(HttpServletRequest request, HttpServletResponse response){...}
// public void init(ServletConfig config) {...} // 继承于父类,Servlet 的构造函数执行后会自动执行此函数,只会执行一次
// 处理 GET 请求
public void doGet(HttpServletRequest request, HttpServletResponse response){
try {
response.getWriter().println("<h1>Hello Servlet!</h1>");
response.getWriter().println(new Date().toLocaleString());
} catch (IOException e) {
e.printStackTrace();
}
}
// 处理 POST 请求
public void doPost(HttpServletRequest request, HttpServletResponse response){
try{
String name = request.getParameter("name");
String password = request.getParameter("password");
}catch (IOException e) {
e.printStackTrace();
}
}
}
每一个 Servlet 都需要继承 HttpServlet。HttpServlet 中有一个 service 成员函数,默认的 service 会判断请求中的方法,并根据方法(GET or POST)来调用当前函数下的 doGet 或者 doPost 方法。结合 Filter(见下文) & 重写service & 反射可以实现一个 servlet 处理不同的请求,如下所示:
使用 Filter 拆分用户的请求,下面代码截取自一段 Filter 对象:
// 假设请求的方法是 ***/admin_category_list
if(uri.startsWith("/admin_")){
String servletPath = StringUtils.substringBetween(uri,"_", "_") + "Servlet"; // 确定 Servlet
String method = StringUtils.substringAfterLast(uri,"_" );
request.setAttribute("method", method); // 确定方法
req.getRequestDispatcher("/" + servletPath).forward(request, response); // 执行 Servlet
return;
}
重写 service 函数并使用反射选择与执行 Servlet 中的方法(此时 Servlet 中可以没有 doGet 和 doPost):
@Override
public void service(HttpServletRequest request, HttpServletResponse response) {
...
try {
/*借助反射,调用对应的方法*/
String method = (String) request.getAttribute("method");
Method m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
javax.servlet.http.HttpServletResponse.class,Page.class);
String redirect = m.invoke(this,request, response,page).toString();
}
...
}
常见方法
- 跳转
- 服务端跳转(返回跳转页的响应内容但不改变浏览器地址):
request.getRequestDispatcher("success.html").forward(request, response);
- 客户端跳转(服务端向客户端发送重定向地址):
response.sendRedirect("fail.html");
- 服务端跳转(返回跳转页的响应内容但不改变浏览器地址):
- Servlet 自启动
- 在 web.xml 中配置
<load-on-startup>10</load-on-startup>
,可以实现 Servlet 的自启动
- 在 web.xml 中配置
JSP
JSP(Java Server Pages),一个混合 Java&HTML 的网页编写技术。JSP 最终将会被翻译为 Servlet 。JSP 继承于 HttpJspBase,而后者继承自 HttpServlet。
JSP 示例
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*"%>
<%
List<String> words = new ArrayList<String>();
words.add("today");
words.add("is");
words.add("great");
%>
<table width="200px" align="center" border="1" cellspacing="0">
<%for (String word : words) {%>
<tr>
<td><%=word%></td>
</tr>
<%}%>
</table>
网页中输出如下:
today |
---|
is |
great |
JSP 组成
JSP 由这些页面元素组成:
- 静态内容,html、css、javascript 等内容
- 指令,以
<%@开始 %>
结尾,比如<%@page import="java.util.*"%>
- 表达式
<%=%>
,用于输出一段html,<%="hello jsp"%>
类似于<%out.println("hello jsp");%>
- Scriptlet,在
<%%>
之间,可以写任何 java 代码 - 声明,在
<%!%>
之间可以声明字段或者方法。但是不建议这么做。 - 动作,
<jsp:include page="Filename" >
在jsp
页面中包含另一个页面。 - 注释
<%-- -- %>
,不同于 html 的注释<!-- -->
通过jsp
的注释,浏览器也看不到相应的代码,相当于在servlet中注释掉了
隐式对象
不需要显式定义就可以使用的对象。JSP 一共有 9 个隐式对象,分别为:
- request、response、out,out 为输出
- pageContext、session、application,分别代表当前页,会话,全局作用域对象
- JSP 会被编译为一个 Servlet 类 ,运行的时候是一个 Servlet 实例。 page 即代表 this
- config,config 可以获取一些在 web.xml 中初始化的参数
- exception,异常对象,指定的异常处理页面才可使用
JSTL
JSTL(JSP Standard Tag Library),JSTL允许开人员可以像使用 HTML 标签那样在 JSP 中开发 Java 功能。
常见函数标签:fmt、fn
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%-- 指明后续的标签使用都会以<c: 开头 --%>
<c:set var="name" value="${'gareen'}" scope="request" />
通过标签获取name: <c:out value="${name}" /> <br> <%-- 类似<%=request.getAttribute("name")%> --%>
<c:remove var="name" scope="request" /> <br>
通过标签获取name: <c:out value="${name}" /> <br>
<c:set var="hp" value="${3}" scope="request" /> <%-- 循环示例 --%>
<c:choose>
<c:when test="${hp<5}">
<p>这个英雄要挂了</p>
</c:when>
<c:otherwise>
<p>这个英雄觉得自己还可以再抢救抢救</p>
</c:otherwise>
</c:choose>
EL 表达式
EL表达式可以从 pageContext、request、session、application四个作用域中取到值,如果 4 个作用域都有相同属性,EL会按照从高到低的优先级顺序获取:pageContext > request > session > application
JSP 常见用法
- include
- 指令 include:
<%@include file="footer.jsp" %>
,当前 JSP 将插入到命令行位置 - 动作 include:
<jsp:include page="footer.jsp" />
,footer.jsp 会被转化未 footer_jsp.java 以 Servlet 的形式存在,footer_jsp.java 的执行结果会插入到命令位置
- 指令 include:
- 跳转
- 客户端跳转:
<%response.sendRedirect("hello.jsp");%>
- 服务端跳转:
<jsp:forward page = "hello.jsp"/>
- 客户端跳转:
JavaBean
JavaBean 的标准
- 提供无参 public 的构造方法(默认提供)
- 每个属性,都有 public 的 getter 和 setter
- 如果属性是 boolean,那么就对应 is 和 setter 方法
Session
Session 就是是会话。会话指的是从用户打开浏览器访问一个网站开始,无论在这个网站中访问了多少页面,点击了多少链接,都属于同一个会话,直到该用户关闭浏览器为止。
Servlet + JSP ≈ MVC
Servlet 方便写 Java 代码,JSP 方便写页面代码,结合二者的特点,使用 Servlet 写业务相关代码,使用 JSP 进行展示。
Servlet,将数据保存到隐式对象中
public class HeroEditServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int id = Integer.parseInt(request.getParameter("id"));
Hero hero = new HeroDAO().get(id);
request.setAttribute("hero", hero); // 设置属性后 JSP 使用 EL 语言 ${hero.id},获得数据
request.getRequestDispatcher("editHero.jsp").forward(request, response);
}
}
JSP,从隐式对象中获得数据
<form action='updateHero' method='post'>
名字 : <input type='text' name='name' value='${hero.name}'> <br>
血量 :<input type='text' name='hp' value='${hero.hp}'> <br>
伤害: <input type='text' name='damage' value='${hero.damage}'> <br>
<input type='hidden' name='id' value='${hero.id}'>
<input type='submit' value='更新'>
</form>
应用举例
- 使用分页技术显示部分数据
- 后台返回在指定范围内的数据,前端显示
Filter
Filter 就像一个一个哨卡,用户的请求需要经过 Filter,可以设置多个 Filter
示例
web.xml 配置
<filter>
<filter-name>FirstFilter</filter-name>
<filter-class>filter.FirstFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FirstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Filter
public class FirstFilter implements Filter {
@Override
public void destroy() { }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
...
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// Filter一定会随着 tomcat 的启动自启动,故 init 随着 tomcat 启动时执行
}
}
Listener
Listener 用于监听 web 应用的创建与销毁、全局属性的变化、Session和Request对象的声明周期。
继承 ServletContextListener 对象并实现其中两个方法的对象可以实现对 web 创建与销毁的监听,每当 web 应用重启或者销毁,系统会自动调用对应的函数。
继承 ContextAttributeListener 并实现其中函数,可以在全局属性发生变化时(增、删、改等)执行指定函数。
同样的,可以编写 Listener 实现 Session&Request 对象的生命周期和属性变化的监控。
举个例子,通过记录 Session 的个数可以实现在线用户的计算。以一种简单的方式,通过编写 Listener 来监控 Session 的创建与销毁并在对应时刻修改全局计数器的值,这样就可以实现在线用户的统计了。可参考