javaWeb学习
javaWeb
后面学spring-boot时遇到了许多的坑,因此回过头来学习中间的缺漏部分,从servlet,到JSP,来复习
serlet入门
┌───────────┐
│My Servlet │
├───────────┤
│Servlet API│
┌───────┐ HTTP ├───────────┤
│Browser│<──────>│Web Server │
└───────┘ └───────────┘
一个简单的serverlet
需要maven依赖javax.servlet-api 且打包类型需要为war<packaging>war</packaging>
执行servlet需要tomcat或类似容器,这里不做实际操作,仅熟悉理论与Servlet代码(war包模式已被淘汰)
// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
// 继承于HttpServlet
public class HelloServlet extends HttpServlet {
// HttpSerletRequest HttpServletResponse表示请求的发送与接受
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 设置响应类型:
resp.setContentType("text/html");
// 获取输出流:
PrintWriter pw = resp.getWriter();
// 写入响应:
pw.write("<h1>Hello, world!</h1>");
// 最后不要忘记flush强制输出:
pw.flush();
}
}
// 构建个人tomcat
public class Main {
public static void main(String[] args) throws Exception {
// 启动Tomcat:
Tomcat tomcat = new Tomcat();
tomcat.setPort(Integer.getInteger("port", 8080));
tomcat.getConnector();
// 创建webapp:
Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
WebResourceRoot resources = new StandardRoot(ctx);
resources.addPreResources(
new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
ctx.setResources(resources);
tomcat.start();
tomcat.getServer().await();
}
}
Servlet概述
实现Servlet有三种方式:
- 实现
javax.servlet.Servlet接口 - 继承
javax.servlet.GenericServlet类 - 继承
javax.servlet.httpServlet类

ServletConfig 与Servlet结构简析
CSDN servlet博客 CSDN servlet结构简析
Servlet重定向
@WebServlet(urlPatterns = "/hi")
public class RedirectServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 构造重定向的路径: /hi -> /hello
String name = req.getParameter("name");
String redirectToUrl = "/hello" + (name == null ? "" : "?name=" + name);
// 发送重定向响应:
resp.sendRedirect(redirectToUrl);
}
}
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 永久重定向
resp.setHeader("Location", "/hello");
Servlet内部重定向
@WebServlet(urlPatterns = "/morning")
public class ForwardServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 将morning转发至hello
req.getRequestDispatcher("/hello").forward(req, resp);
}
}
Session与Cookie
这里要注意的几点是:
JSESSIONID是由Servlet容器自动创建的,目的是维护一个浏览器会话,它和我们的登录逻辑没有关系;- 登录和登出的业务逻辑是我们自己根据
HttpSession是否存在一个"user"的Key判断的,登出后,Session ID并不会改变; - 即使没有登录功能,仍然可以使用
HttpSession追踪用户,例如,放入一些用户配置信息等。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
String expectedPassword = users.get(name.toLowerCase());
if (expectedPassword != null && expectedPassword.equals(password)) {
// 登录成功: 采用getSession获取Session,然后在Session里存入user
req.getSession().setAttribute("user", name);
resp.sendRedirect("/");
} else {
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
// 获取session里的user,进行条件判断
String user = (String) req.getSession().getAttribute("user");
// logout,删除session
req.getSession().removeAttribute("user");
resp.sendRedirect("/");
设置自定义cookie
@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {
private static final Set<String> LANGUAGES = Set.of("en", "zh");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String lang = req.getParameter("lang");
if (LANGUAGES.contains(lang)) {
// 创建一个新的Cookie:
Cookie cookie = new Cookie("lang", lang);
// 该Cookie生效的路径范围:
cookie.setPath("/");
// 该Cookie有效期:
cookie.setMaxAge(8640000); // 8640000秒=100天
// 将该Cookie添加到响应:
resp.addCookie(cookie);
}
resp.sendRedirect("/");
}
}
private String parseLanguageFromCookie(HttpServletRequest req) {
// 获取请求附带的所有Cookie:
Cookie[] cookies = req.getCookies();
// 如果获取到Cookie:
if (cookies != null) {
// 循环每个Cookie:
for (Cookie cookie : cookies) {
// 如果Cookie名称为lang:
if (cookie.getName().equals("lang")) {
// 返回Cookie的值:
return cookie.getValue();
}
}
}
// 返回默认值:
return "en";
}
JSP概述
JSP已经过时,这里只介绍简单用法,供以后参考
JSP是Java Server Pages的缩写,它的文件必须放到/src/main/webapp下,文件名必须以.jsp结尾
语法规则
基础语法
- 包含在
<%--和--%>之间的是JSP的注释,它们会被完全忽略; - 包含在
<%和%>之间的是Java代码,可以编写任意Java代码; - 如果使用
<%= xxx %>则可以快捷输出一个变量的值。 - out:表示HttpServletResponse的PrintWriter;
- session:表示当前HttpSession对象;
- request:表示HttpServletRequest对象。
- 指令:<%@ 指令名称 属性名=“属性值” 属性名=“属性值” …%>
<%@ page import="java.io.*" %> - 引入外部文件:
<%@ include file="header.jsp"%>
一个简单的示例
这里需要tomcat容器及其配置,详情请参见博客园-IDEA tomcat部署
- 需要展示的
User被放入HttpServletRequest中以便传递给JSP,因为一个请求对应一个HttpServletRequest,我们也无需清理它,处理完该请求后HttpServletRequest实例将被丢弃; - 把
user.jsp放到/WEB-INF/目录下,是因为WEB-INF是一个特殊目录,Web Server会阻止浏览器对WEB-INF目录下任何资源的访问,这样就防止用户通过/user.jsp路径直接访问到JSP页面; - JSP页面首先从
request变量获取User实例,然后在页面中直接输出,此处未考虑HTML的转义问题,有潜在安全风险。
// 注意,这里必须均为public
public class School {
public String name;
public String address;
public School(String name, String address) {
this.name = name;
this.address = address;
}
}
public class User {
public long id;
public String name;
public School school;
public User(long id, String name, School school) {
this.id = id;
this.name = name;
this.school = school;
}
}
// 注意,这里tomcat必须配置structure,这里user路由名称必须与jsp文件一致
@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假装从数据库读取:
School school = new School("No.1 Middle School", "101 South Street");
User user = new User(123, "Bob", school);
// 放入Request中:
req.setAttribute("user", user);
// forward给user.jsp:
req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
}
}
jsp文件
<%@ page import="javaLearn.ServletLearn01.User" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/9/13
Time: 15:43
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
User user = (User) request.getAttribute("user");
%>
<html>
<head>
<title>Hello world tomcat</title>
</head>
<body>
<h1>Hello <%= user.name %>!</h1>
<p>School Name:
<span style="color:red">
<%= user.school.name %>
</span>
</p>
<p>School Address:
<span style="color:red">
<%= user.school.address %>
</span>
</p>
</body>
</html>
MVC架构
我们把UserServlet看作业务逻辑处理,把User看作模型,把user.jsp看作渲染,这种设计模式通常被称为MVC:Model-View-Controller,即UserServlet作为控制器(Controller),User作为模型(Model),user.jsp作为视图(View),整个MVC架构如下:
┌───────────────────────┐
┌────>│Controller: UserServlet│
│ └───────────────────────┘
│ │
┌───────┐ │ ┌─────┴─────┐
│Browser│────┘ │Model: User│
│ │<───┐ └─────┬─────┘
└───────┘ │ │
│ ▼
│ ┌───────────────────────┐
└─────│ View: user.jsp │
└───────────────────────┘
MVC部分请参见后续SpringMVC
Servlet过滤器Filter
一个小例子
将输入和输出的编码设置为utf-8
@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("EncodingFilter:doFilter");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
使用Filter注意点
编写Filter时,必须实现Filter接口,在doFilter()方法内部,要继续处理请求,必须调用chain.doFilter()。最后,用@WebFilter注解标注该Filter需要过滤的URL。这里的/*表示所有路径。
如果Filter要使请求继续被处理,就一定要调用chain.doFilter()!
一个较为复杂的示例
上传文件及其签名验证+处理签名验证后文件输入流为空的问题
@WebServlet(urlPatterns = "/upload/file")
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 读取Request Body:
InputStream input = req.getInputStream();
// 设置缓冲区,将缓冲区写入input
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (;;) {
int len = input.read(buffer);
// 缓冲区为空时停止
if (len == -1) {
break;
}
output.write(buffer, 0, len);
}
// 显示上传结果:
String uploadedText = output.toString(StandardCharsets.UTF_8);
PrintWriter pw = resp.getWriter();
pw.write("<h1>Uploaded:</h1>");
pw.write("<pre><code>");
pw.write(uploadedText);
pw.write("</code></pre>");
pw.flush();
}
}
// 设置签名过滤器
@WebFilter("/upload/*")
public class ValidateUploadFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 获取客户端传入的签名方法和签名:
String digest = req.getHeader("Signature-Method");
String signature = req.getHeader("Signature");
if (digest == null || digest.isEmpty() || signature == null || signature.isEmpty()) {
sendErrorPage(resp, "Missing signature.");
return;
}
// 读取Request的Body并验证签名:
MessageDigest md = getMessageDigest(digest);
InputStream input = new DigestInputStream(request.getInputStream(), md);
byte[] buffer = new byte[1024];
for (;;) {
int len = input.read(buffer);
if (len == -1) {
break;
}
}
String actual = toHexString(md.digest());
if (!signature.equals(actual)) {
sendErrorPage(resp, "Invalid signature.");
return;
}
//使用代理模式后,重新返回文件流 chain.doFilter(new ReReadableHttpServletRequest(req, output.toByteArray()), response);
chain.doFilter(request, response);
}
// 将byte[]转换为hex string:
private String toHexString(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
// 根据名称创建MessageDigest:
private MessageDigest getMessageDigest(String name) throws ServletException {
try {
return MessageDigest.getInstance(name);
} catch (NoSuchAlgorithmException e) {
throw new ServletException(e);
}
}
// 发送一个错误响应:
private void sendErrorPage(HttpServletResponse resp, String errorMessage) throws IOException {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
PrintWriter pw = resp.getWriter();
pw.write("<html><body><h1>");
pw.write(errorMessage);
pw.write("</h1></body></html>");
pw.flush();
}
}
一下是利用代理模式处理filter后文件输入流为空的问题
class ReReadableHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
private boolean open = false;
public ReReadableHttpServletRequest(HttpServletRequest request, byte[] body) {
super(request);
this.body = body;
}
// 返回InputStream:
public ServletInputStream getInputStream() throws IOException {
if (open) {
throw new IllegalStateException("Cannot re-open input stream!");
}
open = true;
return new ServletInputStream() {
private int offset = 0;
public boolean isFinished() {
return offset >= body.length;
}
public boolean isReady() {
return true;
}
public void setReadListener(ReadListener listener) {
}
public int read() throws IOException {
if (offset >= body.length) {
return -1;
}
int n = body[offset] & 0xff;
offset++;
return n;
}
};
}
// 返回Reader:
public BufferedReader getReader() throws IOException {
if (open) {
throw new IllegalStateException("Cannot re-open reader!");
}
open = true;
return new BufferedReader(new InputStreamReader(getInputStream(), "UTF-8"));
}
}

浙公网安备 33010602011771号