Java Web

Stone大约 102 分钟

Java Web

创建 Java Web 项目

使用 JavaEE 版的 Eclipse 开发动态的 WEB 工程(Java WEB 项目)

  • 把开发选项切换到 JavaEE
  • 可以在 Window -> Show View 中找到 Package Explorer, 并把其拖拽到开发区的左边
  • 在 Servers 面板中新建 Tomcat 服务器, 一定要关联到 Tomcat 安装的根目录
  • 新建一个 Dynamic Web Project, 其中 Target Runtime 需选择 Tomcat6.0
  • 开发 Java WEB 应用
  • 可以通过 run on server 来运行 WEB 项目
package com.stone.javaweb;
public class Person {
	public String getInfo() {
		return "helloworld";
	}
}
<%@page import="com.stone.javaweb.Person"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%
		Person person = new Person();
		System.out.println(person.getInfo());
	%>
</body>
</html>

第一个 Servlet 程序

创建一个 Servlet 接口的实现类:

package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {

	@Override
	public void destroy() {
		System.out.println("destory");
		
	}

	@Override
	public ServletConfig getServletConfig() {
		System.out.println("getServletConfig");
		return null;
	}

	@Override
	public String getServletInfo() {
		System.out.println("getServletInfo");
		return null;
	}

	@Override
	public void init(ServletConfig arg0) throws ServletException {
		System.out.println("init");
		
	}

	@Override
	public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
		System.out.println("service");
		
	}
	
	public HelloServlet() {
		System.out.println("HelloServlet constructor");
	}

}

在 web.xml 文件中配置和映射这个 Servlet:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	
	<!-- 配置和映射Servlet -->
	<servlet>
		<!-- Servlet注册的名字 -->
		<servlet-name>helloServlet</servlet-name>
		<!-- Servlet的全类名 -->
		<servlet-class>com.stone.javaweb.HelloServlet</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<!-- 需要和某一个Servlet节点的servlet-name子节点的文本节点一致 -->
		<servlet-name>helloServlet</servlet-name>
		<!-- 映射具体的访问路径:/ 代表当前WEB应用的根目录 -->
		<url-pattern>/hello</url-pattern>
	</servlet-mapping>

</web-app>
输出:
HelloServlet constructor
init
service

Servlet 生命周期的方法:

  • 构造器:只被调用一次,只有第一次请求 Servlet 时,创建 Servlet 的实例,调用构造器。
  • init:只被调用一次,在创建好实例后立即被调用,用于初始化当前 Servlet。
  • service:被多次调用,每次请求都会调用 service 方法,实际用于响应请求的。
  • destroy:只被调用一次,在当前 Servlet 所在的 WEB 应用被卸载前调用,用于释放当前 Servlet 所占用的资源。

ServletRequest 和 ServletResponse

  • Servlet 的 service() 方法用于应答请求,因为每次请求都会调用 service() 方法
  • ServletRequest封装了请求信息,可以从中获取到任何的请求信息
  • ServletResponse封装了响应信息,如果想给用户什么响应,具体可以使用该接口的方法实现

例子:获取请求信息

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="loginServlet" method="post">
		user:<input type="text" name="user">
		password:<input type="password" name="password">
		
		<br><br>
		interesting:
		<input type="checkbox" name="interesting" value="reading">Reading
		<input type="checkbox" name="interesting" value="game">Game
		<input type="checkbox" name="interesting" value="party">Party
		<input type="checkbox" name="interesting" value="shopping">Shopping
		<input type="checkbox" name="interesting" value="sport">Sport
		<input type="checkbox" name="interesting" value="tv">Tv
		
		<input type="submit" value="submit">
	</form>
</body>
</html>
package com.stone.javaweb;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class LoginServlet implements Servlet {

	@Override
	public void init(ServletConfig config) throws ServletException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public ServletConfig getServletConfig() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		System.out.println("请求来了!");
		System.out.println(request);
		
		//获取页面传递过来的参数值
		String user = request.getParameter("user"); //参数名为input标签中的name属性值
		String password = request.getParameter("password");
		System.out.println(user + "," + password);
		
		//若请求参数有多个值(例如 checkbox), 该方法只能获取到第一个提交的值
		String interesting = request.getParameter("interesting");
		System.out.println(interesting);
		
		String[] interestings = request.getParameterValues("interesting");
		for (String interest : interestings) {
			System.out.println(interest);
		}
		
		Enumeration<String> names = request.getParameterNames();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			String value = request.getParameter(name);
			System.out.println("^^" + name + "," + value);
		}
		
		Map<String, String[]> map = request.getParameterMap();
		for (Map.Entry<String, String[]> entry : map.entrySet()) {
			System.out.println("--->" + entry.getKey() + ":" + Arrays.asList(entry.getValue()));
		}
		
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		String requestURI = httpServletRequest.getRequestURI();
		System.out.println(requestURI);
		
		String method = httpServletRequest.getMethod();
		System.out.println(method);
		
		String queryString = httpServletRequest.getQueryString();
		System.out.println(queryString);
		
		String servletPath = httpServletRequest.getServletPath();
		System.out.println(servletPath);
		
		PrintWriter writer = response.getWriter();
		writer.println("hello world...");
		
		response.setContentType("application/msword");
	}

	@Override
	public String getServletInfo() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}
}
输出:
请求来了!
org.apache.catalina.connector.RequestFacade@7a5325a0
aaa,111
reading
reading
game
party
^^password,111
^^interesting,reading
^^user,aaa
--->password:[111]
--->interesting:[reading, game, party]
--->user:[aaa]
/javaweb01/loginServlet
POST
null
/loginServlet

例子:获取请求信息并与配置信息进行比对

在 web.xml 文件中设置两个 WEB 应用的初始化参数:user,password

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	
	<!-- 配置当前WEB应用的初始化参数 -->
	<context-param>
		<param-name>user</param-name>
		<param-value>stone</param-value>
	</context-param>
	<context-param>
		<param-name>password</param-name>
		<param-value>123456</param-value>
	</context-param>
	
	<!-- 配置和映射Servlet -->
	<servlet>
		<!-- Servlet注册的名字 -->
		<servlet-name>helloServlet</servlet-name>
		<!-- Servlet的全类名 -->
		<servlet-class>com.stone.javaweb.HelloServlet</servlet-class>
		
		<!-- 配置Servlet的初始化参数 ,且节点必须在 load-on-startup 节点的前面-->
		<init-param>
			<!-- 参数名 -->
			<param-name>user</param-name>
			<!-- 参数值 -->
			<param-value>root</param-value>
		</init-param>
		<init-param>
			<param-name>password</param-name>
			<param-value>123456</param-value>
		</init-param>
		
		<!-- 可以指定Servlet被创建的时机 -->
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<!-- 需要和某一个Servlet节点的servlet-name子节点的文本节点一致 -->
		<servlet-name>helloServlet</servlet-name>
		<!-- 映射具体的访问路径:/ 代表当前WEB应用的根目录 -->
		<url-pattern>/hello</url-pattern>
	</servlet-mapping>
	
	<servlet>
		<servlet-name>loginServlet</servlet-name>
		<servlet-class>com.stone.javaweb.LoginServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>loginServlet</servlet-name>
		<url-pattern>/loginServlet</url-pattern>
	</servlet-mapping>

</web-app>

创建一个 login.html,定义两个请求字段:user, password,发送请求到 loginServlet

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="loginServlet" method="post">
		user:<input type="text" name="user">
		password:<input type="password" name="password">		
		<input type="submit" value="submit">
	</form>
</body>
</html>

创建一个 LoginServlet,在其中获取请求的 user,password,比对其和 web.xml 文件中定义的请求参数是否一致,若一致,响应 Hello:xxx,若不一致,响应 Sorry: xxx xxx 为 user

package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class LoginServlet implements Servlet {
	
	private ServletConfig servletConfig;

	@Override
	public void init(ServletConfig config) throws ServletException {
		this.servletConfig = config;
		
	}

	@Override
	public ServletConfig getServletConfig() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		//1.获取web.xml中的参数
		ServletContext servletContext = servletConfig.getServletContext();
		String inituser = servletContext.getInitParameter("user");
		String initpassword = servletContext.getInitParameter("password");
		
		//2,获取请求参数
		String user = request.getParameter("user");
		String password = request.getParameter("password");
		
		//3.对比参数并输出
		if (inituser.equals(user) && initpassword.equals(password)) {
			response.getWriter().println("Hello: " + user);
		} else {
			response.getWriter().println("Sorry: " + user);
		}
	}

	@Override
	public String getServletInfo() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}
}

GenericServlet

package com.stone.javaweb;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 自定义一个Servlet接口的实现类,简化开发
 */
public abstract class MyGenericServlet implements Servlet, ServletConfig {
	
	private ServletConfig servletConfig;

	//以下方法为Servlet接口的方法
	@Override
	public void init(ServletConfig config) throws ServletException {
		this.servletConfig = config;
		init();
	}

	public void init() throws ServletException {}

	@Override
	public ServletConfig getServletConfig() {
		return servletConfig;
	}

	@Override
	public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; 

	@Override
	public String getServletInfo() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void destroy() {}

	//以下方法为ServletConfig接口的方法
	@Override
	public String getServletName() {
		return servletConfig.getServletName();
	}

	@Override
	public ServletContext getServletContext() {
		return servletConfig.getServletContext();
	}

	@Override
	public String getInitParameter(String name) {
		return servletConfig.getInitParameter(name);
	}

	@Override
	public Enumeration getInitParameterNames() {
		return servletConfig.getInitParameterNames();
	}
}
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class LoginServlet extends MyGenericServlet {
	
	@Override
	public void init() throws ServletException {
		System.out.println("初始化。。。");
	}

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		//1.获取web.xml中的参数
		String inituser = getServletContext().getInitParameter("user");
		String initpassword = getServletContext().getInitParameter("password");
		
		//2,获取请求参数
		String user = request.getParameter("user");
		String password = request.getParameter("password");
		
		//3.对比参数并输出
		if (inituser.equals(user) && initpassword.equals(password)) {
			response.getWriter().println("Hello: " + user);
		} else {
			response.getWriter().println("Sorry: " + user);
		}
	}
}

HttpServlet

  • 是一个 Servlet,继承自 GenericServlet,针对于 HTTP 协议所定制
  • 在 service() 方法中直接把 ServletReuqest 和 ServletResponse 转为 HttpServletRequest 和 HttpServletResponse,并调用了重载的 service(HttpServletRequest, HttpServletResponse),在 service(HttpServletRequest, HttpServletResponse) 获取了请求方式:request.getMethod(),根据请求方式又创建了doXxx() 方法(xxx 为具体的请求方式, 比如 doGet, doPost)
  • 实际开发中,直接继承 HttpServlet,并根据请求方式覆写 doXxx() 方法即可
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 针对于HTTP协议定义的一个Servlet基类
 */
public class MyHttpServlet extends MyGenericServlet {

	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		if (request instanceof HttpServletRequest) {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			if (response instanceof HttpServletResponse) {
				HttpServletResponse httpServletResponse = (HttpServletResponse) response;
				sercive(httpServletRequest, httpServletResponse);
			}
		}
	}

	public void sercive(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取请求方法
		String method = request.getMethod();
		
		//2.根据请求方式再调用对应的处理方法
		if ("GET".equalsIgnoreCase(method)) {
			doGet(request, response);
		} else if ("POST".equalsIgnoreCase(method)) {
			doPost(request, response);
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
	}
}

例子:

在 MySQL 数据库中创建一个 test_users 数据表,添加 3 个字段: id,user,password。并录入几条记录。

创建一个 login.html,定义两个请求字段:user,password,发送请求到 loginServlet。

创建一个 LoginServlet(需要继承自 HttpServlet,并重写其 doPost 方法), 在其中获取请求的 user,password。

利用 JDBC 从 test_users 中查询有没有和页面输入的 user,password 对应的记录,若有,响应 Hello:xxx,若没有,响应 Sorry:xxx,xxx 为 user。

package com.stone.javaweb;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 1.获取请求的user,password
		String user = req.getParameter("user");
		String password = req.getParameter("password");

		//2.获取数据库信息
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		
		try {
			String sql = "select count(id) from test_users where user=? and password=?";
			connection = JDBCTools.getConnection();
			preparedStatement = connection.prepareStatement(sql);
			preparedStatement.setString(1, user);
			preparedStatement.setString(2, password);
			resultSet = preparedStatement.executeQuery();
			if (resultSet.next()) {
				int count = resultSet.getInt(1);
				if (count > 0) {
					resp.getWriter().print("Hello:" + user);
				} else {
					resp.getWriter().print("Sorry:" + user);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCTools.release(resultSet, preparedStatement, connection);
		}
	}
}

JSP

JSP(Java Server Page)是 Servlet 编写的一种技术,它将 Java 代码和 HTML 语句混合在同一个文件中编写,只对网页中的要动态产生的内容采用 Java 代码来编写,而对固定不变的静态内容采用普通静态 HTML 页面的方式编写。

<%@page import="com.stone.javaweb.Person"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%
		Person person = new Person();
		System.out.println(person.getInfo());
	%>
	
	<%
		//隐含对象;没有声明就可以使用的对象
		String name = request.getParameter("name");
		System.out.println(name);
		
		System.out.println(response instanceof HttpServletResponse);
		
		ServletRequest req = pageContext.getRequest();
		System.out.println(req == request);
		
		System.out.println(session.getId());
		
		System.out.println(application.getInitParameter("user"));
		
		out.println(request.getParameter("name"));
		out.println("<br>");
		out.println(application.getInitParameter("user"));
		
	%>
</body>
</html>

域对象属性

域对象:

  • pageContext:属性的作用范围仅限于当前 JSP 页面
  • request:属性的作用范围仅限于同一个请求.
  • session:属性的作用范围限于一次会话,浏览器打开直到关闭称之为一次会话(在此期间会话不失效)
  • application:属性的作用范围限于当前 WEB 应用,是范围最大的属性作用范围,只要在一处设置属性,在其他各处的 JSP 或 Servlet 中都可以获取到
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<%
		pageContext.setAttribute("pageContextAttr", "pageContextValue");
		request.setAttribute("requestAttr", "requestValue");
		session.setAttribute("sessionAttr", "sessionValue");
		application.setAttribute("applicationAttr", "applicationValue");
	%>
	
	<h2>Attr 1 Page</h2>
	
	<br><br>
	pageContextAttr: <%= pageContext.getAttribute("pageContextAttr") %>
	
	<br><br>
	requestAttr: <%= request.getAttribute("requestAttr") %>
	
	<br><br>
	sessionAttr: <%= session.getAttribute("sessionAttr") %>
	
	<br><br>
	applicationAttr: <%= application.getAttribute("applicationAttr") %>
	
	<br><br>
	<a href="att2.jsp">Attr 2 Page</a>
	
	<br><br>
	<a href="testAttr">Attr Servlet</a>
	
</body>
</html>
输出:
pageContextAttr: pageContextValue 
requestAttr: requestValue 
sessionAttr: sessionValue 
applicationAttr: applicationValue
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<h2>Attr 2 Page</h2>
	
	<br><br>
	pageContextAttr: <%= pageContext.getAttribute("pageContextAttr") %>
	
	<br><br>
	requestAttr: <%= request.getAttribute("requestAttr") %>
	
	<br><br>
	sessionAttr: <%= session.getAttribute("sessionAttr") %>
	
	<br><br>
	applicationAttr: <%= application.getAttribute("applicationAttr") %>
	
</body>
</html>
输出:
pageContextAttr: null 
requestAttr: null 
sessionAttr: sessionValue 
applicationAttr: applicationValue
package com.stone.javaweb;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestAttr extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.在Servlet中无法得到pageContext对象
		
		//2.request
		Object requestAttr = request.getAttribute("requestAttr");
		System.out.println("requestAttr:" + requestAttr);
		
		//3.session
		Object sessionAttr = request.getSession().getAttribute("sessionAttr");
		System.out.println("sessionAttr:" + sessionAttr);
		
		//4.application
		Object applicationAttr = getServletContext().getAttribute("applicationAttr");
		System.out.println("applicationAttr:" + applicationAttr);	
	}
}
输出:
requestAttr:null
sessionAttr:sessionValue
applicationAttr:applicationValue

转发和重定向

  • 转发:
    • 只发出了一次请求
    • 地址栏是初次发出请求的地址
    • 在最终的 Servlet 中,request 对象和中转的那个 request 是同一个对象
    • 只能转发给当前 WEB 应用的的资源
    • / 代表的是当前 WEB 应用的根目录
  • 重定向:
    • 发出了两次请求
    • 地址栏不再是初次发出的请求地址,地址栏为最后响应的那个地址
    • 在最终的 Servlet 中,request 对象和中转的那个 request 不是同一个对象
    • 可以重定向到任何资源
    • / 代表的是当前 WEB 站点的根目录

例子:转发

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<a href="loginServlet">Test</a>
	<br><br>
	<a href="forwardServlet">Forward</a>

</body>
</html>
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ForwardServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("ForwardServlet's doGet...");
		
		//请求的转发
		//1.调用HttpServletRequest的getRequestDispatcher()方法获取RequestDispatcher对象
		//调用getRequestDispatcher()需要传入要转发的地址
		String path = "testServlet";
		RequestDispatcher requestDispatcher = request.getRequestDispatcher("/" + path);
		
		//2.调用RequestDispatcher的forward(request, response)进行请求转发
		requestDispatcher.forward(request, response);
	}
}
package com.stone.javaweb;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("TestServlet's doGet...");
	}
}
输出:
ForwardServlet's doGet...
TestServlet's doGet...

例子:重定向

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<a href="loginServlet">Test</a>
	<br><br>
	<a href="forwardServlet">Forward</a>
	<br><br>
	<a href="redirectServlet">Redirect</a>

</body>
</html>
package com.stone.javaweb;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RedirectServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("RedirectServlet's doGet...");
		
		//执行请求的重定向,直接调用response.sendRedirect(path)方法
		//path为要重定向的地址
		String path = "testServlet";
		response.sendRedirect(path);
	}
}
package com.stone.javaweb;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("TestServlet's doGet...");
	}
}
输出:
RedirectServlet's doGet...
TestServlet's doGet...

MVC 设计模式

MVC 模式,全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。

通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示。

img

MVC 模式将应用程序划分成模型(Model)、视图(View)、控制器(Controller)等三层,如下图所示。

MVC 模式

分层描述
Model(模型)它是应用程序的主体部分,主要由以下 2 部分组成:
实体类 Bean:专门用来存储业务数据的对象,它们通常与数据库中的某个表对应,例如 User、Student 等。
业务处理 Bean:指 Service 或 Dao 的对象,专门用于处理业务逻辑、数据库访问。 一个模型可以为多个视图(View)提供数据,一套模型(Model)的代码只需写一次就可以被多个视图重用,有效地减少了代码的重复性,增加了代码的可复用性。
View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。
Controller(控制器)通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。 在这个过程中,Controller 层不会做任何业务处理,它只是 View(视图)层和 Model (模型)层连接的枢纽,负责调度 View 层和 Model 层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。

Java Bean

package com.stone.mvc;

public class Student {
	private Integer flowId;
	private Integer type;
	private String idCard;
	private String examCard;
	private String studentName;
	private String location;
	private Integer grade;
	public Student() {
		super();
	}
	public Student(Integer flowId, Integer type, String idCard, String examCard, String studentName, String location,
			Integer grade) {
		super();
		this.flowId = flowId;
		this.type = type;
		this.idCard = idCard;
		this.examCard = examCard;
		this.studentName = studentName;
		this.location = location;
		this.grade = grade;
	}
	//Geter,Setter略
}

DAO

package com.stone.mvc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class StudentDao {
	public void deleteByFlowId(Integer flowId) {
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		
		try {
			String dirverClass = "com.mysql.jdbc.Driver";
			String url = "jdbc:mysql://192.168.8.138:3306/mybatis";
			String user = "stone";
			String password = "stone";
			
			Class.forName(dirverClass);
			connection = DriverManager.getConnection(url, user, password);
			
			String sql = "delete from examstudent where flowid=?";
			preparedStatement = connection.prepareStatement(sql);
			preparedStatement.setInt(1, flowId);
			preparedStatement.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {			
			try {
				if (preparedStatement != null) {
					preparedStatement.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			try {
				if (connection != null) {
					connection.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}
	
	public List<Student> getAll(){
		List<Student> students = new ArrayList<>(); 
		
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		
		try {
			String dirverClass = "com.mysql.jdbc.Driver";
			String url = "jdbc:mysql://192.168.8.138:3306/mybatis";
			String user = "stone";
			String password = "stone";
			
			Class.forName(dirverClass);
			connection = DriverManager.getConnection(url, user, password);
			
			String sql = "select flowid,type,idcard,examcard,studentname,location,grade from examstudent";
			preparedStatement = connection.prepareStatement(sql);
			resultSet = preparedStatement.executeQuery();
			while (resultSet.next()) {
				Integer flowId = resultSet.getInt(1);
				Integer type = resultSet.getInt(2);
				String idCard = resultSet.getString(3);
				String examCard = resultSet.getString(4);
				String studentName = resultSet.getString(5);
				String location = resultSet.getString(6);
				Integer grade = resultSet.getInt(7);
				Student student = new Student(flowId, type, idCard, examCard, studentName, location, grade);
				students.add(student);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (resultSet != null) {
					resultSet.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			try {
				if (preparedStatement != null) {
					preparedStatement.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			try {
				if (connection != null) {
					connection.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
		return students;
	}
}

请求页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a href="listAllStudents">List All Students</a>
</body>
</html>

处理查询请求

package com.stone.mvc;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ListAllStudents extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		StudentDao studentDao = new StudentDao();
		List<Student> students = studentDao.getAll();
		request.setAttribute("students", students);
		request.getRequestDispatcher("/students.jsp").forward(request, response);
	}
}

响应查询页面

<%@page import="com.stone.mvc.Student"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%
		List<Student> students = (List<Student>)request.getAttribute("students");
	%>
	
	<table border="1" cellpadding="10" cellspacing="0">
		<tr>
			<th>FlowId</th>
			<th>Type</th>
			<th>IdCard</th>
			<th>ExamCard</th>
			<th>StudentName</th>
			<th>Location</th>
			<th>Grade</th>
			<th>Delete</th>
		</tr>
		
		<%
			for(Student student:students){
		%>
				<tr>
					<td><%= student.getFlowId() %></td>
					<td><%= student.getType() %></td>
					<td><%= student.getIdCard() %></td>
					<td><%= student.getExamCard() %></td>
					<td><%= student.getStudentName() %></td>
					<td><%= student.getLocation() %></td>
					<td><%= student.getGrade() %></td>
					<td><a href="deleteStudent?flowId=<%= student.getFlowId() %>">Delete</a></td>
				</tr>
		<%	
			}
		%>
	</table>
</body>
</html>

处理删除请求

package com.stone.mvc;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DeleteStudent extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String flowId = request.getParameter("flowId");
		StudentDao studentDao = new StudentDao();
		studentDao.deleteByFlowId(Integer.parseInt(flowId));
		request.getRequestDispatcher("/success.jsp").forward(request, response);
	}
}

响应删除页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4>操作成功</h4>
	<br><br>
	<a href="listAllStudents">List All Students</a>
</body>
</html>

MVC 案例

Model 层

导入 Jar 包:

c3p0-0.9.5.2.jar
commons-beanutils-1.9.3.jar
commons-dbutils-1.7.jar
mchange-commons-java-0.2.11.jar
mysql-connector-java-5.1.23-bin.jar

创建数据表:

CREATE TABLE `customers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `address` varchar(30) DEFAULT NULL,
  `phone` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

创建 Customer Bean:

package com.stone.bean;

public class Customer {
	private Integer id;
	private String name;
	private String address;
	private String phone;
	public Customer() {
		super();
	}
	public Customer(Integer id, String name, String address, String phone) {
		super();
		this.id = id;
		this.name = name;
		this.address = address;
		this.phone = phone;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	@Override
	public String toString() {
		return "Customer [id=" + id + ", name=" + name + ", address=" + address + ", phone=" + phone + "]";
	}
}

创建DAO类:

package com.stone.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.stone.utils.JdbcUtils;

/**
 * 封装了基本的CRUD方法,以供子类继承使用 当前DAO直接在方法中获取数据库连接 整个DAO采取DBUtils解决方案
 * 
 * @param <T>:当前DAO处理的实体类的类型
 */
public class DAO<T> {
	private QueryRunner queryRunner = new QueryRunner();
	private Class<T> clazz;

	public DAO() {
		Type superclass = getClass().getGenericSuperclass();
		if (superclass instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) superclass;
			Type[] typeArguments = parameterizedType.getActualTypeArguments();
			if (typeArguments != null && typeArguments.length > 0) {
				if (typeArguments[0] instanceof Class) {
					clazz = (Class<T>) typeArguments[0];
				}
			}
		}
	}

	/**
	 * 返回某一个字段的值,例如返回某一条记录的customerName,或者返回数据库表中有多少条记录等
	 * 
	 * @param sql
	 * @param args
	 * @return
	 */
	public <E> E getForValue(String sql, Object... args) {
		Connection connection = null;

		try {
			connection = JdbcUtils.getConnection();
			return queryRunner.query(connection, sql, new ScalarHandler<>(), args);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.releaseConnection(connection);
		}
		
		return null;
	}

	/**
	 * 返回T对应的List
	 * 
	 * @param sql
	 * @param args
	 * @return
	 */
	public List<T> getForList(String sql, Object... args) {
		Connection connection = null;

		try {
			connection = JdbcUtils.getConnection();
			return queryRunner.query(connection, sql, new BeanListHandler<>(clazz), args);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.releaseConnection(connection);
		}
		
		return null;
	}

	/**
	 * 返回对应的T的一个实体类对象
	 * 
	 * @param sql
	 * @param args
	 * @return
	 */
	public T get(String sql, Object... args) {
		Connection connection = null;

		try {
			connection = JdbcUtils.getConnection();
			return queryRunner.query(connection, sql, new BeanHandler<>(clazz), args);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.releaseConnection(connection);
		}
		
		return null;
	}

	/**
	 * 该方法封装了insert,delete,update操作
	 * 
	 * @param sql:SQL语句
	 * @param args:填充SQL语句的占位符
	 */
	public void update(String sql, Object... args) {
		Connection connection = null;

		try {
			connection = JdbcUtils.getConnection();
			queryRunner.update(connection, sql, args);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JdbcUtils.releaseConnection(connection);
		}
	}
}

创建 CustomerDao 接口:

package com.stone.dao;

import java.util.List;

import com.stone.bean.Customer;

//根据具体业务定义方法 
public interface CustomerDAO {
	public List<Customer> getAll();
	
	public void save(Customer customer);
	
	public Customer get(Integer id);
	
	public void delete(Integer id);
	
	//返回name对应的记录数
	public long getCountWithName(String name);
	
	/**
	 * 返回满足查询条件的List
	 * @param cc:封装了查询条件
	 * @return
	 */
	public List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer cc);
	
	public void update(Customer customer);
}

创建 CustomerDaoImpl 类:

package com.stone.dao.impl;

import java.util.List;

import com.stone.bean.Customer;
import com.stone.dao.CriteriaCustomer;
import com.stone.dao.CustomerDAO;
import com.stone.dao.DAO;

public class CustomerDAOImpl extends DAO<Customer> implements CustomerDAO {

	@Override
	public List<Customer> getAll() {
		String sql = "select id,name,address,phone from customers";
		return getForList(sql);
	}

	@Override
	public void save(Customer customer) {
		String sql = "insert into customers(name,address,phone) values(?,?,?)";
		update(sql, customer.getName(), customer.getAddress(), customer.getPhone());
		
	}

	@Override
	public Customer get(Integer id) {
		String sql = "select id,name,address,phone from customers where id=?";
		return get(sql, id);
	}

	@Override
	public void delete(Integer id) {
		String sql = "delete from customers where id=?";
		update(sql, id);		
		
	}

	@Override
	public long getCountWithName(String name) {
		String sql = "select count(id) from customers where name=?";
		return getForValue(sql, name);
	}

	@Override
	public List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer cc) {
		String sql = "select id,name,address,phone from customers where name like ? and address like ? and phone like ?";
		//修改了CriteriaCustomer的getter方法:使其返回的字符串中有"%%".
		return getForList(sql, cc.getName(), cc.getAddress(), cc.getPhone());
	}

	@Override
	public void update(Customer customer) {
		String sql = "update customers set name=?,address=?,phone=? where id=?";
		update(sql, customer.getName(), customer.getAddress(), customer.getPhone(), customer.getId());
	}
}

Controller 层

创建 CustomerServlet,通过反射实现一个 Servlet 对应多个请求,在 web.xml 中需要映射为<url-pattern>*.do</url-pattern>

package com.stone.servlet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.stone.bean.Customer;
import com.stone.dao.CustomerDAO;
import com.stone.dao.impl.CustomerDAOImpl;

public class CustomerServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private CustomerDAO customerDAO = new CustomerDAOImpl();

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}

//	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//		String method = request.getParameter("method");
//		switch (method) {
//			case "insert": insert(request,response); break;
//			case "select": select(request,response); break;
//			case "delete": delete(request,response); break;
//		default:
//			break;
//		}
//	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//1.获取serletPath并去掉第一个字符"/"
		String servletPath = req.getServletPath().substring(1);
		//2.去掉".do"后缀,得到请求的方法名
		String methodName = servletPath.substring(0, servletPath.length()-3);		
		try {
			//3.利用反射创建对应的方法
			Method method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
			//4.利用反射调用对应的方法
			method.invoke(this, req, resp);
		} catch (Exception e) {
			//若调用的方法不存在,响应一个error.jsp页面
			resp.sendRedirect("error.jsp");
		}
	}

	private void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("delete");
		
	}

	private void select(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.调用CustomerDao的getAll()方法得到Customer的集合
		List<Customer> customers = customerDAO.getAll();
		
		//2.把Customers的集合放入request中
		request.setAttribute("customers", customers);
		
		//3.转发页面到index.jsp(不能使用重定向)
		request.getRequestDispatcher("/index.jsp").forward(request, response);
	}

	private void insert(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("insert");
		
	}
	
	private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("update");
		
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>javaweb02</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <description></description>
    <display-name>CustomerServlet</display-name>
    <servlet-name>CustomerServlet</servlet-name>
    <servlet-class>com.stone.servlet.CustomerServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CustomerServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

View 层

创建 index.jsp:

<%@page import="com.stone.bean.Customer"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="select.do">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone"></td>
			</tr>
			<tr>
				<td><input type="submit" value="Select"></td>
				<td><a href="">Create New Customer</a></td>
			</tr>
		</table>
	</form>
	
	<br><br>
	
	<%
		List<Customer> customers = (List<Customer>)request.getAttribute("customers");
		if(customers != null && customers.size() > 0){
	
			
	%>
	<hr>
	<br><br>
	<table border="1" cellpadding="10" cellspacing="0">
		<tr>
			<th>ID</th>
			<th>CustomerName</th>
			<th>Address</th>
			<th>Phone</th>
			<th>Update/Delete</th>
		</tr>
		
		<%
			for(Customer customer:customers){
		%>
				<tr>
					<td><%= customer.getId() %></td>
					<td><%= customer.getName() %></td>
					<td><%= customer.getAddress() %></td>
					<td><%= customer.getPhone() %></td>
					<td>
						<a href="">Update</a>
						<a href="">Delete</a>
					</td>
				<tr>
		<%
			}
		%>
	</table>
	<%
		}
	%>
</body>
</html>

模糊查询

调用路径:

[index]select.do ---> [CustomerServlet]doPost ---> [CustomerServlet]select ---> [CustomerDaoImpl] getForListWithCriteriaCustomer(cc) ---> [DAO]getForList ---> [QueryRunner]query(connection, sql, new BeanListHandler<>(clazz), args)

创建封装了查询条件的 Bean,注意要修改其 getter 方法,使其加上 “%%”

package com.stone.dao;

public class CriteriaCustomer {
	private String name;
	private String address;
	private String phone;
	public CriteriaCustomer() {
	}
	public CriteriaCustomer(String name, String address, String phone) {
		super();
		this.name = name;
		this.address = address;
		this.phone = phone;
	}
	public String getName() {
		if (name == null) {
			name = "%%";
		} else {
			name = "%" + name + "%";
		}
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAddress() {
		if (address == null) {
			address = "%%";
		} else {
			address = "%" + address + "%";
		}
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getPhone() {
		if (phone == null) {
			phone = "%%";
		} else {
			phone = "%" + phone + "%";
		}
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
}

修改 CustomerDao 接口,增加模糊查询方法:

    /**
	 * 返回满足查询条件的List
	 * @param cc:封装了查询条件
	 * @return
	 */
	public List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer cc);

修改 CustomerDaoImpl 方法,增加模糊查询方法的实现:

	@Override
	public List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer cc) {
		String sql = "select id,name,address,phone from customers where name like ? and address like ? and phone like ?";
		//修改了CriteriaCustomer的getter方法:使其返回的字符串中有"%%".
		return getForList(sql, cc.getName(), cc.getAddress(), cc.getPhone());
	}

修改 CustomerServlet 的 select 方法:

private void select(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取模糊查询的请求参数
		String name = request.getParameter("name");
		String address = request.getParameter("address");
		String phone = request.getParameter("phone");
		//将请求参数封装为一个CriteriaCustomer对象
		CriteriaCustomer cc = new CriteriaCustomer(name, address, phone);
		
		//1.调用CustomerDao的getForListWithCriteriaCustomer(cc)方法得到Customer的集合
		List<Customer> customers = customerDAO.getForListWithCriteriaCustomer(cc);
		
		//2.把Customers的集合放入request中
		request.setAttribute("customers", customers);
		
		//3.转发页面到index.jsp(不能使用重定向)
		request.getRequestDispatcher("/index.jsp").forward(request, response);
	}

删除操作

  • 超链接:delete.do?id=<%=customer.getId()%>
  • Servlet 的 delete 方法
    • 获取 id
    • 调用 DAO 执行删除
    • 重定向到 query.doopen in new window(若目标页面不需要读取当前请求的 request 属性,就可以使用重定向),将显示删除后的 Customer 的 List
  • JSP 上的 jQuery 提示:
    • 确定要删除 xx 的信息吗?

修改 CustomerServlet,增加 delete 方法:

	private void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String idStr = request.getParameter("id");
		int id = 0;
		
		//try...catch的作用:防止idStr不能转为int类型,若不能转,则id=0,无法进行任何删除操作
		try {
			id = Integer.parseInt(idStr);
			customerDAO.delete(id);
		} catch (Exception e) {}
		response.sendRedirect("select.do");
	}

修改 index.jsp,增加删除链接以及删除提示( jQuery 实现):

<%@page import="com.stone.bean.Customer"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="scripts/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
	$(function() {
		$(".delete").click(function() {
			var content = $(this).parent().parent().find("td:eq(1)").text();
			var flag = confirm("确定要删除" + content + "的信息吗");
			return flag;
		})
	})
</script>
</head>
<body>
	<form action="select.do" method="post">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone"></td>
			</tr>
			<tr>
				<td><input type="submit" value="Select"></td>
				<td><a href="">Create New Customer</a></td>
			</tr>
		</table>
	</form>
	
	<br><br>
	
	<%
		List<Customer> customers = (List<Customer>)request.getAttribute("customers");
		if(customers != null && customers.size() > 0){
	
			
	%>
	<hr>
	<br><br>
	<table border="1" cellpadding="10" cellspacing="0">
		<tr>
			<th>ID</th>
			<th>CustomerName</th>
			<th>Address</th>
			<th>Phone</th>
			<th>Update/Delete</th>
		</tr>
		
		<%
			for(Customer customer:customers){
		%>
				<tr>
					<td><%= customer.getId() %></td>
					<td><%= customer.getName() %></td>
					<td><%= customer.getAddress() %></td>
					<td><%= customer.getPhone() %></td>
					<td>
						<a href="">Update</a>
						<a class="delete" href="delete.do?id=<%= customer.getId() %>">Delete</a>
					</td>
				<tr>
		<%
			}
		%>
	</table>
	<%
		}
	%>
</body>
</html>

新增操作

修改 CustomerServlet,增加 insert 方法:

	private void insert(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取表单参数:name,address,phone
		String name = request.getParameter("name");
		String address = request.getParameter("address");
		String phone = request.getParameter("phone");
		
		//1.1 检查name是否已经存在,调用CustomerDao的getCountWithName(String name)方法
		if (customerDAO.getCountWithName(name) > 0) {
			//1.2 若存在,则通过转发的方式响应newcustomer.jsp页面:
			//1.2.1 要求在newcustomer.jsp页面显示一个错误信息:用户名name已经被使用,请重新输入
			//在request中放入一个属性message:用户名name已经被使用,请重新输入
			//在页面上通过 request.getAttribute("message")的方式来显示
			request.setAttribute("message", "用户名" + name + "已经被使用,请重新输入");
			request.getRequestDispatcher("/newcustomer.jsp").forward(request, response);
			
			//1.2.2 newcustomer.jsp表单值可以回显,通过value="<%= request.getParameter("name") %>来进行回显
			//1.2.3 结束方法 return
			return;
		}
		
		//2.把表单参数封装为一个Customer对象
		Customer customer = new Customer(name, address, phone);
		
		//3.调用CustomerDao的save(Customer customer)方法执行保存操作
		customerDAO.save(customer);
		
		//4.重定向到success.jsp页面,使用重定向可以避免出现重新提交问题
		response.sendRedirect("success.jsp");
	}

创建 newcustomer.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4><font color="red"><%= request.getAttribute("message") == null ? "" : request.getAttribute("message") %></font></h4>
	<form action="insert.do" method="post">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name" value="<%= request.getParameter("name") == null ? "" : request.getParameter("name") %>"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address" value="<%= request.getParameter("address") == null ? "" : request.getParameter("address") %>"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone" value="<%= request.getParameter("phone") == null ? "" : request.getParameter("phone") %>"></td>
			</tr>
			<tr>
				<td colspan="2"><input type="submit" value="Insert"></td>
			</tr>
		</table>
	</form>
</body>
</html>

创建 success.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4>操作成功</h4>
	<h4><a href="index.jsp">返回</a></h4>
</body>
</html>

修改操作

修改 CustomerServlet,增加 edit 和 update 方法:

	private void edit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String forwardPath = "/error.jsp";
		// 1.获取请求参数id
		String idStr = request.getParameter("id");

		// 2.调用CustomerDAO的customerDAO。get(id)获取id对应的Customer对象
		try {
			Customer customer = customerDAO.get(Integer.parseInt(idStr));
			if (customer != null) {
				forwardPath = "/updatecustomer.jsp";
				// 3.将customer放入request中
				request.setAttribute("customer", customer);
			}
		} catch (Exception e) {
		}

		// 4.通过转发响应updatecustomer.jsp页面
		request.getRequestDispatcher(forwardPath).forward(request, response);
	}

	private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 1.获取表单参数:id,name,address,phone,oldName
		String id = request.getParameter("id");
		String name = request.getParameter("name");
		String address = request.getParameter("address");
		String phone = request.getParameter("phone");
		String oldName = request.getParameter("oldName");

		// 2.验证name是否已经被占用
		// 2.1 比较name和oldName是否相同,若相同说明name不做修改,而是修改其他字段,若不同,说明会修改name
		if (!oldName.equals(name)) {
			// 2.2 若不相同,则调用CustomerDAO的getCountWithName(String name)查看name在数据库中是否存在
			if (customerDAO.getCountWithName(name) > 0) {
				// 2.3 若返回值大于0,则通过转发响应updatecustomer.jsp
				// 2.3.1 要求在updatecustomer.jsp页面显示一个错误信息:用户名name已经被使用,请重新输入
				// 在request中放入一个属性message:用户名name已经被使用,请重新输入
				// 在页面上通过 request.getAttribute("message")的方式来显示
				request.setAttribute("message", "用户名" + name + "已经被使用,请重新输入");
				// 2.3.2 updatecustomer.jsp表单值可以回显,
				// address,phone显示提交表单的新的值,而name显示oldName,而不是新提交的name
				// 2.3.3 结束方法 return
				request.getRequestDispatcher("/updatecustomer.jsp").forward(request, response);
				return;
			}
		}

		// 3.若验证通过,把表单参数封装为一个Customer对象
		Customer customer = new Customer(Integer.parseInt(id), name, address, phone);

		// 4.调用CustomerDao的update(Customer customer)方法执行更新操作
		customerDAO.update(customer);

		// 5.重定向到select.jsp页面,使用重定向可以避免出现重新提交问题
		response.sendRedirect("select.do");
	}

修改 index.jsp:

<a class="edit" href="edit.do?id=<%= customer.getId() %>">Edit</a>

创建 updatecustomer.jsp:

<%@page import="com.stone.bean.Customer"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4><font color="red"><%= request.getAttribute("message") == null ? "" : request.getAttribute("message") %></font></h4>
	
	<%
		String id = null;
		String oldName = null;
		String name = null;
		String address = null;
		String phone = null;
		Customer customer = (Customer)request.getAttribute("customer");
		if( customer != null){
			id = customer.getId() + "";
			oldName = customer.getName();
			name = customer.getName();
			address = customer.getAddress();
			phone = customer.getPhone();
		} else {
			id = request.getParameter("id");
			oldName = request.getParameter("oldName");
			name = request.getParameter("oldName");
			address = request.getParameter("address");
			phone = request.getParameter("phone");
		}
	%>
	
	<form action="update.do" method="post">
	
		<input type="hidden" name="id" value="<%= id %>">
		<input type="hidden" name="oldName" value="<%= oldName %>">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name" value="<%= name %>"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address" value="<%= address %>"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone" value="<%= phone %>"></td>
			</tr>
			<tr>
				<td colspan="2"><input type="submit" value="Update"></td>
			</tr>
		</table>
	</form>
</body>
</html>

会话与状态管理

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<%
		//在JavaWeb规范中使用Cookie类表示cookie

		//3.获取Cookie
		Cookie[] cookies = request.getCookies();
		if (cookies != null && cookies.length > 1) {
			for (Cookie cookie : cookies) {
				out.print(cookie.getName() + " : " + cookie.getValue());
				out.print("<br>");
			}
		} else {
			out.print("没有一个Cookie,正在创建并返回");
			//1.创建一个Cookie对象
			Cookie cookie = new Cookie("name", "stone");
			//设置Cookie的最大失效,以秒为单位,若为0表示立即删除该Cookie,若为负数,表示不存储该Cookie
			cookie.setMaxAge(30);

			//2.调用response的一个方法把Cookie传给客户端
			response.addCookie(cookie);
		}
	%>

</body>
</html>

自动登录

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="index.jsp" method="post">
		name: <input type="text" name="name" />
		<input type="submit" value="Submit" />
	</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<%
		//若可以获取到请求参数 name, 则打印出欢迎信息。把登录信息存储到 Cookie 中,并设置 Cookie 的最大时效为 30S
		String name = request.getParameter("name");
		if (name != null && !name.trim().equals("")) {
			Cookie cookie = new Cookie("name", name);
			cookie.setMaxAge(30);
			response.addCookie(cookie);
		} else {
			//从 Cookie 中读取用户信息,若存在则打印欢迎信息
			Cookie[] cookies = request.getCookies();
			if (cookies != null && cookies.length > 0) {
				for (Cookie cookie : cookies) {
					String cookieName = cookie.getName();
					if ("name".equals(cookieName)) {
						String cookieValue = cookie.getValue();
						name = cookieValue;
					}
				}
			}
		}

		if (name != null && !name.trim().equals("")) {
			out.print("Hello: " + name);
		} else {
			//若既没有请求参数,也没有 Cookie,则重定向到 login.jsp
			response.sendRedirect("login.jsp");
		}
	%>

</body>
</html>

显示浏览过的商品信息

  • books.jsp
    • 显示最近浏览的 5 本书
    • 获取所有的 Cookie
    • 从中筛选出 Book 的 Cookie:如果 cookieName 为 STONE_BOOK_ 开头的即符合条件
    • 显示 cookieValue
  • book.jsp
    • 把书的信息以 Cookie 方式传回给浏览器,删除一个 Cookie
    • 确定要被删除的 Cookie: STONE_BOOK_ 开头的 Cookie 数量大于或等于 5,且若从 books.jsp 页面传入的 book 不在 STONE_BOOK_ 的 Cookie 中则删除较早的那个 Cookie( STONE_BOOK_ 数组的第一个 Cbookie),若在其中,则删除该 Cookie
    • 把从 books.jsp 传入的 book 作为一个 Cookie 返回

books.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<h4>Books Page</h4>
	
	<a href="book.jsp?book=JavaWeb">Java Web</a><br><br>
	<a href="book.jsp?book=Java">Java</a><br><br>
	<a href="book.jsp?book=Oracle">Oracle</a><br><br>
	<a href="book.jsp?book=Ajax">Ajax</a><br><br>
	<a href="book.jsp?book=JavaScript">JavaScript</a><br><br>
	<a href="book.jsp?book=Android">Android</a><br><br>
	<a href="book.jsp?book=Jbpm">Jbpm</a><br><br>
	<a href="book.jsp?book=Struts">Struts</a><br><br>
	<a href="book.jsp?book=Hibernate">Hibernate</a><br><br>
	<a href="book.jsp?book=Spring">Spring</a><br><br>
	
	<br><br>
	
	<% 
		//显示最近浏览的 5 本书
		//获取所有的 Cookie
		Cookie[] cookies = request.getCookies();
		
		//从中筛选出 Book 的 Cookie:如果 cookieName 为 STONE_BOOK_ 开头的即符合条件
		//显示 cookieValue
		if (cookies != null && cookies.length > 0) {
			for (Cookie c : cookies) {
				String cookieName = c.getName();
				if (cookieName.startsWith("STONE_BOOK_")) {
					out.print(c.getValue());
					out.print("<br>");
				}
			}
		}

	%>

</body>
</html>

book.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" import="java.util.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h4>Book Detail Page</h4>

	Book:
	<%=request.getParameter("book")%>

	<br>
	<br>

	<a href="books.jsp">Return</a>

	<%
		String book = request.getParameter("book");

		//把书的信息以 Cookie 方式传回给浏览器,删除一个 Cookie
		//1.确定要被删除的 Cookie: 
		//STONE_BOOK_ 开头的 Cookie 数量大于或等于 5,
		Cookie[] cookies = request.getCookies();
		//保存所有的S_BOOK_开头的Cookie
		List<Cookie> bookCookies = new ArrayList<Cookie>();
		//用来保存books。jsp传入的book匹配的那个Cookie
		Cookie tempCookie = null;
		if (cookies != null && cookies.length > 0) {
			for (Cookie c : cookies) {
				String cookieName = c.getName();
				if (cookieName.startsWith("STONE_BOOK_")) {
					bookCookies.add(c);
					if (c.getValue().equals(book)) {
						tempCookie = c;
					}
				}
			}
		}

		//1.1 且若从 books.jsp 页面传入的 book 不在 STONE_BOOK_ 的 Cookie 中则删除较早的那个 Cookie( STONE_BOOK_  数组的第一个 Cbookie),
		if(bookCookies.size() >= 5 && tempCookie == null){
			tempCookie = bookCookies.get(0);
		}

		//1.2 若在其中,则删除该 Cookie
		if(tempCookie != null){
			tempCookie.setMaxAge(0);
			response.addCookie(tempCookie);
		}

		//2.把从 books.jsp 传入的 book 作为一个 Cookie 返回
		Cookie cookie = new Cookie("STONE_BOOK_" + book, book);
		response.addCookie(cookie);
	%>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<%
		//向客户端浏览器写入一个Cookie:cookiePath,cookiePathValue
		Cookie cookie = new Cookie("cookiePath","cookiePathValue");
		//设置Cookie的作用范围
		cookie.setPath(request.getContextPath());
		response.addCookie(cookie);
		
		//Cookie的作用范围:可以作用当前目录和当前目录的子目录,但不能作用于当前目录的上一级目录
		//可以通过setPath来设置Cookie的作用范围,其中/代表站点的根目录
		
	%>
	
	<a href="../cookieRead.jsp">To Read Cookie</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<%
		//读取一个name为cookiePath的Cookie
		
		String cookieValue = null;
		Cookie[] cookies = request.getCookies();
		if(cookies != null && cookies.length > 0){
			for(Cookie cookie:cookies){
				if("cookiePath".equals(cookie.getName())){
					cookieValue = cookie.getValue();
				}
			}
		}
		
		if(cookieValue != null){
			out.print(cookieValue);
		} else {
			out.print("没有指定的cookie");
		}
	%>

</body>
</html>

Session 机制

HttpSession 接口中的方法:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login.jsp</title>
</head>
<body>

	SessionID: <%= session.getId() %>
	<br><br>
	
	IsNew: <%= session.isNew() %>
	<br><br>
	
	MaxInactiveInterval: <%= session.getMaxInactiveInterval() %>
	<br><br>
	
	CreateTime: <%= session.getCreationTime() %>
	<br><br>
	
	lastAccessedTime: <%= session.getLastAccessedTime() %>
	<br><br>
	
	<%
		Object username = session.getAttribute("username");
		if(username == null){
			username = "";
		}
	%>

	<form action="hello.jsp">
		username: <input type="text" name="username" value="<%= username %>"> 
		<input type="submit" value="Submit">
	</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>hello.jsp</title>
</head>
<body>

	SessionID: <%= session.getId() %>
	<br><br>
	
	IsNew: <%= session.isNew() %>
	<br><br>
	
	MaxInactiveInterval: <%= session.getMaxInactiveInterval() %>
	<br><br>
	
	CreateTime: <%= session.getCreationTime() %>
	<br><br>
	
	lastAccessedTime: <%= session.getLastAccessedTime() %>
	<br><br>
	
	Hello: <%= request.getParameter("username") %>
	<br><br>
	
	<%
		session.setAttribute("username", request.getParameter("username"));
	%>
	
	<a href="login.jsp">重新登录</a>
	&nbsp;&nbsp;&nbsp;&nbsp;
	<a href="logout.jsp">注销</a>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>logout.jsp</title>
</head>
<body>

	SessionID: <%= session.getId() %>
	<br><br>
	
	IsNew: <%= session.isNew() %>
	<br><br>
	
	MaxInactiveInterval: <%= session.getMaxInactiveInterval() %>
	<br><br>
	
	CreateTime: <%= session.getCreationTime() %>
	<br><br>
	
	lastAccessedTime: <%= session.getLastAccessedTime() %>
	<br><br>
	
	Bye: <%= session.getAttribute("username") %>
	<br><br>
	
	<a href="login.jsp">重新登录</a>
	
	<%
		session.invalidate();
	%>

</body>
</html>

利用 URL 重写实现 Session 跟踪

Servlet 规范中引入了一种补充的会话管理机制,它允许不支持 Cookie 的浏览器也可以与WEB服务器保持连续的会话。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的 URL 地址的一个特殊参数。

将会话标识号以参数形式附加在超链接的 URL 地址后面的技术称为 URL 重写。如果在浏览器不支持 Cookie 或者关闭了 Cookie 功能的情况下,WEB 服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、form 表单的 action 属性设置和重定向的 URL )进行 URL 重写。

HttpServletResponse 接口中定义了两个用于完成 URL 重写方法:

  • encodeURL 方法
  • encodeRedirectURL 方法
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	SessionID: <%= session.getId() %>
	<br><br>
	
	IsNew: <%= session.isNew() %>
	<br><br>
	
	MaxInactiveInterval: <%= session.getMaxInactiveInterval() %>
	<br><br>
	
	CreateTime: <%= session.getCreationTime() %>
	<br><br>
	
	lastAccessedTime: <%= session.getLastAccessedTime() %>
	<br><br>
	
	<%
		Object username = session.getAttribute("username");
		if(username == null){
			username = "";
		}
	%>

	<form action="<%= response.encodeURL("hello.jsp") %>" method="post">
		username: <input type="text" name="username" value="<%= username %>"> 
		<input type="submit" value="Submit">
	</form>
</body>
</html>

使用 session 实现简易购物车

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>step1.jsp</title>
</head>
<body>

	<h4>Step1: 选择要购买的图书:</h4>
	<form action="<%= request.getContextPath() %>/processStep1" method="post">
		<table border="1" cellpadding="10" cellspacing="0">
			<tr>
				<td>Java</td>
				<td><input type="checkbox" name="book" value="Java"></td>
			</tr>
			<tr>
				<td>Oracle</td>
				<td><input type="checkbox" name="book" value="Oracle"></td>
			</tr>
			<tr>
				<td>MySQL</td>
				<td><input type="checkbox" name="book" value="MySQL"></td>
			</tr>
			<tr>
				<td colspan="2">
					<input type="submit" value="Submit">
				</td>
			</tr>
		</table>
	</form>

</body>
</html>
package com.stone.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/processStep1")
public class ProcessStep1 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取选中的图书信息
		String[] books = request.getParameterValues("book");
		
		//2.把图书信息放入到HttpSession中
		request.getSession().setAttribute("books", books);
		
		//2.重定向页面到shoppingcart/step2.jsp
		response.sendRedirect(request.getContextPath() + "/shoppingcart/step2.jsp");
	}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>step2.jsp</title>
</head>
<body>

	<h4>Step2: 请输入寄送地址和信用卡信息</h4>
	<form action="<%= request.getContextPath() %>/processStep2" method="post">
		<table border="1" cellpadding="10" cellspacing="0">
			<tr>
				<td colspan="2">寄送信息</td>
			</tr>
			<tr>
				<td>姓名:</td>
				<td><input type="text" name="name"></td>
			</tr>
			<tr>
				<td>寄送地址:</td>
				<td><input type="text" name="address"></td>
			</tr>
			
			<tr>
				<td colspan="2">信用卡信息</td>
			</tr>
			<tr>
				<td>种类:</td>
				<td>
					<input type="radio" name="cardType" value="Visa">Visa
					<input type="radio" name="cardType" value="Master">Master
				</td>
			</tr>
			<tr>
				<td>卡号:</td>
				<td><input type="text" name="card"></td>
			</tr>
			
			<tr>
				<td colspan="2"><input type="submit" value="Submit"></td>
			</tr>
		</table>
	</form>

</body>
</html>
package com.stone.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.stone.bean.Customer;

@WebServlet("/processStep2")
public class ProcessStep2 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取请求参数:name,address,cardType,card
		String name = request.getParameter("name");
		String address = request.getParameter("address");
		String cardType = request.getParameter("cardType");
		String card = request.getParameter("card");
		Customer customer = new Customer(name, address, cardType, card);
		
		//2.把请求信息存入到HttpSession中
		HttpSession session = request.getSession();
		session.setAttribute("customer", customer);
		
		//3.重定向页面到/shoppingcart/confirm.jsp
		response.sendRedirect(request.getContextPath() + "/shoppingcart/confirm.jsp");
	}
}
<%@page import="com.stone.bean.Customer"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>confirm.jsp</title>
</head>
<body>

	<%
		Customer customer = (Customer)session.getAttribute("customer");
		String[] books = (String[])session.getAttribute("books");
	%>

	<h4>Step3: 确认用户信息</h4>
	<table>
		<tr>
			<td>顾客姓名:</td>
			<td><%= customer.getName() %>
		</tr>
		<tr>
			<td>地址:</td>
			<td><%= customer.getAddress() %>
		</tr>
		<tr>
			<td>卡类型:</td>
			<td><%= customer.getCardType() %>
		</tr>
		<tr>
			<td>卡号:</td>
			<td><%= customer.getCard() %>
		</tr>
		<tr>
			<td>Books: </td>
			<td>
				<%
					for(String book:books){
						out.print(book);
						out.print("<br>");
					}
				%>
			</td>
		</tr>
	</table>

</body>
</html>

使用 Session 防止表单重复提交

包含有 FORM 表单的页面必须通过一个服务器程序动态产生,服务器程序为每次产生的页面中的 FORM 表单都分配一个唯一的随机标识号,并在 FORM 表单的一个隐藏字段中设置这个标识号,同时在当前用户的 Session 域中保存这个标识号。

当用户提交 FORM 表单时,负责接收这一请求的服务器程序比较 FORM 表单隐藏字段中的标识号与存储在当前用户的 Session 域中的标识号是否相同,如果相同则处理表单数据,处理完后清除当前用户的 Session 域中存储的标识号。在下列情况下,服务器程序将忽略提交的表单请求:

  • 当前用户的 Session 中不存在表单标识号
  • 用户提交的表单数据中没有标识号字段
  • 存储在当前用户的 Session 域中的表单标识号与表单数据中的标识号不同

浏览器只有重新向 WEB 服务器请求包含 FORM 表单的页面时,服务器程序才又产生另外一个随机标识号,并将这个标识号保存在 Session 域中和作为新返回的 FORM 表单中的隐藏字段值。

TokenProcessor.java:用于管理表单标识号的工具类,它主要用于产生、比较和清除存储在当前用户 Session 中的表单标识号。为了保证表单标识号的唯一性,每次将当前 SessionID 和系统时间的组合值按 MD5 算法计算的结果作为表单标识号,并且将 TokenProcessor 类设计为单例类。

<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>index.jsp</title>
</head>
<body>
	
	<%
		//在原表单页面, 生成一个随机值 token
		String tokenValue = new Date().getTime() + "";
		//在原表单页面, 把 token 值放入 session 属性中
		session.setAttribute("token", tokenValue);
	%>
	
	<form action="<%= request.getContextPath() %>/tokenServlet" method="post">
		//在原表单页面, 把 token 值放入到 隐藏域 中
		<input type="hidden" name="token" value="<%= tokenValue %>">
		name: <input type="text" name="name">
		<input type="submit" value="Submit">
	</form>

</body>
</html>
package com.stone.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/tokenServlet")
public class TokenServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		HttpSession session = request.getSession();
		//获取 session的 token 值
		Object token = session.getAttribute("token");
		//获取 隐藏域 中的 token 值
		String tokenValue = request.getParameter("token");
		System.out.println(token);
		System.out.println(tokenValue);
		//比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
		if (token != null && token.equals(tokenValue)) {
			session.removeAttribute("token");
		} else {
			//若不一致, 则直接响应提示页面: "重复提交"
			response.sendRedirect(request.getContextPath() + "/token/token.jsp");
			return;
		}
		
		String name = request.getParameter("name");
		System.out.println(name);
		request.getRequestDispatcher("/token/success.jsp").forward(request, response);
	}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>success.jsp</title>
</head>
<body>
	<h4>转发到success.jsp</h4>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>token.jsp</title>
</head>
<body>
	<h4>已经提交过了</h4>
</body>
</html>

使用 Session 实现一次性验证码

一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码,其原理与使用 Session 防止表单重复提交的原理基本一样,只是将表单标识号变成了验证码的形式,并且要求用户将提示的验证码手工填写进一个表单字段中,而不是通过表单的隐藏字段自动回传给服务器。

服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。

密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程。

<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>index.jsp</title>
</head>
<body>
	<font color="red">
	<%= session.getAttribute("message") == null ? "" : session.getAttribute("message") %>
	</font>
	<form action="<%= request.getContextPath() %>/checkCodeServlet" method="post">
		name: <input type="text" name="name">
		checkCode: <input type="text" name="CHECK_CODE_PARAM_NAME">
		<img alt="" src="<%= request.getContextPath() %>/validateColorServlet">
		<input type="submit" value="Submit">
	</form>

</body>
</html>
package com.stone.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取请求参数 CHECK_CODE_PARAM_NAME
		String paraCode = request.getParameter("CHECK_CODE_PARAM_NAME");
		
		//2. 获取session中的 CHECK_CODE_KEY 属性值
		String sessionCode = (String) request.getSession().getAttribute("CHECK_CODE_KEY");
		
		System.out.println(paraCode);
		System.out.println(sessionCode);
		
		//3.比对,看是否一致,若一致说明验证码正确,若不一致说明验证码错误
		if (!(paraCode != null && paraCode.equals(sessionCode))) {
			request.getSession().setAttribute("message", "验证码不一致");
			response.sendRedirect(request.getContextPath() + "/check/index.jsp");
			return;
		}
		System.out.println("受理请求");
	}
}
package com.stone.servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/validateColorServlet")
public class ValidateColorServlet extends HttpServlet {

	public static final String CHECK_CODE_KEY = "CHECK_CODE_KEY";
	
	private static final long serialVersionUID = 1L;
	
	//设置验证图片的宽度, 高度, 验证码的个数
	private int width = 152;
	private int height = 40;
	private int codeCount = 6;
	
	//验证码字体的高度
	private int fontHeight = 4;
	
	//验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (codeX, codeY) 位置处
	private int codeX = 0;
	private int codeY = 0;
	
	//验证码由哪些字符组成
	char [] codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789".toCharArray();
	
	//初始化验证码图形属性
	public void init(){
		fontHeight = height - 2;
		codeX = width / (codeCount + 2);
		codeY = height - 4;
	}

	public void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//定义一个类型为 BufferedImage.TYPE_INT_BGR 类型的图像缓存
		BufferedImage buffImg = null;
		buffImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
	
		//在 buffImg 中创建一个 Graphics2D 图像
		Graphics2D graphics = null;
		graphics = buffImg.createGraphics();
		
		//设置一个颜色, 使 Graphics2D 对象的后续图形使用这个颜色
		graphics.setColor(Color.WHITE);
		
		//填充一个指定的矩形: x - 要填充矩形的 x 坐标; y - 要填充矩形的 y 坐标; width - 要填充矩形的宽度; height - 要填充矩形的高度
		graphics.fillRect(0, 0, width, height);
		
		//创建一个 Font 对象: name - 字体名称; style - Font 的样式常量; size - Font 的点大小
		Font font = null;
		font = new Font("", Font.BOLD, fontHeight);
		//使 Graphics2D 对象的后续图形使用此字体
		graphics.setFont(font);
		
		graphics.setColor(Color.BLACK);
		
		//绘制指定矩形的边框, 绘制出的矩形将比构件宽一个也高一个像素
		graphics.drawRect(0, 0, width - 1, height - 1);
		
		//随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到
		Random random = null;
		random = new Random();
		graphics.setColor(Color.GREEN);
		for(int i = 0; i < 55; i++){
			int x = random.nextInt(width);
			int y = random.nextInt(height);
			int x1 = random.nextInt(20);
			int y1 = random.nextInt(20);
			graphics.drawLine(x, y, x + x1, y + y1);
		}
		
		//创建 randomCode 对象, 用于保存随机产生的验证码, 以便用户登录后进行验证
		StringBuffer randomCode;
		randomCode = new StringBuffer();
		
		for(int i = 0; i < codeCount; i++){
			//得到随机产生的验证码数字
			String strRand = null;
			strRand = String.valueOf(codeSequence[random.nextInt(36)]);
			
			//把正在产生的随机字符放入到 StringBuffer 中
			randomCode.append(strRand);
			
			//用随机产生的颜色将验证码绘制到图像中
			graphics.setColor(Color.BLUE);
			graphics.drawString(strRand, (i + 1)* codeX, codeY);
		}
		
		//再把存放有所有随机字符的 StringBuffer 对应的字符串放入到 HttpSession 中
		request.getSession().setAttribute(CHECK_CODE_KEY, randomCode.toString());
		
		//禁止图像缓存
		response.setHeader("Pragma", "no-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		
		//将图像输出到输出流中
		ServletOutputStream sos = null;
		sos = response.getOutputStream();
		ImageIO.write(buffImg, "jpeg", sos); 
		sos.close();
	}
}

使用 JavaBean

  • 用作 JavaBean 的类必须具有一个公共的、无参数的构造方法。
  • JavaBean 的属性与普通Java类的属性的概念不一样,JavaBean 的属性是以方法定义的形式出现的。
  • 用于对属性赋值的方法称为属性修改器或 setter 方法,用于读取属性值的方法称为属性访问器或 getter 方法。
  • 属性修改器必须以小写的 set 前缀开始,后跟属性名,且属性名的第一个字母要改为大写,例如,nickName 属性的修改器名称为 setNickName,password 属性的修改器名称为 setPassword。
  • 属性访问器通常以小写的 get 前缀开始,后跟属性名,且属性名的第一个字母要改为大写,例如,nickName 属性的访问器名称为 getNickName,password 属性的访问器名称为 getPassword。
  • JavaBean 的属性名是根据 setter 方法与 getter 方法的名称来生成的, setter 方法或 getter 方法中除去前缀 “set” 和 “get” 后的部分即为属性名,但属性名的首字母必须小写。
  • JSP 规范专门定义了三个JSP 标签:<jsp:useBean><jsp:setProperty><jsp:getPropperty>,它们分别用于创建和查找 JavaBean 的实例对象、设置 JavaBean 对象的属性、读取 JavaBean 对象的属性。
  • 对于 JSP 页面来说,只要一个类具有一个公共的、无参数的构造方法,就可以把这个类当作 JavaBean 来使用,如果类中有不接受任何参数的 getter 方法或只接受一个参数的 setter 方法,就可以把前缀 “get” 或 “set” 后面的部分当着一个属性名来引用。
  • JSP 页面可以像调用一个普通 Java 类的方式去调用 JavaBean,即先使用 Java 代码创建 JavaBean 的实例对象,然后直接调用 JavaBean 对象的 getter 方法和 setter 方法。

使用 JavaBean 的注意事项:

  • JavaBean 应放置在 JSP 页面的类装载器或其父级类装载器所能装载的目录中,通常放置于 WEB 应用程序下的 WEB-INF/classes 目录中。
  • 有些版本的 Tomcat 不会自动重新加载修改过的 JavaBean,如果 JSP 页面加载 JavaBean 以后又修改和重新编译了 JavaBean 程序,那么需要修改 JSP 页面或者重新启动 Tomcat。
  • JavaBean 必须带有包名,不能用缺省包名。
  • 在选择存储 JavaBean 的域范围时,如果使用 request 域能够满足需求的话,则不要使用 Session 域。
package com.stone.bean;

public class Customer {

	private Integer id;
	private String name;
	private int age;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public Customer() {
		System.out.println("Customer's Constructor..");
	}

	public Customer(Integer id, String name, int age) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
	}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bean.jsp</title>
</head>
<body>
	
	<jsp:useBean id="customer" class="com.stone.bean.Customer" 
		scope="request"></jsp:useBean>
	
	<jsp:useBean id="customer2" beanName="com.stone.bean.Customer"
		type="java.lang.Object" scope="request"></jsp:useBean>
		
	<%-- 
		Object customer2 = request.getAttribute("customer2");
		if(customer2){
			customer2 = Class.forName("com.stone.javaweb.Customer").newInstance();
			request.setAttribute("customer2", customer2);
		}
	--%>	
		
	<!-- 若 property 的值为 *, 省略 value 属性值, 则将自动为所有属性赋值为对应的请求参数的值.  -->	
	<jsp:setProperty property="*" name="customer"/>
	
	<%-- 
	<jsp:setProperty property="name" value="stone" name="customer"/>
	--%>
	
	age: <jsp:getProperty property="age" name="customer"/> 
	<br>
	name: <jsp:getProperty property="name" name="customer"/>
	<br>
	id: <jsp:getProperty property="id" name="customer"/>
	
	<%-- 
	<%= customer.getAge() %>
	--%>
		
	<%-- 
		customer.setAge(10);
	--%>	
	
	<%-- 
		//1. 从 scope(session) 中获取 id(customer) 属性值, 赋给 class(com.stone.javaweb.Customer) 
		//类型的 id(customer) 变量
		Customer customer = (Customer)session.getAttribute("customer");
		
		//2. 若属性值为 null, 则利用反射创建一个新的对象, 把该对象赋给 id(customer), 并以 id(customer) 
		//为属性名让如到 scope(session) 中.
		if(customer == null){
			customer = (Customer)Class.forName("com.stone.javaweb.Customer").newInstance();
			session.setAttribute("customer", customer);
		}
	--%>
	
</body>
</html>

EL

EL(Expression Language)是为了使 JSP 写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让 JSP 的代码更加简化。

EL 语法很简单,它最大的特点就是使用上很方便。EL主要的语法结构:

${sessionScope.user.sex}

所有EL都是以${为起始、以}为结尾的。上述 EL 范例的意思是:从 Session 的范围中,取得用户的性别。假若依照之前 JSP 的写法如下:

User user = (User)session.getAttribute("user");
String sex = user.getSex( );

两者相比较之下,可以发现 EL 的语法比传统 JSP 更为方便、简洁。

EL隐含对象总共有11 个:

隐含对象类型说明
PageContextjavax.servlet.ServletContext表示此JSP的PageContext
PageScopejava.util.Map取得Page范围的属性名称所对应的值
RequestScopejava.util.Map取得Request范围的属性名称所对应的值
sessionScopejava.util.Map取得Session范围的属性名称所对应的值
applicationScopejava.util.Map取得Application范围的属性名称所对应的值
paramjava.util.Map如同ServletRequest.getParameter(String name)。回传String类型的值
paramValuesjava.util.Map如同ServletRequest.getParameterValues(String name)。回传String[]类型的值
headerjava.util.Map如同ServletRequest.getHeader(String name)。回传String类型的值
headerValuesjava.util.Map如同ServletRequest.getHeaders(String name)。回传String[]类型的值
cookiejava.util.Map如同HttpServletRequest.getCookies()
initParamjava.util.Map如同ServletContext.getInitParameter(String name)。回传String类型的值

可以使用param和paramValues两者来取得数据。

  • ${param.name}
  • ${paramValues.name}

这里 param 的功能和 request.getParameter(String name) 相同,而 paramValues 和 request.getParameterValues(String name) 相同。如果用户填了一个表格,表格名称为 username,则可以使用 ${param.username} 来取得用户填入的值。

可以使用 ${pageContext} 来取得其他有关用户要求或页面的详细信息:

Expression说明
${pageContext.request.queryString}取得请求的参数字符串
${pageContext.request.requestURL}取得请求的URL,但不包括请求之参数字符串,即servlet的HTTP地址。
${pageContext.request.contextPath}服务的webapplication的名称
${pageContext.request.method}取得HTTP的方法(GET、POST)
${pageContext.request.protocol}取得使用的协议(HTTP/1.1、HTTP/1.0)
${pageContext.request.remoteUser}取得用户名称
${pageContext.request.remoteAddr}取得用户的IP地址
${pageContext.session.new}判断session是否为新的,所谓新的session,表示刚由server产生而client尚未使用
${pageContext.session.id}取得session的ID
${pageContext.servletContext.serverInfo}取得主机端的服务信息
<%@page import="com.stone.bean.Customer"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>el.jsp</title>
</head>
<body>
	
	<form action="el.jsp" method="post">
		
		username: <input type="text" name="username" 
			value="<%= request.getParameter("username") == null ? "" : request.getParameter("username")%>"/>
		
		<!--  
			EL 表达式的特点: 简洁!
		-->	
		username: <input type="text" name="username" 
			value="${param.username }"/>
		<input type="submit" value="Submit"/>
		
	</form>
	
	username: <%= request.getParameter("username") %>
	
	<br><br>
	
	<jsp:useBean id="customer" class="com.stone.bean.Customer" scope="session"></jsp:useBean>
	<jsp:setProperty property="age" value="12" name="customer"/>
	
	age: 
	<% 
		Customer customer2 = (Customer)session.getAttribute("customer");
		out.print(customer2.getAge());
	%>
	<br>
	age: <jsp:getProperty property="age" name="customer"/>

	<br>
	<br>
	
	<% 
		application.setAttribute("time", new Date());
	%>
	
	<a href="el2.jsp?score=89&name=A&name=B&name=C">To El2 Page</a>
	
</body>
</html>

EL 关系运算符:

关系运算符说明范例结果
== 或 eq等于${5==5}${5eq5}TRUE
!= 或 ne不等于${5!=5}${5ne5}FALSE
< 或 lt小于${3<5}${3lt5}TRUE
> 或 gt大于${3>5}{3gt5}FALSE
<= 或 le小于等于${3<=5}${3le5}TRUE
>= 或 ge大于等于${3>=5}${3ge5}FALSE

EL 逻辑运算符:

逻辑运算符范例结果
&&或and交集 ${A && B}${A and B}true/false
||或or并集 `${A
!或not${! A }${not A}true/false

Empty 运算符:

  • Empty 运算符主要用来判断值是否为空( NULL ,空字符串,空集合)

条件运算符:

  • ${ A ? B : C}
<%@page import="com.stone.bean.Customer"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<!-- 7. EL 的运算符 -->
	${param.score > 60 ? "及格" : "不及格" }
	<br>
	
	<% 
		List<String> names = new ArrayList<String>();
		names.add("abc");
		request.setAttribute("names", names);
	%>
	<!-- empty 可以作用于一个集合, 若该集合不存在或集合中没有元素, 其结果都为 true -->
	names is empty: ${empty requestScope.names }
	<br>
	
	<!-- 6. 其他隐含对象: pageContext 等(cookie, header, initParam 只需了解.) -->
	pageContext: pageContext 即为 PageContext 类型, 但只能读取属性就可以一直的 . 下去。 
	<br>
	contextPath: ${pageContext.request.contextPath }
	
	<br>
	sessionId: ${pageContext.session.id }
	
	<br>
	sessionAttributeNames: ${pageContext.session.attributeNames }
	
	<br>
	
	
	initParam: ${initParam.initName }
	<br>
	
	Accept-Language: ${header["Accept-Language"] }
	<br>

	JSESSIONID: ${cookie.JSESSIONID.name } -- ${cookie.JSESSIONID.value }
	<br>

	<!-- 5. 与输入有关的隐含对象: param, paramValues -->
	score: ${param.score }
	<%-- 
	<%= request.getParameter("score") %>
	--%>
	<br>
	names: ${paramValues.name[0].class.name }
	<%-- 
	<%= 
		request.getParameterValues("name")[0].getClass().getName()
	%>
	--%>
	<br>
	
	<!-- 4. 隐含对象之与范围相关的: pageScope, requestScope, sessionScope, applicationScope -->
	time: ${applicationScope.time.time }
	<%-- 
	<%= application.getAttribute("time") %>
	--%>
	<br>
	
	<!-- 3. EL 可以进行自动的类型转换 -->
	score: ${param.score + 11}
	<br>
	score: <%= request.getParameter("score") + 11 %>
	<br>
	
	<!-- 2. EL 中的隐含对象 -->
	<% 
		Customer cust2 = new Customer();
		cust2.setAge(28);
		request.setAttribute("customer", cust2);
	%>
	
	age: ${customer.age } 

	<br>
	<!-- 1. EL 的 . 或 [] 运算符 -->
	age: ${sessionScope.customer["age"] }
	
	<%-- 
		Customer customer = (Customer)session.getAttribute("customer");
		out.print(customer.getAge());
	--%>
	
	<% 
		Customer customer = new Customer();
		customer.setName("STONE");	
	
		session.setAttribute("com.stone.customer", customer);
	%>
	
	<br>
	<!--  
		如果域对象中的属性名带有特殊字符, 则使用 [] 运算符会很方便. 
	-->
	name: ${sessionScope["com.stone.customer"].name }
	
</body>
</html>

简单标签

自定义标签

创建一个标签处理器类,实现 SimpleTag 接口:

package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTag;

public class HelloSimpleTag implements SimpleTag {

	private String value;
	private String count;
	
	public void setValue(String value) {
		this.value = value;
	}
	
	public void setCount(String count) {
		this.count = count;
	}
	
	//标签体逻辑实际应该编写到该方法中. 
	@Override
	public void doTag() throws JspException, IOException {
//		System.out.println("value: " + value  + ", count: " + count);
//		
//		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
//		pageContext.getOut().print("Hello: " + request.getParameter("name"));
		
		JspWriter out = pageContext.getOut();
		int c = 0;
		
		c = Integer.parseInt(count);
		for(int i = 0; i < c; i++){
			out.print((i + 1) + ": " + value);
			out.print("<br>");
		}
	}

	@Override
	public JspTag getParent() {
		System.out.println("getParent");
		return null;
	}

	@Override
	public void setJspBody(JspFragment arg0) {
		System.out.println("setJspBody");
	}

	private PageContext pageContext;
	
	//JSP 引擎调用, 把代表 JSP 页面的 PageContext 对象传入
	//PageContext 可以获取 JSP 页面的其他 8 个隐含对象. 
	//所以凡是 JSP 页面可以做的标签处理器都可以完成. 
	@Override
	public void setJspContext(JspContext arg0) {
		System.out.println(arg0 instanceof PageContext);  
		this.pageContext = (PageContext) arg0;
	}

	@Override
	public void setParent(JspTag arg0) {
		System.out.println("setParent");
	}
}

在 WEB-INF 文件夹下新建一个.tld(标签库描述文件)为扩展名的 xml 文件,拷入固定的部分,对 description、display-name、tlib-version、short-name、uri做出修改,并描述自定义的标签:

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
	version="2.0">

	<!-- 描述 TLD 文件 -->
	<description>MyTag 1.0 core library</description>
	<display-name>MyTag core</display-name>
	<tlib-version>1.0</tlib-version>

	<!-- 建议在 JSP 页面上使用的标签的前缀 -->
	<short-name>stone</short-name>
	<!-- 作为 tld 文件的 id, 用来唯一标识当前的 TLD 文件, 多个 tld 文件的 URI 不能重复. 通过 JSP 页面的 taglib 
		标签的 uri 属性来引用. -->
	<uri>http://www.stone.com/mytag/core</uri>

	<tag>
		<name>readerFile</name>
		<tag-class>com.stone.javaweb.tag.ReadFileTag</tag-class>
		<body-content>empty</body-content>

		<attribute>
			<name>src</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>

	<tag>
		<name>max</name>
		<tag-class>com.stone.javaweb.tag.MaxTag</tag-class>
		<body-content>empty</body-content>

		<attribute>
			<name>num1</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>

		<attribute>
			<name>num2</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>

	<!-- 描述自定义的 HelloSimpleTag 标签 -->
	<tag>
		<!-- 标签的名字: 在 JSP 页面上使用标签时的名字 -->
		<name>hello</name>

		<!-- 标签所在的全类名 -->
		<tag-class>com.stone.javaweb.tag.HelloSimpleTag</tag-class>
		<!-- 标签体的类型 -->
		<body-content>empty</body-content>

		<!-- 描述当前标签的属性 -->
		<attribute>
			<!-- 属性名 -->
			<name>value</name>
			<!-- 该属性是否被必须 -->
			<required>true</required>
			<!-- rtexprvalue: runtime expression value 当前属性是否可以接受运行时表达式的动态值 -->
			<rtexprvalue>true</rtexprvalue>
		</attribute>

		<attribute>
			<name>count</name>
			<required>false</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
	</tag>

</taglib>  

在 JSP 页面上使用自定义标签:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!-- 导入标签库(描述文件) -->    
<%@taglib uri="http://www.stone.com/mytag/core" prefix="stone" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<stone:readerFile src="/WEB-INF/note.txt"/>

	<br><br>
	
	<stone:max num2="${param.a }" num1="${param.b }"/>
	
	<br>
	
	<stone:hello value="${param.name }" count="10"/>
	
</body>
</html>

通常情况下开发简单标签直接继承 SimpleTagSupport 就可以了. 可以直接调用其对应的 getter 方法得到对应的 API:

package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class MaxTag extends SimpleTagSupport{

	private String num1;
	private String num2;
	
	public void setNum1(String num1) {
		this.num1 = num1;
	}
	
	public void setNum2(String num2) {
		this.num2 = num2;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		int a = 0;
		int b = 0;
		
		PageContext pageContext = (PageContext) getJspContext();
		
		JspWriter out = pageContext.getOut();
		
		try {
			a = Integer.parseInt(num1);
			b = Integer.parseInt(num2);
			out.print(a > b ? a : b);
		} catch (Exception e) {
			out.print("输入的属性的格式不正确!");
		}	
	}
}
package com.stone.tag;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Pattern;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ReadFileTag extends SimpleTagSupport{

	//相对于当前 WEB 应用的根路径的文件名
	private String src;

	public void setSrc(String src) {
		this.src = src;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		PageContext pageContext = (PageContext) getJspContext();
		InputStream in = pageContext.getServletContext().getResourceAsStream(src);
		BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 
		
		String str = null;
		while((str = reader.readLine()) != null){
			
			str = Pattern.compile("<").matcher(str).replaceAll("&lt");
			str = Pattern.compile(">").matcher(str).replaceAll("&gt");
			
			pageContext.getOut().println(str);
			pageContext.getOut().println("<br>"); 
		}
	}	
}

带标签体的自定义标签

若一个标签有标签体:

<stone:testJspFragment>abcdefg</stone:testJspFragment>

在自定义标签的标签处理器中使用 JspFragment 对象封装标签体信息。

若配置了标签含有标签体,则 JSP 引擎会调用 setJspBody() 方法把 JspFragment 传递给标签处理器类,在 SimpleTagSupport 中还定义了一个 getJspBody() 方法,用于返回 JspFragment 对象。

JspFragment 的 invoke(Writer) 方法把标签体内容从 Writer 中输出,若为 null,则等同于invoke(getJspContext().getOut()),即直接把标签体内容输出到页面上。

package com.stone.tag;

import java.io.IOException;
import java.io.StringWriter;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class TestJspFragment extends SimpleTagSupport {
	
	@Override
	public void doTag() throws JspException, IOException {
		JspFragment bodyContent = getJspBody();
		//JspFragment.invoke(Writer): Writer 即为标签体内容输出的字符流, 若为 null, 则
		//输出到 getJspContext().getOut(), 即输出到页面上.
		
		//1. 利用 StringWriter 得到标签体的内容.
		StringWriter sw = new StringWriter();
		bodyContent.invoke(sw);
		
		//2. 把标签体的内容都变为大写
		String content = sw.toString().toUpperCase();
		
		//3. 获取 JSP 页面的 out 隐含对象, 输出到页面上
		getJspContext().getOut().print(content);
	}
}

在 tld 文件中,使用 body-content 节点来描述标签体的类型。

<body-content>:指定标签体的类型,大部分情况下取值为 scriptless。可能取值有3种:

  • empty:没有标签体
  • scriptless:标签体可以包含 el 表达式和 JSP 动作元素,但不能包含 JSP 的脚本元素
  • tagdependent:表示标签体交由标签本身去解析处理

若指定 tagdependent,在标签体中的所有代码都会原封不动的交给标签处理器,而不是将执行结果传递给标签处理器

	<tag>
		<name>testJspFragment</name>
		<tag-class>com.stone.tag.TestJspFragment</tag-class>
		<body-content>scriptless</body-content>
	</tag>

定义一个自定义标签<stone:printUpper time="10">abcdefg</stone> 把标签体内容转换为大写, 并输出 time 次到浏览器上

package com.stone.tag;

import java.io.IOException;
import java.io.StringWriter;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class PrintUpperTag extends SimpleTagSupport {
	private String time;
	public void setTime(String time) {
		this.time = time;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		//1.得到标签体内容
		JspFragment jspFragment = getJspBody();
		StringWriter stringWriter = new StringWriter();
		jspFragment.invoke(stringWriter);
		String content = stringWriter.toString();
		
		//2.变为大写
		content  = content.toUpperCase();
		
		//3.得到out隐含变量
		//4.循环输出
		int count = 1;
		try {
			count = Integer.parseInt(time);
		} catch (NumberFormatException e) {}
		for (int i = 0; i < count; i++) {
			getJspContext().getOut().print(i + 1 + "." + content + "<br>");
		}
	}
}
	<tag>
		<name>printUpper</name>
		<tag-class>com.stone.tag.PrintUpperTag</tag-class>
		<body-content>scriptless</body-content>
		
		<attribute>
			<name>time</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>

实现 forEach 标签:

  • 两个属性:items(集合类型, Collection),var(String 类型)
  • doTag:
    • 遍历 items 对应的集合
    • 把正在遍历的对象放入到 pageContext 中,键:var,值:正在遍历的对象
    • 把标签体的内容直接输出到页面上
package com.stone.tag;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ForEach extends SimpleTagSupport {
	private Collection<?> items;
	private String var;
	public void setItems(Collection<?> items) {
		this.items = items;
	}
	public void setVar(String var) {
		this.var = var;
	}
	@Override
	public void doTag() throws JspException, IOException {
		//1. 遍历Items对应的集合
		if (items != null) {
			for (Object obj : items) {
				//2.把正在遍历的对象放入到 pageContext 中, 键: var, 值: 正在遍历的对象
				getJspContext().setAttribute(var, obj);
				//3.把标签体的内容直接输出到页面上
				getJspBody().invoke(null);
			}
		}
	}
}
	<tag>
		<name>forEach</name>
		<tag-class>com.stone.tag.ForEach</tag-class>
		<body-content>scriptless</body-content>
		
		<attribute>
			<name>items</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>var</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="com.stone.tag.Customer"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!-- 导入标签库(描述文件) -->    
<%@taglib uri="http://www.stone.com/mytag/core" prefix="stone" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<stone:testJspFragment>HelloWorld</stone:testJspFragment>
	
	<stone:printUpper time="10">abcdefg</stone:printUpper>
	
	<%
		List<Customer> customers = new ArrayList<Customer>();
		customers.add(new Customer(1,"aaa"));
		customers.add(new Customer(2,"bbb"));
		customers.add(new Customer(3,"ccc"));
		customers.add(new Customer(4,"ddd"));
		customers.add(new Customer(5,"eee"));
		request.setAttribute("customers", customers);
		
		Map<String, Customer> customerMap = new HashMap<String, Customer>();
		customerMap.put("a", customers.get(0));
		customerMap.put("b", customers.get(1));
		customerMap.put("c", customers.get(2));
		customerMap.put("d", customers.get(3));
		customerMap.put("e", customers.get(4));
		request.setAttribute("customerMap", customerMap);
	%>
	
	<stone:forEach items="${requestScope.customers }" var="cust">
		${cust.id } -- ${cust.name } <br>
	</stone:forEach>
	
	<br><br>
	<c:forEach items="${customerMap }" var="cust">
		--${cust.key } -- ${cust.value.id } -- ${cust.value.name } <br>
	</c:forEach>

	<%-- <stone:readerFile src="/WEB-INF/note.txt"/>

	<br><br>
	
	<stone:max num2="${param.a }" num1="${param.b }"/>
	
	<br>
	
	<stone:hello value="${param.name }" count="10"/> --%>
	
</body>
</html>

带父签体的自定义标签

父标签无法获取子标签的引用,父标签仅把子标签作为标签体来使用。

package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ParentTag extends SimpleTagSupport{
	private String name = "STONE";
	public String getName() {
		return name;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		System.out.println("父标签的标签处理器--name:" + name);
		getJspBody().invoke(null);
	}
}

子标签可以通过 getParent() 方法来获取父标签的引用(需继承 SimpleTagSupport 或自实现 SimpleTag 接口的该方法),若子标签的确有父标签, JSP 引擎会把代表父标签的引用通过 setParent(JspTag parent) 赋给标签处理器。

package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class SonTag extends SimpleTagSupport{
	@Override
	public void doTag() throws JspException, IOException {
		//1.得到父标签的引用
		JspTag parent = getParent();
		
		//2.获取父标签的name属性
		ParentTag parentTag = (ParentTag) parent;
		String name = parentTag.getName();
		
		//3.把name值打印到JSP页面上
		getJspContext().getOut().print("子标签输出name:" + name);
	}
}

在 tld 配置文件中,无需为父标签有额外的配置。但子标签是是以标签体的形式存在的,所以父标签的 <body-content></body-content>需设置为 scriptless。

	<tag>
		<name>parentTag</name>
		<tag-class>com.stone.tag.ParentTag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	<tag>
		<name>sonTag</name>
		<tag-class>com.stone.tag.SonTag</tag-class>
		<body-content>empty</body-content>
	</tag>
	<!-- 父标签打印name到控制台 -->
	<stone:parentTag>
		<!-- 子标签以父标签的标签体存在,子标签把父标签的name属性打印到JSP页面上 -->
		<stone:sonTag/>
	</stone:parentTag>

开发 3 个标签:choose、when、otherwise:

  • when 有一个 boolean 类型的属性:test
  • choose 是 when 和 otherwise 的父标签
  • when 在 otherwise 之前使用
  • 在父标签 choose 中定义一个 "全局" 的 boolean 类型的 flag,用于判断子标签在满足条件的情况下是否执行
    • 若 when 的 test 为 true,且 when 的父标签的 flag 也为 true,则执行 when 的标签体(正常输出标签体的内容),同时把 flag 设置为 false
    • 若 when 的 test 为 true,且 when 的父标签的 flag 为 false,则不执行标签体
    • 若 flag 为 true,otherwise 执行标签体
package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ChooseTag extends SimpleTagSupport {
	private boolean flag = true;

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		getJspBody().invoke(null);
	}
}
package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class WhenTag extends SimpleTagSupport {
	private boolean test;
	public void setTest(boolean test) {
		this.test = test;
	}
	@Override
	public void doTag() throws JspException, IOException {
		if (test) {
			ChooseTag chooseTag = (ChooseTag) getParent();
			boolean flag = chooseTag.isFlag();
			if (flag) {
				getJspBody().invoke(null);
				chooseTag.setFlag(false);
			}
		}
	}
}
package com.stone.tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class OtherwiseTag extends SimpleTagSupport {
	@Override
	public void doTag() throws JspException, IOException {
		ChooseTag chooseTag = (ChooseTag) getParent();
		if (chooseTag.isFlag()) {
			getJspBody().invoke(null);
		}
	}
}
	<tag>
		<name>choose</name>
		<tag-class>com.stone.tag.ChooseTag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	<tag>
		<name>when</name>
		<tag-class>com.stone.tag.WhenTag</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>test</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
	
	<tag>
		<name>otherwise</name>
		<tag-class>com.stone.tag.OtherwiseTag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	<stone:choose>
		<stone:when test="${param.age > 24}">大学毕业</stone:when>
		<stone:when test="${param.age > 22}">高中毕业</stone:when>
		<stone:otherwise>高中以下...</stone:otherwise>
	</stone:choose>

EL 自定义函数

EL 自定义函数:在 EL 表达式中调用的某个 Java 类的静态方法,这个静态方法需在 web 应用程序中进行配置才可以被 EL 表达式调用。

EL 自定义函数可以扩展 EL 表达式的功能,让 EL 表达式完成普通 Java 程序代码所能完成的功能。

EL 自定义函数开发步骤:

  • 编写 EL 自定义函数映射的Java 类中的静态方法:这个 Java 类必须带有 public 修饰符,方法必须是这个类的带有 public 修饰符的静态方法
  • 编写标签库描述文件(tld 文件),在 tld 文件中描述自定义函数
  • 在 JSP 页面中导入和使用自定义函数
package com.stone.tag;

public class MyELFunction {
	public static String concat(String str1, String str2) {
		return str1 + str2;
	}
}
	<!-- 描述EL的自定义函数 -->
	<function>
		<name>concat</name>
		<function-class>com.stone.tag.MyELFunction</function-class>
		<function-signature>java.lang.String concat(java.lang.String, java.lang.String)</function-signature>
	</function>
	<!-- 测试自定义EL函数 -->
	${stone:concat(param.name1, param.name2) };

JSTL

JSTL(Java server pages standarded tag library,即JSP标准标签库)是提供给Java Web开发人员一个标准通用的标签库,开发人员可以利用这些标签取代JSP页面上的Java代码,从而提高程序的可读性,降低程序的维护难度。

JSTL 包含 5 类标签库:core 标签库、fmt 标签库、fn 标签库、XML 标签库和 SQL 标签库。

JSTL 核心(core)标签是最常用的 JSTL 标签,导入核心标签库的语法如下:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
标签说明
<c:out>将表达式的结果输出到页面中,类似于 <%= ...%>
<c:set>在指定范围内设置变量或属性值
<c:if>类似于 Java if 语句,用于条件判断
<c:choose>类似于 Java switch 关键字,为 <c:when><c:otherwise> 的父标签
<c:when><c:choose> 的子标签,用来判断条件是否成立
<c:otherwise><c:choose> 的子标签,当所有的 <c:when> 标签判断为 false 时被执行
<c:forEach>类似于 Java for,用于迭代集合中的信息
<c:forTokens>类似于 Java split,用于分隔字符串
<c:remove>用于删除数据
<c:catch>用于捕获异常
<c:import>用来导入静态或动态文件
<c:param>用来传入参数
<c:redirect>用于将当前页面重定向至另一个 URL
<c:url>用于将 URL 格式化为一个字符串
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@page import="com.stone.tag.Customer"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h4>c:out: 可以对特殊字符进行转换</h4>
	<% 
		request.setAttribute("book", "<<Java>>"); 
	%>
	book: ${requestScope.book }
	<br>
	book: <c:out value="${requestScope.book }"></c:out>
	
	<br><br>
	
	<h4>c:set: 可以为域赋属性值,其中value属性支持EL表达式,还可以为域对象中JavaBean的属性赋值,target,value都支持EL表达式</h4>
	<c:set var="name" value="STONE" scope="page"></c:set>
	name: ${pageScope.name }
	<br>
	<%--
		pageContext.setAttribute("name", "STONE");
	--%>
	<c:set var="subject" value="${param.subject }" scope="session"></c:set>
	subject: ${sessionScope.subject }
	<br>
	<%
		Customer customer = new Customer();
		customer.setId(1001);
		request.setAttribute("customer", customer);
	%>
	ID: ${requestScope.customer.id }
	<br>
	<c:set target="${requestScope.customer }" property="id" value="${param.id }"></c:set>
	ID: ${requestScope.customer.id }
	
	<br><br>
	
	<h4>c:remove: 移除指定域对象的指定属性</h4>
	<c:set var="date" value="1997-07-01" scope="session"></c:set>
	date: ${sessionScope.date }
	<br>
	<c:remove var="date" scope="session"/>
	date: ${sessionScope.date }
	
	<br><br>
	
	<h4>c:if: 没有else,可以把判断的结果存储起来,以备之后使用</h4>
	<c:set var="age" value="20" scope="request"></c:set>
	<c:if test="${requestScope.age > 18 }">成年</c:if>
	<br>
	<c:if test="${param.age > 18 }" var="isAudlt" scope="request"></c:if>
	isAudlt: <c:out value="${requestScope.isAudlt }"></c:out>
	
	<br><br>
	
	<h4>c:choose, c:when, c:otherwise: 可以使用if...else if...else if...else的效果,但较为麻烦</h4>
	<c:choose>
		<c:when test="${param.age > 60 }">老年</c:when>
		<c:when test="${param.age > 40 }">中年</c:when>
		<c:when test="${param.age > 18 }">青年</c:when>
		<c:otherwise>未成年</c:otherwise>
	</c:choose>
	
	<br><br>
	
	<h4>c:forEach: 可以对数组,Collection,Map进行遍历,begin从0开始遍历</h4>
	<c:forEach begin="1" end="10" step="3" var="i">
		${i };
	</c:forEach>
	<br>
	<%
		List<Customer> customers = new ArrayList<Customer>();
		customers.add(new Customer(1,"aaa"));
		customers.add(new Customer(2,"bbb"));
		customers.add(new Customer(3,"ccc"));
		customers.add(new Customer(4,"ddd"));
		customers.add(new Customer(5,"eee"));
		request.setAttribute("customers", customers);
	%>
	<!-- 遍历Collection,遍历数组同Collection -->
	<c:forEach items="${requestScope.customers }" var="customer" begin="0" end="4" step="2" varStatus="status">
		${status.index }:${status.count }:${status.first }:${status.last }:${customer.id } -- ${customer.name } <br>
	</c:forEach>
	
	<!-- 遍历Map -->
	<%
		Map<String, Customer> customersMap = new HashMap<String, Customer>();
		customersMap.put("a", new Customer(1,"aaa"));
		customersMap.put("b", new Customer(2,"bbb"));
		customersMap.put("c", new Customer(3,"ccc"));
		customersMap.put("d", new Customer(4,"ddd"));
		customersMap.put("e", new Customer(5,"eee"));
		request.setAttribute("customersMap", customersMap);
	%>
	<c:forEach items="${requestScope.customersMap }" var="cust">
		${cust.key } - ${cust.value.id } - ${cust.value.name }<br>
	</c:forEach>
	
	<%
		String[] names = new String[]{"a","b","c"};
		request.setAttribute("names", names);
	%>
	<c:forEach items="${names }" var="name">${name }--</c:forEach>
	<br>
	<c:forEach items="${pageContext.session.attributeNames }" var="attrName">
		${attrName }--
	</c:forEach>
	
	<br><br>
	
	<h4>c:forTaken: 处理字符串的,类似于String的split()方法</h4>	
	<c:set var="test" value="a,b,c.d.e.f;g;h;j" scope="request"></c:set>
	<c:forTokens items="${requestScope.test }" delims="," var="s">
		${s }<br>
	</c:forTokens>
	
	<br><br>
	
	<h4>c:import: 可以包含任何页面到当前页面</h4>
	<%-- <c:import url="http://www.baidu.com"></c:import> --%>
	
	<br><br>
	
	<h4>
		c:redirect: 使当前JSP页面重定向到指定的页面.
		使当前JSP转发到指定页面可以使用
		<%-- <jsp:forward page="/el.jsp"></jsp:forward> --%>
	</h4>
	<%-- <c:redirect url="/el.jsp"></c:redirect> --%>
	
	<br><br>
	
	<h4>
		c:url: 产生一个url地址,可以根据Cookie是否可用来智能进行URL重写,对GET请求的参数进行编码
		可以把产生的URL存储在域对象的属性中
		还可以使用c:param为URL添加参数。c:url会对参数进行自动的转码
		value中的"/"代表当前Web应用的根目录
	</h4>
	<c:url value="/el.jsp" var="elurl" scope="page">
		<c:param name="name" value="STONE"></c:param>
	</c:url>
	url: ${elurl }

</body>
</html>

使用JSTL改写MVC案例:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="scripts/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
	$(function() {
		$(".delete").click(function() {
			var content = $(this).parent().parent().find("td:eq(1)").text();
			var flag = confirm("确定要删除" + content + "的信息吗");
			return flag;
		})
	})
</script>
</head>
<body>
	<form action="select.do" method="post">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone"></td>
			</tr>
			<tr>
				<td><input type="submit" value="Select"></td>
				<td><a href="newcustomer.jsp">Create New Customer</a></td>
			</tr>
		</table>
	</form>
	
	<br><br>
	
	<c:if test="${!empty requestScope.customers }">
		<hr>
		<br><br>
		<table border="1" cellpadding="10" cellspacing="0">
			<tr>
				<th>ID</th>
				<th>CustomerName</th>
				<th>Address</th>
				<th>Phone</th>
				<th>Update/Delete</th>
			</tr>
			
			<c:forEach items="${requestScope.customers }" var="cust">
					<tr>
						<td>${cust.id }</td>
						<td>${cust.name }</td>
						<td>${cust.address }</td>
						<td>${cust.phone }</td>
						<td>
							<c:url value="edit.do" var="editurl">
								<c:param name="id" value="${cust.id }"></c:param>
							</c:url>
							<a class="edit" href="${editurl }">Edit</a>
							<c:url value="delete.do" var="deleteUrl">
								<c:param name="id" value="${cust.id }"></c:param>
							</c:url>
							<a class="delete" href="${deleteUrl }">Delete</a>
						</td>
					<tr>
			</c:forEach>
		</table>
	</c:if>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<c:if test="${requestScope.message != null }">
		<font color="red">${requestScope.message }</font>
	</c:if>
	<br>
	<form action="insert.do" method="post">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name" value="${param.name }"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address" value="${param.address }"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone" value="${param.phone }"></td>
			</tr>
			<tr>
				<td colspan="2"><input type="submit" value="Insert"></td>
			</tr>
		</table>
	</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<c:if test="${requestScope.message != null }">
		<font color="red">${requestScope.message }</font>
	</c:if>
	<br>
	
	<c:set var="id" value="${customer != null ? customer.id : param.id }"></c:set>
	<c:set var="oldName" value="${customer != null ? customer.name : param.oldName }"></c:set>
	<c:set var="name" value="${customer != null ? customer.name : param.oldName }"></c:set>
	<c:set var="address" value="${customer != null ? customer.address : param.address }"></c:set>
	<c:set var="phone" value="${customer != null ? customer.phone : param.phone }"></c:set>
	
	<form action="update.do" method="post">
	
		<input type="hidden" name="id" value="${id }">
		<input type="hidden" name="oldName" value="${oldName }">
		<table>
			<tr>
				<td>CustomerName:</td>
				<td><input type="text" name="name" value="${name }"></td>
			</tr>
			<tr>
				<td>Address:</td>
				<td><input type="text" name="address" value="${address }"></td>
			</tr>
			<tr>
				<td>Phone:</td>
				<td><input type="text" name="phone" value="${phone }"></td>
			</tr>
			<tr>
				<td colspan="2"><input type="submit" value="Update"></td>
			</tr>
		</table>
	</form>
</body>
</html>

Filter

Overview

Filter 是 JavaWEB 的一个重要组件, 可以对发送到 Servlet 的请求进行拦截,并对响应也进行拦截。

例子:login.jsp 请求提交到 hello.jsp。该页面中有两个 text,分别为 username 和 password

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login.jsp</title>
</head>
<body>

	<font color="red">${message }</font>
	<br><br>
	
	<form action="hello.jsp" method="post">
	
		username: <input type="text" name="username" value="${param.username }"/>
		password: <input type="password" name="password"/>
	
		<input type="submit" value="Submit"/>
		
	</form>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>hello.jsp</title>
</head>
<body>
	
	Hello: ${param.username }
	
</body>
</html>

UserNameFilter:若 username 不等于 Tom,则将请求转发到 login.jsp,并提示用户:“用户名错误”,若等于 Tom,则把请求转给下一个 Filter

package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class UserNameFilter implements Filter {

    public UserNameFilter() {
        // TODO Auto-generated constructor stub
    }

	public void destroy() {
		// TODO Auto-generated method stub
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		String initUser = filterConfig.getInitParameter("username");
		String username = request.getParameter("username");
		
		if(!initUser.equals(username)){
			request.setAttribute("message", "用户名不正确");
			request.getRequestDispatcher("/login.jsp").forward(request, response);
			return;
		}
		
		chain.doFilter(request, response);
	}

	private FilterConfig filterConfig;
	
	public void init(FilterConfig fConfig) throws ServletException {
		this.filterConfig = fConfig;
	}
}

PasswordFilter:若 passord 不等于 1234,则将请求转发到 login.jsp,并提示用户:“密码错误”,若等于 1234,则把请求转给目标页面

package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class PasswordFilter implements Filter {

    public PasswordFilter() {
        // TODO Auto-generated constructor stub
    }

	public void destroy() {
		// TODO Auto-generated method stub
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		String initPassword = filterConfig.getServletContext().getInitParameter("password");
		String password = request.getParameter("password");
		
		if(!initPassword.equals(password)){
			request.setAttribute("message", "密码不正确");
			request.getRequestDispatcher("/login.jsp").forward(request, response);
			return;
		}
		
		chain.doFilter(request, response);
	}

	private FilterConfig filterConfig;
	
	public void init(FilterConfig fConfig) throws ServletException {
		this.filterConfig = fConfig;
	}
}

在 web.xml 文件中配置并映射 Filter。其中 url-pattern 指定该 Filter 可以拦截哪些资源,即可以通过哪些 url 访问到该 Filter。UserNameFilter、PasswordFilter 拦截 login.jsp 的请求页面,即 hello.jsp。Username(Tom)需配置为 UserNameFilter 的初始化参数,password(1234)需要配置为当前 WEB 应用的初始化参数。

  <context-param>
    <param-name>password</param-name>
    <param-value>1234</param-value>
  </context-param>
  <filter>
    <display-name>UserNameFilter</display-name>
    <filter-name>UserNameFilter</filter-name>
    <filter-class>com.stone.javaweb.UserNameFilter</filter-class>
    <init-param>
      <param-name>username</param-name>
      <param-value>Tom</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>UserNameFilter</filter-name>
    <url-pattern>/hello.jsp</url-pattern>
  </filter-mapping>
  <filter>
    <display-name>PasswordFilter</display-name>
    <filter-name>PasswordFilter</filter-name>
    <filter-class>com.stone.javaweb.PasswordFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>PasswordFilter</filter-name>
    <url-pattern>/hello.jsp</url-pattern>
  </filter-mapping>

HttpFilter

package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义的 HttpFilter, 实现自 Filter 接口
 *
 */
public abstract class HttpFilter implements Filter {

	/**
	 * 用于保存 FilterConfig 对象. 
	 */
	private FilterConfig filterConfig;
	
	/**
	 * 不建议子类直接覆盖. 若直接覆盖, 将可能会导致 filterConfig 成员变量初始化失败
	 */
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.filterConfig = filterConfig;
		init();
	}

	/**
	 * 供子类继承的初始化方法. 可以通过 getFilterConfig() 获取 FilterConfig 对象. 
	 */
	protected void init() {}

	/**
	 * 直接返回 init(ServletConfig) 的 FilterConfig 对象
	 */
	public FilterConfig getFilterConfig() {
		return filterConfig;
	}
	
	/**
	 * 原生的 doFilter 方法, 在方法内部把 ServletRequest 和 ServletResponse 
	 * 转为了 HttpServletRequest 和 HttpServletResponse, 并调用了 
	 * doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
	 * 
	 * 若编写 Filter 的过滤方法不建议直接继承该方法. 而建议继承
	 * doFilter(HttpServletRequest request, HttpServletResponse response, 
	 *		FilterChain filterChain) 方法
	 */
	@Override
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		
		doFilter(request, response, chain);
	}
	
	/**
	 * 抽象方法, 为 Http 请求定制. 必须实现的方法. 
	 * @param request
	 * @param response
	 * @param filterChain
	 * @throws IOException
	 * @throws ServletException
	 */
	public abstract void doFilter(HttpServletRequest request, HttpServletResponse response, 
			FilterChain filterChain) throws IOException, ServletException;

	/**
	 * 空的 destroy 方法。 
	 */
	@Override
	public void destroy() {}

}

多个 Filter 的执行顺序

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<a href="test.jsp">To Test Page</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>test.jsp</title>
</head>
<body>
	
	<h4>Test Page</h4>
	
	<% 
		System.out.println("5. Test JSP"); //5
	%>
	
</body>
</html>
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("init..");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		System.out.println("1. Before HelloFilter's chain.doFilter ..."); //1
		
		//放行
		chain.doFilter(request, response);
		
		System.out.println("2. After HelloFilter's chain.doFilter ..."); //2
	}

	@Override
	public void destroy() {
		System.out.println("destroy...");
	}
}
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class SecondFilter implements Filter{

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("second init...");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		System.out.println("3. Before SecondFilter's chain.doFilter ..."); //3
		
		//放行
		chain.doFilter(request, response);
		
		System.out.println("4. After SecondFilter's chain.doFilter ..."); //4
	}
	
	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		System.out.println("second destroy");
	}
}
	<filter>
		<filter-name>helloFilter</filter-name>
		<filter-class>com.stone.javaweb.HelloFilter</filter-class>
		<init-param>
			<param-name>name</param-name>
			<param-value>root</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>helloFilter</filter-name>
		<url-pattern>/test.jsp</url-pattern>
	</filter-mapping>
	<filter>
		<filter-name>secondFilter</filter-name>
		<filter-class>com.stone.javaweb.SecondFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>secondFilter</filter-name>
		<url-pattern>/test.jsp</url-pattern>
	</filter-mapping>
输出:
1. Before HelloFilter's chain.doFilter ...
3. Before SecondFilter's chain.doFilter ...
5. Test JSP
4. After SecondFilter's chain.doFilter ...
2. After HelloFilter's chain.doFilter ...

禁用浏览器缓存的过滤器

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>a.html</title>
</head>
<body>	

	<a href="b.html">TO BBB PAGE</a>

	<br><br>
	
	<img alt="" src="Hydrangeas.jpg">

</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>b.html</title>
</head>
<body>	

	<a href="a.html">TO AAA PAGE</a>

	<br><br>

</body>
</html>
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter("/cache/*")
public class NoCacheFilter extends HttpFilter {

	@Override
	public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		System.out.println("NoCacheFilter's doFilter...");
		
		response.setDateHeader("Expires",-1);
		response.setHeader("Cache-Control","no-cache");
		response.setHeader("Pragma","no-cache");

		filterChain.doFilter(request, response);
	}
}

字符编码过滤器

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<form action="b.jsp" method="post">
		name: <input type="text" name="name"/>
		<input type="submit" value="Submit"/>
	</form>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>b.jsp</title>
</head>
<body>

	<%--
		//编写一个 EncodingFilter
		//1. 读取 web.xml 文件中配置的当前 WEB 应用的初始化参数 encoding
		//2. 指定请求的字符编码为 1 读取到的编码
		//3. 调用 chain.doFilter() 方法 "放行" 请求
		request.setCharacterEncoding("UTF-8");
	--%>
	
	Hello: ${param.name }
	
</body>
</html>
package com.stone.javaweb;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter(urlPatterns="/*")
public class EncodingFilter extends HttpFilter{

	private String encoding;
	
	@Override
	protected void init() {
		encoding = getFilterConfig().getServletContext().getInitParameter("encoding");
	}
	
	@Override
	public void doFilter(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		System.out.println(encoding); 
		request.setCharacterEncoding(encoding);
		filterChain.doFilter(request, response);
	}
}
  <context-param>
  	<param-name>encoding</param-name>
  	<param-value>UTF-8</param-value>
  </context-param>

检查用户是否登录的过滤器

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>a.jsp</title>
</head>
<body>
	
	<h4>AAA PAGE</h4>
	
	<a href="list.jsp">Return...</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>b.jsp</title>
</head>
<body>

	<%-- 
		//检验用户是否登录. 若没有登录, 则直接重定向到 login.jsp
		Object user = session.getAttribute("user");
	
		if(user == null){
			response.sendRedirect("login.jsp");
		}
	--%>
	
	<h4>BBB PAGE</h4>
	
	<a href="list.jsp">Return...</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>c.jsp</title>
</head>
<body>
	
	<%-- 
		//检验用户是否登录. 若没有登录, 则直接重定向到 login.jsp
		Object user = session.getAttribute("user");
	
		if(user == null){
			response.sendRedirect("login.jsp");
		}
	--%>
	
	<h4>CCC PAGE</h4>
	
	<a href="list.jsp">Return...</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>d.jsp</title>
</head>
<body>

	<%-- 
		//检验用户是否登录. 若没有登录, 则直接重定向到 login.jsp
		Object user = session.getAttribute("user");
	
		if(user == null){
			response.sendRedirect("login.jsp");
		}
	--%>
	
	<h4>DDD PAGE</h4>
	<a href="list.jsp">Return...</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>e.jsp</title>
</head>
<body>

	<%-- 
		//检验用户是否登录. 若没有登录, 则直接重定向到 login.jsp
		Object user = session.getAttribute("user");
	
		if(user == null){
			response.sendRedirect("login.jsp");
		}
	--%>
	
	<h4>EEE PAGE</h4>
	<a href="list.jsp">Return...</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>login.jsp</title>
</head>
<body>
	
	<form action="doLogin.jsp" method="post">
		username: <input type="text" name="username"/>
		<input type="submit" value="Submit"/>
	</form>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>list.jsp</title>
</head>
<body>
	
	<a href="a.jsp">AAA</a>
	<br><br>
	
	<a href="b.jsp">BBB</a>
	<br><br>
	
	<a href="c.jsp">CCC</a>
	<br><br>
	
	<a href="d.jsp">DDD</a>
	<br><br>
	
	<a href="e.jsp">EEE</a>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>doLogin.jsp</title>
</head>
<body>
	
	<% 
		//1. 获取用户的登录信息
		String username = request.getParameter("username");
		
		//2. 若登录信息完整, 则把登录信息放到 HttpSession
		if(username != null && !username.trim().equals("")){
			System.out.println(application.getInitParameter("userSessionKey"));
			session.setAttribute(application.getInitParameter("userSessionKey"), username);
			//3. 重定向到 list.jsp
			response.sendRedirect("list.jsp");
		}else{
			response.sendRedirect("login.jsp");
		}
		
	%>
	
</body>
</html>
package com.stone.javaweb;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginFilter extends HttpFilter{

	//1. 从 web.xml 文件中获取 sessionKey, redirectUrl, uncheckedUrls
	private String sessionKey;
	private String redirectUrl;
	private String unchekcedUrls;
	
	@Override
	protected void init() {
		ServletContext servletContext = getFilterConfig().getServletContext();
		
		sessionKey = servletContext.getInitParameter("userSessionKey");
		redirectUrl = servletContext.getInitParameter("rediretPage");
		///login/a.jsp,/login/list.jsp,/login/login.jsp,/login/doLogin.jsp
		unchekcedUrls = servletContext.getInitParameter("uncheckedUrls");
	}
	
	@Override
	public void doFilter(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		
		//1. 获取请求的 servletPath
		// http://localhost:8080/javaweb06/login/list.jsp
		String requestURL = request.getRequestURL().toString();
		// /javaweb06/login/list.jsp
		String requestURI = request.getRequestURI();
		// /login/b.jsp
		String servletPath = request.getServletPath();
		
		System.out.println(requestURL);
		System.out.println(requestURI);
		System.out.println(servletPath);
		
		//2. 检查 1 获取的 servletPath 是否为不需要检查的 URL 中的一个, 若是, 则直接放行. 方法结束
		List<String> urls = Arrays.asList(unchekcedUrls.split(","));
		if(urls.contains(servletPath)){
			filterChain.doFilter(request, response);
			return;
		}
		
		//3. 从 session 中获取 sessionKey 对应的值, 若值不存在, 则重定向到 redirectUrl
		Object user = request.getSession().getAttribute(sessionKey);
		if(user == null){
			response.sendRedirect(request.getContextPath() + redirectUrl);
			return;
		}
		
		//4. 若存在, 则放行, 允许访问. 
		filterChain.doFilter(request, response);
	}
}
	<!-- 用户信息放入到 session 中的键的名字 -->
	<context-param>
		<param-name>userSessionKey</param-name>
		<param-value>USERSESSIONKEY</param-value>
	</context-param>

	<!-- 若未登录, 需重定向的页面 -->
	<context-param>
		<param-name>rediretPage</param-name>
		<param-value>/login/login.jsp</param-value>
	</context-param>

	<!-- 不需要拦截(或检查)的 URL 列表 -->
	<context-param>
		<param-name>uncheckedUrls</param-name>
		<param-value>/login/a.jsp,/login/list.jsp,/login/login.jsp,/login/doLogin.jsp,/login/b.jsp</param-value>
	</context-param>

	<filter>
		<filter-name>loginFilter</filter-name>
		<filter-class>com.stone.javaweb.LoginFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>loginFilter</filter-name>
		<url-pattern>/login/*</url-pattern>
	</filter-mapping>

权限管理

使用 Filter 完成一个简单的权限模型。

需求:

  • 管理权限
    • 查看某人的权限
    • 修改某人的权限
  • 对访问进行权限控制:有权限则可以访问,否则提示”没有对应的权限,请返回“

基础类

package com.stone.privilege;

import java.util.List;

public class User {
	private String username;
	private List<Authority> authorities;
	public User() {
		super();
	}
	public User(String username, List<Authority> authorities) {
		super();
		this.username = username;
		this.authorities = authorities;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public List<Authority> getAuthorities() {
		return authorities;
	}
	public void setAuthorities(List<Authority> authorities) {
		this.authorities = authorities;
	}
}
package com.stone.privilege;

public class Authority {
	
	private String displayName;
	private String url;
	public Authority() {
	}
	public Authority(String displayName, String url) {
		super();
		this.displayName = displayName;
		this.url = url;
	}
	public String getDisplayName() {
		return displayName;
	}
	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((url == null) ? 0 : url.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Authority other = (Authority) obj;
		if (url == null) {
			if (other.url != null)
				return false;
		} else if (!url.equals(other.url))
			return false;
		return true;
	}
}
package com.stone.privilege;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UserDao {
	
	private static Map<String, User> users;
	private static List<Authority> authorities = null;
	static {
		authorities = new ArrayList<>();
		authorities.add(new Authority("Article-1", "/app/article-1.jsp"));
		authorities.add(new Authority("Article-2", "/app/article-2.jsp"));
		authorities.add(new Authority("Article-3", "/app/article-3.jsp"));
		authorities.add(new Authority("Article-4", "/app/article-4.jsp"));
		
		users = new HashMap<String, User>();
		User user1 = new User("AAA", authorities.subList(0, 2));
		users.put("AAA", user1);
		
		User user2 = new User("BBB", authorities.subList(2, 4));
		users.put("BBB", user2);
	}
	
	public User get(String username) {
		
		return users.get(username);
	}
	
	public void update(String username, List<Authority> authorities) {
		users.get(username).setAuthorities(authorities);
	}
	
	public static List<Authority> getAuthorities() {
		return authorities;
	}

	public List<Authority> getAuthorities(String[] urls) {
		List<Authority> authorities2 = new ArrayList<>();
		
		for (Authority authority : authorities) {
			if (urls != null) {
				for (String url : urls) {
					if (url.equals(authority.getUrl())) {
						authorities2.add(authority);
					}
				}
			}
		}
		
		return authorities2;
	}
}

权限管理页面及对应的Servlet

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>authority-manager.jsp</title>
</head>
<body>
	
	<center>
		<br><br>
		<form action="${pageContext.request.contextPath }/AuthorityServlet?method=getAuthorities" method="post">
			name: <input type="text" name="username">
			<input type="submit" value="Submit">
		</form>
		<br><br>
	
		<c:if test="${requestScope.user != null }">
			${requestScope.user.username }的权限是:
			<br><br>
			
			<form action="${pageContext.request.contextPath }/AuthorityServlet?method=updateAuthorities" method="post">
				<input type="hidden" name="username" value="${requestScope.user.username }">
				<c:forEach items="${authorities }" var="auth">
					<c:set var="flag" value="false"></c:set>
					<c:forEach items="${user.authorities }" var="ua">
						<c:if test="${ua.url == auth.url }">
							<c:set var="flag" value="true"></c:set>
						</c:if>
					</c:forEach>
					<c:if test="${flag == true }">
						<input type="checkbox" name="authority" value="${auth.url }" checked="checked">${auth.displayName }<br><br>
					</c:if>
					<c:if test="${flag == false }">
						<input type="checkbox" name="authority" value="${auth.url }">${auth.displayName }<br><br>
					</c:if>
				</c:forEach>
				<input type="submit" value="update">
			</form>
		</c:if>
		
	</center>

</body>
</html>
package com.stone.privilege;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/AuthorityServlet")
public class AuthorityServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String methodName = request.getParameter("method");
		try {
			Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
			method.invoke(this, request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private UserDao userDao = new UserDao();

	public void getAuthorities(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String username = request.getParameter("username");
		User user = userDao.get(username);
		request.setAttribute("user", user);
		request.setAttribute("authorities", UserDao.getAuthorities());
		request.getRequestDispatcher("/app/authority-manager.jsp").forward(request, response);
	}
	
	public void updateAuthorities(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String username = request.getParameter("username");
		String[] urls = request.getParameterValues("authority");
		List<Authority> authorityList = userDao.getAuthorities(urls);
		userDao.update(username, authorityList);
		response.sendRedirect(request.getContextPath() + "/app/authority-manager.jsp");
	}
}

登录退出页面及对应Servlet

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>login.jsp</title>
</head>
<body>
	
	<form action="${pageContext.request.contextPath }/LoginServlet?method=login" method="post">
		name: <input type="text" name="name" />
		<input type="submit" value="Submit" />
	</form>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>logout.jsp</title>
</head>
<body>
	
	Bye!
	
	<br><br>
	<a href="${pageContext.request.contextPath }/login.jsp">Login</a>
	
	<% 
		session.invalidate();
	%>
	
</body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>articles.jsp</title>
</head>
<body>
	
	<a href="${pageContext.request.contextPath }/app/article-1.jsp">Article111 Page</a>
	<br /><br />
	
	<a href="${pageContext.request.contextPath }/app/article-2.jsp">Article222 Page</a>
	<br /><br />
	
	<a href="${pageContext.request.contextPath }/app/article-3.jsp">Article333 Page</a>
	<br /><br />
	
	<a href="${pageContext.request.contextPath }/app/article-4.jsp">Article444 Page</a>
	<br /><br />
	
	<a href="${pageContext.request.contextPath }/LoginServlet?method=logout">Logout...</a>
	
</body>
</html>
package com.stone.privilege;

import java.io.IOException;
import java.lang.reflect.Method;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String methodName = request.getParameter("method");
		try {
			Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
			method.invoke(this, request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private UserDao userDao = new UserDao();

	public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取name
		String name = request.getParameter("name");
		
		//2.调用UserDao获取用户信息,把用户信息放入到HttpSession中
		User user = userDao.get(name);
		request.getSession().setAttribute("user", user);
		
		//3.重定向到articles.jsp
		response.sendRedirect(request.getContextPath() + "/app/articles.jsp");
	}

	public void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取HttpSession
		//2.使HttpSession失效
		request.getSession().invalidate();
		
		//3.重定向到/login.jsp
		response.sendRedirect(request.getContextPath() + "/app/login.jsp");
	}
}

被拦截页面及对应的Filter

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>article-1.jsp</title>
</head>
<body>
	
	Article 111

</body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>article-2.jsp</title>
</head>
<body>
	
	<%-- 
	
		//检查用户是否登录: session 中是否有 LoginSuccessSessionKey(SESSIONKEY 所对应的参数值) 的属性
		
		String sessionKey = application.getInitParameter("SESSIONKEY");
		Object obj = session.getAttribute(sessionKey);
		
		//1. 若存在, 表示已经登录, 继续浏览
		//2. 若不存在, 则表示用于未登录, 则重定向到 login.jsp 页面, 使其登录。 
		if(obj == null){
			response.sendRedirect(request.getContextPath() + "/app/login.jsp");
		}		
	
	--%>
	
	Article 222

</body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>article-3.jsp</title>
</head>
<body>

	<%-- 
	
		//检查用户是否登录: session 中是否有 LoginSuccessSessionKey(SESSIONKEY 所对应的参数值) 的属性
		
		String sessionKey = application.getInitParameter("SESSIONKEY");
		Object obj = session.getAttribute(sessionKey);
		
		//1. 若存在, 表示已经登录, 继续浏览
		//2. 若不存在, 则表示用于未登录, 则重定向到 login.jsp 页面, 使其登录。 
		if(obj == null){
			response.sendRedirect(request.getContextPath() + "/app/login.jsp");
		}		
	
	--%>

	Article 333

</body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>article-4.jsp</title>
</head>
<body>

	<%-- 
	
		//检查用户是否登录: session 中是否有 LoginSuccessSessionKey(SESSIONKEY 所对应的参数值) 的属性
		
		String sessionKey = application.getInitParameter("SESSIONKEY");
		Object obj = session.getAttribute(sessionKey);
		
		//1. 若存在, 表示已经登录, 继续浏览
		//2. 若不存在, 则表示用于未登录, 则重定向到 login.jsp 页面, 使其登录。 
		if(obj == null){
			response.sendRedirect(request.getContextPath() + "/app/login.jsp");
		}		
	
	--%>

	Article 444

</body>
</html>
package com.stone.privilege;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.stone.javaweb.HttpFilter;

@WebFilter("/app/*")
public class AuthorityFilter extends HttpFilter {

	@Override
	public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		//获取 servletPath, 类似于 /app/article1.jsp
		String servletPath = request.getServletPath();
		
		//不需要被拦截的url列表
		List<String> uncheckServletPaths = Arrays.asList("/app/403.jsp","/app/articles.jsp","/app/authority-manager.jsp","/app/login.jsp","/app/logout.jsp");
		if (uncheckServletPaths.contains(servletPath)) {
			filterChain.doFilter(request, response);
			return;
		}
		
		//在用户已经登录(可使用 用户是否登录 的过滤器)的情况下, 获取用户信息. session.getAttribute("user")
		User user = (User) request.getSession().getAttribute("user");
		if (user == null) {
			response.sendRedirect(request.getContextPath() + "/app/login.jsp");
			return;
		}
		
		//再获取用户所具有的权限的信息: List<Authority>
		List<Authority> authorities = user.getAuthorities();
		
		//检验用户是否有请求 servletPath 的权限: 可以思考除了遍历以外, 有没有更好的实现方式
		Authority authority = new Authority(null, servletPath);
		//若有权限则: 响应(此处需要重写Authority的hashCode()和equals()方法,只判断url即可)
		if (authorities.contains(authority)) {
			filterChain.doFilter(request, response);
			return;
		}
		
		//若没有权限: 重定向到 403.jsp 
		response.sendRedirect(request.getContextPath() + "/app/403.jsp");
		return;
	}
}

错误页面

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Insert title here</title>
</head>
<body>
	<h4>
		没有对应的权限, 
		请 <a href="${pageContext.request.contextPath }/app/articles.jsp">返回</a>
	</h4>
</body>
</html>

文字过滤

Servlet API 中提供了一个 HttpServletRequestWrapper 类来包装原始的 request 对象,HttpServletRequestWrapper 类实现了 HttpServletRequest 接口中的所有方法,这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<form action="bbs.jsp" method="post">
		
		content: <textarea rows="5" cols="21" name="content"></textarea>
		<input type="submit" value="Submit"/>
		
	</form>
	
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	content: ${param.content }
	
	<br><br>
	
	method: <%= request.getMethod() %>
	
	
	<br><br>
	<%= request %>
	
	
</body>
</html>
package com.stone.wrapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MyHttpServletRequest extends HttpServletRequestWrapper {

	public MyHttpServletRequest(HttpServletRequest request) {
		super(request);
	}
	
	@Override
	public String getParameter(String name) {
		String val = super.getParameter(name);
		if (val != null && val.contains(" fuck ")) {
			val = val.replace("fuck", "****");
		}
		return val;
	}
}
package com.stone.wrapper;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.stone.javaweb.HttpFilter;

@WebFilter("/bbs.jsp")
public class ContentFilter extends HttpFilter {

	@Override
	public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {
		//1.获取请求参数content的值
		//String val = request.getParameter("content");
		
		//2.把其中fuck,shit等字符串替换为***
			//ServletRequest, HttpServletRequest中并没有提供诸如setParameter(paraName, paraValue)类似于这样的方法
			//目标:改变HttpServletRequest的getParameter(String)方法的行为:若该方法的返回值中包含"fuck",则替换为"****"
			//1.若对于一个类的方法不满意,需要进行重写,最常见的方式是继承父类,重写方法。此处如果要重写setParameter(String)方法,
			//则需要继承org.apache.catalina.connector.RequestFacade@35480daa,而这仅是Tomcat服务器的实现,若更换服务器,则该方案无法使用
			//2.直接写一个HttpServletRequest接口的实现类:无法实现其中方法.
			//3.装饰目前的HttpServletRequest对象:装饰getParameter方法,而其他方法还和其实现相同
			//创建一个类,该类实现HttpServletRequest接口,把当前doFilter中的request传入到该类中,作为其成员变量,使用该成员变量去实现接口的全部方法
		MyHttpServletRequest req = new MyHttpServletRequest(request);
		
		//3.转到目标页面
		filterChain.doFilter(req, response);
	}
}

Listener

ServletContextListener 是最常用的 Listener,可以在当前 WEB 应用被加载时对当前 WEB 应用的相关资源进行初始化操作,例如创建数据库连接池,创建 Spring 的 IOC 容器,读取当前 WEB 应用的初始化参数。

利用 ServletRequestListener、HttpSessionListener 以及 ServletContextListener 可以对 request、session 及 application 的生命周期进一步的了解。

package com.stone.javaweb;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class HelloServletContextListner implements ServletContextListener, HttpSessionListener, ServletRequestListener {

    public HelloServletContextListner() {
        
    }
    
    public void contextInitialized(ServletContextEvent sce)  { 
    	System.out.println("ServletContext对象被创建" + sce.getServletContext());
    }	

    public void contextDestroyed(ServletContextEvent sce)  { 
         System.out.println("ServletContext对象被销毁" + sce.getServletContext());
    }

	@Override
	public void requestDestroyed(ServletRequestEvent sre) {
		System.out.println("ServletRequest被销毁");
	}

	@Override
	public void requestInitialized(ServletRequestEvent sre) {
		System.out.println("ServletRequest被创建");
	}

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		System.out.println("HttpSession被创建");
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		System.out.println("HttpSession被销毁");
	}
}

文件上传下载

Overview

进行文件上传时,表单需要做的准备:

  • 请求方式为 post: <form action="uploadServlet" method="post" ... >
  • 表单域为 file:<input type="file" name="file"/>
  • 请求编码方式为 multipart/form-data:<form action="uploadServlet" method="post" enctype="multipart/form-data">

导入Jar包:

commons-fileupload-1.4.jar
commons-io-2.6.jar
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload.jsp</title>
</head>
<body>
	
	<form action="UploadServlet" method="post" enctype="multipart/form-data">
		File: <input type="file" name="file">
		<br><br>
		Desc: <input type="text" name="desc">
		<br><br>
		<input type="submit" value="Submit">
	</form>
	
</body>
</html>
package com.stone.javaweb;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet("/UploadServlet")
public class UploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 1.得到FileItem的集合
		// Create a factory for disk-based file items
		DiskFileItemFactory factory = new DiskFileItemFactory();

		// 设置内存中最多可以存放的上传文件的大小, 若超出则把文件写到一个临时文件夹中. 以 byte 为单位
		factory.setSizeThreshold(1024 * 500);
		// 设置那个临时文件夹
		File temp = new File("d:\\temp");
		factory.setRepository(temp);

		// Create a new file upload handler
		ServletFileUpload upload = new ServletFileUpload(factory);

		// 设置上传文件的总的大小. 也可以设置单个文件的大小.
		upload.setSizeMax(1024 * 1024 * 5);

		// Parse the request
		try {
			List<FileItem> /* FileItem */ items = upload.parseRequest(request);
			for (FileItem item : items) {
				// 2.遍历items:若是一个一般的表单域,打印信息
				if (item.isFormField()) {
					String name = item.getFieldName();
					String value = item.getString();
					System.out.println(name + ":" + value);
				} else {
					// 若是文件域,则把文件保存到d:\\code目录下
					String fieldName = item.getFieldName();
				    String fileName = item.getName();
				    String contentType = item.getContentType();
				    long sizeInBytes = item.getSize();
				    
				    System.out.println(fieldName);
				    System.out.println(fileName);
				    System.out.println(contentType);
				    System.out.println(sizeInBytes);
				    
				    InputStream in = item.getInputStream();
				    byte[] buffer = new byte[1024];
				    int len = 0;
				    
				    System.out.println(fileName);
				    int ind = fileName.lastIndexOf("\\");
	                if (ind != -1) {
	                    fileName = fileName.substring(ind + 1);
	                }
	                fileName = "d:\\code\\" + fileName;
				    System.out.println(fileName);
				    
				    FileOutputStream out = new FileOutputStream(fileName);
				    while ((len = in.read(buffer)) != -1) {
						out.write(buffer, 0, len);
					}
				    
				    out.close();
				    in.close();
				}
			}
		} catch (FileUploadException e) {
			e.printStackTrace();
		}
	}
}
输出:
file
D:\software\commons-logging-1.2-bin.zip
application/x-zip-compressed
370991
D:\software\commons-logging-1.2-bin.zip
d:\code\commons-logging-1.2-bin.zip
desc:aaa

文件上传

需求分析:

  • 在 upload.jsp 页面上使用 jQuery 实现 "新增一个附件","删除附件"。但至少需要保留一个。
  • 对文件的扩展名和文件的大小进行验证。以下的规则是可配置的,而不是写死在程序中的。
    • 文件的扩展名必须为 pptx,docx,doc
    • 每个文件的大小不能超过 1 M
    • 总的文件大小不能超过 5 M
  • 若验证失败,则在 upload.jsp 页面上显示错误消息:
    • 若某一个文件不符合要求: xxx 文件扩展名不合法 或 xxx 文件大小超过 1 M
    • 总的文件大小不能超过 5 M
  • 若验证通过,则进行文件的上传操作
    • 文件上传,并给一个不能和其他文件重复的名字,但扩展名不变
    • 在对应的数据表中添加一条记录

导入Jar包:

c3p0-0.9.5.2.jar
commons-beanutils-1.9.3.jar
commons-dbutils-1.7.jar
commons-fileupload-1.4.jar
commons-io-2.6.jar
jstl.jar
mchange-commons-java-0.2.11.jar
mysql-connector-java-5.1.23-bin.jar
standard.jar

创建数据库表:

CREATE TABLE `upload_files` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `file_name` varchar(255) DEFAULT NULL,
  `file_path` varchar(255) DEFAULT NULL,
  `file_desc` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8

创建FileUploadBean,对应数据库中的upload_files表,封装上传文件信息:

package com.stone.javaweb.app;

public class FileUploadBean {
	private Integer id;
	private String fileName;
	private String filePath;
	private String fileDesc;
	public FileUploadBean() {
	}
	public FileUploadBean(String fileName, String filePath, String fileDesc) {
		super();
		this.fileName = fileName;
		this.filePath = filePath;
		this.fileDesc = fileDesc;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getFileName() {
		return fileName;
	}
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	public String getFilePath() {
		return filePath;
	}
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}
	public String getFileDesc() {
		return fileDesc;
	}
	public void setFileDesc(String fileDesc) {
		this.fileDesc = fileDesc;
	}
}

创建c3p0-config.xml,用于配置数据库连接信息:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<named-config name="mysql">
		<!-- 指定连接数据源的基本属性 -->
		<property name="user">stone</property>
		<property name="password">stone</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://192.168.8.138:3306/mvctest</property>
		
		<!-- 若数据库中连接数不足时,一次向数据库服务器申请多少个连接 -->
		<property name="acquireIncrement">5</property>
		<!-- 初始化数据库连接池连接的数量 -->
		<property name="initialPoolSize">5</property>
		<!-- 数据库连接池中最小的数据库连接数 -->
		<property name="minPoolSize">5</property>
		<!-- 数据库连接池中最大的数据库连接数 -->
		<property name="maxPoolSize">100</property>
		<!-- C3P0数据库连接池可以维护的Statement的个数 -->
		<property name="maxStatements">20</property>
		<!-- 每个连接可以同时使用的Statement对象的个数 -->
		<property name="maxStatementsPerConnection">5</property>
	</named-config>
	
	<named-config name="oracle">
		<!-- 指定连接数据源的基本属性 -->
		<property name="user">hr</property>
		<property name="password">hr</property>
		<property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
		<property name="jdbcUrl">jdbc:oracle:thin:@192.168.8.138:1521/stone</property>
		
		<!-- 若数据库中连接数不足时,一次向数据库服务器申请多少个连接 -->
		<property name="acquireIncrement">5</property>
		<!-- 初始化数据库连接池连接的数量 -->
		<property name="initialPoolSize">5</property>
		<!-- 数据库连接池中最小的数据库连接数 -->
		<property name="minPoolSize">5</property>
		<!-- 数据库连接池中最大的数据库连接数 -->
		<property name="maxPoolSize">100</property>
		<!-- C3P0数据库连接池可以维护的Statement的个数 -->
		<property name="maxStatements">20</property>
		<!-- 每个连接可以同时使用的Statement对象的个数 -->
		<property name="maxStatementsPerConnection">5</property>
	</named-config>
</c3p0-config>

创建工具类,包括ReflectionUtils,JDBCUtils,FileUtils:

package com.stone.javaweb.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * 反射的 Utils 函数集合
 * 提供访问私有变量, 获取泛型类型 Class, 提取集合中元素属性等 Utils 函数
 * @author Administrator
 *
 */
public class ReflectionUtils {

	
	/**
	 * 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
	 * 如: public EmployeeDao extends BaseDao<Employee, String>
	 * @param clazz
	 * @param index
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Class getSuperClassGenricType(Class clazz, int index){
		Type genType = clazz.getGenericSuperclass();
		
		if(!(genType instanceof ParameterizedType)){
			return Object.class;
		}
		
		Type [] params = ((ParameterizedType)genType).getActualTypeArguments();
		
		if(index >= params.length || index < 0){
			return Object.class;
		}
		
		if(!(params[index] instanceof Class)){
			return Object.class;
		}
		
		return (Class) params[index];
	}
	
	/**
	 * 通过反射, 获得 Class 定义中声明的父类的泛型参数类型
	 * 如: public EmployeeDao extends BaseDao<Employee, String>
	 * @param <T>
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static<T> Class<T> getSuperGenericType(Class clazz){
		return getSuperClassGenricType(clazz, 0);
	}
	
	/**
	 * 循环向上转型, 获取对象的 DeclaredMethod
	 * @param object
	 * @param methodName
	 * @param parameterTypes
	 * @return
	 */
	public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes){
		
		for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
			try {
				//superClass.getMethod(methodName, parameterTypes);
				return superClass.getDeclaredMethod(methodName, parameterTypes);
			} catch (NoSuchMethodException e) {
				//Method 不在当前类定义, 继续向上转型
			}
			//..
		}
		
		return null;
	}
	
	/**
	 * 使 filed 变为可访问
	 * @param field
	 */
	public static void makeAccessible(Field field){
		if(!Modifier.isPublic(field.getModifiers())){
			field.setAccessible(true);
		}
	}
	
	/**
	 * 循环向上转型, 获取对象的 DeclaredField
	 * @param object
	 * @param filedName
	 * @return
	 */
	public static Field getDeclaredField(Object object, String filedName){
		
		for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
			try {
				return superClass.getDeclaredField(filedName);
			} catch (NoSuchFieldException e) {
				//Field 不在当前类定义, 继续向上转型
			}
		}
		return null;
	}
	
	/**
	 * 直接调用对象方法, 而忽略修饰符(private, protected)
	 * @param object
	 * @param methodName
	 * @param parameterTypes
	 * @param parameters
	 * @return
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 */
	public static Object invokeMethod(Object object, String methodName, Class<?> [] parameterTypes,
			Object [] parameters) throws InvocationTargetException{
		
		Method method = getDeclaredMethod(object, methodName, parameterTypes);
		
		if(method == null){
			throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]");
		}
		
		method.setAccessible(true);
		
		try {
			return method.invoke(object, parameters);
		} catch(IllegalAccessException e) {
			System.out.println("不可能抛出的异常");
		} 
		
		return null;
	}
	
	/**
	 * 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
	 * @param object
	 * @param fieldName
	 * @param value
	 */
	public static void setFieldValue(Object object, String fieldName, Object value){
		Field field = getDeclaredField(object, fieldName);
		
		if (field == null)
			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
		
		makeAccessible(field);
		
		try {
			field.set(object, value);
		} catch (IllegalAccessException e) {
			System.out.println("不可能抛出的异常");
		}
	}
	
	/**
	 * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
	 * @param object
	 * @param fieldName
	 * @return
	 */
	public static Object getFieldValue(Object object, String fieldName){
		Field field = getDeclaredField(object, fieldName);
		
		if (field == null)
			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
		
		makeAccessible(field);
		
		Object result = null;
		
		try {
			result = field.get(object);
		} catch (IllegalAccessException e) {
			System.out.println("不可能抛出的异常");
		}
		
		return result;
	}
}
package com.stone.javaweb.utils;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.stone.javaweb.exception.DBException;

public class JDBCUtils {

	private static DataSource dataSource = null;
	
	static{
		dataSource = new ComboPooledDataSource("mysql");
	}
	
	public static Connection getConnection(){  
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DBException("");
		}
	}
 
	public static void release(Connection connection) {
		try {
			if(connection != null){
				connection.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DBException("");
		}
	}	
}
package com.stone.javaweb.utils;

import java.io.File;

public class FileUtils {

	/**
	 * 删除文件夹
	 * @param folderPath:  文件夹完整绝对路径
	 */
	public static void delFolder(String folderPath) {
		try {
			// 删除完里面所有内容
			delAllFile(folderPath);
			String filePath = folderPath;
			filePath = filePath.toString();
			java.io.File myFilePath = new java.io.File(filePath);
			// 删除空文件夹
			myFilePath.delete();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 删除指定文件夹下所有文件
	 * @param path: 文件夹完整绝对路径
	 * @return
	 */
	public static boolean delAllFile(String path) {
		boolean flag = false;
		File file = new File(path);
		if (!file.exists()) {
			return flag;
		}
		if (!file.isDirectory()) {
			return flag;
		}
		String[] tempList = file.list();
		File temp = null;
		for (int i = 0; i < tempList.length; i++) {
			if (path.endsWith(File.separator)) {
				temp = new File(path + tempList[i]);
			} else {
				temp = new File(path + File.separator + tempList[i]);
			}
			if (temp.isFile()) {
				temp.delete();
			}
			if (temp.isDirectory()) {
				// 先删除文件夹里面的文件
				delAllFile(path + "/" + tempList[i]);
				// 再删除空文件夹
				delFolder(path + "/" + tempList[i]);
				flag = true;
			}
		}
		
		return flag;
	}
}

创建DAO和UploadFileDao:

package com.stone.javaweb.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

public class DAO<T>{

	public static QueryRunner runner = new QueryRunner();
	
	private Class<T> clazz;
	
	public DAO() {
		
		Type type = getClass().getGenericSuperclass();
		
		if(type instanceof ParameterizedType){
			ParameterizedType pt = (ParameterizedType) type;
			
			Type [] parameterArgs = pt.getActualTypeArguments();
			
			if(parameterArgs != null && parameterArgs.length > 0){
				if(parameterArgs[0] instanceof Class){
					clazz = (Class<T>) parameterArgs[0]; 
				}
			}
		}
		
	}
	
	protected void update(Connection conn, String sql, Object ... args) throws SQLException{
		runner.update(conn, sql, args);
	}
	
	protected T get(Connection conn, String sql, Object ... args) throws SQLException{
		return runner.query(conn, sql, new BeanHandler<>(clazz), args); 
	}
	
	protected List<T> getForList(Connection conn, String sql, Object ... args) throws SQLException{
		return runner.query(conn, sql, new BeanListHandler<>(clazz), args); 
	}
	
	protected <E> E getValue(Connection conn, String sql, Object ... args) throws SQLException{
		E result = null;
		result = (E) runner.query(conn, sql, new ArrayHandler(), args)[0];
		return result;
	}	
}
package com.stone.javaweb.dao;

import java.sql.Connection;
import java.util.List;

import com.stone.javaweb.app.FileUploadBean;
import com.stone.javaweb.utils.JDBCUtils;

public class UploadFileDao extends DAO<FileUploadBean>{
	
	public List<FileUploadBean> getFiles(){
		
		Connection conn = null;
		
		try {
			conn = JDBCUtils.getConnection();
			String sql = "SELECT id, file_name fileName, file_path filePath, " +
					"file_desc fileDesc FROM upload_files";
			return getForList(conn, sql);			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			JDBCUtils.release(conn);
		}
		
		return null;
	} 
	
	public void save(List<FileUploadBean> uploadFiles){
		
		Connection conn = null;
		
		try {
			conn = JDBCUtils.getConnection();
			String sql = "INSERT INTO upload_files (file_name, file_path, file_desc) VALUES " +
					"(?, ?, ?)";
			for(FileUploadBean file: uploadFiles){
				update(conn, sql, file.getFileName(), file.getFilePath(), file.getFileDesc());
			}			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			JDBCUtils.release(conn);
		}
		
	}	
}

创建属性文件,用于指定允许上传文件的后缀名以及大小:

exts=pptx,docx,doc
file.max.size=1048576
total.file.max.size=5242880

创建FileUploadProperties和FileUploadListener,用于处理属性文件加载:

package com.stone.javaweb.app;

import java.util.HashMap;
import java.util.Map;

public class FileUploadProperties {
	private Map<String, String> properties = new HashMap<>();
	private static FileUploadProperties instance = new FileUploadProperties();
	private FileUploadProperties() {}
	public static FileUploadProperties getInstance() {
		return instance;
	}
	
	public void addProperty(String propertyName, String propertyValue) {
		properties.put(propertyName, propertyValue);
	}
	public String getProperty(String propertyName) {
		return properties.get(propertyName);
	}
}
package com.stone.javaweb.app;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class FileUploadListener implements ServletContextListener {

    public FileUploadListener() {
        // TODO Auto-generated constructor stub
    }

    public void contextDestroyed(ServletContextEvent sce)  { 
         // TODO Auto-generated method stub
    }

    public void contextInitialized(ServletContextEvent sce)  { 
         InputStream in = getClass().getClassLoader().getResourceAsStream("/upload.properties");
         Properties properties = new Properties();
         try {
			properties.load(in);
			
			for (Map.Entry<Object, Object> prop : properties.entrySet()) {
				String propertyName = (String) prop.getKey();
				String propertyValue = (String) prop.getValue();
				FileUploadProperties.getInstance().addProperty(propertyName, propertyValue);
			}			
		} catch (IOException e) {
			e.printStackTrace();
		}
    }	
}

创建upload.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="${pageContext.request.contextPath }/scripts/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
	$(function() {
		var i = 2;
		$("#addFile").click(function() {
			$(this).parent().parent().before("<tr class='file'><td>File"
					+ i + "</td><td><input type='file' name='file"
					+ i + "'></td></tr><tr class='desc'><td>Desc" 
					+ i + "</td><td><input type='text' name='desc" 
					+ i + "'><button type='button' id='delete" 
					+ i + "'>删除</button></td></tr>");
			i++;
			
			//获取新添加的删除按钮
			$("#delete" + (i-1)).click(function() {
				var $tr = $(this).parent().parent();
				$tr.prev("tr").remove();
				$tr.remove();

				//对i重新排序
				$(".file").each(function(index) {
					var n = index + 1;
					$(this).find("td:first").text("File" + n);
					$(this).find("td:last input").attr("name", "file" + n);
				})
				$(".desc").each(function(index) {
					var n = index + 1;
					$(this).find("td:first").text("Desc" + n);
					$(this).find("td:last input").attr("name", "desc" + n);
				})
				i = i -1;
			});
			return false;
		});
	})
</script>
</head>
<body>
	
	<font color="red">${message }</font>
	<br><br>
	
 	<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
		<input type="hidden" id="fileNum" name="fileNum" value="1">
		
		<table>
			<tr class="file">
				<td>File1</td>
				<td><input type="file" name="file1"></td>
			</tr>
			<tr class="desc">
				<td>Desc1</td>
				<td><input type="text" name="desc1"></td>
			</tr>
			<tr>
				<td><input type="submit" id="submit" value="提交"></td>
				<td><button id="addFile" type="button">新增一个附件</button></td>
			</tr>
		</table>
	</form>
	
</body>
</html>

创建FileUploadServlet,是upload.jsp中上传表单对应的FileUploadServlet:

package com.stone.javaweb.app;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.stone.javaweb.dao.UploadFileDao;
import com.stone.javaweb.exception.InvalidExtNameException;
import com.stone.javaweb.utils.FileUtils;

@WebServlet("/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private static final String FILE_PATH = "/files";
	private static final String TEMP_DIR = "d:\\tmp\\upload";
	private UploadFileDao dao = new UploadFileDao();

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		String path = null;
		ServletFileUpload upload = getServletFileUpload();
		
		try {
			//把需要上传的FileItem都放入到该Map中
			//键:文件的待存放的路径;值:对应的FileItem对象
			Map<String, FileItem> uploadFiles = new HashMap<String, FileItem>();
			
			//解析请求,得到FileItem的集合
			List<FileItem> items = upload.parseRequest(request);
			
			//1.构建FileUploadBean的集合,同时填充uploadFiles
			List<FileUploadBean> beans = buildFileUploadBeans(items, uploadFiles);
			
			//2.验证扩展名
			validateExtName(beans);
			
			//3.校验文件的大小:在解析时,已经校验了,只需要通过异常得到结果
			
			//4.进行文件的上传操作
			upload(uploadFiles);
			
			//5.把上传的信息保存到数据库中
			saveBeans(beans);
			
			//6.删除临时文件夹的临时文件
			FileUtils.delAllFile(TEMP_DIR);
			
			path = "/app/success.jsp";
			
		} catch (Exception e) {
			e.printStackTrace();
			path = "/app/upload.jsp";
			request.setAttribute("message", e.getMessage());
		}
		request.getRequestDispatcher(path).forward(request, response);
	}

	private void saveBeans(List<FileUploadBean> beans) {
		dao.save(beans);
	}

	/**
	 * 文件上传前的准备工作,得到filePath和InputStream
	 * @param uploadFiles
	 * @throws IOException
	 */
	private void upload(Map<String, FileItem> uploadFiles) throws IOException {
		for (Map.Entry<String, FileItem> uploadFile : uploadFiles.entrySet()) {
			String filePath = uploadFile.getKey();
			FileItem item = uploadFile.getValue();
			upload(filePath, item.getInputStream());
		}
	}

	/**
	 * 文件上传的IO方法
	 * @param filePath
	 * @param inputStream
	 * @throws IOException
	 */
	private void upload(String filePath, InputStream inputStream) throws IOException {
		OutputStream out = new FileOutputStream(filePath);
		
		byte[] buffer = new byte[1024];
		int len = 0;
		
		while ((len = inputStream.read(buffer)) != -1) {
			out.write(buffer, 0, len);
		}
		
		inputStream.close();
		out.close();
		System.out.println(filePath);
	}

	/**
	 * 校验扩展名是否合法
	 * @param beans
	 */
	private void validateExtName(List<FileUploadBean> beans) {
		String exts = FileUploadProperties.getInstance().getProperty("exts");
		List<String> extList = Arrays.asList(exts.split(","));
		for (FileUploadBean bean : beans) {
			String fileName = bean.getFileName();
			String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
			if (!extList.contains(extName)) {
				throw new InvalidExtNameException(fileName + "文件扩展名不合法");
			}
		}
	}

	/**
	 * 利用传入的FileItem集合构建FileUploadBean集合,同时填充uploadFiles
	 * FileUploadBean对象封装了:id,fileName,filePath,fileDesc
	 * uploadFiles:Map<String, FileItem>类型,存放文件域类型的FileItem,键:待保存的文件的名字;值:FileItem对象
	 * 
	 * 构建过程:
	 * 1.对传入的List<FileItem>进行遍历,得到desc的Map,键:desc的fieldName(desc1, desc2 ... );值:desc输入框输入值
	 * 2.对传入的List<FileItem>进行遍历,到文件域的FileItem对象。构建对应的key(desc1 ... )来获取其desc。构建FileUploadBean对象,并填充beans和uploadFiles
	 * @param items
	 * @param uploadFiles
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	private List<FileUploadBean> buildFileUploadBeans(List<FileItem> items, Map<String, FileItem> uploadFiles) throws UnsupportedEncodingException {
		
		List<FileUploadBean> beans = new ArrayList<>();
		
		//1.遍历FileItem的集合,先得到desc的Map<String, String>,其中
		//键:fieldName(desc1, desc2 ... );值:desc输入框输入值
		Map<String, String> descs = new HashMap<>();
		for (FileItem item : items) {
			if (item.isFormField()) {
				descs.put(item.getFieldName(), item.getString("UTF-8"));
			}
		}
		
		//2.再遍历FileItem的集合,得到文件域的FileItem对象。
		//每得到一个FileItem对象都创建一个FileUploadBean对象
		//得到fileName,构建filePath,从1的Map中得到当前FileItem对应的那个desc,使用fileName后面的数字去匹配
		for (FileItem item : items) {
			if (!item.isFormField()) {
				//获取文件上传框中name="file1"中的“file1”
				String fieldName = item.getFieldName();
				//获取最后的序号“1”
				String index = fieldName.substring(fieldName.length() - 1);
				//得到该文件对应的描述“desc1”
				String fileDesc = descs.get("desc" + index);
				//获取文件名
			    String fileName = item.getName();
			    //获取路径
			    String filePath = getFilePath(fileName);
			    //根据前面得到的字段值,生成FileUploadBean对象
			    FileUploadBean bean = new FileUploadBean(fileName, filePath, fileDesc);
			    beans.add(bean);
			    
			    uploadFiles.put(filePath, item);
			}
		}
		return beans;
	}

	/**
	 * 根据给定的文件名构建一个随机的文件名
	 * 1.文件的扩展名和给定的文件扩展名一致
	 * 2.利用getServletContext().getRealPath()方法获取了绝对路径
	 * 3.利用了Random和当前的系统时间构建了文件名
	 * @param fileName
	 * @return
	 */
	private String getFilePath(String fileName) {
		String extName = fileName.substring(fileName.lastIndexOf("."));
		Random random = new Random();
		int randomNumber = random.nextInt(10000);
		String filePath = getServletContext().getRealPath(FILE_PATH) + "\\" +  System.currentTimeMillis() + randomNumber + extName;
		return filePath;
	}

	/**
	 * 构建ServletFileUpload对象
	 * 从配置文件中读取了部分属性,用于设置约束
	 * 该方法代码来源于文档
	 * @return
	 */
	private ServletFileUpload getServletFileUpload() {
		String exts = FileUploadProperties.getInstance().getProperty("exts");
		String fileMaxSize = FileUploadProperties.getInstance().getProperty("file.max.size");
		String totalFileMaxSize = FileUploadProperties.getInstance().getProperty("total.file.max.size");

		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setSizeThreshold(1024 * 500);
		File temp = new File(TEMP_DIR);
		factory.setRepository(temp);
		ServletFileUpload upload = new ServletFileUpload(factory);
		upload.setSizeMax(Integer.parseInt(totalFileMaxSize));
		upload.setFileSizeMax(Integer.parseInt(fileMaxSize));
		return upload;
	}
}

创建异常类:

package com.stone.javaweb.exception;

public class DBException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public DBException() {
		// TODO Auto-generated constructor stub
	}
	
	public DBException(String msg) {
		super(msg);
	}
	
	public DBException(String msg, Exception ex) {
		super(msg, ex);
	}
}
package com.stone.javaweb.exception;

public class InvalidExtNameException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public InvalidExtNameException(String msg) {
		super(msg);
	}
}

文件下载

步骤:

  • 设置 contentType 响应头,通知浏览器是个下载的文件,response.setContentType("application/x-msdownload");
  • 设置 Content-Disposition 响应头,通知浏览器不再由浏览器来自行处理(或打开)要下载的文件,而由用户手工完成response.setHeader("Content-Disposition", "attachment;filename=abc.txt");
  • 文件可以调用 response.getOutputStream 的方式,以 IO 流的方式发送给客户端
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<!-- 静态下载 -->
	<a href="xyz.txt">download xyz.txt</a>
	
	<br>
	<br>
	
	<a href="test.jsp">download test.jsp</a>
	
	<br>
	<br>
	
	<a href="${pageContext.request.contextPath }/downloadServlet">DownLoad abcd.pptx</a>
	
</body>
</html>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<% 
		//1. 通知客户端浏览器: 这是一个需要下载的文件, 不能再按普通的 html 的方式打开.
		//即设置一个响应的类型: application/x-msdownload
		response.setContentType("application/x-msdownload"); 
	
		//2. 通知客户端浏览器: 不再由浏览器来处理该文件, 而是交由用户自行处理
		//设置用户处理的方式: 响应头: Content-Disposition
		response.setHeader("Content-Disposition", "attachment;filename=abc.txt");
	%>
	
	<h4>Test Page</h4>
	
	Time: <%= new Date() %>
	
</body>
</html>
package com.stone.javaweb.app;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("application/x-msdownload"); 
		String fileName = "文件下载.pptx";
		response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"UTF-8"));
		
		ServletOutputStream out = response.getOutputStream();
		String pptFileName = "D:\\document\\java\\JavaWeb\\监听器.pptx";
		InputStream in = new FileInputStream(pptFileName);

		byte [] buffer = new byte[1024];
		int len = 0;

		while((len = in.read(buffer)) != -1){
			out.write(buffer, 0, len);
		}

		in.close();
	}
}

文件下载的需求:

  • 在文件上传成功后的 success.jsp 页面上提供一个 "下载资源" 的超链接
  • 点击 "下载资源" 的超链接,会把请求发送到 Servlet,读取数据库,在页面上显示可以下载的资源信息
  • 再点击下载,即可完成对应文件的下载

创建下载成功页面success.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4>上传成功</h4>
	<a href="${pageContext.request.contextPath }/app/upload.jsp">回退</a>
	<a href="${pageContext.request.contextPath }/ListFiles">下载文件</a>
</body>
</html>

创建下载文件链接对应的ListFiles Servlet:

package com.stone.javaweb.app;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.stone.javaweb.dao.UploadFileDao;

@WebServlet("/ListFiles")
public class ListFiles extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private UploadFileDao dao = new UploadFileDao();

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		List<FileUploadBean> beans = dao.getFiles();
		request.setAttribute("files", beans);
		request.getRequestDispatcher("/app/download.jsp").forward(request, response);
	}
}

创建download.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

	<c:if test="${requestScope.files != null }">
		<c:forEach items="${requestScope.files }" var="file">
			FileName: ${file.fileName } <br>
			Desc: ${file.fileDesc } <br>
			<a href="${pageContext.request.contextPath }/downloadServlet?id=${file.id }">下载</a>
			<hr>
		</c:forEach>
	</c:if>

</body>
</html>

创建downloadServlet处理下载请求:

package com.stone.javaweb.app;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.stone.javaweb.dao.UploadFileDao;

@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private UploadFileDao dao = new UploadFileDao();

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("application/x-msdownload");
		String idStr = request.getParameter("id");
		int id = Integer.parseInt(idStr);
		FileUploadBean file = dao.getFile(id);
		System.out.println(file.getFileName());
		System.out.println(file.getFilePath());
		response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getFileName(),"UTF-8"));
		
		ServletOutputStream out = response.getOutputStream();
		InputStream in = new FileInputStream(file.getFilePath());

		byte [] buffer = new byte[1024];
		int len = 0;

		while((len = in.read(buffer)) != -1){
			out.write(buffer, 0, len);
		}

		in.close();
	}
}

在UploadFileDao中增加get()方法:

	public FileUploadBean getFile(int id) {

		Connection conn = null;

		try {
			conn = JDBCUtils.getConnection();
			String sql = "select id, file_name fileName, file_path filePath, file_desc fileDesc FROM upload_files where id=? ";
			FileUploadBean fileUploadBean = get(conn, sql, id);
			return fileUploadBean;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.release(conn);
		}
		return null;
	}

国际化

国际化和本地化:

  • 本地化:一个软件在某个国家或地区使用时,采用该国家或地区的语言,数字,货币,日期等习惯
  • 国际化:软件开发时,让它能支持多个国家和地区的本地化应用。使得应用软件能够适应多个地区的语言和文化风俗习惯
  • 本地敏感数据:随用户区域信息而变化的数据称为本地敏感数据。例如数字,货币, 日期,时间等数据

相关的 API:

  • DateFormat 和 SimpleDateFormat
  • NumberFormat
  • MessageFormat
  • ResourceBundle
  • Locale

WEB 的国际化:

  • 可以使用 request.getLocale() 获取 Locale 对象
  • 可以使用 JSTL 的 fmt 标签完成的国际化
package com.stone.i18n;

import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;

import org.junit.Test;

public class I18nTest {
	
	/*
	@Test
	public void testMessageFormat2(){
		String str = "Date: {0}, Salary: {1}";
		
		Locale locale = Locale.CHINA;
		Date date = new Date();
		double sal = 12345.12;
		
		StringBuffer result = new StringBuffer();
		FieldPosition fieldPosition = new FieldPosition(0);
		
		MessageFormat messageFormat = new MessageFormat(str, locale);
		messageFormat.format(date, result, fieldPosition);
		
		System.out.println(result); 
	}
	*/
	
	/**
	 * ResourceBundle: 资源包类.
	 * 
	 * 1. 在类路径下需要有对应的资源文件: baseName.properties. 其中 baseName 是基名.
	 * 2. 可以使用 基名_语言代码_国家代码.properties 来添加不同国家或地区的资源文件. i18n_zh_CN.properties
	 * 3. 要求所有基名相同的资源文件的 key 必须完全一致. 
	 * 4. 可以使用 native2ascii 命令来得到 汉字 对应的 asc 码. Eclipse 内置了工具
	 * 5. 可以调用 ResourceBundle 的 getBundle(基名, Locale 实例) 获取获取 ResourceBundle 对象
	 * 6. 可以调用 ResourceBundle 的 getString(key) 来获取资源文件的 value 字符串的值. 
	 * 7. 结合 DateFormat, NumberFormat, MessageFormat 即可实现国际化. 
	 * 
	 */
	@Test
	public void testResourceBundle(){
		Locale locale = Locale.CHINA;  
		ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n", locale);
	
		System.out.println(resourceBundle.getString("date"));
		System.out.println(resourceBundle.getString("salary"));
		
		String dateLabel = resourceBundle.getString("date");
		String salLabel = resourceBundle.getString("salary");
		
		String str = "{0}:{1}, {2}:{3}";
		
		Date date = new Date();
		double sal = 12345.12;
		
		DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
		String dateStr = dateFormat.format(date);
		
		NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
		String salStr = numberFormat.format(sal);
		
		String result = MessageFormat.format(str, dateLabel, dateStr, salLabel, salStr);
		System.out.println(result); 
	}
	
	/**
	 * MessageFormat: 可以格式化模式字符串
	 * 模式字符串: 带占位符的字符串: "Date: {0}, Salary: {1}"
	 * 可以通过 format 方法对模式字符串进行格式化
	 */
	@Test
	public void testMessageFormat(){
		String str = "Date: {0}, Salary: {1}";
		
		Locale locale = Locale.CHINA;
		Date date = new Date();
		double sal = 12345.12;
		
		DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
		String dateStr = dateFormat.format(date);
		
		NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
		String salStr = numberFormat.format(sal);
		
		String result = MessageFormat.format(str, dateStr, salStr);
		System.out.println(result); 
	}
	
	/**
	 * NumberFormat: 格式化数字到数字字符串, 或货币字符串的工具类
	 * 1. 通过工厂方法获取 NumberFormat 对象
	 * NumberFormat.getNumberInstance(locale); //仅格式化为数字的字符串
	 * NumberFormat.getCurrencyInstance(locale); //格式为货币的字符串
	 * 
	 * 2. 通过 format 方法来进行格式化
	 * 3. 通过 parse 方法把一个字符串解析为一个 Number 类型. 
	 */
	@Test
	public void testNumberFormat() throws ParseException{
		double d = 123456789.123d;
		Locale locale = Locale.FRANCE;
		
		//
		NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
		
		String str = numberFormat.format(d);
		System.out.println(str); 
		
		NumberFormat numberFormat2 = NumberFormat.getCurrencyInstance(locale);
		str = numberFormat2.format(d);
		System.out.println(str); 
		
		str = "123 456 789,123";
		d = (Double) numberFormat.parse(str);
		System.out.println(d); 
		
		str = "123 456 789,12 €";
		d = (Double) numberFormat2.parse(str);
		System.out.println(d);
		
	}
	
	@Test
	public void testDateFormat2() throws ParseException{
		String str = "1990-12-12 12:12:12";
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		
		Date date = dateFormat.parse(str);
		System.out.println(date); 
	}
	
	/**
	 * DateFormat: 格式化日期的工具类. 
	 * DateFormate 本身是一个抽象类. 
	 * 
	 * 1. 若只希望通过 DateFormat 把一个 Date 对象转为一个字符串, 则可以通过 DateFormat 的工厂方法来获取 DateFormat 对象
	 * 2. 可以获取只格式化 Date 的 DateFormat 对象: getDateInstance(int style, Locale aLocale) 
	 * 3. 可以获取只格式化 Time 的 DateFormat 对象: getTimeInstance(int style, Locale aLocale) 
	 * 4. 可以获取既格式化 Date, 也格式化 Time 的 DateFormat 对象: 
	 * getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale) 
	 * 5. 其中 style 可以取值为: DateFormat 的常量: SHORT, MEDIUM, LONG, FULL. Locale 则为代表国家地区的 Locale 对象
	 * 6. 通过 DateFormat 的 format 方法来格式化个 Date 对象到字符串. 
	 * 
	 * 7. 若有一个字符串, 如何解析为一个 Date 对象呢 ? 
	 * I. 先创建 DateFormat 对象: 创建 DateFormat 的子类 SimpleDateFormat 对象
	 * SimpleDateFormat(String pattern). 
	 * 其中 pattern 为日期, 时间的格式, 例如: yyyy-MM-dd hh:mm:ss
	 * II. 调用 DateFormat 的 parse 方法来解析字符串到 Date 对象.  
	 * 
	 */
	@Test
	public void testDateFormat(){
		Locale locale = Locale.US;
		
		Date date = new Date();
		System.out.println(date); 
		
		//获取 DateFormat 对象
		DateFormat dateFormat = 
				DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, locale);
		String str = dateFormat.format(date);
		System.out.println(str); 
		
	}

	/**
	 * Locale: Java 中表示国家或地区的类. JDK 中提供了很多常量.
	 * 也可以通过 Locale(languageCode, countryCode) 的方式来创建 
	 * 在 WEB 应用中可以通过 request.getLocale() 方法来获取. 
	 */
	@Test
	public void testLocale(){
		Locale locale = Locale.CHINA;
		System.out.println(locale.getDisplayCountry());
		System.out.println(locale.getLanguage()); 
		
		locale = new Locale("en", "US");
		System.out.println(locale.getDisplayCountry());
		System.out.println(locale.getLanguage()); 
	}	
}
<%@page import="java.util.Locale"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	 
	<% 
		Date date = new Date();
		request.setAttribute("date", date);
		
		request.setAttribute("salary", 12345.67);
	%>
	
	<%-- 
	<fmt:bundle basename="i18n">
		<fmt:message key="date"></fmt:message>: 
		<fmt:formatDate value="${date }"/>,
		<fmt:message key="salary"></fmt:message>:
		<fmt:formatNumber value="${salary }"></fmt:formatNumber>
	</fmt:bundle>
	<br><br>
	--%>
	
	<% 
		String code = request.getParameter("code");
	
		if(code != null){
			if("en".equals(code)){
				session.setAttribute("locale", Locale.US);
			}else if("zh".equals(code)){
				session.setAttribute("locale", Locale.CHINA);
			}
			
		}
	%>
	
	<c:if test="${sessionScope.locale != null }">
		<fmt:setLocale value="${sessionScope.locale }"/>
	</c:if>
	
	<fmt:setBundle basename="i18n"/>
	
	<fmt:message key="date"></fmt:message>: 
	<fmt:formatDate value="${date }" dateStyle="FULL"/>,
	<fmt:message key="salary"></fmt:message>:
	<fmt:formatNumber value="${salary }" type="currency"></fmt:formatNumber>
	<br><br>
	
	<a href="index.jsp?code=en">English</a>
	<a href="index.jsp?code=zh">中文</a>
	
</body>
</html>
上次编辑于:
贡献者: stonebox