【学习笔记】深入理解及个人感悟JavaWeb
文章目录
- 贯穿全文的问题
- 一、域名解析
- 二、HTTP
- 三、Maven
- 3.1、Maven环境配置
- 3.2、IDEA中使用Maven
- 3.2.1、标准目录结构布局(重要)
- 3.2.2、pom.xml分析
- 3.2.3、注意配置文件的过滤
- 四、Servlet
- 4.1、Servlet是什么
- 4.2、初探究Servlet——HelloServlet
- 4.2.1、从中得到的结论
- 4.2.2、Mapping问题
- 4.3、ServletContext
- 4.3.1、数据共享
- 4.3.2、初始化参数
- 4.3.3、请求转发
- forward和include映像图解释
- 请区分请求转发和重定向
- 4.3.4、读取资源文件
- 项目部署再理解
- 4.4、HttpServletResponse
- 4.4.1、下载文件
- 4.4.2、重定向
- 请求转发和重定向区别
- 4.5、HttpServletRequest
- 五、Cookie和Session
- 5.1、cookie和session之我见
- 5.2、区分Session和ServletContext
- 六、JSP
- 6.1、JSP基础
- 6.2、九大内置对象
- 6.3、JSP标签、JSTL标签、EL表达式
- 6.4、JSP之我见
- 七、MVC三层架构
- 八、Filter(过滤器)和Listener(监听器)
- 8.1、过滤器
- 8.2、监听器
- 8.3、Filter实现权限拦截
- 8.3.1、项目部署、servlet和JSP再理解
- 回答最初的问题
- JavaWeb映像图
贯穿全文的问题
在学习JavaWeb的时候先提出一个问题,这个问题贯穿了整个JavaWeb,所以一开始提出这个问题,在JavaWeb学完后希望给一个你所理解的、可以独自口述、可以在心里或者纸上画成图像的完整答案。
Q:当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历了什么?
A:简单回答。 域名解析 —>TCP三次握手—>HTTP请求—>服务器响应—>解析HTML资源—>页面渲染
一、域名解析
计算机网络里的知识。在此总结并理解。
DNS(Domain Name System,域名系统)。
首先说什么是域名,也就是我们平时在网址栏输入的网址。
DNS服务:简单的来说为 域名-> ip地址。DNS为互联网的各种网络应用提供核心服务。为什么这么说呢?如果一个用户要与网络中某台主机进行通信的话,必须要知道对方的ip地址,我们知道ip的是为了区分互联网上的各个主机的,但是ip地址不方便且没有特殊的规律,使用起来不方便,因此被域名代替。域名人读和使用起来方便,但机器不行。所以为了平衡人机之间的问题,就有了DNS服务。
从理论上来讲,整个互联网可以使用一个域名服务器,让它记载所有域名和ip地址的映射关系,但这种做法并不可取。原因其一随着使用互联网的人越来越多,一个DNS服务器受到的压力就会越来越大,DNS服务器这边超负荷工作对机器不好,同时被服务的用户体验感也会不好会感到慢。其二一旦这个域名服务器崩溃了,整个全球的互联网就会瘫痪。因此需要使用分布式的域名系统DNS。
根域名服务器:是最高层次的域名服务器,所有根域名服务器都知道所有顶级域名服务器的域名和ip地址的映射。它是最重要的域名服务器,因为不管是哪一个本地域名服务器,若要对互联网上任一个域名进行解析,只要自己无法解析,就首先求助根域名服务器。(看图不要以为全世界就一个,不同国家个数不同)
顶级域名服务器:这些域名服务器负责管理在该顶级域名服务器注册的二级域名。当收到DNS查询请求时,就给出相应回答。
本地域名服务器:当一台主机发出DNS查询请求时,这个请求报文发给离提请用户较近的本地域名服务器(在网络属性里的DNS服务器),当要解析的地址和提请用户是同一个地方时,该域名服务器就立即将查询结果返回给提请用户,不需要再去询问其他域名服务器。若不能解析,则求助根域名服务器。同时本地服务器有存有最近查询到的域名的缓存(就是近期常访问的域名ip地址映射),以便高速反馈给用户。
为什么localhost就是127.0.0.1,本地环回地址?也需要进行查询吗?并不是这样的,我们在没联网的情况下也是localhost,这对映射关系就在本地存储着。
在每个计算机的C:\Windows\System32\drivers\etc\hosts
配置文件。
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
所以网址上输入一个域名,首先会在这个文件下找有没有对应的映射关系,有则直接返回,无再去DNS服务器找。
如果需要经常访问一个网址,就可以将其域名和ip地址映射关系填在这里,以增快访问速度。(调优)
可以这样认为,DNS就是将IP地址和一组有意义的字符产生关联(映射)。DNS相当于人和计算机之间的一个桥梁,它方便人以记住有意义的字符的方式来记住无规律复杂的ip地址;方便计算机获得到ip地址以精确定位到互联网中的某一特定主机。
二、HTTP
超文本传输协议(Hyper Text Transfer Protocol,HTTP),是一个简单的请求-响应协议。协议(Protocol)就是规定,双方约定好的。所以http指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
其作用:规定了服务器(server)和浏览器(browser)之间信息传递的规范。
- HTTP/1.0:客户端可以与web服务器连接后,只能获得一个web资源,断开连接
- HTTP/1.1:客户端可以与web服务器连接后,可以获得多个web资源(Connection: keep-alive)。
请求行(Request):
get:请求能够携带的参数比较少,大小有限制,会在浏览器的URL地址栏显示数据内容,不安全,但高效
post:请求能够携带的参数没有限制,大小没有限制,不会在浏览器的URL地址栏显示数据内容,安全,但不高效。
但其实两者还是不安全,攻击者可以通过抓包分析来破解。因此有了https(Hyper Text Transfer Protocol SSL),广泛用于安全敏感的通信,https服务器可是要申请相关部门申请证书的,并且期限到了还要续。
响应(Response)状态码
200:OK 请求成功
3xx:重定向
4xx:客户端错误。常见404资源不存在。
5xx:服务器错误。常见504网关错误(两个网络之间的通信,例如192.168.1.和192.168.2.之间没有路由器转发,就需要网关,看作守门人,有自己的ip地址)。
三、Maven
Maven的核心思想:约定大于配置。
也就是规定编程者遵守规范,不能去违反。
3.1、Maven环境配置
下载:https://maven.apache.org/download.cgi
配置环境变量
- M2_HOME maven目录下的bin目录
- MAVEN_HOME maven的目录
- 在系统的path中配置%MAVEN_HOME%\bin
阿里云镜像
apache-maven-xxxx\conf\settings.xml
<mirror><id>nexus-aliyun</id> <mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
本地仓库
要去相应目录下建立,然后为settings.xml增加。
<localRepository>F:\Environment\apache-maven-3.8.2\maven-repo</localRepository>
IDEA更改Maven环境
建议在Welcome界面更改,Configure—>setting(保证以后的工程都是)。
更改为相应的位置,否则默认为C盘。这其实就是相当于设置全局,以后创建Maven项目都是这个目录下。
规定:当每次maven项目创建成功后。一定留个心眼看一下maven的配置符不符合你的预期的地方。
3.2、IDEA中使用Maven
分别可以使用模板快速建一个web的Maven项目,也可以不使用模板创建一个干净的Maven项目。我们两种创建方式都使用,然后将遇到的问题汇总如下。
Maven的GAV
GroupId,ArtifactId,Version。可以认为是Maven中的坐标,我们知道在平面几何中坐标(x,y)可以标识平面中唯一的一点。在maven中坐标就是为了定位一个唯一确定的jar包。Maven仓库由大量jar包构建组成,我们需要找一个用来唯一标识一个构建的统一规范。GAV就是这个作用。
- GroupId:定义当前Maven组织名称
- ArtifactId:定义实际项目名称
- Version:定义当前项目的当前版本
出现Cannot resolve plugin org.apache.maven.plugins:xxxx问题
出现这个问题就是因为jar包已经下载了存在本地的仓库内,然后现在又检索到这个包了,就不会去远程下载了。删除相关目录下的所有文件,然后刷新重新下载即可。(参考https://www.jianshu.com/p/1864f9d2f85d)。
3.2.1、标准目录结构布局(重要)
我们结合使用模板不使用模板创建Maven项目,将两者综合起来总结出Maven中的工程目录结构。
在Maven中它对工程的目录结构有要求,虽然不是严格要求,但是约定大于配置,这样做可以被绝大多数人认可,这样就统一了市面上各种复杂配置的工程,方便程序员们分析项目结构。
首先先来看官方文档描述。总结如下。
- src- main- java- resources- webapp- test- java- resources- target
目录 | 描述 |
---|---|
src/main/java | 应用程序/库源 |
src/main/resources | 应用程序/库资源、配置文件 |
src/main/webapp | Web应用程序源 |
src/test/java | 测试源 |
src/test/resources | 测试资源、配置文件 |
target | 用于存放构建的所有输出 |
target
目录由Maven创建。它包含所有编译的类,JAR文件等。当执行mvn clean
命令时,Maven将清除目标目录。同时在项目根目录的顶部有pom.xml文件(这个pom.xml文件很重要,是maven项目的核心配置文件)。
IDEA帮助我们标识文件夹
IDEA这个编辑器是真的用心和细心。文件夹颜色标识这个功能是锦上添花,没有只需要按照上述目录结构也可以;有了颜色使得识别各个文件夹更方便。
设置方式:File下的Project Structure。
进行相关文件的选中即可。
webapp变色
方式一:
修改pom.xml。增加<packaging>war</packaging>
意思是打包类型 war包。
方式二:创建web模板。File 选中 Project Structure;点击 Modules,选中要操作的哪一个Modules,点击左上+ , 选择 web;更改Web Resource Directory下面的默认路径,指定webapp路径。
3.2.2、pom.xml分析
不使用模板创建方式,得到最干净的POM文件。
<?xml version="1.0" encoding="UTF-8"?>
<!--Maven版本和头文件-->
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><!--这里是我们最开始设置的GAV--><groupId>com.zmj</groupId><artifactId>javawebTest02</artifactId><version>1.0-SNAPSHOT</version><!--以下为空白创建新增--><!--Package:项目打包方式jar:java应用wav:JavaWeb应用--><packaging>war</packaging><properties><!--项目默认构建编码--><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!--编译版本--><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><!--项目依赖--><dependencies><!--具体依赖的jar包配置文件--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency></dependencies><!--项目构建用的东西--><build><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><!-- 插件 --><plugins><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-war-plugin</artifactId><version>3.2.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin></plugins></pluginManagement></build>
</project>
3.2.3、注意配置文件的过滤
我们看过刚才的标准目录结构布局,明白了.xml,.properties等配置文件需要放在resources目录下(遵守maven约定大于配置)。但是我们往往在项目中不仅仅会把所有的资源配置文件都放在resources中,同时我们也有可能放在项目中的其他位置(mybatis会遇到),无法被导出或者生效的问题。建议大家,每新建一个maven项目,就把该固定设置导入pom.xml文件中。
<build>...<resources><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources>...
</build>
以上配置会使src/main/resources和src/main/java目录下的xml和properties配置文件再进行打包的时候依然存在(不过滤)。
四、Servlet
4.1、Servlet是什么
Servlet是SUN公司开发动态web(千人千面的淘宝等网页)的一门技术。如果想使用这门技术,需要完成两个小步骤。
- 编写一个类,实现Servlet接口
- 将编写好的类部署到web服务器中
总的来说,Servlet是一个接口,人们常理解为任意实现了Servlet接口的类。
4.2、初探究Servlet——HelloServlet
项目分而治之的思想
构建一个普通的Maven项目,删除src目录,以后我们创建Moudel相当于一个子项目;该空的项目为Maven主项目。
直接在主项目的pom.xml下添加肯定需要的servlet和jsp的jar包。
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version>
</dependency><!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.1</version>
</dependency>
主项目为空的maven进行开始设置,而子模块(New Module)可以选择根据模板生成。
创建完成后,这时候看主项目和子项目的pom.xml文件就可以看出来父子关系。
父项目的pom.xml有
<modules><module>HelloServletPartOne</module></modules>
子项目pom.xml有
<parent><artifactId>HelloServlet</artifactId><groupId>com.zmj</groupId><version>1.0-SNAPSHOT</version></parent>
父项目的jar包,子项目可以直接使用;而子项目的东西,父项目不可以使用。类似于Java中的继承(extends)。
这里体现了分而治之的思想,将一个大的主项目拆分成或按功能模块分出不同的小项目,进行分别处理,最后聚在一起完成整个工程。
优化步骤
- 如果子项目是Web项目,则将其中的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">
</web-app>
- 将Maven结构按照上面的标准目录结构布局添加缺失的目录。(很重要)
可以为了观感好看进行收缩,然后到时候该访问哪个再展开也不迟。
编写第一个Servlet程序
回到子项目的src下的main目录下的java目录,这时候就和原来的Java编程方式一模一样了。
tips:为了好看,包名在一行。左边代码工具栏—>齿轮—>选中Flatten Packages和Hide Empty Middle Packages。
编写一个类实现Servlet接口,但是我们知道实现接口就要实现其所有的方法;我们只需要其中几个方法即可,所以使用已经实现Servlet接口的HttpServlet就行。只用去覆盖你想用的方法即可。
public class FirstServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setCharacterEncoding("utf-8");resp.setContentType("text/html;charset=utf-8");System.out.println("我可以在后台输出、执行一些东西");PrintWriter writer = resp.getWriter();writer.println("我还可以在前端写些东西");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}
}
编写Servlet的映射
建议:编写(创建)完一个servlet就立刻去web.xml将其注册并绑定。
为什么需要映射:我们写的是JAVA程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以我们需要再web服务中注册我们写的Servlet,还需给他一个浏览器能够访问的路径;
在相关web.xml中添加。
<!--注册servlet-->
<servlet><servlet-name>myFirstServlet</servlet-name><servlet-class>com.zmj.servlet.FirstServlet</servlet-class>
</servlet>
<!--Servlet的请求路径-->
<servlet-mapping><servlet-name>myFirstServlet</servlet-name><!--注意这里url-pattern标签里带有斜杠--><!--记住这个url-pattern标签的值--><url-pattern>/abc</url-pattern>
</servlet-mapping>
尽量这样成对的写,注册一个servlet(类),就去写它的映射路径。
记住url-pattern标签里必须加/,必须加/,必须加/,否则会报错。
配置tomcat
前面的基础配置(端口号,JDK版本等)就不啰嗦了。主要在第二步配置项目发布路径这里。
新加Artifact;选择当前项目的war包;修改Application context的值为任意名字,记住这个名字,以后靠这个进主页。
启动测试
测试输出结果完全ok。我们主要目的不在于此。
我们查看下,tomcat运行成功后产生了什么。
4.2.1、从中得到的结论
本地存储的变化
首先,我们这些项目代码都是在IDEA环境下编写的,所以IDEA的WorkSpace有这些东西是毋庸置疑的。当tomcat成功运行后,IDEA左边会多出一个target目录。
并且经过解压子项目包压缩包,发现它解压出来与子项目包完全一致。
然后,再去查tomcat环境下的webapps目录下,会多出来s1一个目录,且此目录与上述的子项目包也完全一致。
页面的变化
我们知道,访问localhost:8080
是tomcat的ROOT页面。
然后加上localhost:8080/s1
就到了index.jsp主页面上去了。
然后再加上localhost:8080/s1/abc
就到了我们由servlet映射关系(web.xml)映射的类相关处理方法得到的结果页面上去了。
也就是说/s1访问的是这个webapp的index;/s1/abc是请求那个servlet。
所以,再回看我前面让记住的s1和abc(无论怎样命名都无所谓),就想到得到如下这样么一个结论。
当你将servlet成功部署在tomcat上之后,IDEA的WorkSpace会多出一个网页的压缩包,tomcat会多出一个网页目录,且这个目录的名称为在进行配置项目发布路径时Application context的值,此名称就是访问该项目索引(index.jsp)的地址。同时还要注意编写Servlet的映射时填写的url-pattern,网页地址填写索引/此名称即可访问该名称映射的Servlet类形成动态页面。
因此,看到这里。希望你对servlet映射和项目部署有深刻的理解即可。还希望你对一个web工程(例s1)和一个servlet(/abc)分辨清楚。
映像图
4.2.2、Mapping问题
承接上文,一个Servlet绑定了一个映射路径。其还可以指定多个映射路径。
<servlet-mapping><servlet-name>myFirstServlet</servlet-name><url-pattern>/abc</url-pattern></servlet-mapping><servlet-mapping><servlet-name>myFirstServlet</servlet-name><url-pattern>/qwe</url-pattern></servlet-mapping><servlet-mapping><servlet-name>myFirstServlet</servlet-name><url-pattern>/zxc</url-pattern></servlet-mapping>
这就是访问不同网址得到同一页面的原因。
- 修改默认请求路径
<servlet-mapping><servlet-name>myFirstServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>
这样做,访问项目网址时,就不走index.jsp了,直接走的这个映射路径。
所以,想修改欢迎界面的就如此修改就行。
- 优先级问题
指定了固有的映射路径优先级最高,如果找不到就会走默认(/*)的处理请求(先精确,后模糊);
因此,我们可以利用这点实现美化404页面。
<servlet><servlet-name>notFoundServlet</servlet-name><servlet-class>com.zmj.servlet.NotFoundServlet</servlet-class></servlet><servlet-mapping><servlet-name>notFoundServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>
4.3、ServletContext
又称servelet上下文,web容器再启动的时候,它会为每一个web应用创建一个对应的ServletContext,该ServletContext对象包含了所属web应用下所有servlet共享的资源信息。
4.3.1、数据共享
很容易理解,你可以把ServletContext看作一个粮仓(map),任何属于该ServletContext的servlet都可以向这个粮仓添加(put)一些值或属性。
//一个servlet里放值
@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext servletContext = this.getServletContext();servletContext.setAttribute("userName", "张三");}//一个servlet取值
@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext servletContext = this.getServletContext();String userName = (String) servletContext.getAttribute("userName");resp.setContentType("text/html");resp.setCharacterEncoding("utf-8");resp.getWriter().print("名字"+userName);}//web.xml配置相关映射
4.3.2、初始化参数
可以写在相关web.xml中,也可以写在程序里。
<!--配置一些web应用初始化参数--><context-param><param-name>url</param-name><param-value>jdbc:mysql://localhost:3306/mybatis</param-value></context-param>
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext context = this.getServletContext();String url = context.getInitParameter("url");resp.getWriter().print(url);
}
4.3.3、请求转发
public class ServletDemo3 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletContext servletContext = this.getServletContext();System.out.println("进入了ServletDemo3的doGet方法");RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/sd4");//填转发请求的路径,值为在web.xml配置的,当前在根工程下/s2requestDispatcher.forward(req, resp); //可以看到把相关的请求和响应给别人了。像踢皮球,我处理不了,你来处理。System.out.println("我还可以输出吗?"); //控制台层面是还可以输出的PrintWriter writer = resp.getWriter();writer.println("我还可以写吗?");//结果没有写在页面上,说明resp控制权已经交换出去,不属于我了,我不能再操作了。//但是如果改成include就可以继续写,控制权还要回到我这里的,画映像图解释。}
}
public class ServletDemo4 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html");resp.setCharacterEncoding("utf-8");resp.getWriter().println("Hello Hello Alone");}
}
forward和include映像图解释
include
方式:浏览器发出请求给servletA,servletA不能满足请求,就将这个请求转发给servletB让它去处理;servletB处理完形成的响应返回给servletA,所以servletA还可以对这个响应做一些操作然后再发给浏览器。
forward
方式:浏览器发出请求给servletA,servletA不满足请求转发给servletB,servletB处理完形成的响应不经过servletA直接发回给浏览器,servletA没有了响应的控制权,它就没法对响应进行加工。
两者相同点是都需要经过中间商servletA转发请求,才能转交给真正的处理人servletB。而异同点是响应经不过经过中间商。
请区分请求转发和重定向
我们现在见到的是ServletContext的转发功能,这不是重定向!
- 请求转发因为借助ServletContext,所以只能转发给同一个Web应用中的servlet,而重定向到同一个站点上的其他应用程序中的资源,甚至可以用绝对URL重定向到其他站点的资源。
- 请求转发完成后URL地址不会变,而重定向访问过程结束后,URL发生变化。
- 请求转发浏览器只发送了一次请求和收到一次响应;而重定向一共发出了两次请求,两次响应。
- 请求转发不知道自己要的实际资源来自于谁,它只认定中间商(URL没发生变化看出);而重定向知道自己的资源不出自于中间商,来源是谁它也知道(借钱例子)。
4.3.4、读取资源文件
我们以后接触的项目中,配置文件可以放在resources(Maven规定)目录下,也可以随着java代码一起存放(mybatis遇到),就拿properties文件举例子吧。
我们需要借助InputStream这个类才可以进行解析,ServletContext提供了相关方法,只需要弄清楚相关路径问题即可明白。
也就是说ServletContext可以获得当前工程根目录下的路径。
public class ServletDemo5 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println(new File("./").getAbsoluteFile());//E:\AKuangLearn\TomcatEnvironment\apache-tomcat-9.0.14\bin\ 说明运行在tomcat下。ServletContext servletContext = this.getServletContext();System.out.println(servletContext.getContextPath());//输出/s2。就是我们刚才命名的Application context。就是tomcat的webapp下的s2目录,//此时,这个目录就是我们左边Project的HelloServletPartTwo-1.0-SNAPSHOT这个目录!InputStream resourceAsStream = servletContext.getResourceAsStream("./WEB-INF/classes/resoDB.properties");//知道了当前再哪个目录下,相对定位参照左边就十分容易写出来了。Properties properties = new Properties();properties.load(resourceAsStream);String userName = properties.getProperty("userName");String password = properties.getProperty("password");resp.setContentType("text/html");resp.setCharacterEncoding("utf-8");resp.getWriter().println(userName + ":" + password);//resourceAsStream = servletContext.getResourceAsStream("./WEB-INF/classes/com/zmj/servlet/JavaDB.properties");//InputStream的对象规范是只能读取一次,不能复用,可以看源码InputStream resourceAsStream1 = servletContext.getResourceAsStream("./WEB-INF/classes/com/zmj/servlet/JavaDB.properties");properties.load(resourceAsStream1);userName = properties.getProperty("userName");password = properties.getProperty("password");resp.getWriter().println(userName + ":" + password);resourceAsStream.close();resourceAsStream1.close();}
}
代码结果十分简单,但从无到有的思考过程得到的结论很有意思,在此小结下。
项目部署再理解
面向对象和面向过程文件级别分析
我以前一直的梦想就是用Java编写一个程序,然后我把这个程序文件发给用户,用户裸机任何环境都不用安装,可以直接在桌面进行点击就运行;然后直到今天我才发现,这是不可能的;是我搞错了。Java是不能形成直接可执行的.exe文件,它只能形成class文件。
面向对象文件级别:Java编写程序,其源程序(.java)经过编译形成的都是一个一个class字节码文件,这些字节码文件一起合力并且借助JVM和JRE(Java运行时环境),才能让用户在dos命令下进行程序执行。
面向过程文件级别:C语言编写程序,其源文件(.c)经过编译和链接直接形成了.exe可执行文件,点击这个可执行文件就直接一顺溜的全部执行完了。
所以,梦想破灭了。Java程序不能像C语言程序那样直接在裸机上进行运行。所以运行Java程序,必须需要JRE(Java Runtime Environment)。
所以这就是为什么Java程序与同样的C语言程序运行慢的原因,Java还要去启动那些前期工作,JVM加载类等;但是世界总是关一扇门,开一扇窗的;Java正是因为有了JVM的存在,使得它可移植性非常高,因为运行时启动JVM就相当于一个中间件,隔离了不同平台的差异性,只需要不同平台编写不同平台的JVM即可,实现了一次编写(形成class文件),处处运行(不同平台的JVM);而C语言就要进行重新编译链接了。
所以这就是为什么web项目,用Java开发;因为访问网页的终端可能平台、型号、操作系统都不同,但它们就是可以看到同样的页面。
扯远了,回到正题来。
部署理解
我们在IDEA编辑器环境下进行编写代码,增加依赖,添加资源等都仅仅只是在IDEA的workSpace下进行的,仅仅是在IDEA的workspace下增加了源代码级别的文件添加。这些都是还没有点击Run Tomcat之前,当点击了之后,就会发生变化。
Tomcat目录下的webapps目录下多了一个目录并且可以通过网页访问到相关程序,这时候就代表部署成功;这个多出来的文件与IDEA下Workspce多出来的target是一模一样。这个文件就是我们以后web开发需要进行部署的文件,部署(deploy)就是这个意思。
以后的网站发布也差不多是这意思,将建立好的包上传到服务器上进行部署,用户进行访问就是了。
修改代码,重启tomcat
为什么每次修改完代码,都需要重新启动tomcat,因为tomcat用的是未修改前的java的class文件,已经经过编译成了写死的class文件了,修改Java源代码,就需要修改class文件,就需要tomcat重新部署。这也就是网站更新的最初原型。
虽然上述的ServletContext的四个功能好像都十分有用,但是工程上我们不用;而是使用另外两个重要的对象Request和Response。数据共享可以用cookie和session去做;初始化参数几乎不用;请求转发可以用重定向去做;读取资源文件我们可以用反射去做。
4.4、HttpServletResponse
web服务器接收到客户端的http请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,代表响应的一个HttpServletResponse。
- 如果要获取客户端请求过来的参数:找HttpServletRequest
- 如果要给客户端响应一些信息:找HttpServletResponse
去看源码,有常量(响应状态码),有方法(设置头部、编码等信息)。
4.4.1、下载文件
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 要获取下载文件的路径String realPath = "F:\\班级管理\\西开【19525】\\2、代码\\JavaWeb\\javaweb-02-servlet\\response\\target\\classes\\1.png";System.out.println("下载文件的路径:"+realPath);// 2. 下载的文件名是啥?String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);// 3. 设置想办法让浏览器能够支持(Content-Disposition)下载我们需要的东西,中文文件名URLEncoder.encode编码,否则有可能乱码resp.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(fileName,"UTF-8"));// 4. 获取下载文件的输入流FileInputStream in = new FileInputStream(realPath);// 5. 创建缓冲区int len = 0;byte[] buffer = new byte[1024];// 6. 获取OutputStream对象ServletOutputStream out = resp.getOutputStream();// 7. 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端!while ((len=in.read(buffer))>0){//8.用outputStream将缓冲区的数据输出到客户端。out.write(buffer,0,len);}in.close();out.close();
}
4.4.2、重定向
void sendRedirect(String var1) throws IOException;
请求转发和重定向区别
再次复习一遍。
- 请求转发因为借助ServletContext,所以只能转发给同一个Web应用中的servlet,而重定向到同一个站点上的其他应用程序中的资源,甚至可以用绝对URL重定向到其他站点的资源。
- 请求转发完成后URL地址不会变,而重定向访问过程结束后,URL发生变化。
- 请求转发浏览器只发送了一次请求和收到一次响应;而重定向一共发出了两次请求,两次响应。
- 请求转发不知道自己要的实际资源来自于谁,它只认定中间商(URL没发生变化看出);而重定向知道自己的资源不出自于中间商,来源是谁它也知道(朋友借钱例子)。
4.5、HttpServletRequest
HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器, HTTP请求中的所有信息会被封装到HttpServletRequest,通过这个HttpServletRequest的方法,获得客户端的所有信息;
可以得到表单的信息getParameter("")。
Request和Response十分重要,可以看到参数就为它俩,因此浏览器传递的信息在底层就是以Request和Response存在。
request和response到底可以看作什么
其实本质就是一个填充信息封装包,拆包获得信息的过程。浏览器与后台的交互就是靠request和response,浏览器给后台的称为request,后台给浏览器的称为response,并且这两个对象可以说是绑定在一起的,也就是说浏览器知道自己发了什么请求,得到了什么响应;后台也知道自己收到了什么请求,给了什么响应。可以类比Java中的getter和setter。
五、Cookie和Session
会话
会话:用户打开一个浏览器,同一个web工程(服务器)下,点击了很多超链接,访问多个web资源,关闭浏览器,这个打开浏览器直到关闭浏览器过程可以称之为一次完整的会话;不同的个浏览器打开同一个页面,或打开不同的web工程(访问不同的服务器)就是不同的会话了。
无状态连接和有状态连接(短连接和长连接)
我们在学习以前的C/S模式时,Client和Server之间维护了一个通信信道(Socket、DataInputStream和DataOutputStream),它们之间传输数据都是通过这个通信信道进行的,信道是双端的、共有的且可感知的,一旦某一方出现异常掉了,另外一端可以立刻知道,这就是有状态连接,即它可以感知对端存在。
- 优点:稳定。因为是双端之间建立的一个双方独有的通信信道,一个通道将两端连接起来,可以稳定收到对端发的消息。
- 缺点:服务器需要和每个客户端都维持一个通信信道,如果客户端数量多了,服务器可能会变慢,因为每个通信信道都消耗服务器的内存资源,也就是说不能维持大量客户端连接。
而现在学的是B/S模式,发送一次HTTP请求和响应就断开连接了(在HTTP/1.0 时代),远端服务器根本感知不到客户端的存在,更别说状态了,服务器相当于一个收发站,收到请求,根据请求形成响应结果,将响应返回。世界上客户端(浏览器)这么多,我还记你的信息干嘛,服务器根本不管你来了几次请求,感知不到是谁发的请求,发了几次根本不在意。通信信道用一次就关了,不管发没发到,相关通信信道只建立了一次,后面再建立和这次没有任何关系。
- 优点:可支持大量客户端连接。是一次短平快的连接。
- 缺点:无法感知对端状态,不能保证信息是否完整的到达。
用一句很简单的话总结就是:服务器认识客户端(有状态、长连接),服务器不认识客户端(无状态、短连接)。
其实双方上面的缺点现在早都有解决方法了。对于长连接,有个NIO技术,采用多路复用技术来解决维持大量通信信道,即开一个通信信道来解决多个连接的客户端。对于短连接,其在HTTP/1.1时代HTTP首部有个Connection属性来标识保持持久连接,Connection: keep-alive 。
Cookie和Session
回顾下原来的知识,又扯远了。
前面说B/S是无状态的,不能识别客户端的身份,但现在大部分网站登录之后,下次再访问就不需要再登录了,直接登陆好了。这就是使用到了Cookie和Session技术。也就是说Cookie和Session就是为了维持会话。
通俗的一个问题:一个网站,你怎么证明你来过呢?
- 服务端给客户端一个信件,客户端下次访问服务端带上信件就可以了;Cookie
- 服务器登记你来过了,下次你来的时候我来匹配你;Session
cookie工作原理
(1)浏览器端第一次发送请求到服务器端
(2)服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie通过Request-Response方式发送到浏览器端。浏览器收到该cookie会将该cookie存在本地APPData文件中。
(3)浏览器端再次访问服务器端时会携带服务器端创建的Cookie
(4)服务器端通过Cookie中携带的数据区分不同的用户
session工作原理
session技术是基于cookie技术的。
(1)浏览器端第一次发送请求到服务器端,服务器端创建一个Session对象,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端。
(2)浏览器端发送第N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID的Cookie对象
(3)服务器端根据name为JSESSIONID的Cookie的value(sessionId),去查询并获得Session对象,从而区分不同用户。
5.1、cookie和session之我见
首先,通俗易懂的来说,它俩就是一个大池子(map),由多个键值对组成,不要想的太难,就是用来存放常用/安全信息的,cookie是存储在客户端的一个池子,session是存储在服务器上的一个池子。
- cookie数据保存在客户端,session数据保存在服务端。
- 代码级别:cookie 存 String,String。session存String,Object(实际上是json)。
- 敏感(密码)信息的存储是个问题。存在cookie中,则客户机可以查看,一些别有用心的破坏者可以复制/抓包拦截获得cookie信息,以进行伪造攻击(cookie欺骗)。而Session存在服务器上,对客户机是透明的,所以不存在安全问题;但是如果该服务器有大量的客户端,服务器就维护大量Session对象,耗费大量内存,这对服务器性能是很大挑战。所以,这又是一次性能和安全的取舍,如果为了安全就选用Session存储敏感信息,如果为了高并发,就选用Cookie。Cookie不安全,但服务器性较高。Session安全,但服务器性能较低。
- 两者有效期不同。cookie只要你本地cookie信息没有被删除,它就一直有效(setMaxAge方法设置时间)。而session,如果关闭浏览器这个session就会消失,再打开就是重新申请session(即cookie中添加一个键为sessionID的键值对)了。
- 两者跨域支持不同。Cookie支持跨域名访问(只能跨二级域名来访问,不能跨一级域名来访问),例如将domain属性设置为“.abc.com”,则以 “.abc.com为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中。而Session则不会支持跨域名访问。Session仅在他所在的域名有效。原理:cookie存在在本地上,一些键值对一直在本地保存着;session不同的服务器会创建自己不同的session对象。
- cookie大小有限制的,这点确实,因为毕竟存在客户端的本地存储里面。但不要认为session就是无限制的,它也要耗费存储空间啊,耗费的是服务器的,所以你感觉好像是无限的。应该说一次request和response过程发送的cookie的数量是有限制的,因为如果你发多了,整个消息头全部都是cookie,还怎么传输真正有效的信息?
- cookie在request和response中传递,seesion是服务器建立的,sessionid放在cookie里,传递过来。每个用户(浏览器)有自己独有的sessionid。
Cookie与Session的搭配运用在实践项目中会完成很多意想不到的效果。
5.2、区分Session和ServletContext
不少人就会感觉Session和ServletContext的数据共享的功能好像一样了,都是放在远端的存储池子,其实不然。
两者的作用范围不同。Session是对于某一个特定用户(浏览器),只有该用户可以从session池子取值,当他进行完一次会话后就会销毁了;而ServletContext(后面演化为applicationContext)是对整个webApp工程的存储池子,他对任何访问这个web的用户都是可见的,公有的,所有用户都可以从servletContext池子取值。
B/S的用户的概念再解释下:
- 开多种浏览器。例如一个chrome,一个firefox,这就是两个用户。
- 开同一个浏览器完成一次会话后,即关闭浏览器,然后再打开这也是多个用户。
六、JSP
6.1、JSP基础
Java Server Pages : Java服务器端页面,也和Servlet一样,用于动态Web技术!
写JSP就像在写HTML,HTML只给用户提供静态的数据,JSP页面中可以嵌入JAVA代码,为用户提供动态数据。
浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet!
也就是说,JSP本质还是一个Servlet,JSP写的任何东西最后都会转换成一个实现Servlet的Java类。。
在JSP页面中;只要是 JAVA代码就会原封不动的输出;如果是HTML代码,就会被转换为:
out.write("<html>\r\n");
这样的格式,输出到前端!
JSP基础语法
<%%> 写java代码。
<%= %>变量或表达式
<%!%> 声明,编译到JSP生成Java的类中!除了这个其他的都在_jspService方法中!。
<%--注释--%>
JSP指令
<%@page args.... %> //美化404页面
<%@include file=""%> //公共页面,// =====================================//第一种添加公共页面方式
<%--@include会将两个页面合二为一--%><%@include file="common/header.jsp"%>
<h1>网页主体</h1><%@include file="common/footer.jsp"%><hr>//第二种添加公共页面方式
<%--jSP标签jsp:include:拼接页面,本质还是三个--%>
<jsp:include page="/common/header.jsp"/>
<h1>网页主体</h1>
<jsp:include page="/common/footer.jsp"/>
建议使用第二种,因为这样不会造成变量命名冲突,而方式一合成为一个页面会导致变量命名冲突。
6.2、九大内置对象
前面说了,jsp文件本质还是一个类,去看任意一个jsp类的源代码,都会存在九个成员。
- pageContext( javax.servlet.jsp.PageContext)JSP的页面容器
- request(javax.servlet.http.HttpServletrequest) 获取用户的请求信息
- response(javax.servlet.http.HttpServletResponse) 服务器向客户端的回应信息
- session(javax.servlet.http.HttpSession) 服务器为每个用户都生成一个session对象,用来保存每一个用户的信息
- application (javax.servlet.ServletContext) 表示所有用户的共享信息
- config (javax.servlet.ServletConfig) 服务器配置信息,可以取得初始化参数
- out (javax.servlet.jsp.jspWriter) 页面输出
- page (java.lang.object)代表JSP本身,类似于Java编程中的 this 指针
- exception (java.lang.Throwable)异常
四种作用域范围。
page:只在一个页面中保存属性。 跳转之后无效。
request:只在一次请求中有效,服务器跳转之后有效。 客户端跳转无效
session:在一次会话中有效。服务器跳转、客户端跳转都有效。 网页关闭重新打开无效
application:在整个服务器上保存,所有用户都可使用。 重启服务器后无效
pageContext.setAttribute("name1","x1号"); //保存的数据只在一个页面中有效
request.setAttribute("name2","x2号"); //保存的数据只在一次请求中有效,请求转发会携带这个数据
session.setAttribute("name3","x3号"); //保存的数据只在一次会话中有效,从打开浏览器到关闭浏览器
application.setAttribute("name4","x4号"); //保存的数据只在服务器中有效,从打开服务器到关闭服务器
request:客户端向服务器发送请求,产生的数据,用户看完就没用了,比如:新闻,用户看完没用的!
session:客户端向服务器发送请求,产生的数据,用户用完一会还有用,比如:购物车;
application:客户端向服务器发送请求,产生的数据,一个用户用完了,其他用户还可能使用,比如:聊天数据、访问量等;
四种作用域之我见
作用域想象成该池子可以维持存在的时间。
page作用域是代表仅仅就在该页面下的一个存放容器,一旦进行了页面跳转,即发生重定向或超链跳转(请求转发不算),也就是看URL栏有无变化,有变化该容器存的数据和该容器就消失了。
request作用域:仅代表一次request的容器,将键值对都放在一次请求里,是客户端向服务器发起的一次请求,这次请求完了,该容器和数据消失。
session作用域:前面我们讲过,新的用户产生一次新的会话,服务器就会为它产生一个session对象并存在服务器端,再产生一个sessionId掺在response里发给客户端,以便下次服务器认识客户端。可以想象为这个sessionId对应了服务器那边一个存储池子。该作用域进行了用户的切换(不同浏览器)和一次完整的会话(关闭浏览器)就会消失。
application作用域:对于同一个web工程,想象成服务器那端的一个公共存储空间,任何访问该web工程的用户都可以使用,类似于一个全局变量(map);随着web工程服务器的关闭而消失。
6.3、JSP标签、JSTL标签、EL表达式
<!-- JSTL表达式的依赖 -->
<dependency><groupId>javax.servlet.jsp.jstl</groupId><artifactId>jstl-api</artifactId><version>1.2</version>
</dependency>
<!-- standard标签库 -->
<dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version>
</dependency>
EL表达式: ${ }
核心标签
c:if
<head><title>Title</title>
</head>
<body><h4>if测试</h4><hr><form action="coreif.jsp" method="get"><%--EL表达式获取表单中的数据${param.参数名}--%><input type="text" name="username" value="${param.username}"><input type="submit" value="登录">
</form><%--判断如果提交的用户名是管理员,则登录成功--%>
<c:if test="${param.username=='admin'}" var="isAdmin"><c:out value="管理员欢迎您!"/>
</c:if><%--自闭合标签--%>
<c:out value="${isAdmin}"/></body>
记住param.xxxx是从HTML中拿去控件元素,而var出现是在当前jsp下定义一个变量,{param.xxxx}是从HTML中拿去控件元素,而var出现是在当前jsp下定义一个变量,param.xxxx是从HTML中拿去控件元素,而var出现是在当前jsp下定义一个变量,{xxxx}就直接是从当前的jsp里拥有的变量取得了。
c:forEach
<%ArrayList<String> people = new ArrayList<>();people.add(0,"张三");people.add(1,"李四");people.add(2,"王五");people.add(3,"赵六");people.add(4,"田六");request.setAttribute("list",people);
%><%--
var , 每一次遍历出来的变量
items, 要遍历的对象
begin, 哪里开始
end, 到哪里
step, 步长
--%><%--类似Java的foreach循环--%>
<c:forEach var="people" items="${list}"><c:out value="${people}"/> <br>
</c:forEach><hr>
<%--类似Java的for循环--%>
<c:forEach var="people" items="${list}" begin="1" end="3" step="1" ><c:out value="${people}"/> <br>
</c:forEach>
6.4、JSP之我见
在学完JSP之后,它是一个动态网页开发技术,我认为JSP = HTML + JAVA + JSP自己的语法。其本质就是一个Servlet,在JSP被访问时,tomcat会把JSP转换为一个JAVA类,然后编译执行这个JAVA类。JSP在第一次被访问时会被转换为一个Servlet类并执行。
JSP技术之所以现在工程上没落就是因为眉毛胡子一把抓,前端代码和后台代码出现在一个文件中,没有前后端分离,这样使得耦合性太高,维护起来极不容易。工程上不要耦合性很高的系统,不然会牵一发而动全身。
因此,如今很火的前后端分离才是目标。术业有专攻。前端只负责展现和交互,前端开发者尽力搞前端,搞数据如何在页面优美的展示,如何设计优美的界面;后端负责核心业务逻辑,后端开发者就使劲优化服务器,处理响应,往服务器处理效率这方面走。前后端通过API进行交互。服务器端把数据返回给前端就不再关心这些数据用在哪里、如何布局、什么样式。
但是,后端开发者需要懂一些前端基础知识才能站在它们的角度上考虑更需要什么形式的数据,也为后端开发者自己如何操作提供方向。
七、MVC三层架构
MVC:Model模型、View视图、Controller控制器模型。
以前的架构。
Controller层由Servlet直接实现,View由JSP实现。
用户直接访问控制层,控制层就可以直接操作数据库;
servlet--CRUD-->数据库
弊端:程序十分臃肿,不利于维护
servlet的代码中:处理请求、响应、视图跳转、处理JDBC、处理业务代码、处理逻辑代码
controller层要做的太多了。架构:没有什么是加一层解决不了的!
程序猿调用
↑
JDBC (实现该接口)
↑
Mysql Oracle SqlServer ....(不同厂商)
再细分的架构。
Model
- 业务处理 :业务逻辑(Service)
- 数据持久层:CRUD (Dao - 数据持久化对象)
- 作用:封装数据,执行业务逻辑
- 包含:JavaBean(实体类)、Service(业务逻辑层)、DAO(数据访问层\持久层)
View
- 展示数据
- 提供链接发起Servlet请求 (a,form,img…)
- 作用:和用户进行交互
- 包含:JSP和HTML
Controller (Servlet)
- 接收用户的请求 :(req:请求参数、Session信息….)
- 交给业务层处理对应的代码
- 控制视图的跳转
- 作用:获取用户输入,并进行功能的分发
- 包含:Servlet
八、Filter(过滤器)和Listener(监听器)
8.1、过滤器
Filter:过滤器 ,用来过滤网站的数据;
- 处理中文乱码(每次每个servlet都要设置utf-8)
- 登陆验证
使用过滤器的步骤
1.导包(servlet-api)
2.编写过滤器(实现javax.servlet.Filter)
public class CharacterEncodingFilter implements Filter {//初始化:web服务器启动,就以及初始化了,随时等待过滤对象出现!public void init(FilterConfig filterConfig) throws ServletException {System.out.println("CharacterEncodingFilter初始化");}//Chain : 链/*1. 过滤中的所有代码,在过滤特定请求的时候都会执行2. 必须要让过滤器继续同行chain.doFilter(request,response);*/public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {request.setCharacterEncoding("utf-8");response.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=UTF-8");System.out.println("CharacterEncodingFilter执行前....");chain.doFilter(request,response); //让我们的请求继续走,如果不写,程序到这里就被拦截停止!System.out.println("CharacterEncodingFilter执行后....");}//销毁:web服务器关闭的时候,过滤器会销毁public void destroy() {System.out.println("CharacterEncodingFilter销毁");}}
3.在web.xml进行声明配置
<filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>com.kuang.filter.CharacterEncodingFilter</filter-class></filter><filter-mapping><filter-name>CharacterEncodingFilter</filter-name><!--只要是 /servlet的任何请求,会经过这个过滤器--><url-pattern>/servlet/*</url-pattern><!--<url-pattern>/*</url-pattern>--><!-- 别偷懒写个 /* --></filter-mapping>
过滤器就相当于一个中间人,浏览器到servlet都要经过绑定好的Filter,之前我们的映像图是访问某个地址直接到相关映射的servlet,而现在会在双方进行传输request和response之时都会经过filter。
8.2、监听器
实现一个监听器的接口;(有n种监听器)
1.编写一个监听器。
//统计网站在线人数 : 统计session
public class OnlineCountListener implements HttpSessionListener {//创建session监听: 看你的一举一动//一旦创建Session就会触发一次这个事件!public void sessionCreated(HttpSessionEvent se) {ServletContext ctx = se.getSession().getServletContext();System.out.println(se.getSession().getId());Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");if (onlineCount==null){onlineCount = new Integer(1);}else {int count = onlineCount.intValue();onlineCount = new Integer(count+1);}ctx.setAttribute("OnlineCount",onlineCount);}//销毁session监听//一旦销毁Session就会触发一次这个事件!public void sessionDestroyed(HttpSessionEvent se) {ServletContext ctx = se.getSession().getServletContext();Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");if (onlineCount==null){onlineCount = new Integer(0);}else {int count = onlineCount.intValue();onlineCount = new Integer(count-1);}ctx.setAttribute("OnlineCount",onlineCount);}/*Session销毁:1. 手动销毁 getSession().invalidate();2. 自动销毁*/
}
2.在web.xml中注册监听器
<!--注册监听器-->
<listener><listener-class>com.kuang.listener.OnlineCountListener</listener-class>
</listener>
8.3、Filter实现权限拦截
这个例子是我经过理解后自己进行实现的,其中又理解许多Javaweb中项目部署、servlet和jsp两者区别的道理,在此进行总结。
整体分包如图所示。
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>欢迎登录</h1>
<%--TODO 学习点一 大坑。form表单(action)提交如果有项目名写法一:<form action="servlet/login" method="post">写法二:<form action=${pageContext.request.contextPath}/servlet/login method="post">这个pageContext.request.contextPath就是子工程名称/s1总结一句话 加/记得加工程名, 不加/就在当前工程下直接写如果采用分治工程思想,/代表最根部的主工程,不加/就代表当前工程。当前仅有一个工程时,两者一样。一旦相关tomcat配置相关deployment设置相关application context,该值就是该子工程的根目录。猜想这可能到时候与子工程页面之间相互跳转有关。所以如果采用分治工程解决主工程,在工程部署时关tomcat配置相关deployment设置相关application context,如果有了这个application context就一定注意路径问题(加不加/问题)。
--%><form action=${pageContext.request.contextPath}/servlet/login method="post">
<%--还可以这样写--%>
<%--<form action="servlet/login" method="post">--%><input type="text" name="userNameInput"><input type="submit">
</form><%-- TODO 学习点二:每次直接访问localhost:8080/s1/servlet/login直接就自动提交表单,因为输入框是空字符串,所以直接跳到error页面。
这是对的,/servlet/login本来实际的意义就是访问一个找这个urlpattern映射的servlet。所以直接执行servlet。
因此,以后访问首页/欢迎页面就在url填精确到某个文件(.jsp或.hmtl)即可。
--%>
</body>
</html>
可以看到登录界面仅仅是一个提交数据的表单,提交的地方为一个servlet(后台操作),其URL为${pageContext.request.contextPath}/servlet/login
。
然后就去编写有关登录时后台进行的判断等操作。
UserLoginServletDemo
package com.zmj.servlet;import com.zmj.util.Common;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class UserLoginServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Object userNameInput = req.getParameter("userNameInput");if ("admin".equals(userNameInput)) {req.getSession().setAttribute(Common.USER_SESSION,req.getSession().getId());resp.sendRedirect(req.getContextPath()+"/sys/success.jsp");} else {resp.sendRedirect(req.getContextPath() + "/error.jsp");}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}
}
Servlet(后台操作)编写完后,立刻去web.xml去绑定它(每写完一个servlet,就去绑定它这是必须的)。
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"><filter><filter-name>sysFilter</filter-name><filter-class>com.zmj.filter.SysFilter</filter-class></filter><filter-mapping><filter-name>sysFilter</filter-name><url-pattern>/sys/*</url-pattern><!--过滤sys目录下的一切,说明有权限限制(过滤器)访问这个文件下的--></filter-mapping><servlet><servlet-name>userLoginServletDemo</servlet-name><servlet-class>com.zmj.servlet.UserLoginServletDemo</servlet-class></servlet><servlet><servlet-name>userLogoutServletDemo</servlet-name><servlet-class>com.zmj.servlet.UserLogoutServletDemo</servlet-class></servlet><servlet-mapping><servlet-name>userLoginServletDemo</servlet-name><url-pattern>/servlet/login</url-pattern></servlet-mapping><servlet-mapping><servlet-name>userLogoutServletDemo</servlet-name><url-pattern>/servlet/logout</url-pattern></servlet-mapping></web-app>
可以看到绑定时<url-pattern>
却没出现子项目的根路径,这是因为web.xml就在这个项目里,默认前面就有该子项目的路径,所以不用加,但记住前面强调的**<url-pattern>
必须加/,必须加/,必须加/**。
回头看UserLoginServletDemo的判断操作,如果用户名匹配正确跳转sys下的success页面,如果匹配不正确跳转error页面。
error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body><h1>用户名有误</h1><%--TODO 三 jsp表达式 这样拼接字符串,直接写在后面--%>
<%--<a href= ${pageContext.request.contextPath} + "/login.jsp">点击回到登录页面</a> 这种拼接都是错误的--%>
<%--<a href= ${pageContext.request.contextPath + "/login.jsp"}>点击回到登录页面</a> 这种拼接都是错误的--%>
<%--<a href= ${pageContext.request.contextPath}"/login.jsp">点击回到登录页面</a> 这种拼接都是错误的--%>
<a href="${pageContext.request.contextPath}/login.jsp">点击返回登陆页面</a>
<%--TODO 四 返回登陆页面直接就返回页面(入口)即可,不要再走servlet,一旦路径跳转servlet就会执行操作
从这里,明白了web设计为什么会有个欢迎入口页面了,牌面;也是展示一切后台动作的开始。
--%></body>
</html>
从这学习了jsp中字符串拼接的方法。以及理解servlet(后台操作)和jsp(前台展示)的划分。
/sys/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>主页</h1>
<lable>里面放一些只有登录才可以访问的数据</lable>
<%--<p><a href="../login.jsp">注销</a></p>--%>
<%--todo 五 一开始直接写的跳转登录页面,发现这样是不对的,你还有些事情没做呢,清除一些信息,所以又要借助servlet
脑子始终记住,jsp只是用来展示数据的view层,servlet进行业务、逻辑判断操作等,是controller层。
所以href也可以不能直接跳转页面(view),应该让他先进行一些操作(servlet)再进行页面跳转
所以如果页面要进行跳转,有一些必要的操作就走servlet,然后servlet再进行页面跳转
如果没什么操作需要进行,直接进行jsp页面跳转即可
--%>
<a href=${pageContext.request.contextPath}/servlet/logout>注销</a><%--
todo 六 这里写不好,在jsp(view)加了逻辑代码,不职责单一,写个过滤器去实现它
<%if(request.getSession().getAttribute(Common.USER_SESSION) == null) {response.sendRedirect(request.getContextPath()+"/login.jsp");}
%>
--%>
</body>
</html>
从中再次学习到了servlet和jsp的区别。servlet就是后台,通过url-pattern进行访问跳转到相应(web.xml映射)的类;jsp就是前台,只是展示数据的,页面跳转就是jsp之间进行的,只不过在跳转时始终想明白如果有需要进行的操作则必须再去servlet(后台)进行,然后通过servlet(反正拥有request和response)再进行页面跳转。
UserLogoutServletDemo
编写注销的servlet
package com.zmj.servlet;import com.zmj.util.Common;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class UserLogoutServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Object attribute = req.getSession().getAttribute(Common.USER_SESSION);if(attribute != null) {req.getSession().removeAttribute(Common.USER_SESSION);}resp.sendRedirect(req.getContextPath()+"/login.jsp");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}
}
注销的操作仅仅是把相关键值对删除。
去web.xml绑定相关servlet。
到现在,基本页面跳转功能实现了。只不过有这样的问题,没有进行登录操作,直接http://localhost:8080/s1/sys/success.jsp,直接成功进入成功页面。这样肯定不行,没有登陆银行账号,直接拿钱,这是当然不可以的。所以一定要进行权限。
可以采用直接在success.jsp加java判断代码,只不过这样不满足职责单一,jsp只管数据的显示。所以分离出去,我们用过滤器实现。
SysFilter
package com.zmj.filter;import com.zmj.util.Common;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class SysFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) resp;if(request.getSession().getAttribute(Common.USER_SESSION) == null){response.sendRedirect(request.getContextPath() + "/login.jsp");}chain.doFilter(request,response);}@Overridepublic void destroy() {}
}
再去web.xml配置相关过滤器,即可实现。
然后提取公共部分,使用变量达到一改全改。
package com.zmj.util;public class Common {public static final String USER_SESSION = "USER_SESSION";
}
8.3.1、项目部署、servlet和JSP再理解
- 采用分治思想编写web工程(项目)。如果在工程部署时关于tomcat配置相关deployment设置相关application context如果有了这个application context就一定注意路径问题(加不加/问题)。加/记得加工程名, 不加/就代表在当前工程下。(具体看TODO一)
- 关于servlet和jsp。URL写/servlet/login本来实际的意义就是访问一个在web.xml找这个urlpattern映射的servlet,然后直接执行servlet。以后访问首页/欢迎页面就在url填精确到某个文件(.jsp或.hmtl)即可。(具体看TODO二)
- jsp中拼接字符串是和平常语言是不一样的。(具体看TODO三)
- 再进行页面跳转时,如果有一些必要的操作就写servlet,绑定servlet,跳转相关servlet,然后servlet再进行页面跳转。如果没什么操作需要进行,直接进行jsp页面跳转即可。跳转(sendRedirect)到最终一定是落实某个具体页面的(HTML、JSP)(具体看TODO五)
- 强烈建议:web开发中,所有涉及到路径填写的地方(例如sendRedirect和form的action提交),每个路径把当前子工程的根目录加上,然后对照IDEA左边去进行深入看。
- servlet就是后台操作,就是controller层,关注业务逻辑;jsp就是前台,就是view层,只关心数据如何显示的,两者各司其职。
同时,一个映像图也出来了。
web.xml相当于一个中间纽带,将浏览器,servlet,jsp和html连接起来了。它们之间的关系需要经过web.xml将它们联系起来,才能实现相关的跳转。一定注意路径问题!
回答最初的问题
Q:当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历了什么?
A:简单回答。 域名解析 —>发起TCP三次握手建立连接—>浏览器发HTTP请求(request)—>服务器响应请求,返回给浏览器(response)—>解析HTML资源—>页面渲染
参考
https://www.cnblogs.com/tangjian07/p/10835687.html
https://www.cnblogs.com/wupeixuan/p/8747918.html
一、域名解析
浏览器会首先解析这个域名。具体解析过程是首先在浏览器缓存里查找(最近最快呗),有则取;没有就会去读取host文件,通常是在C:\Windows\System32\drivers\etc\hosts
,有则取;还没的话就需要去网络配置好的DNS服务器去查找了,相关DNS服务器是一个树形结构,向上查找的过程,有则返回。经过域名解析后,浏览器就得到了要访问的网址真正的IP地址,只有IP才可以唯一定位一个网络中的主机。
递归解析就按逐级返回。
迭代解析就是客户机和服务器对话。
二、发起TCP的三次握手
浏览器得到对应的IP地址,会开启一个随机端口(1024< port <65535)向Web服务器的80(http:80 https:443)端口发起TCP的连接请求。这个请求在浏览器这边形成(层层封装,向下传递),传输到服务器端(途径各种路由器,交换机等),进入网卡,然后到TCP/IP协议栈(层层解包,向上传递),达到应用层,可以识别报文内容。如此经过三次,建立了TCP/IP的连接。(SYN同步,ACK确认)。
关于三次握手,为什么三次我以自己认为最简单方式解释。它们需要的是一个双工通信,即,既可以发送,也可以接受;Client发送第一个syn包,它什么都不能确定;Server收到第一个syn包只能证明自己接受功能正常,返回syn,ack包;Client收到这个包,就明白了我发送功能和接受功能都正常,然后再返回一个ack包;Server收到才能确定自己发送功能也是正常的。至此,双方满足双工通信。
三次握手的作用就是为了确定双方有一个稳定的连接。
三、浏览器发送HTTP请求
双方建立好连接后。浏览器就要开始准备发送一个HTTP请求(request)了。分析任意一种request会发现其由三个部分组成。
- 请求方法版本信息(通常找HTTP/1.1)
- Request Header(请求头)包含许多有用的信息,例如编码格式,connection键值对。
- 请求正文。主体部分。
四、服务器响应HTTP请求,返回HTTP响应
服务器收到HTTP请求后,就会处理浏览器的request,处理结果形成一个HTTP响应(response)。HTTP响应与HTTP请求相似,HTTP响应也由3个部分构成。
- 状态行。例如200 OK,404请求资源不存在,500服务器错误。
- 响应头。Content-Type文本格式,Content-Encoding解码格式等键值对。
- 响应体。正文部分,根据格式返回具体信息。
五、浏览器解析HTML资源,并请求HTML中的资源
浏览器解析html以构建dom树,自上而下加载。如果遇到请求外部资源时,如图片、外链的CSS、JavaScript等,请求过程是异步的,并不会影响html文档进行加载。所以有时候网络情况不好的情况下,你会看到网站首先出来条条框框,再过一会图片和样式之类才加载上去。
六、页面渲染
浏览器把请求到的静态资源和HTML代码进行渲染,渲染之后呈现给用户。
至此,再用自己的话描述一遍。浏览器地址栏输入一个网址,计算机肯定是不能认识的,需要借助DNS域名解析,所谓域名解析就是IP地址和域名的一个映射关系。先在浏览器缓存中找,没有再去本地host文件找,没有再去配置好的DNS服务器去找,DNS服务器整体按级域名划分,再找不到浏览器就会报错误。找到了就获得服务器的IP地址。然后进行TCP三次握手建立连接,三次握手确定双方都是双工通信,有一个稳定的连接。连接建立成功后,浏览器准备HTTP请求,由请求版本HTTP/1.1,请求头键值对组成,请求体具体信息组成,发送给服务器。服务器收到HTTP请求后,进行响应,处理结果组织一个HTTP响应,由状态码200,响应头文本信息键值对,响应体正文部分组成发回给浏览器。一般第一个都是首页文件index.html,浏览器从上至下解析HTML以构建DOM树,遇到所需要的图片,js,css资源因为connection:keep-alive没断开再去服务器进行请求。浏览器对页面进行渲染,至此一个多姿多彩的首页就显示在我们浏览器页面上了。
JavaWeb映像图
学习完subms项目之后画的,有不同见解希望指出。
希望这个图可以帮到大家认识JavaWeb的主体框架,还有很多细节没有画。
css背景色应该为五彩斑斓的(它最花哨),我不会画= =。
【学习笔记】深入理解及个人感悟JavaWeb相关推荐
- SpringMVC:学习笔记(1)——理解MVC及快速入门
SprigMVC-理解MVC及快速入门 说明: 传统MVC-->JSPModel2-->Front Controller + Application Controller + Page C ...
- TCP/IP学习笔记-如何理解
任何技术的掌握都需要做到应用技能的熟练掌握,比如让你写一个实现亮灯的程序,你本能的知道加载头文件,写main函数,这就是熟练掌握的应用技能,让一个刚学C的人,肯定就会为为什么家在头文件,为什么要写ma ...
- tipi 深入理解php内核 pdf_大牛的学习笔记-深入理解Linux内核(完整版)
第一章.绪论 1.Unix文件可以是下列类型之一: a.正规文件(regular file) b.目录(directroy) c.符号链(symbolic link) d.块设备文件(block-or ...
- JavaScript --- [学习笔记]观察者模式 理解对象 工厂模式 构造函数模式
说明 本系列(JS基础梳理)为后面TCP的模拟实现做准备 本篇的主要内容: 观察者模式.工厂模式.构造函数模式 和 对对象的理解 1. 观察者模式 参考JavaScript设计模式 1.1 消息注册方 ...
- 莫队算法(普通莫队、带修莫队、树上莫队、不删除莫队)学习笔记【理解+套路/核心代码+例题及题解】
一.理解 我的理解就是巧妙的暴力,利用双指针以及分块思想,巧妙的移动双指针,时间复杂度可以达到O(NlogN). 强推博客:写的又好又全.链接 二.套路 1.普通莫队 [1]核心代码 bool cmp ...
- 【go学习笔记】理解Go语言的nil 【转】
最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频. nil是什么 相信写过Golang的程序员对下面一段代码是非常非常熟 ...
- 多线程学习笔记-深入理解ThreadPoolExecutor
java多线程中,线程池的最上层接口是Executor,ExecutorService实现了Executor,是真正的管理线程池的接口,ThreadPoolExecutor间接继承了ExecutorS ...
- 四元数左乘右乘_四元数、欧拉角学习笔记个人理解
一.背景知识:点乘.叉乘 复数的点乘:(ai+bj+ck)•(xi+yj+zk)=-(ax+by+cz) 复数的叉乘:(ai+bj+ck)×(xi+yj+zk)=(ax)i×i+(ay)i×j+(az ...
- JPA学习笔记---JPA理解---第一个JPA程序
1. a.JPA和Hibernate是sun提出的java持久化的规范.JPA是Hibernate,和TopLink,JDO等ORM框架 基础上发展而来的. b.和jdbc类似,比如刚刚没有jdbc的 ...
最新文章
- 市场营销部门OKR案例
- safair浏览器页面局部滑动问题
- 儿童编程软件python-Python编程工具pycharm的使用
- THYMELEAF 如何用TH:IF做条件判断
- 开发工具:收集12 个顶级 Bug 跟踪工具,值得收藏!
- 如何通过 反射 调用某个对象的私有方法?
- 计算机的发展英语600词,程序员必备的600个英语词汇
- system.exception所有子类详解
- TikTok是下一个流量蓝海吗?用户规模超抖音?
- Android在WindowManagerService和ActivityManagerService中的Token
- 公布几个流氓软件分析报告——哇哇资讯精灵
- 手动挡五个档位示意图_小型C1手动挡汽车档位分布示意图
- 常用域名管理后台网址
- 作为一个面试官,我是怎么来面试测试人员的?
- i5-8500 搭配 RTX3090 算不算奇葩
- 前端开发:webstorm永久破解
- java 实时监控微信扫码支付,支付成功跳转到成功页面
- 霍尔效应传感器的典型应用场合解析
- 【CSS3 霓虹字体特效】
- 【并行计算】Slurm的学习笔记
热门文章
- php ctr b,用PHP解密AES CTR Little Endian
- android 寺库trytry_wpf 中使用 ttf
- 如何在mmdetection3d下批量的预测单目3d检测结果并保存
- IDC机房之水冷系统
- dell 7040m 黑苹果_感受下价值三千多的移动硬盘:Dell移动Thunderbolt? 3固态硬盘
- 航班编程代码c语言,航班查询系统C语言源程序
- SAP CO 成本的分配
- 【IT项目管理】第七章课后习题
- Liquibase集成达梦数据库、Activiti集成达梦数据库
- 从一个URL到页面渲染完成发生了什么?