目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

JavaWeb

javase、javaweb、javaee

  • javase :就是java基础技术栈,做java相关开发的基础,比如javaweb、javaee

  • javaweb:使用java开发网站相关技术,比如Servlet、JDBC、Tomcat、Session/Cookie等技术栈,javaweb里面很多技术,但有部分慢慢被弃用了,比如JSP等技术点企业很少用了

  • javaee:全称Java Platform,Enterprise Edition,可以构建企业级的面向服务体系结构(service-oriented architecture,SOA)微服务、组件等的技术栈,更多的是框架层面开发构建大型应用。

    • 服务架构:MicroService : SpringCloud /Alibaba Cloud
      无服务架构:ServerLess
      服务网格:Service Mesh
    • 主流框架(当然现在还有少数公司用老旧项目):
      2015年:Struts + Hibernate + Spring SSH
      2015~2018之间:SpringMVC + Spring + Mybatis SSM
      2018年到现在:SpringBoot + Spring + Myabtis 新版SSM

静态网页、动态网页

  • 静态网页:

    • 主要指的是网页中没有程序代码,主要是HTML+CSS+JS,一般后缀为.html,htm。
    • 任何人任何时间打开的页面的内容都是不变的。
  • 动态网页:

    • 前端:HTML + CSS + JS + HTTP
    • 后端:Servlet+Request+Response+Cookie+Session
    • 数据库(类似excel):Mysql
    • 浏览器和服务器进行数据交互,服务器端根据客户的不同请求动态的生成网页内容
    • 用户通过浏览器-》后端程序-》数据库的数据

BS、CS架构

  • CS架构 :客户机-服务器,即Client-Server(C/S)结构 但是缺少通用性,系统维护、升级需要重新设计和开发,增加了维护和管理的难度,用户体验更加流畅。
  • BS架构 :B/S架构即浏览器和服务器架构模式,是WEB兴起后的一种网络架构模式 WEB浏览器是客户端最主要的应用软件 统一了客户端,将系统功能实现的核心部分集中到服务器上,简化了系统的开发、维护和使用,但比较依赖网络环境。

URL统⼀资源定位符

  • 标准格式: 协议://服务器IP:端⼝/路径1/路径N ? key1=value1 & key2=value2

    • 协议:不同的协议有不同的解析方式
    • 服务器ip: ⽹络中存在⽆数的主机,要访问的哪⼀台, 通过公⽹ip区分
    • 端⼝: ⼀台主机上运⾏着很多的进程,为了区分不同进程,⼀个端⼝对应⼀个进程,http默认的端口是80
    • 路径: 资源N多种,为了更进⼀步区分资源所在的路径(后端接⼝,⼀般称为 “接⼝路径”,“接口”)

http协议

  • 即超⽂本传送协议(Hypertext Transfer Protocol ),是Web联⽹的基础,也是⼿机PC联⽹常⽤的协议之⼀,HTTP协议是建⽴在TCP协议之上的⼀种应⽤

  • HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,从建⽴连接到关闭连接的过程称为“⼀次连接”

  • Http请求消息结构

    • 请求行:请求方法 + URL地址 + 协议名
    • 请求头:报文头包含若干个属性 格式为“属性名:属性值”,服务端据此获取客户端的基本信息。
    • 请求体:请求的参数,可以是json对象,也可以是前端表单生成的key=value&key=value的字符串Http请求消息结构
  • HTTP响应消息结构

    • 响应行:报文协议+ 状态码
    • 响应头:报文头包含若干个属性 格式为“属性名:属性值”
    • 响应正文:我们需要的内容、多种形式,比如:html、json、图片、视频。HTTP响应消息结构
  • HTTP请求-HTTP响应

    • 响应码:

      • 1xx:信息
      • 2xx:成功 200 OK,请求正常
      • 3xx:重定向
      • 4xx:客户端错误 404 Not Found 服务器⽆法找到被请求的⻚⾯
      • 5xx:服务器错误 503 Service Unavailable,服务器挂了或者不 可⽤
  • HTTPS协议

    • Hyper Text Transfer Protocol over SecureSocket Layer
    • 主要由两部分组成:HTTP + SSL / TLS
    • 比 HTTP 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性,增加破解成本
    • 缺点:相同网络环境下,HTTPS 协议会使页面的加载时间延长近 50%,增加额外的计算资源消耗,增加 10%到 20%的耗电等;不过利大于弊,所以Https是趋势,相关资源损耗也在持续下降
    • 如果做软件压测:直接压测内网ip,通过压测公网域名,不管是http还是https,都会带来额外的损耗导致结果不准确

HTTP常见响应状态码

  • 1XX: 收到请求,需要请求者继续执行操作,比较少用

  • 2XX: 请求成功,常用的 200

  • 3XX: 重定向,浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取;

    • 好处:网站改版、域名迁移等,多个域名指向同个主站导流
    • 必须记住:
      • 301:永久性跳转,比如域名过期,换个域名
      • 302:临时性跳转
  • 4XX: 客服端出错,请求包含语法错误或者无法完成请求

    • 必须记住:
      • 400: 请求出错,比如语法协议
      • 403: 没权限访问
      • 404: 找不到这个路径对应的接口或者文件
      • 405: 不允许此方法进行提交,Method not allowed,比如接口一定要POST方式,而你是用了GET
  • 5XX: 服务端出错,服务器在处理请求的过程中发生了错误

    • 必须记住:
      • 500: 服务器内部报错了,完成不了这次请求
      • 503: 服务器宕机

HTTP的九种请求方法

  • http1.0定义了三种:

    • GET: 向服务器获取资源,比如常见的查询请求
    • POST: 向服务器提交数据而发送的请求
    • Head: 和get类似,返回的响应中没有具体的内容,用于获取报头
  • http1.1定义了六种

    • PUT:一般是用于更新请求,比如更新个人信息、商品信息全量更新
    • PATCH:PUT 方法的补充,更新指定资源的部分数据
    • DELETE:用于删除指定的资源
    • OPTIONS: 获取服务器支持的HTTP请求方法,服务器性能、跨域检查等
    • CONNECT: 方法的作用就是把服务器作为跳板,让服务器代替用户去访问其它网页,之后把数据原原本本的返回给用户,网页开发基本不用这个方法,如果是http代理就会使用这个,让服务器代理用户去访问其他网页,类似中介
    • TRACE:回显服务器收到的请求,主要用于测试或诊断

HTTP常见请求头、响应头

常见的请求头

  • Accept: 览器支持的 MIME 媒体类型, 比如 text/html,application/json,image/webp,/
  • Accept-Encoding: 浏览器发给服务器,声明浏览器支持的编码类型,gzip, deflate
  • Accept-Language: 客户端接受的语言格式,比如 zh-CN
  • Connection: keep-alive , 开启HTTP持久连接
  • Host:服务器的域名
  • Origin:告诉服务器请求从哪里发起的,仅包括协议和域名 CORS跨域请求中可以看到response有对应的header,Access-Control-Allow-Origin
  • Referer:告诉服务器请求的原始资源的URI,其用于所有类型的请求,并且包括:协议+域名+查询参数; 很多抢购服务会用这个做限制,必须通过某个入来进来才有效
  • User-Agent: 服务器通过这个请求头判断用户的软件的应用类型、操作系统、软件开发商以及版本号、浏览器内核信息等; 风控系统、反作弊系统、反爬虫系统等基本会采集这类信息做参考
  • Cookie: 表示服务端给客户端传的http请求状态,也是多个key=value形式组合,比如登录后的令牌等
  • Content-Type: HTTP请求提交的内容类型,一般只有post提交时才需要设置,比如文件上传,表单提交等

常见的响应头

  • Allow: 服务器支持哪些请求方法
  • Content-Length: 响应体的字节长度
  • Content-Type: 响应体的MIME类型
  • Content-Encoding: 设置数据使用的编码类型
  • Date: 设置消息发送的日期和时间
  • Expires: 设置响应体的过期时间,一个GMT时间,表示该缓存的有效时间
  • cache-control: Expires的作用一致,都是指明当前资源的有效期, 控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据,优先级高于Expires,控制粒度更细,如max-age=240,即4分钟
  • Location:表示客户应当到哪里去获取资源,一般同时设置状态代码为3xx
  • Server: 服务器名称
  • Transfer-Encoding:chunked 表示输出的内容长度不能确定,静态网页一般没,基本出现在动态网页里面
  • Access-Control-Allow-Origin: 定哪些站点可以参与跨站资源共享

Http常见请求/响应头content-type内容类型
 用来指定不同格式的请求响应信息,俗称 MIME媒体类型

  • 常见取值
    • text/html :HTML格式
    • text/plain :纯文本格式
    • text/xml : XML格式
    • image/gif :gif图片格式
    • image/jpeg :jpg图片格式
    • image/png:png图片格式
    • application/json:JSON数据格式
    • application/pdf :pdf格式
    • application/octet-stream :二进制流数据,一般是文件下载
      application/x-www-form-urlencoded:form表单默认的提交数据的格式,会编码成key=value格式
      multipart/form-data: 表单中需要上传文件的文件格式类型

Apache Tomcat

 web服务器和应用服务器是有很大的交集,没有很严格的区别,更多的是web服务器处理的是Http协议,应用服务器还可以处理其他协议。
官网

# web服务器
	Apache、IIS、Nginx等

# 应用服务器
	Tomcat、Jboss等

# Apache 组织
	Apache软件基金会(也就是Apache Software Foundation,简称为ASF)是专门为运作一个开源软件项目的Apache 的团体提供支持的非盈利性组织,也是我们java开发里面顶级的组织,阿里也向apache组织贡献了多个开源项目。

# Apache tomcat
	Apache是web服务器,Tomcat是应用(java)服务器,它是一个servlet容器,是Apache的扩展。
	简单来说:是一个开放源代码的轻量级 Web应用服务器,目前javaweb开发里面用的最多一个web服务器之一。

# 启动脚本
	mac或者linux: bin/startup.sh  (需要增加执行权限:chmod 777 ./*)
	windows: bin/startup.bat

# 关闭脚本
	mac或者linux: bin/shutdown.sh
	windows: bin/shutdown.bat 或者关闭CMD窗口

# Windows启动乱码
	原因:控制台展示编码问题
	编辑 conf/logging.properties 文件
	java.util.logging.ConsoleHandler.encoding = UTF-8 改为 GBK

Tomcat目录结构

# bin
	启动和关闭tomcat脚本

# conf
	存放配置文件
	server.xml: 配置整个服务器信息 比如修改端口号,添加虚拟主机

# lib
	存放Tomcat运行需要的第三方包,这个目录中的jar所有项目都可以共享
	如果需要添加Tomcat都依赖的jar文件,可以把它放到这个目录中

# log
	运行的相关日志文件,Tomcat启动和关闭的信息,如果启动Tomcat时有错误
	catalina.out 运行日志文件,即标准输出和标准出错,也包含tomcat运行自己输出的日志以及应用里向console输出的日志

# webapps
	Tomcat的主要Web发布目录,存放web项目,其中每个文件夹都是一个项目
	其中ROOT是一个特殊的项目,在地址栏中没有给出具体项目名时,默认会加载ROOT项目

# temp
	存放tomcat运行时的临时性数据

# work
	编译后产生的class文件

Servlet快速上手

  • 简介:是JavaServlet的简称,用Java编写的运行在Web服务器或应用服务器上的程序,具有独立于平台和协议的特性, 主要功能在于交互式地浏览和生成动态Web内容

  • 作用:接收用户通过浏览器传来的表单数据,或者读取数据库信息返回给浏览器查看,创建动态网页

  • 接口路径:package javax.servlet

    • 有两个常见的子类:HttpServlet、GenericServlet
  • 创建servlet类

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class UserServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/html;charset=utf-8");
            PrintWriter printWriter = resp.getWriter();
            printWriter.write("<div>This is a servlet test! </div>");
        }
    }
    
    
  • 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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_4_0.xsd"
             version="4.0">
        <servlet>
            <servlet-name>userServlet</servlet-name>
            <servlet-class>net.xdclass.web.UserServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>userServlet</servlet-name>
            <url-pattern>/userServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • 启动tomcat,访问http://localhost:8080/userServlet

Servlet生命周期

Servlet 接口里面有5个方法,其中:三个生命周期方法、两个普通方法

  • 实例化->使用构造方法创建对象
  • 初始化->执行init方法:Servlet 的生命期中,仅执行一次 init() 方法,它是在服务器装入 Servlet 时执行的,即第一次访问这个Servlet才执行
  • 服务->执行service方法,service() 方法是 Servlet 的核心。每当一个客户请求一个HttpServlet 对象,该对象的service() 方法就要被调用
  • 销毁-> 执行destroy方法,destroy() 方法仅执行一次,即在服务器停止且卸装 Servlet 时执行该方法
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

  • 查看继承和实现关系 XXXServlet->HttpServlet->GenericServlet->implements Servlet
  • HttpServlet里面实现了service方法,里面会根据不同的http method调用不同的方法,所以我们自定义servlet只要重写对应的doXXX方法即可

Servlet3.0 ~ Servlet5.0 VS 旧版

  • 旧版Servlet,在xml里面配置类目和路径,不方便
  • 新版Servlet,支持注解的方式,提高开发效率
@WebServlet("/userServlet/find")
//@WebServlet(name = "userServlet",urlPatterns = {"/user1","/user2","/test"})
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<div>This is a servlet test! from doGet!</div>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<div>This is a servlet test! from doPost!</div>");
    }
}

@WebServlet的元注解配置项

//servlet名称,若不指定,则为Servlet的完整类名
String name() default "";

//路径映射,配置多个,需要/开头
 String[] value() default {};

//路径映射,支持配置多个,需要/开头
  String[] urlPatterns() default {};

 //标记容器是否在启动应用时就加载Servlet,默认或数值为负数时表示第一次请求Servlet时再加载;0或正数表示启动应用就加载
  int loadOnStartup() default -1;

 //配置初始参数
  WebInitParam[] initParams() default {};

//是否支持异步操作模式
 boolean asyncSupported() default false;

作用域对象

就是对象的生命周期,在javaweb开发里面有多个不同生命周期的对象。

比如:PageContext(当前页面)、ServletRequest(一次请求)、HttpSession(同一会话)、ServletContext(整个应用)

对象里面包含属性和对应的数据,所以不同作用域对象使用场景会不同。

ServletContext(应用上下文)

  • 它代表了servlet环境的上下文,相当于一个全局存储空间
  • 同一个WEB应用程序中,所有的Servlet和JSP都可以共享同一个区域,是最大的作用域对象

(webapps下的每个目录就是一个应用程序)

  • 使用场景:加载应用参数
    web.xml
        <context-param>
            <param-name>url</param-name>
            <param-value>test.net</param-value>
        </context-param>
        <context-param>
            <param-name>username</param-name>
            <param-value>jack</param-value>
        </context-param>
    

servlet中可以从ServletContext中获取和存储数据

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter printWriter = resp.getWriter();
        printWriter.write("<div>This is a servlet test! from doGet!</div>");

        //获取servletContext中的共享数据
        ServletContext servletContext = req.getServletContext();
        String url = servletContext.getInitParameter("url");
        System.out.println(url);

        //向servletContext中存储数据
        servletContext.setAttribute("appInfo","xdclass.net info");
        String appInfo = (String)servletContext.getAttribute("appInfo");
        System.out.println("TestServlet appInfo="+appInfo);
    }

JSP和Servlet的关系

  • 什么是JSP: 全称Java Server Pages,是一种动态网页开发技术;

    • 使用JSP标签在HTML网页中插入Java相关代码,标签通常以<%开头 以%>结束
    • JSP本身就是一种Servlet, JSP在第一次被访问的时候会被编译为HttpJspPage类,是HttpServlet的一个子类
    • 为什么用这个:和原生Servle 相比JSP可以很方便的编写HTML网页而不用去大量的用println语句输出html代码
    • 通俗来说:jsp就是在html里面写java代码,servlet就是在java里面写html代码
  • 添加jsp-api.jar到项目里面,和添加servlet-api.jar一样的步骤

  • JSP内置了9个对象可以直接用:out、session、response、request、config、page、application、pageContext、exception


request  HttpServletRequest类的实例

response HttpServletResponse类的实例

out PrintWriter类的实例,用于把结果输出至网页上

session HttpSession类的实例

application ServletContext类的实例,与应用上下文有关

config  ServletConfig类的实例

pageContext PageContext类的实例,提供对JSP页面所有对象以及命名空间的访问

page    Java类中的this关键字

Exception   Exception类的对象,代表发生错误的JSP页面中对应的异常对象
  • JSP脚本程序
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <div>test</div>

    <div>
      <%
        out.println("getRequestURL " + request.getRequestURL());
      %>
    </div>

  </body>
</html>
  • JSP表达式的语法格式:(不能用分号结束)
  <div>
    <%=request.getRequestURL()%>
  </div>
  • 中文编码问题,顶部添加这些信息
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

HttpServletRequest请求对象

代表浏览器客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求中的所有信息都封装在这个对象中,通过这个对象提供的方法可以获得请求的所有信息。

常用核心API测试

http://127.0.0.1:8080/request?userName=jack&age=11&sport=ball&sport=sleep
        //客户端请求信息
        System.out.println("应用上下文路径getContextPath=" + request.getContextPath());//应用上下文路径getContextPath=
        System.out.println("客户端发出请求时的完整URL getRequestURL=" + request.getRequestURL());//客户端发出请求时的完整URL getRequestURL=http://127.0.0.1:8080/request
        System.out.println("请求行中的资源名部分 getRequestURI=" + request.getRequestURI());//请求行中的资源名部分 getRequestURI=/request
        System.out.println("请求行中的参数部分 getQueryString=" + request.getQueryString());//请求行中的参数部分 getQueryString=userName=jack&age=11&sport=ball&sport=sleep
        System.out.println("发出请求的客户机的IP地址 getRemoteAddr=" + request.getRemoteAddr());//发出请求的客户机的IP地址 getRemoteAddr=127.0.0.1
        System.out.println("客户机发请求使用的网络端口号 getRemotePort=" + request.getRemotePort());//客户机发请求使用的网络端口号 getRemotePort=2636
        //获取请求头
        System.out.println("获取请求头 getHeader(Accept)=" + request.getHeader("Accept"));//获取请求头 getHeader(Accept)=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
        //获取请求参数
        System.out.println("客户端请求参数 getParameter=" + request.getParameter("userName"));//客户端请求参数 getParameter=jack
        String[] sport = request.getParameterValues("sport");
        System.out.println("客户端请求参数列表,多个值 getParameterValues="+sport.toString());//客户端请求参数列表,多个值 getParameterValues=[Ljava.lang.String;@34cfebb3
        Map<String, String[]> map = request.getParameterMap();
        System.out.println("客户端请求参数封装成的map类型 getParameterMap=" + map.toString());//客户端请求参数封装成的map类型 getParameterMap=org.apache.catalina.util.ParameterMap@67adac54
  • 四大作用域对象:PageContext->【ServletRequest】->HttpSession->ServletContext;
//ServletRequest作用域:对像生命周期存在一次请求里面
//存储java对象到request作用域
request.setAttribute("name","jack");

HttpServletRequest表单提交案例

  • jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8"%>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <div>test</div>

  <form action="/user/login" method="post">
    <div>用户名:<input type="text" name="userName"></div>
    <div>密码: <input type="password" name="pwd"></div>

    <div><input type="submit" value="登录"></div>
  </form>

  </body>
</html>
  • servlet
@WebServlet("/user/login")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	//解决中文乱码问题
        resp.setContentType("text/html;charset=utf-8");
        String userName = req.getParameter("userName");
        String pwd = req.getParameter("pwd");

        if (userName.equals("Howard") && pwd.equals("123")) {
            resp.getWriter().write("登录成功");
        } else {
            resp.getWriter().write("帐号或密码错误");
        }
    }
}
  • 乱码问题解决的核心:通过字节数组以指定的编码构建字符串,这里指定的编码是根据客户端那边提交数据时使用的字符编码来定。
//POST方式遇到中文乱码,如果客户端是以UTF-8字符编码,需要服务器以UTF-8的编码接收数据,
req.setCharacterEncoding("UTF-8");

//GET方式传输的中文数据乱码需要另外一种方式,默认的还是使用ISO8859-1这个字符编码来接收数据
//办法:对接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题
String name = request.getParameter("name");//接收数据
name = new String(name.getBytes("ISO8859-1"), "UTF-8")

HttpServletResponse响应对象

简介:代表服务器的响应,封装了向客户端发送数据、发送响应头,发送响应状态码的方法

@WebServlet("/response")
public class ResponseServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //设置有多个值的http响应头(客户单收到的响应头中就会有以下两个自定义头信息)
        resp.addHeader("name", "howard");
        resp.addHeader("name", "alice");
        //设置响应头
        resp.setHeader("age", "2");
        //设置http状态码
        resp.setStatus(1000);

/*        //getOutputStream():创建的字节输出流对象,可以按字节形式输出响应正文,直接输出字节数组中的二进制数据,更多用于下载输出文件流
        //设置编码格式为UTF-8
        resp.setCharacterEncoding("UTF-8");
        //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        resp.setHeader("Content-Type", "text/html;charset=utf-8");
        //响应的数据
        String data = "Java学习";
        //获取OutputStream输出流
        OutputStream outputStream = resp.getOutputStream();
        //将字符转换成字节数组,指定以UTF-8编码进行转换
        byte[] dataByteArr = data.getBytes("UTF-8");
        //使用OutputStream流向客户端输出字节数组
        outputStream.write(dataByteArr);*/

        //getWriter():创建的字符输出流对象,可以按字符形式输出响应正文,只能输出输出字符文本内容,和上面的互斥
        //设置编码格式为UTF-8
        resp.setCharacterEncoding("UTF-8");
        //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        resp.setHeader("content-type", "text/html;charset=UTF-8");
        String data2 = "学习java";
        //获取PrintWriter输出流
        PrintWriter out = resp.getWriter();
        //使用PrintWriter流向客户端输出字符
        out.write(data2);
    }
}

RequestDispatcher请求转发

**什么是请求转发:**在浏览器地址栏中不会显示出转发后的地址,属于服务器内部转发,整个过程处于同一个请求当中,所以转发中数据的存取可以用request作用域

  • 客户端发送请求,Servlet做出业务逻辑处理。
  • Servlet调用forword()方法,服务器Servlet把目标资源返回给客户端浏览器
  • 可以访问WEB-INF下的文件,WEB-INF的文件一般是需要一定的权限才可以访问
  • 例子:req.getRequestDispatcher("/WEB-INF/admin.jsp").forward(req,resp);

在WEB-INF目录下创建JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    index.jsp
    <%=request.getAttribute("name")%>
</body>
</html>

创建servlet做转发处理

@WebServlet("/forward")
public class DispatchServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("DispatchServlet doGet");
        req.setAttribute("name","howard");
        req.getRequestDispatcher("/WEB-INF/admin.jsp").forward(req,resp);
    }
}

EL表达式

让JSP访问JavaBean中的数据更简单,全称 Expression Language,让JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。

  • 语法

    EL表达式的格式都是以 ${ }表示。例如 $ {userinfo}代表获取变量userinfo的值,${对象.属性},可以有多层操作
    
    当EL表达式中的变量不给定范围时,则默认在page范围查找,然后依次在request、session、application范围查找,如果找到不再继续找下去,但是假如全部的范围都没有找到时,就回传""
    
    可以用范围作为前缀表示属于哪个范围的变量,例如:${pageScope.userinfo}表示访问page范围中的userinfo变量
    
  • 属性范围在EL中的名称

    【jsp中】【EL表达式中】
    Page    pageScope
    
    Request requestScope
    
    Session sessionScope
    
    Application applicationScope
    
  • 对比

    <%= (String)request.getAttribute("name")%> 等价于 ${name}
    

sendRedirect请求重定向

  • 客户端发送请求,Servlet做出业务逻辑处理
  • Servlet调用response.sendRedirect("xxx.jsp")方法,把要访问的目标资源作为response响应信息发给客户端浏览器
  • 客户端浏览器重新访问服务器资源xx.jsp,服务器再次对客户端浏览器做出响应
  • 请求重定向,不能访问WEB-INF下的文件,浏览器上的窗口地址会改版,可以用于跳转第三方地址或者应用里面的其他Servelt、jsp等
    @WebServlet("/response")
    public class ResponseServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            resp.sendRedirect("/index.jsp");resp.sendRedirect("/index.jsp");
    
    	}
    }
    

注意点

  • 重定向是取不到request中的存储的数据,如果当前servlet是重定向,浏览器可以看到两个请求

    • 案例测试:在reqeust中设置值,然后在请求转发到页面,使用EL表达式取值
  • 调用sendRedirect()方法,会在响应中设置Location响应报头,这个过程对于用户来说是透明的,浏览器会自动完成新的访问

  • 重定向路径问题:如果没有加 http 开头,则认为是当前应用里面的servlet重定向,默认加上应用上下文;如果有加http则会使用配置的全路径进行跳转,如果需要跳转到第三方站点,应改使用http开头。

  • 如果请求转发可以满足需要时,尽量使用请求转发,而不是重定向,效率性能更好

Cookie


HTTP协议作是无状态协议,无状态指每次request请求之前是相互独立的,当前请求并不会记录它的上一次请求信息。 存在这样的问题,既然无状态,那完成一套完整的业务逻辑,需要发送多次请求,那么怎么标识这些请求都是同个浏览器操作呢?

  • 解决方案

    • 浏览器发送request请求到服务器,服务器除了返回请求的response之外,还给请求分配一个唯一标识ID和response一并返回给浏览器
    • 服务器在本地创建一个map结构,专门以key-value存储这个ID标识和浏览器的关系
    • 当浏览器的第一次请求后已经分配一个ID,当第二次访问时会自动带上这个标识ID,服务会获取这个标识ID去map里面找上一次request的信息状态且做对应的更新操作 服务端生成这个全局的唯一标识,传递给客户端用于标记这次请求就是cookie; 服务器创建的那个map结构就是session。
    • cookies由服务端生成,用于标记客户端的唯一标识,在每次网络请求中,都会被传送。
    • session服务端自己维护的一个map数据结构,记录key-Object上下文内容状态
    • 核心:Cookie用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。 浏览器查看多个站点的cookie
@WebServlet("/cookie_test")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取cookie,cookie有很多,是一个数组
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            cookie.getDomain();
        }

        Cookie cookie = new Cookie("token","sfwerawefewadaewfafewafa");
        //20秒过期时间,过期后访问服务器时则不会携带(token可以传输在URL中或在head中)
        cookie.setMaxAge(20);
        response.addCookie(cookie);

        request.getRequestDispatcher("/index.jsp").forward(request,response);
    }
}

cookie的属性

  • Name : 名称

  • Value : 值

  • Domain:表示当前cookie所属于哪个域或子域下面

  • Expires/Max-age:表示了cookie的有效期,是一个时间,过了这个时间,该cookie就失效了

  • Path:表示cookie的所属路径。

  • size: 大小,多数浏览器都是4000多个字节

  • http-only: 表示这个cookie不能被客户端使用js读取到,是不公开的cookie

    • (chrome调试器的console中输入document.cookie将得不到标记为HttpOnly的字段)
  • Secure: 标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端,

    • 从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记
  • SameSite(特有的,可以忽略)

Cookie的缺陷

  • cookie会被附加在每个HTTP请求中,增加了流量。
  • 在HTTP请求中的cookie是明文传递的,所以安全性成问题,除非用HTTPS
  • Cookie的大小有限制,对于复杂的存储需求来说不满足。
  • 如果cookie泄露,会造成安全性问题。

浏览器允许每个域名所包含的cookie数量

  • 多数浏览器允许最多是50个,部分浏览器是30或者20;
  • 满后会有多种剔除策略,比如LRU,权重等

Cookie的现状

  • Cookie曾一度用于客户端数据的存储,因当时并没有其它合适的存储办法而作为唯一的存储手段
  • 现代浏览器开始支持各种各样的存储方式,Cookie渐渐被少用了,新的浏览器API已经允许开发者直接将数据存储到本地,比如localStorage、SessionStorage等 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies

Session

  • 什么是Session

    • 背景:HTTP协议作是无状态协议,无状态指每次request请求之前是相互独立的,当前请求并不会记录它的上一次请求信息。
    • 存在这样的问题,既然无状态,那完成一套完整的业务逻辑,需要发送多次请求,那么怎么标识这些请求都是同个浏览器操作呢?
    • cookie和session都是为了弥补http协议的无状态特性,对server端来说无法知道两次http请求是否来自同一个用户,利用cookie和session就可以让server端知道多次http请求是否来自同一用户
  • 生成和使用流程(和Cookie知识点一样,两者互相配合)

    • 浏览器第一次发送request请求到服务器,服务器除了返回请求的response之外,还给请求分配一个唯一标识sessionId和response一并返回给浏览器
    • 服务器在本地创建一个map结构,专门以key-value存储这个sessionId和浏览器的关系
    • 当浏览器的第一次请求后已经分配一个sessionId,当第二次访问时会自动带上这个标识sessionId
    • 服务器通过查找这个sessionId就知道用户状态了,并更新sessionId的最后访问时间。
    • 注意: Session是有时限性的:比如如果30分钟内某个session都没有被更新,服务器就会删除这个它。
  • 总结:

    • 服务端生成这个全局的唯一标识,传递给客户端用于标记这次请求就是cookie;
    • 服务器创建的那个map结构就是session。
    • cookies由服务端生成,用于标记客户端的唯一标识,在每次网络请求中,都会被传送。
    • session服务端自己维护的一个map数据结构,记录key-Object上下文内容状态
    • 总言之cookie是保存在客户端,session是存在服务器,session依赖于cookie
    • cookie里面存储的就是JSESSIONID
  • session的现状

    • session是存储在服务端的内存中,在javaweb里面叫HttpSession也是一个作用域
    • PageContext(页面)->ServletRequest(请求)->【HttpSession】(会话)->ServletContext(一个应用)
    • 是可以存储很多key-value的,作用域比较广,所以也不能存储过多内容,因为内存是有限制的,互联网企业使用比较少,传统IT公司使用比较多
  • 知识延伸:

    • 服务端是单机情况下session是可以很用的使用的,但是分布式(多台机器)情况下就存在不能共享的问题。
    • 用户A在当前机器登录,突然某次请求到B机器,由于B服务器不存在这个用户的登录信息,所以就会提示重新登录
    • 这个场景下就用到分布式存储方案-比如Redis(跟着学习路线,后续的专题路线课程会讲)

HttpSession 类操作api

@WebServlet("/session_servlet")
public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();

        //获取sessionid,java里面叫jsessionid
        System.out.println("sessionid="+session.getId());//sessionid=AB61B40183F4105E61E64726C13EB476

        //创建时间戳,毫秒
        System.out.println("getCreationTime="+session.getCreationTime());//getCreationTime=1603336135984

        //是否是初次创建,记得清空浏览器的cookie,验证sessionid
        System.out.println("isNew="+session.isNew());//isNew=true

        //往session存储东西
        session.setAttribute("name","学习java test.net");
    }
}

登录JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


<form action="<%=request.getContextPath()%>/loginServlet" method="post">


    名称:<input type="text" name="name"/>

    <br/>
    密码:<input type="password" name="pwd"/>

    <input type="submit" value="登录">

    消息提示 ${msg}
</form>

</body>
</html>

登录成功JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


id:${loginUser.id}
<br>
name:${loginUser.name}

<a href="/logout_servlet">退出</a>
</body>
</html>

JavaBean

public class User {

    private int id;

    private String name;

    private String host;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}

LoginServlet

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String pwd = req.getParameter("pwd");

        if(name.equals("howard") && pwd.equals("123")){

            User user = new User();
            user.setId(121);
            user.setName(name);
            user.setHost("test.net");
            req.getSession().setAttribute("loginUser",user);
            req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req,resp);

        }else{
            req.setAttribute("msg","账号密码错误");
            req.getRequestDispatcher("/login.jsp").forward(req,resp);
        }

    }
}

LogoutServlet

@WebServlet("/logout_servlet")
public class LogoutServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        HttpSession session = request.getSession();
	//吊销session,下次客户端请求将会重新生成新的jsession
        session.invalidate();
        request.getRequestDispatcher("/login.jsp").forward(request,response);
    }
}

访问 http://localhost:8080/loginServlet

Filter过滤器

  • 什么是过滤器:(检验是否符合要求,或者 对内容做二次处理,设置编码响应等)

    • filter简单理解:人--->检票员(filter)---> 景点
  • Servlet里面的过滤器作用

    • 动态地拦截请求和响应,变换或使用包含在请求或响应中的信息
    • 在客户端的请求访问后端资源之前,拦截这些请求。
    • 在服务器的响应发送回客户端之前,处理这些响应。
  • Filter的生命周期

    • init(FilterConfig filterConfig) //只容器初始化的时候调用一次,即应用启动的时候加载一次
    • doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 只要命中过滤规则就触发,可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法, 即是否让目标资源执行
    • destroy() //只容器销毁的时候调用一次,即应用停止的时候调用一次
  • 元注解: @WebFilter

cookieServlet

@WebServlet(name="cookieServlet", urlPatterns = "/cookie_servlet")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取cookie,cookie有很多,是一个数组
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            cookie.getDomain();
        }
    }
}

CustomFilter

//servletNames 和 urlPatterns 可以同时使用
//@WebFilter(filterName = "xxx",servletNames = {"cookieServlet"},urlPatterns = {"/user/*","/order/*"})
//@WebFilter(servletNames = {"cookieServlet"})
@WebFilter(filterName = "CustomFilter",urlPatterns = {"/*"})
public class CustomFilter implements Filter {
    //只容器初始化的时候调用一次,即应用启动的时候加载一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("CustomFilter init");
    }

    //只要命中过滤规则就触发,可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法, 即是否让目标资源执行
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("CustomFilter doFilter");

	//统一设置请求、响应编码
	request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

	//统一设置响应类型
        response.setContentType("text/html;charset=utf-8");

        //放行
        chain.doFilter(request,response);
    }

    //只容器销毁的时候调用一次,即应用停止的时候调用一次
    @Override
    public void destroy() {
        System.out.println("CustomFilter destroy");
    }
}

元注解:@WebFilter

//该Filter是否支持异步操作模式
asyncSupported  

//指定Filter对那种dispatcher模式进行过滤 该属性支持 Async,Error Forward,include,request  
dispatcherType  

//Filter 显示的名称
displayName  

//Filter的名称
filterName  

//Filter的配置参数
initParams  

//过滤的Servlet可以指定多个,表示对这几个特定的的servlet 进行过滤
servletNames  

//指定 Filter拦截的 URL,和上面的servletNames配置一样,用*可以表示通配符,但是不用字母后加*,应该按照模块划分,比如/user/*
urlPatterns/value  

Servlet Filter 配置参数

FilterConfig类

  • 过滤器配置类,可以通过这个获取过滤器基本信息

dispatcherTypes 参数

  • 指定Filter对那种dispatcher模式进行过滤,不符合的则不进行过滤
    • REQUEST:默认值,浏览器直接请求的资源会被过滤器拦截
    • FORWARD:转发访问资源会被过滤器拦截
    • INCLUDE:包含访问资源
    • ERROR:错误跳转资源
    • ASYNC:异步访问资源
@WebFilter(filterName = "xxx",urlPatterns = {"/*"},initParams = {
        @WebInitParam(name = "encoding",value = "UTF-8"),
        @WebInitParam(name = "loginPage",value = "/login.jsp"),
},dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})

public class CustomFilter implements Filter {


    private FilterConfig filterConfig;

    private String encoding;

    private String loginPage;


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("CustomFilter init ");

        String filterName = filterConfig.getFilterName();
        System.out.println("filterName="+filterName);

        this.filterConfig = filterConfig;
        this.encoding = filterConfig.getInitParameter("encoding");
        this.loginPage = filterConfig.getInitParameter("loginPage");

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("CustomFilter doFilter ");

        //统一进行设置
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset=utf-8");

        //让请求继续往下走
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

        System.out.println("CustomFilter destroy ");
    }
}

用户登录访问个人页面拦截示例

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


<form action="<%=request.getContextPath()%>/loginServlet" method="post">


    名称:<input type="text" name="name"/>

    <br/>
    密码:<input type="password" name="pwd"/>

    <input type="submit" value="登录">

    消息提示 ${msg}
</form>

</body>
</html>

个人页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


id:${loginUser.id}
<br>
name:${loginUser.name}

<a href="/logout_servlet">退出</a>
</body>
</html>

LoginServlet

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String pwd = req.getParameter("pwd");

        if(name.equals("howard") && pwd.equals("123")){

            User user = new User();
            user.setId(121);
            user.setName(name);
            user.setHost("test.net");
            req.getSession().setAttribute("loginUser",user);
            req.getRequestDispatcher("/user/user.jsp").forward(req,resp);

        }else{
            req.setAttribute("msg","账号密码错误");
            req.getRequestDispatcher("/login.jsp").forward(req,resp);
        }
    }
}

LoginFilter

@WebFilter(filterName = "loginFilter",urlPatterns = {"/user/*","/order/*"},initParams = {
        @WebInitParam(name = "encoding",value = "UTF-8"),
        @WebInitParam(name = "loginPage",value = "/login.jsp"),
})
public class LoginFilter implements Filter {
    private FilterConfig filterConfig;
    private String encoding;
    private String loginPage;


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("CustomFilter init ");
        this.filterConfig = filterConfig;

        this.encoding = filterConfig.getInitParameter("encoding");
        this.loginPage = filterConfig.getInitParameter("loginPage");
    }

    //被拦截的URL会进入此方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("CustomFilter doFilter ");

        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset=utf-8");

        //将ServletRequest => HttpServletRequest
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;

        //session里面有用户信息
        if(httpServletRequest.getSession().getAttribute("loginUser") !=null){
            //则放行
            chain.doFilter(request,response);

        }else {
            httpServletRequest.setAttribute("msg","非法访问,请登录");
            httpServletRequest.getRequestDispatcher(loginPage).forward(httpServletRequest,httpServletResponse);
        }
    }

    //停止tomcat的时候会执行
    @Override
    public void destroy() {
        System.out.println("CustomFilter destroy");
    }
}

Listener监听器

监听器是一个实现了特定接口的普通Java类,用于监听其他对象的创建和销毁,监听其他对象的方法执行和属性改变;

javaweb里面的监听器

  • 作用:监听域对象的创建和销毁,比如request/session/context

  • 分类:

    • ServletContextLitener(应用上下文)
    • HttpSessionListener(会话)
    • ServletRequestListener(请求)
  • 监听器的实现步骤

    (1)创建一个普通的Java类

    (2)让该类实现监听器的接口

    (3)在该类中实现监听器接口的所有方法

    (4)旧版的在web.xml文件中通过标签来配置监听器,新版使用 @WebListener

ServletContextListener全局配置加载

使用场景:加载全局配置,初始化项目信息

配置应用上下文信息web.xml

<?xml version="1.0" encoding="UTF-8"?>
<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_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>url</param-name>
        <param-value>https://xdclass.net</param-value>
    </context-param>
    <context-param>
        <param-name>topic</param-name>
        <param-value>学习Java</param-value>
    </context-param>
</web-app>

ContextListener

@WebListener
public class ContextListener implements ServletContextListener {
    /**
     * 初始化
     * @param sce
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ContextListener contextInitialized");

        //获取上下文对象
        ServletContext servletContext = sce.getServletContext();
        String url = servletContext.getInitParameter("url");
        String topic = servletContext.getInitParameter("topic");

        //自定义Config类
        Config config = new Config();
        config.setTopic(topic);
        config.setUrl(url);
        //将config类设置到应用上下文中,方便读取
        servletContext.setAttribute("config",config);
    }

    /**
     *销毁
     * @param sce
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ContextListener contextDestroyed");
    }
}

在servlet中可以获取全局配置,避免每次使用都需要重新加载的繁琐,显然使用ContextListener将JavaBean设置到应用上下文更便捷。

        Config config = (Config) req.getServletContext().getAttribute("config");
        System.out.println(config.getTopic());

HttpSessionListener统计当前在线人数

  • session使用场景

    • 和session相关操作,比如统计网站在线人数,当前服务器的负载情况等
  • ContextLisener配置

    //获取上下文对象
    ServletContext sc = sce.getServletContext();
    sc.setAttribute("onlineNum",0);
    
  • HttpSessionListener开发

@WebListener
public class SessionListener implements HttpSessionListener {

    //监听session的创建
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("SessionListener sessionCreated");

        ServletContext servletContext  = se.getSession().getServletContext();

        //获取在线人数
        Integer onlineNum = (Integer)servletContext.getAttribute("onlineNum");

        //新增1
        servletContext.setAttribute("onlineNum",++onlineNum);

    }

    //监听session的销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("SessionListener sessionDestroyed");

        ServletContext servletContext  = se.getSession().getServletContext();

        //获取在线人数
        Integer onlineNum = (Integer)servletContext.getAttribute("onlineNum");

        //减少1
        servletContext.setAttribute("onlineNum",--onlineNum);

    }
}

add.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>javaweb统计在线人数</title>
</head>
<body>

<hr>
近30分钟在线人数: ${applicationScope.onlineNum}
</body>
</html>

delete.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title> 销毁</title>
</head>
<body>

销毁session
<hr>

<% request.getSession().invalidate(); %>

</body>
</html>

ServletRequestListener统计网站请求量

ContextLisener中初始化

        //设置在线人数的初始值
        servletContext.setAttribute("totalVist", 0);

RequestListener

@WebListener
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("RequestListener requestDestroyed");
    }

    //初始化
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("RequestListener requestInitialized");
        Integer totalVisit = (Integer)sre.getServletContext().getAttribute("totalVisit");
        System.out.println("历史总访问次数:" + totalVisit);
        totalVisit++;
        sre.getServletContext().setAttribute("totalVisit",totalVisit);
    }
}

add.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>javaweb统计在线人数</title>
</head>
<body>

<hr>
近30分钟在线人数: ${applicationScope.onlineNum}

<hr>
应用服务器启动后总访问次数: ${applicationScope.totalVisit}
</body>
</html>

文件上传

Web应用系统开发中,文件上传和下载功能是非常常用的功能,浏览器在上传的过程中是将文件以流的形式提交到服务器端的

前端开发

1)表单的提交方法必须是post
2)需要声明是一个文件上传组件 [ ]
3)必须设置表单的enctype="multipart/form-data
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传样例</title>
</head>
<body>

<form action="<%=request.getContextPath()%>/fileUpload" method="post" enctype="multipart/form-data">

    用户名:<input type="text" name="username"/>
    头像:<input type="file" name="img">
    <input type="submit" value="提交">

</form>


</body>
</html>

后端开发

@WebServlet("/fileUpload")
@MultipartConfig    //专门用于文件上传的注解,有相关文件上传的相关配置(上传大小等…)
public class FileUploadServlet extends HttpServlet {


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //获取上传文件的用户名
        String username = request.getParameter("username");
        System.out.println("username="+username);

        //获取Part,用来获取文件名和文件输入流
        Part part = request.getPart("img");

        //获取真实文件名称 test.png
        String header = part.getHeader("content-disposition");// form-data; name="img" filename="test.png"
        String realFileName = header.substring(header.indexOf("filename=")+10,header.length()-1);//test.png

        System.out.println("realFileName="+realFileName);


        //获取真实的文件内容
        InputStream is =  part.getInputStream();

        //web-inf目录外界不能直接访问,如果文件机密性强则放这里
        //String dir = this.getServletContext().getRealPath("/WEB-INF/file");

        //file目录真实路径
        String dir = this.getServletContext().getRealPath("/file");

        File dirFile = new File(dir);

        //如果目录不存在,则创建
        if(!dirFile.exists()){
            dirFile.mkdirs();
        }

        //UUID + 上传文件的全名
        String uniqueName = UUID.randomUUID()+realFileName;

        //文件流拷贝
        //File file = new File(dir,realFileName);

        //创建目标文件
        File file = new File(dir,uniqueName);

        FileOutputStream out = new FileOutputStream(file);


        byte[] buf = new byte[1024];
        int len;

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

        out.close();
        is.close();

        //跳转到刚刚上传的图片访问
        request.getRequestDispatcher("/file/"+uniqueName).forward(request,response);
    }
}

注意点

  • 考虑上传文件存储的目录
  • 防止文件重名覆盖
  • 防止一个目录下面出现太多文件
  • 限制上传文件的最大值
  • 上传的文件判断后缀名是否合法

文件下载

  • 只需通过超链接即可实现,就是通过超链接,在连接地址里写上文件的路径,浏览器会自动打开该文件
  • 普通的文本,图片等浏览器能直接显示内容的浏览器都能直接打开并显示
  • 如果是浏览器无法打开的文件,比如exe等浏览器就会提示你下载改文件或者使用当前系统自带的工具打开该文件

前端开发

<a href="<%=request.getContextPath()%>/download?file=test1.png">下载</a>

后端开发

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //客户端传递需要下载的文件名
        String file = request.getParameter("file");


        //获取文件在我们项目中的路径,发布到tomcat的实际路径
        String path = request.getServletContext().getRealPath("/file/");

        //要下载的文件在项目中的真实路径
        String filePath = path+file;

        //拿到filePath的输入流
        FileInputStream fis = new FileInputStream(filePath);

        //设置编码
        response.setCharacterEncoding("UTF-8");

        //指明响应的配置信息,包含附件:告诉浏览器
        response.setHeader("Content-Disposition","attachment; filename="+file);

        //如果文件包含中文名称,需要进行编码转换
        //response.setHeader("Content-Disposition","attachment; filename="+new String(file.getBytes("gb2312"),"ISO-8859-1"));

        //拿到response的输出流
        ServletOutputStream out = response.getOutputStream();

        byte[] buf = new byte[1024];
        int len;

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

        out.close();
    }
}


作者:Soulboy