源码地址:https://github.com/erlieStar/servlet-learning
以前我们调试web项目的时候,需要在本地下载一个tomcat,为了项目的复用性,方便他人快速调试,这里演示一个用maven插件启动web项目的方法
maven7
在pom文件中加入如下配置
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<port>8080port>
<path>/path>
<uriEncoding>UTF-8uriEncoding>
<server>tomcat7server>
configuration>
plugin>
plugins>
执行如下命令即可启动
mvn tomcat7:run
<pluginRepositories>
<pluginRepository>
<id>alfresco-publicid>
<url>https://artifacts.alfresco.com/nexus/content/groups/publicurl>
pluginRepository>
<pluginRepository>
<id>alfresco-public-snapshotsid>
<url>https://artifacts.alfresco.com/nexus/content/groups/public-snapshotsurl>
<snapshots>
<enabled>trueenabled>
<updatePolicy>dailyupdatePolicy>
snapshots>
pluginRepository>
<pluginRepository>
<id>beardedgeeks-releasesid>
<url>http://beardedgeeks.googlecode.com/svn/repository/releasesurl>
pluginRepository>
pluginRepositories>
<build>
<finalName>servlet-learningfinalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat8-maven-pluginartifactId>
<version>3.0-r1655215version>
<configuration>
<port>8080port>
<path>/path>
<uriEncoding>UTF-8uriEncoding>
<server>tomcat8server>
configuration>
plugin>
plugins>
build>
在命令行中输入如下命令
mvn tomcat8:run
或者在idea中点击如下按钮
使用部署描述文件把URL映射到Servlet
一个Servlet可以有3个名字,(1)用户知道的URL名,(2)部署人员知道的内部名,(3)实际的文件名
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>Internal Name 1servlet-name>
<servlet-class>com.makenv.controller.BeerSelect1servlet-class>
servlet>
<servlet-mapping>
<servlet-name>Internal Name 1servlet-name>
<url-pattern>/SelectBeer1.dourl-pattern>
servlet-mapping>
<servlet>
<servlet-name>Internal Name 2servlet-name>
<servlet-class>com.makenv.controller.BeerSelect2servlet-class>
servlet>
<servlet-mapping>
<servlet-name>Internal Name 2servlet-name>
<url-pattern>/SelectBeer2.dourl-pattern>
servlet-mapping>
web-app>
根据url-pattern->servlet-name->servlet-class的三级映射关系,容器即可根据用户输入的URL找到对应的Servlet
使用servlet的请求过程
使用servlet和jsp的请求过程
form1.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>Beer Selection Pageh1>
<form method="post" action="SelectBeer1.do">
Select beer
<select name="color" size="1">
<option value="light">lightoption>
<option value="amber">amberoption>
<option value="brown">brownoption>
<option value="dark">darkoption>
select>
<input type="submit">
form>
body>
html>
result.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" import="java.util.*" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<%
List styles = (List)request.getAttribute("styles");
Iterator it = styles.iterator();
while (it.hasNext()) {
out.print("<br>try: " + it.next());
}
%>
body>
html>
BeerExpert.java
public class BeerExpert {
//获得品牌
public List getBrands(String color) {
List brands = new ArrayList();
if (color.equals("amber")) {
brands.add("Jack Amber");
brands.add("Red Moose");
} else {
brands.add("Jail Pale Ale");
brands.add("Gout Stout");
}
return brands;
}
}
BeerSelect1.java(只使用servlet)
public class BeerSelect1 extends HttpServlet {
//使用doPost来处理Http请求,因为Html表单指出method = POST
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("Beer Selection Advice
");
String colorValue = req.getParameter("color");
BeerExpert be = new BeerExpert();
List result = be.getBrands(colorValue);
Iterator it = result.iterator();
while (it.hasNext()) {
out.print("
try: " + it.next());
}
}
}
BeerSelect2.java(使用servlet和jsp)
public class BeerSelect2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String c = req.getParameter("color");
BeerExpert be = new BeerExpert();
List result = be.getBrands(c);
//为请求对象增加一个属性,供JSP使用
req.setAttribute("styles", result);
//为JSP实例化一个请求分派器
RequestDispatcher view = req.getRequestDispatcher("result.jsp");
//使用请求分派器要求容器准备好JSP,并向JSP发送请求和响应
view.forward(req, resp);
}
}
ServletConfig对象
ServletContext
//客户的平台和浏览器信息
String client = request.getHeader("User-Agent");
//与请求相关的cookie
Cookie[] cookies = request.getCookies();
//与客户相关的会话
HttpSession session = request.getSession();
//请求的HTTP方法
String theMethod = request.getMethod();
//请求的输入流
InputStream input = request.getInputStream();
//告诉浏览器这是一个html文件
response.setContentType("text/html");
//获取输出对象
PrintWriter out = response.getWriter();
//也可以使用响应设置其他首部,发送错误,以及增加cookie
我想在部署描述文件(web.xml)中放入email地址,这样servlet就能以某种方式从DD“读取”email地址,而不是直接写在servlet中,如下
PrintWriter out = response.getWriter();
out.println("[email protected]");
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>adminEmailparam-name>
<param-value>[email protected]param-value>
context-param>
<servlet>
<servlet-name>BeerParamTestsservlet-name>
<servlet-class>com.makenv.controller.TestInitParamsservlet-class>
<init-param>
<param-name>adminEmailparam-name>
<param-value>[email protected]param-value>
init-param>
<init-param>
<param-name>mainEmailparam-name>
<param-value>[email protected]param-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>BeerParamTestsservlet-name>
<url-pattern>/Tester.dourl-pattern>
servlet-mapping>
web-app>
TestInitParams.java
public class TestInitParams extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("test init parameters
");
Enumeration e = getServletConfig().getInitParameterNames();
while (e.hasMoreElements()) {
out.println("
servlet param name = " + e.nextElement() + "
");
}
//获取servlet初始化参数
out.println("servlet amdin email is " + getServletConfig().getInitParameter("adminEmail"));
out.println("
");
//获取上下文初始化参数
out.println("context admin email is " + getServletContext().getInitParameter("adminEmail"));
out.println("
");
out.println("servlet main email is " + getServletConfig().getInitParameter("mainEmail"));
}
}
新问题来了,这些初始化参数只能是String,如果我想用一个数据库DataSource来出初始化我的应用,以便所有servlet都能使用,该怎么办?谁将String参数装换成由Web各部分共享的一个具体DataSource引用呢?不能把代码放在servlet中,因为你选择谁作为第一个servlet来查找DataSource并把它存储在一个属性中呢?我们可以使用ServletContextListener
上下文初始化时得到通知(应用得到部署)
上下文撤销时得到通知(应用取消部署或结束)
web.xml
<web-app>
<context-param>
<param-name>breedparam-name>
<param-value>Great Daneparam-value>
context-param>
<servlet>
<servlet-name>ListenerTesterservlet-name>
<servlet-class>com.makenv.controller.ListenerTesterservlet-class>
servlet>
<servlet-mapping>
<servlet-name>ListenerTesterservlet-name>
<url-pattern>/ListenTest.dourl-pattern>
servlet-mapping>
<listener>
<listener-class>com.makenv.listener.MyServletContextListenerlistener-class>
listener>
web-app>
MyServletContextListener.java
public class MyServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
//由事件得到ServletContext
ServletContext sc = sce.getServletContext();
String dogBreed = sc.getInitParameter("breed");
Dog d = new Dog(dogBreed);
sc.setAttribute("dog", d);
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
ListenerTester.java
public class ListenerTester extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("test context attributes set by listener
");
Dog dog = (Dog)getServletContext().getAttribute("dog");
out.println("Dog's breed is: " + dog.getBreed());
}
}
HTTP协议使用的无状态连接,为了让容器知道客户是谁,可以使用会话,过程很简单,对客户的第一个请求,容器会生成一个唯一的会话ID,并通过响应把它返回给客户。客户再在以后的每一个请求中发回这个会话ID。容器看到ID后,就会找到匹配的会话,并把这个会话与请求关联。容器必须以某种方式把会话ID作为响应的一部分交给客户,而客户必须把会话ID作为请求的一部分发回。最简单而且最常用的方式是通过cookie交换这个会话ID信息
在响应中发送一个会话cookie
HttpSession session = request.getSession();
这个方法不止是创建一个会话,在请求上第一次调用这个方法时,会导致随响应发送一个cookie。当你请求会话时,容器会帮你做这些事
从请求中得到会话ID
HttpSession session = request.getSession();
if (请求包含一个会话ID cookie) {
找到该ID匹配的会话
} else if (没有会话ID cookie OR 没有与此会话ID匹配的当前会话) {
创建一个新会话
}
HttpSession常用方法
方法名 | 解释 |
---|---|
getCreationTime | 返回第一次创建会话的时间 |
getLastAccessedTime | 返回最后一次得到有此会话ID的请求的时间(毫秒数) |
setMaxInactiveInterval | 对于此会话,指定用户请求的最大间隔时间,如果超过指定时间,客户未对此会话做任何请求,就会导致会话被撤销。可以用这个方法减少服务器中无用会话的个数 |
getMaxInactiveInterval | 对于此会话,返回客户请求的最大间隔时间(秒数) |
invalidate | 结束会话。当前存储在这个会话中的所有会话属性也会解除绑定 |
会话有三种死法
我们在login.html页面输入用户名和密码,然后在另一个页面打印用户名和密码
其中用户名存在cookie中,而password则调用session.setAttribute(“password”, password)存储,可以看到跳转到checkcookie.do页面username和password都取出来了,关闭浏览器,不登录直接访问checkcookie.do页面,可以看到只取出来用户名,而密码没有值,因为session的存活时间是一次会话
login.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>Beer Selection Pageh1>
<form method="post" action="cookietest.do">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password">
<input type="submit">
form>
body>
html>
CookieTest.java
//注意加上/
@WebServlet("/cookietest.do")
public class CookieTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
String username = req.getParameter("username");
String password = req.getParameter("password");
Cookie cookie = new Cookie("username", username);
//在客户端上存活30分钟
cookie.setMaxAge(30 * 60);
resp.addCookie(cookie);
HttpSession session = req.getSession();
//设置会话的有效时间为30 * 60
session.setMaxInactiveInterval(30 * 60);
session.setAttribute("password", password);
RequestDispatcher view = req.getRequestDispatcher("cookieresult.jsp");
view.forward(req, resp);
}
}
cookieresult.jsp
click here
CheckCookie.java
//注意加上/
@WebServlet("/checkcookie.do")
public class CheckCookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
Cookie[] cookies = req.getCookies();
for (int i=0; i<cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("username")) {
String username = cookie.getValue();
out.println("Hello " + username);
break;
}
}
HttpSession session = req.getSession();
out.println("your password is " + session.getAttribute("password"));
}
}
想跟踪访问某些servlet的用户,可以使用过滤器
请求过滤器可以
响应过滤器可以
建立请求跟踪过滤器
web.xml
<web-app>
<filter>
<filter-name>BeerRequestfilter-name>
<filter-class>com.makenv.filter.BeerRequestFilterfilter-class>
<init-param>
<param-name>LogFileNameparam-name>
<param-value>UserLog.txtparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>BeerRequestfilter-name>
<url-pattern>*.dourl-pattern>
filter-mapping>
<filter-mapping>
<filter-name>BeerRequestfilter-name>
<servlet-name>Internal Name 1servlet-name>
filter-mapping>
web-app>
BeerRequestFilter.java
public class BeerRequestFilter implements Filter {
/*
* 记录访问的用户
*/
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String name = httpServletRequest.getRemoteUser();
if (name != null) {
filterConfig.getServletContext().log("User" + name + "is updating");
}
chain.doFilter(request, response);
}
public void destroy() {
//完成清理工作
}
}
如果你想创建定制请求或响应对象,只需要派生某个便利请求或响应“包装器”类就行了,Sun提供了4个遍历类
ServletRequestWrapper
HttpServletRequestWrapper
ServletResponseWrapper
HttpServletResponseWrapper
request:浏览器地址栏中的url会变化,修改提交的request数据无法传到重定向的地址。因为重定向后重新进行request(request无法共享)
forward:浏览器地址栏url不变,request可以共享
如果把会话相关的页面都关了,本次会话结束,只要有一个页面还打开着,本次会话就没有结束,长时间不操作,session也会失效,应该是设置了session的生存期限
创建新的会话并不意味着旧的会话已经结束,除非它已经超时,原有旧会话才会结束
page对象就是指向当前JSP页面本身,有点像类中的this指针,它是java.lang.Object类的实例
<h1>系统登录h1>
<form action="dologin.jsp?name=urlname" method="post">
<table>
<tr>
<td>用户名td>
<td><input type="text" name="username"/>td>
tr>
<tr>
<td>密码td>
<td><input type="text" name="password"/>td>
tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="提交"/>td>
tr>
table>
form>
dologin.jsp
<body>
<%--这里取四次只是为了同时演示4种情况--%>
<jsp:useBean id="myUser1" class="com.makenv.po.User" scope="page"/>
<jsp:useBean id="myUser2" class="com.makenv.po.User" scope="page"/>
<jsp:useBean id="myUser3" class="com.makenv.po.User" scope="page"/>
<jsp:useBean id="myUser4" class="com.makenv.po.User" scope="page"/>
<h4>根据表单自动匹配所有的属性h4>
<jsp:setProperty name="myUser1" property="*"/>
用户名:<%=myUser1.getUsername()%><br>
密码:<%=myUser1.getPassword()%><br><hr>
<h4>根据表单自动匹配部分的属性h4>
<jsp:setProperty name="myUser2" property="username"/>
用户名:<%=myUser2.getUsername()%><br>
密码:<%=myUser2.getPassword()%><br><hr>
<h4>跟表单无关,通过手工赋值给属性h4>
<jsp:setProperty name="myUser3" property="username" value="admin"/>
<jsp:setProperty name="myUser3" property="password" value="456"/>
用户名:<%=myUser3.getUsername()%><br>
密码:<%=myUser3.getPassword()%><br><hr>
<h4>通过URL传参数给属性赋值h4>
<jsp:setProperty name="myUser4" property="username" param="name"/>
用户名:<%=myUser4.getUsername()%><br><hr>
<h4>通过getProperty获取属性h4>
<jsp:getProperty name="myUser1" property="username"/>
body>
由于http协议的无状态性,保存用户的状态有两大机制,session
login.jsp保存用户登录的信息,跳转到dologin.jsp,在users.jsp中读取cookie中的信息并显示出来
login.jsp
<body>
<h1>用户登录h1>
<hr>
<%
// 如果能从cookie中读取到用户信息,则直接显示在登录页面中
request.setCharacterEncoding("utf-8");
String username="";
String password = "";
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0) {
for(Cookie c:cookies) {
if(c.getName().equals("username")) {
username = URLDecoder.decode(c.getValue(),"utf-8");
}
if(c.getName().equals("password")) {
password = URLDecoder.decode(c.getValue(),"utf-8");
}
}
}
%>
<form name="loginForm" action="dologin.jsp" method="post">
<table>
<tr>
<td>用户名:td>
<td><input type="text" name="username" value="<%=username %>"/>td>
tr>
<tr>
<td>密码:td>
<td><input type="password" name="password" value="<%=password %>" />td>
tr>
<tr>
<td colspan="2"><input type="checkbox" name="isUseCookie" checked="checked"/>十天内记住我的登录状态td>
tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="登录"/><input type="reset" value="取消"/>td>
tr>
table>
form>
body>
dologin.jsp
<body>
<h1>登录成功h1>
<hr>
<br>
<%
request.setCharacterEncoding("utf-8");
//首先判断用户是否选择了记住登录状态
String[] isUseCookies = request.getParameterValues("isUseCookie");
if(isUseCookies != null && isUseCookies.length > 0) {
//把用户名和密码保存在Cookie对象里面
String username = URLEncoder.encode(request.getParameter("username"),"utf-8");
//使用URLEncoder解决无法在Cookie当中保存中文字符串问题
String password = URLEncoder.encode(request.getParameter("password"),"utf-8");
Cookie usernameCookie = new Cookie("username",username);
Cookie passwordCookie = new Cookie("password",password);
usernameCookie.setMaxAge(864000);
passwordCookie.setMaxAge(864000);//设置最大生存期限为10天
response.addCookie(usernameCookie);
response.addCookie(passwordCookie);
} else {
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0) {
for(Cookie c:cookies) {
if(c.getName().equals("username")||c.getName().equals("password")) {
c.setMaxAge(0); //设置Cookie失效
response.addCookie(c); //重新保存。
}
}
}
}
%>
<a href="users.jsp" target="_blank">查看用户信息a>
body>
users.jsp
<body>
<h1>用户信息h1>
<hr>
<%
//从cookie中读取用户信息,并且显示出来
request.setCharacterEncoding("utf-8");
String username="";
String password = "";
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0) {
for(Cookie c:cookies) {
if(c.getName().equals("username")) {
username = URLDecoder.decode(c.getValue(),"utf-8");
}
if(c.getName().equals("password")) {
password = URLDecoder.decode(c.getValue(),"utf-8");
}
}
}
%>
<br>
用户名:<%=username %><br>
密码:<%=password %><br>
body>
include指令和include动作使用如下
<body>
<h1>Include指令h1>
<hr>
<%@ include file="date.jsp"%>
<%--flush被包含的页面是否从缓冲区读取--%>
<jsp:include page="date.jsp" flush="false"/>
body>
<%--跳转指令,下面这两种语法等同--%>
<%--里面第一个email是为了增加一个email属性,第二个password是把login.jsp里面的password参数改为888888--%>
<jsp:forward page="users.jsp">
<jsp:param name="email" value="[email protected]"/>
<jsp:param name="password" value="888888"/>
jsp:forward>
<%
request.getRequestDispatcher("/users.jsp").forward(request,response);
%>
maven插件
[1]https://zhuanlan.zhihu.com/p/31739991
[2]https://www.cnblogs.com/JAYIT/p/9366060.html