Java网页应用开发。它是【基于请求和响应】来开发的。
JavaSE中的结构是C/S Client-Server。
而JavaEE中Web是B/S Browser-Server。(浏览器不同,会有兼容问题,例如很多标签IE不兼容)
● 请求:客户端(浏览器)给服务器发送数据,叫请求Request;
● 响应:服务器给客户端(浏览器)回传数据,叫响应Response
1、JavaEE三层架构
项目中的包结构(分成多个包为了解耦,即降低代码的耦合度,方便项目后期的升级和维护):
● web/servlet/controller 接受请求和响应的web包
● service Service接口包
● service.impl Service接口实现类包
● dao Dao接口包
● dao.impl Dao接口实现类包
● pojo/entity/domain/bean JavaBean类包
● test 测试包
● utils 工具包
2、web资源的分类
web资源按照实现的技术和呈现的效果,可以分为静态资源和动态资源。
● 静态资源:html、css、js、txt文本、jpg图片、mp4视频...
● 动态资源:【Java中的】:jsp页面、Servlet程序
3、⭕ 创建Java Web项目:
(1) 新建一个项目(Web)。可以是静态Web等...
(2) 创建并书写相应的文件。
(3) 浏览器运行。
4、web中的【/】意义
在web中,【/】是一种绝对路径。
● 浏览器解析时,得到的地址是:http://ip:port/
● 服务器解析是,得到的地址是:http://ip:port/工程名/
但注意,服务器中使用response.sendRedirect("/");时,会将该内容发送给浏览器重定向,服务器不解析,而是浏览器解析,得到http://ip:port/。
● XML
● JSON
● Tomcat
● Servlet
● Filter
● Listener
● JSP
● EL
● JSTL
● Cookie
● Session
● AJAX
一【HTML和CSS】、二【JavaScript】、三【JQuery】
参考:框架1:Java Web - 前端 - 简书 (jianshu.com)
四、XML
XML全称为Extensible Markup Language,可扩展的标记性语言。
● 主要作用:
(1) 保存数据,而且这些数据具有自我描述性;
e.g
Java中有Student对象:
[ Student[id=1, name="张三"], Student[id=2, name="李四"] ]
students.xml
<students>
<student>
<id>1</id>
<name>张三</name>
</student>
<student>
<id>2</id>
<name>李四</name>
</student>
</students>
⭕ 注意:XML文件必须要有且仅有一个根元素,即没有父标签的元素。
(2) 作为配置文件
(3) 也可以作为网络传输数据的格式。(现在主要使用json文件)
1.1 文档声明
<?xml version="1.0" encoding="utf-8" ?>
以上内容就是xml的声明,其中:
● version 表示xml的版本
● encoding 表示xml文件的编码方式
1.2 元素(标签)
从(且包括)开始标签直到(且包括)结束标签的部分。
元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。
和html的元素(标签)定义一样,与之不同在于xml元素可以自命名。
● xml命名规则
● xml元素属性
属性可以提供元素的额外信息。
和html一样,每个属性的值必须使用引号引起来。
● xml注释
< !-- 注释内容 -- > 【去掉空格箭号左右空格】
● 文本区域(CDATA区)
当要显示特殊字符时(例如【<】),除了可以像html一样,用特殊字符【& lt;】(去掉多余空格)。
还可以使用文本区域语法,告诉浏览器,无需解析xml,直接显示成文本。
<![CDATA[
显示的文本内容
]]>
1.3 xml解析技术
不管是html文件,还是xml文件,他们都是标记型文档,都可以使用w3c组织制定的dom技术来解析。
dom技术会将整个文档视作一个document对象。
(1) 历史背景
早期JDK为我们提供了两种xml解析技术DOM和SAX【已经过时,但我们需要了解一下】
dom解析技术最初是由W3C组织制定的,而所有的编程语言都结合自身特点对该项技术进行实现,java也不例外。
● sun公司在JDK1.5时对dom解析技术进行了升级:SAX(Simple API for XML)
SAX解析,和W3C制定的解析不太一样。它是以类似事件机制通过回调告诉用户当前正在解析的内容。也就是说,它不是一次性创建大量dom对象,而是用到时一行一行的解析。因此在内存和性能使用上,都优于DOM解析。
● 第三方解析:
jdom 在dom基础上进行了封装;
dom4j 在jdom的基础上进行了封装;
pull 主要用在安卓手机开发,与sax非常类似,都是事件机制解析xml文件。
(2) dom4j解析技术 ()
很多框架基于dom4j来解析xml文件。可以去dom4j官网下载jar包。
● dom4j 解析步骤
I. 项目中创建一个lib目录,并添加dom4j的jar包,然后添加到项目类库中。
II. 创建SAXReader输入流,读取xml配置文件,生成Document对象。
public class test{
public static void main(String args[]) throws Exception{
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("xml文件路径");
}
}
III. 通过document对象得到根元素,通过根元素得到对象元素(标签)。
Element rootElement = document.getRootElement();
IV. 遍历将对象元素(标签)进行处理,创建成我们想要的Java对象。
IV(1) 传入标签名,返回标签元素的集合
List<Element> elements= rootElement.elements("标签名");
for(Element e: elements){
IV(2) 传入标签名,返回想要的标签元素
Element subElement = e.element("标签名");
IV(3) 通过Element对象的getText()方法,得到标签内容
String content = subElement.getText();
IV(4) 也可以直接通过Element对象的elementText("标签名")方法,得到标签内容
String content2 = subElement.elementText("标签名");
IV(5) 通过Element对象的attributeValue()方法,得到属性值
String attributeValue = subElement.attributeValue("属性名");
...根据得到的数据,创建Java对象...
}
五、Tomcat
由Apache组织提供的一款Web服务器,提供对jsp和Servlet的支持。它是一种轻量级的javaWeb容器(服务器),也是当前应用最广的JavaWeb服务器(免费)。
● 补充:tomcat的端口号默认8080;而http协议的默认端口号是80,省略不显示【看不到端口号的address就是80端口】。
1、安装和启动
直接找到需要用的Tomcat版本对应的zip(windows)/tar.gz(Linux)压缩包,解压到需要安装的目录即可。
● 文件目录
| - Tomcat
| - bin 可执行文件
| - conf 配置文件
可以通过【server.xml】文件,修改默认的8080端口号,然后重启tomcat服务器,才生效。
| - lib jar包
| - logs 运行时输出的日志信息
| - temp 运行时产生的临时数据
| - webapps 部署的Web工程。一个目录对应一个工程!
| - work 工作目录,用来存放运行时jsp翻译为Servlet的源码,以及Session钝化(对象序列化)的目录
Session的"钝化"和"活化"
当用户在一段时间内没有与Web应用程序进行交互时,服务器可能会将该session视为“不活动”,并将其保存在内存或磁盘中,这就是所谓的"Session钝化"。这样可以释放内存资源,避免空闲的session占用太多服务器资源。但是,一旦用户再次与应用程序进行交互,服务器就会重新“激活”这个session,重新加载之前保存的用户数据,并让用户可以继续他们之前的操作,这就是所谓的"Session活化"。
● 启动和关闭tomcat服务器
【终端输入】
1、启动tomcat服务器
>sh ./startup.sh 【也可以去指定sh文件双击】
2、关闭tomcat服务器
>sh ./shutdown.sh 【也可以去指定sh文件双击】
(windows下是双击bat批处理文件,并且要保持小黑框一直打开)
⭕ 测试是否成功启动tomcat:
在浏览器中输入任意一项地址:
(1) http://localhost:8080
(2) http://127.0.0.1:8080
(3) http://真实ip地址:8080
MacOS安装、启动和关闭 参考链接:Mac-Tomcat安装教程小白教学mac 安装tomcat爱吃Java的猴子的博客-CSDN博客
2、部署web工程到tomcat
部署后可以实现网络访问。
● 部署前工作【程序员的工作】:告诉tomcat,要部署哪个工程
● 方式一:将web工程拷贝到tomcat的webapps目录下。
● 方式二:配置文件映射。(这种方式不要求项目必须存在于webapps下)
在【tomcat/conf/Catalina/localhost】目录下新建xml文件。【一般一个工程一个文件】
● Context表示一个工程上下文
● 属性path表示工程在浏览器中的访问路径,属性docBase表示你的工程目录实际存放的位置。
mytest.xml
<? xml version="1.0" encoding="utf-8" ?>
<Context path="/browserDir" docBase="/home/usrname/myProject"/>
● 访问webapps的工程:http://ip:port/工程名/目录/html文件
● http://localhost:8080 实质是搜索tomcat的webapps目录
● http://localhost:8080(没有工程名)实质是访问到tomcat的webapps目录下的Root工程
● http://localhost:8080/工程名(没有资源名)实质是访问到对应工程的index.html
● 部署时工作【IDE工作】:IDE拷贝了一份Tomcat【副本】,在【副本】中部署了【编译后的工程路径】(含有原工程的所有资源文件+class文件),映射方式是上面提到的方式二。
理解:相当于有一份tomcat备份,一份工程备份(不含java源文件,仅含编译后的class文件)。拷贝一份tomcat,是为了不影响源tomcat。程序员做了一次映射,IDE又做了一次映射,项目运行时,实际跑的是IDE的映射环境。
⭕ 将页面文件拖入浏览器 vs 访问部署到tomcat的页面文件 区别
(1) 将文件拖入浏览器:走的是【file】协议,直接访问本地文件管理器,拿到文件后浏览器进行解析,不走网络。
(2) 访问部署到tomcat的页面文件:走的协议是【http】,通过网络向服务器发送请求,tomcat响应回传页面文件,客户端拿到后再使用浏览器进行解析显示。
3、tomcat部署到IDE
(1) IntelliJ IDEA
IntelliJ IDEA > Settings... > Build, Execution, Deployment > Application Servers
(2) Eclipse
Window > Preferences > Server > Runtime Environments
4、动态的web工程
与上面的创建web工程、部署tomcat到IDE两步走不同,
创建动态web工程,会一次性将服务器等准备好,并且项目目录也会不一样。
(1) 目录介绍
有时候,WEB-INF里面是没有lib目录的,我们习惯上将其创建出来,存放第三方的jar包。
(2) 动态web工程 vs 静态web工程
I. 静态 WEB
静态 WEB指的以*.htm、*.html 为后缀的网页,这些网页的访问只是从服务器上读取这些内容,然后返回给客户端浏览器解析呈现在用户面前。静态WEB【缺点】在于:所有用户看到的效果一样,无法实现与用户动态交互:不能登录连接数据库。
此外,静态资源(如html文件)可以直接手动拖入浏览器,进行显示。
II. 动态WEB
动态 WEB是指利用某些技术实现连接数据库,能够与用户交互,使 WEB的展示效果“因时因人而变”。它的好处是能够连接数据库,实现与用户的交互。强调一点,不是网站中有动态的效果就是动态WEB,动态WEB是指的是客户端与用户能够进行交互。
此外,动态资源(如jsp文件)无法拖入浏览器直接显示,它是需要服务器动态拼接内容。
主要是将请求先转交给WEB Container(WEB服务器的容器),在WEB Container中连接数据库,从数据库中取出数据等一系列操作后动态拼凑页面的展示内容,拼凑页面的展示内容后,把所有的展示内容交还给WEB服务器,之后通过WEB服务器将内容发送回客户端浏览器进行解析执行。
(3) 热部署
一般服务器(比如tomcat,jboss等)启动以后,我们还需要进一步修改java代码,或者是jsp代码。一般来说,改完重启以后才会生效。但如果配置了服务器的热部署,就可以改完代码后立即生效,而不是重启服务器再生效。这样就会节省大量时间!
● 如何热部署
一般有两个选项:
I. On Update action:当代码改变的时候,需要IDEA为你做什么
-Update resources:如果发现有更新,而且更新的是资源文件(*.jsp,*.xml等,不包括java文件),就会立刻生效
-Update classes and resources【推荐】:如果发现有更新,这个是同时包含java文件和资源文件的,就会立刻生效
⭕ 注意:在运行模式下,修改java文件时不会立刻生效的;而debug模式下,修改java文件时可以立刻生效的。当然,两种运行模式下,修改resources资源文件都是可以立刻生效的。
-Redploy:重新部署,只是把原来的war删掉(项目重新部署),不重启服务器
-Restart:重启服务器
II. On Frame deactivation:当失去焦点(比如最小化了IDEA窗口),需要IDEA为你做什么
-Do nothing【推荐】: 不做任何事 (一般推荐这个,因为失去焦点的几率太大)
-Update resources: 失去焦点后,修改的resources文件都会立刻生效
-Update classes and resources:失去焦点后,修改的java ,resources文件都会立刻生效(与On update action中的Update classes and resources一样,也是运行模式修改的java文件不会生效,debug模式修改的java文件会立刻生效)
参考链接:IDEA 服务器热部署详解(On Update action/On frame deactivation)_王溺码的博客-CSDN博客
5、使用tomcat统一的错误页面配置
在web.xml配置<error-page>标签,出错后,会自动跳转对应页面。
web.xml
<web-app ...>
...
<error-page>
<error-code>错误响应码(500、404...)</error-code>
<location>出错后,对应跳转的错误页面路径</location>
</error-page>
</web-app>
⭕ 一定要将异常一直抛到最外层,抛给Tomcat容器,如果在内部捕获后不抛出,Tomcat无法感知。
六、Servlet
Servlet是JavaEE规范之一,即接口。
它是运行在服务器上的一个Java小程序(Server applet),功能:用来接收客户端发送的请求,并响应数据给客户端。
Servlet是JavaWeb三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。
1、编写类实现Servlet接口+接入动态WEB 流程
(0) 前期工作:动态web项目的 web/WEB-INF 目录下 (web/WEB-INF/lib) 导入额外的jar包 ——【serverlet-api.jar】(这个包在tomcat的lib目录下)。
(1) 编写Java类,实现Servlet接口
public class myServlet implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException{
super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】
}
@Override
public ServletConfig getServletConfig(){
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
该方法非常重要!它是专门用来处理请求和响应的!
}
@Override
public String getServletInfo(){
return null;
}
@Override
public void destroy(){
}
}
(2) 编写web.xml文件,将编写好的Servlet实现类映射到浏览器访问路径下
<?xml version="1.0" encoding=""utf-8?>
<web-app ....> <!-- 默认的配置,不要去动 -->
<!-- 配置servlet项 -->
<servlet>
<servlet-name>自定义servlet别名(一般和实现类名保持一致)</servlet-name>
<servlet-class>servlet实现类的全类名(含包名)</servlet-class>
</servlet>
<!-- 配置servlet和浏览器访问路径的映射项 -->
<servlet-mapping>
<servlet-name>需要映射的servlet名字(和上面保持一致)</servlet-name>
<url-pattern>资源访问路径(例如/hello)</url-pattern>
</servlet-mapping>
</web-app>
成对配置,一个是配置servlet本身,另一个是配置servlet和浏览器的访问路径的映射。
● <url-pattern>中的资源访问路径,必须要以【/】打头,它表示项目的根目录。
● 此后,当浏览器访问 http://ip:port/工程路径/资源访问路径 时,会默认调用实现类中的service方法。
2、Servlet的生命周期
2.1 执行servlet的构造器 (单例,仅执行一次)
2.2 执行init初始化方法 (仅执行一次)
2.3 执行service方法 (每次刷新对应的访问路径,都会被调用)
2.4 执行destroy销毁方法 (web工程停止时调用,仅执行一次)
3、GET方法和POST方法的分发
(1) 自定义Servlet类,实现Servlet接口。
一般客户端向服务器发送get请求和post请求,所要实现的功能不一样。
而servlet中只有service方法,因此需要进行分发处理。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
3.1 向下转型为HttpServletRequest,使用其中的getMethod方法
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
3.2 通过method进行分发
String method = httpServletRequest.getMethod();
if("GET".equals(method)){
...
}else if("POST".equals(method)){
...
}
}
(2) 自定义Servlet类,继承HttpServlet类。【常用】
根据业务需要,重写HttpServlet的doGet()和doPost()方法。
public class myServlet2 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
...
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
...
}
}
4、Servlet的继承体系
5、ServletConfig
Servlet程序的配置信息接口。一个Servlet内含有对应的一个ServletConfig实现类对象。
● 作用:
(1) 获取servlet在web.xml中配置的name。
(2) 获取servlet在web.xml中配置的init-param(键值对),通过键取值。
(3) 获取servlet的上下文对象ServletContext。
public class HelloServlet implements Servlet{
@Override
public void init(ServletConfig servletConfig)throws ServletException{
super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】
System.out.println("HelloServlet程序的别名:" + servletConfig.getServletName());
System.out.println("HelloServlet程序的初始化参数username值是:" + servletConfig.getInitParameter("username"));
System.out.println("HelloServlet程序的初始化参数url值是:" + servletConfig.getInitParameter("url"));
System.out.println("HelloServlet程序的上下文对象是:" + servletConfig.getServletContext());
}
}
web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>HelloServlet全类名</servlet-class>
<!-- 初始化参数,里面含有一个键值对 -->
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet>
</web-app>
6、ServletContext
接口,它表示Servlet上下文对象。
● ServletContext是一个域对象。
域对象,是可以像Map一样存取数据的对象。这里的域,是指存取数据的操作范围,即整个web工程。
● 如何获得ServletContext对象
(1) 可以通过Servlet内的ServletConfig对象得到。
(2) 调用servlet的getServletContext()得到。【本质:GenericServlet中封装的方法,内部还是用方法(1)!!!】。
在Servlet实现类中:
方法一:
ServletConfig servletConfig = getServletConfig();
ServletContext context = servletConfig.getServletContext();
方法二:【本质还是方法一】
ServletContext context = getServletContext();
● 作用:
(1) 获取在web.xml中配置的context-param(键值对),通过键取值。
web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>
<context-param>
<param-name>...</param-name>
<param-value>...</param-value>
</context-param>
<context-param>
<param-name>...</param-name>
<param-value>...</param-value>
</context-param>
<servlet>
...
</servlet>
<servlet-mapping>
...
</servlet-mapping>
</web-app>
Java
String value = context.getInitParameter("属性名");
(2) 获取当前的工程路径,格式:【/工程名】
(3) 获取工程部署后在服务器硬盘上的绝对路径。(提供一个相对路径,【/】代表当前工程目录)
System.out.println("工程部署的路径是:" + context.getContextPath());
System.out.println("工程下css目录的绝对路径是:" + context.getRealPath("/"));
(4) 可以像Map一样存取数据。
7、Http协议
● 协议:双方或多方,相互约定好,大家都需要遵守的规则。
● HTTP协议:客户端(浏览器)和服务器之间通信时,发送的数据【又称报文】,需要遵守的规则。
7.1 请求
客户端给服务器发送的数据叫【请求】;
(0) Http协议格式:
● 请求行【3部分组成】
(a) 请求方式:GET/POST
(b) 请求url路径:url路径[?参数名1=参数值1&参数名2=参数值2&...]
(c) 协议及版本号:http/1.1
● 请求头
格式【key:value】
● 空行
● 请求体
(1) GET请求
HTTP 协议没有为 GET 请求的 body 赋予语义,也就是即不要求也不禁止 GET 请求带 body。一些实现会禁止,一些允许。一般来说,GET请求没有请求体,即把应该放在请求体的内容放到了请求行的后置参数中。
⭕ 什么时候会用到GET请求:【常用】
a) form标签中有属性mothod=get
b) a标签、img标签引入图片、iframe引入html页面
c) link标签引入css文件、script标签引入js文件
d) 在浏览器地址栏中输入地址敲回车
(2) POST请求
一般来说,POST请求体中存放了发送给服务器的数据内容。格式为【参数名1=参数值1&参数名2=参数值2&...】
⭕ 什么时候会用到POST请求:
form标签中有属性mothod=post
7.2 响应
服务器给客户端回传的数据叫【响应】。
(0) Http协议格式:(和请求格式类似,区别在于响应行和响应头的一些字段)
● 响应行【3部分组成】
(a) 协议及版本号:http/1.1
(b) 响应状态码:200
(c) 响应状态码描述:OK
● 响应头
格式【key:value】
● 空行
● 响应体
(1) 常用的响应码
● 200:请求成功。
● 302:请求重定向。
● 404:服务器已经收到了请求,但是数据不存在。
● 500:服务器已经收到了请求,但是服务器内部错误(代码)。
7.3 MIME类型
MIME(Multipurpose Internet Mail Extensions)多功能Internet邮件扩充服务。
它是HTTP协议中的数据类型。MIME类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。
8、HttpServletRequest
只要每次有请求进入Tomcat服务器,它会将请求的HTTP协议信息解析,封装到Request对象中。
我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Request对象。
8.1 常用的方法
1、getRequestURI();
获取资源路径(e.g /myProject/helloServlet)
2、getRequestURL();
获取统一资源定位符(e.g http://localhost:8080/myProject/helloServlet)
3、getRemoteHost();
获取客户端的ip地址
4、getHeader("请求的键名");
获取请求头中对应键的值
5、getMethod();
获取请求方式GET或POST
6-1、String getParameter("参数名") 如果复选框、下拉框等选中了多个值,只会返回一个值【重要!!!】
6-2、String[] getParameterValues("参数名") 用于获取复选框、下拉框等选中多个值的情况【重要!!!】
获取用户提交表单中的信息,参数名对应html中标签的name属性。
7、setCharacterEncoding("字符集(e.g UTF-8)")【解决post请求的中文乱码问题!!!】
设置请求体中的字符集【注意:要在获取请求参数方法(即上面的6)调用前使用,才能生效!!】
8.2 请求转发
有时候多个servlet资源协作完成一个业务,就需要用到请求转发。
即从一个servlet的service(或者doGet/doPost)方法跳到另一个servlet的service(或者doGet/doPost)方法。
● 步骤
(1) 获取RequestDispatcher对象;
RequestDispatcher requestDispatcher = request对象.getRequestDispatcher("以【/】打头的资源路径");
(2) 可以使用Request域对象进行数据传递;
request对象.setAttribute("键", "值");
request对象.getAttribute("键");
(3) 调用RequestDispatcher的forward方法进行转发。
requestDispatcher.forward(request对象, response对象);
e.g
class Servlet1 extends HttpServlet{
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp){
System.out.println("在Servlet1中查看参数:" + username);
req.setAttribute("key", "servlet1的数据");
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2");
requestDispatcher.forward(req, resp);
}
}
class Servlet2 extends HttpServlet{
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp){
System.out.println("在Servlet2中查看参数:" + username);
String value = req.getAttribute("key");
System.out.println("处理servlet2的业务逻辑");
}
}
● 特点:
(1) 浏览器的地址不会随着转发而跳转。
引发的问题:由于转发时浏览器地址不会跟着跳转,所以容易导致各种相对路径混乱。
解决方案:① 写绝对路径; ② 使用<base href="路径(如果最后是目录,不能省略【/】)"/>标签【base标签往往放在head标签内,title标签下】。
<head>
...
<title></title>
<base href="..." />
</head>
jsp文件
<%@ page contentType="text/html; charset=utf-8" language="java" %>
<head>
...
<title></title>
<%
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort
+ request.getContextPath() + "/";
%>
<base href="<%= basePath %>" />
</head>
(2) 用户在地址栏回车后,尽管可能进行了多次转发,但是仍然是一次请求和一次响应。这就意味着这几个协作的servlet资源共享Request对象。
(3) 可以转发到WEB-INF目录下,该目录无法使用浏览器直接访问。
(4) 只允许访问工程下的资源。
9、HttpServletResponse
类似HttpServletRequest,只要每次有请求进入Tomcat服务器,它会创建一个Response对象传递给Servlet程序。
我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Response对象,来设置返回给客户端的信息。
9.1 两种输出流
● 类似request,response的默认字符集为“ISO-8859-1”,我们可以通过setCharacterEncoding("字符集");对服务器进行字符集的设置,且要在获取流对象设置!!!。但是
方式一:
response.setHeader("Content-Type", "text/html; charset=UTF-8");
方式二:
response.setContentType("text/html; charset=UTF-8");
(1) 字节输出流:PrintStream printStream = response.getOutputStream();
常用于二进制传输。
(2) 字符输出流:PrintWriter printWriter = response.getWriter();【常用】
常用于字符传输,效率高。
使用里面的write()或者print()【其本质也是write】写入新的页面(如html字符串),回传发送给浏览器。
⭕ 原因:在某种输出流使用完成后,会自动(当然也可以手动关闭)帮我们关闭输出流。由于是一次响应,因此不能再使用输出流来进行操作!
9.2 请求重定向
重新让浏览器跳转到另一个地址。
● 重定向方式一
response.setStatus(302);
response.setHeader("Location", "新的浏览器访问地址");
● 重定向方式二 【推荐】
response.sendRedirect("新的浏览器访问地址");
● 特点:
(1) 浏览器的地址会发生跳转。
(2) 用户在地址栏回车后,由于重定向,发生了两次请求和两次响应。这就意味着彼此不共享Request对象。
(3) 不可以转发到WEB-INF目录下。本质还是地址栏访问WEB-INF目录。
(4) 允许访问非工程下的资源。
10、ServletContextListener
Listener监听器:JavaEE的规范,即接口。JavaWeb的三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。共有八大监听器,其中ServletContextListener比较有用,用的比较多。
● 监听器的作用:监听某种事物的变化,然后通过回调函数,反馈后方便做一些业务处理。
ServletContextListener 它可以监听 ServletContext对象的创建和销毁。
ServletContext对象在web工程启动的时候创建,在web工程停止的时候销毁。
public interface ServletContextListener extends EventListener{
1. 监听ServletContext【创建后】,调用
public void contextInitialized(ServletContextEvent sce);
2. 监听ServletContext【销毁后!!!】,调用
public void contextDestroyed(ServletContextEvent sce);
}
● 使用步骤
(1) 编写ServletContextListener的实现类;
(2) 实现这两个回调方法;
(3) 到web.xml中配置监听器。
web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>
<context-param>
...
</context-param>
<servlet>
...
<init-param>
...
</init-param>
</servlet>
<servlet-mapping>
...
</servlet-mapping>
<listener>
<listener-class>监听器实现类的全类名</listener-class>
</listener>
</web-app>
⭕ JavaWeb三大组件的执行顺序:ServletContextListener → Filter → Servlet
七、JSP
jsp(java server pages)是Java服务器页面。
它的主要作用是代替Servlet程序回传html页面的数据。
【传统做法:通过response对象获取输出流,利用print/write方法回传html字符串。】
1、jsp文件的存放和访问
jsp页面和html页面一样,都是存放在web目录下。访问也是跟html页面一样。
2、jsp文件的本质:servlet程序
当我们第一次访问jsp文件时,Tomcat服务器会帮我们把jsp文件翻译成一个对应的java源文件【它间接继承了HttpServlet】,及对应的class字节码文件。 ()
e.g 假定我们有一个a.jsp文件
(1) 将其访问目录放入浏览器地址栏
(2) 在文件管理器打开:tomcat新拷贝的环境(实际部署环境)下的work/Catalina/localhost/工程名
(3) 打开该目录的资源,发现 a_jsp.java 和 a_jsp.class文件。
3、jsp语法
3.1 page指令
page指令可以修改jsp页面中的一些重要属性,或行为。
<%@ page contentType="text/html; charset=utf-8" language="java" %>
以上page指令可以写多个。
● language属性:表示jsp翻译后是什么语言文件。暂时只支持java。
● contentType属性:表示jsp返回的数据类型是什么。对应了源码中的response.setContentType()。
● pageEncoding属性:表示当前jsp页面文件本身的字符集,默认“utf-8”。【不建议改】
● import属性:对应java源代码中的导包。
● autoFlush属性:设置当输出流out缓冲区满了之后,是否自动刷新缓冲区,默认是true。【不建议改】
● buffer属性:设置输出流out缓冲区大小,默认是8kb。【不建议改】
● errorPage属性:设置当jsp页面运行出错时,自动跳转的错误页面路径。
● isErrorPage属性:设置当前jsp页面是否是错误信息页面,默认是false。如果是true可以获取异常信息。
● session:设置访问当前jsp页面,是否会创建HttpSession对象,默认是true。【不建议改】
● extends:设置jsp翻译出来的java类默认继承哪个类。
3.2 jsp脚本
(1) 声明脚本【极少使用】
● 格式:
<%!
声明类的属性、方法、静态方法、内部类等等...
%>
可以写多个。
e.g
<%-- 声明类的属性 --%>
<%!
private Integer id;
private String name;
%>
<%-- 声明类的方法 --%>
<%!
public int abc(){
return 12;
}
%>
<%-- 声明类的静态代码块 --%>
<%!
static{
id = 3;
name = "3";
}
%>
<%-- 声明类的内部类 --%>
<%!
class myInnerClass{
private int id = 3;
private double price = 1.1;
}
%>
(2) 表达式脚本【较多使用】
● 格式:
<%=
变量,字符串,对象...
%>
● 特点:
I. 所有的表达式脚本都会被翻译到java源文件的_jspService()方法中。也就是说,_jspService()中的对象都可以直接使用。
II. 表达式脚本会被翻译成out.print("表达式脚本的内容"),即会直接渲染到页面上。另外,表达式脚本不能以分号结束。
(3) 代码脚本【较多使用】
在jsp页面中,编写我们自己需要的功能。
● 如果写的是【java语句】,翻译后直接写入_jspService()方法里。即当访问该jsp文件,会执行这些java语句。
● 如果写的是【html语句】,解析后渲染到页面上。
也就是说在代码脚本中,可以实现java+html的混用。
● 格式:
<%
...
%>
3.3 jsp注释
jsp支持三种注释。
(1) html注释(翻译后在_jspService()方法中用out.write("html注释")输出)
(2) java注释(翻译后在_jspService()方法中,以普通的注释形式存在)
(3) jsp注释(不会被翻译)
4、jsp九大内置对象
jsp中的内置对象,是指tomcat在翻译jsp页面称为servlet源代码后,内部提供的九大对象,称为内置对象。
(1) HttpServletRequest request
(2) HttpServletResponse response
(3) ServletContext application
(4) ServletConfig servletConfig
(5) HttpSession session:会话对象
(6) PageContext pageContext:page上下文对象
(7) Throwable exception 对象:异常对象(用page指令,将isErrorPage="true"时出现)
(8) JspWriter out:输出流对象
(9) Object page:指当前jsp页面对象。
5、jsp四大域对象 (
)
域对象是可以像Map一样存取数据的对象。四个域对象的功能一样,不同的是它们对数据的存取范围。
(1) PageContext对象:pageContext,【当前jsp页面】有效
(2) HttpServletRequest对象:request,【一次请求】内有效
(3) HttpSession对象:session,【一次会话】内有效(打开浏览器访问服务器,直到关闭浏览器)
(4) ServletContext对象:application,整个【web工程内】有效(随着工程的销毁而销毁)
6、jsp中的out输出流对象 vs response.getWriter()输出流对象
6.1 显示到浏览器的先后顺序
问题:如果在jsp代码脚本中,同时用以上两种输出流输出,先后顺序如何?
结果是:先显示response的输出流对象的内容,再显示out输出流对象的内容。
⭕ 原理
jsp的输出流out对象,持有一个自身的【缓冲区】;
response对象的输出流,也持有一个自身的【缓冲区】。
当out对象缓冲区进行刷新flush()时,才会把自身缓冲区的内容,全部追加到response对象的输出流缓冲区中!
当回传响应给客户端时,response对象将其输出流缓冲区的内容全部回传给客户端解析显示。
6.2 如何使用
由于jsp翻译之后,底层源代码都是使用out来进行输出。
为避免页面输出内容的顺序出现错乱,一般情况下,我们统一在jsp页面中使用out来输出。
● out.write():会将内容强制转换成char类型输出。(因此如果放入int类型,会打印出对应的ASCII码值)
● out.print():先进行字符串转换,即String.valueOf("内容"),然后再进行输出。⭐【推荐使用】
7、jsp常用标签
7.1 包含
当有成千上万个页面中的部分内容相同时,我们想要将相同的内容抽取出来,只维护一份。此时就需要用到包含。
(1) 静态包含【使用的比较多,因为现在jsp文件主要是用来输出页面数据,不会做太多复杂功能】
● 格式:
<% include file="/被包含的页面资源路径" %>
● 特点:
I. 不会对被包含的页面进行翻译,即不生成servlet程序;
II. 会把被包含的页面的html内容,拷贝到当前需要的页面。
(2) 动态包含
● 格式:
<jsp:include page="/被包含的页面资源路径">
<jsp:param name="键1" value="值1"/>
<jsp:param name="键2" value="值2"/>
...
</jsp:include>
● 特点:
I. 对被包含的页面进行翻译,即生成servlet程序;
II. 可以【通过request域对象】传递参数给被包含的页面。
7.2 标签-转发
功能对应于request.getRequestDispatcher("资源路径").forward(request, response);
● 格式:
<jsp:forward page="资源路径"></jsp:forward>
八、EL表达式(jsp扩展)
EL(Expression Language)表达式语言。用于简化jsp的表达式脚本,进行数据输出。
1、语法
${ 表达式 }
可以直接使用域对象的key来获取对应的值
e.g
pageContext.setAttribute("key", "value");
${ key }$
注意:EL表达式在输出null值的时候,输出的是空串(不显示内容)。而jsp表达式脚本输出null值的时候,输出的是"null"字符串。
1.1 表达式搜索域对象的顺序
当jsp中四个域对象均有相同的key时,EL表达式会按照四个域从小到大的顺序获取【pageContext→request→session→application】。
1.2 使用 "."运算 和 "[]"运算
● ${ Bean对象 }:会调用该类的toString()方法。
● ${ Bean对象.XXX }:一般用于获取对象的属性或者map中键对应的值。如果是获取对象的属性,底层会调用该类的公有读【getXXX() / isXXX()】方法。
● ${ 对象[下标 / "键"] }:用于获取有序集合中某个下标的值,或者获取map中的键对应的值。
2、运算
(1) 关系运算:==、!=、<、>、<=、>=
(2) 逻辑运算:&&、||、!
(3) 算术运算:+、-、*、/、%、三元运算符( exp1 ? exp2 : exp3 )
(4) 点"."运算和中括号"[]"运算
(5) empty运算:判断是否为空,返回true或者false
判断为空的几种情况:
● null
● ""(空串)
● 数组大小为0
● 集合大小为0
3、EL表达式的11个隐含对象
EL表达式中的11个隐含对象,是EL表达式中自己定义的,可以直接使用。
(1) 变量 pageContext:类型为pageContextImpl
它存储了jsp中的九大内置对象。
常常使用该对象获取如下信息:
1、获取协议
<%= request.getScheme() %>
${ pageContext.request.scheme }
2、获取服务器ip
<%= request.getServerName() %>
${ pageContext.request.serverName }
3、获取服务器端口
<%= request.getServerPort() %>
${ pageContext.request.serverPort }
4、获取工程路径
<%= request.getContextPath() %>
${ pageContext.request.contextPath }
5、获取请求方法
<%= request.getMethod() %>
${ pageContext.request.method }
6、获取客户端IP地址
<%= request.getRemoteHost() %>
${ pageContext.request.remoteHost }
7、获取会话的ID编号
<%= session.getId() %>
${ pageContext.session.id }
(2) 变量 pageScope:类型为HashMap<String, Object>
它存储了pageContext域数据。
(3) 变量 requestScope:类型为HashMap<String, Object>
它存储了request域数据。
(4) 变量 sessionScope:类型为HashMap<String, Object>
它存储了session域数据。
(5) 变量 applicationScope:类型为HashMap<String, Object>
它存储了application域数据。
(6) 变量 param:类型为HashMap<String, String>
它存储了请求参数,但一个键只对应一个值。
(7) 变量 paramValues:类型为HashMap<String, String[]>
当其中一个请求参数存在一个键对应多个值时,使用。
(8) 变量 initParam:类型为HashMap<String, String>
它存储了在web.xml中配置的<context-param>上下文参数。
(9) 变量 header:类型为HashMap<String, String>
它存储了该请求的头部信息,但一个键只对应一个值。
(10) 变量 headerValues:类型为HashMap<String, String[]>
当其中一个请求头信息存在一个键对应多个值时,使用。
(11) 变量 cookie:类型为HashMap<String, Cookie>
它存储了当前请求的Cookie信息。
九、JSTL标签库(jsp扩展)
JSTL(JSP Standard Tag Library),全称JSP标准标签库。用于替换jsp中的代码脚本。
1、使用标签库步骤
1.1 导入jstl标签库的jar包;
1.2 使用taglib指令引入标签库;
(1) CORE标签库
<%@ tablib prefix="c" uri="http://java.sum.com/jsp/jstl/core" %>
(2) FMT标签库
<%@ tablib prefix="fmt" uri="http://java.sum.com/jsp/jstl/fmt" %>
(3) FUNCTIONS标签库
<%@ tablib prefix="fn" uri="http://java.sum.com/jsp/jstl/functions" %>
(4) XML标签库
<%@ tablib prefix="x" uri="http://java.sum.com/jsp/jstl/xml" %>
(5) SQL标签库
<%@ tablib prefix="sql" uri="http://java.sum.com/jsp/jstl/sql" %>
1.3 使用jstl标签书写
2、CORE核心库的常用标签
(1) set标签:给域对象设置键值对
<c:set scope="page" var="键" value="值" />
● scope属性:指明是哪个域对象。可选值:page、request、session、application
● var属性:键
● value属性:值
(2) if标签:如果test值为true,则会输出标签里的内容
<c:if test="${ 12 == 12 }">
<h1>12==12</h1>
</c:if>
● test属性:表达式【用EL书写】,为真时输出标签里的内容。
(3) choose+when+otherwise标签:类似于switch...case...default (区别在于:switch组需要手动break)
<c:choose>
<c:when test="表达式1">输出内容1</c:when>
<c:when test="表达式2">输出内容2</c:when>
<c:when test="表达式3">输出内容3</c:when>
<c:otherwise>输出内容4</c:otherwise>
</c:choose>
注意:otherwise标签也可以不写。此外,执行的顺序从上到下,要满足其中一条就跳出。
(4) forEach标签
● begin属性:开始值(索引)
● end属性:结束值(索引)【包括自身】
● items属性:需要被遍历的Object数组对象
● var属性:遍历的变量
● step属性:递增(递减)的增量
● varStatus:var的状态,它的实现类有很多方法可以调用。(详情见下图)
<c:forEach begin="1" end="10" var="i">
${ i }
</c:forEach>
<%
request.setAttribute("arr", new String[]{"1234", "5678", "91011"});
HashMap<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
request.setAttribute("map ", map);
%>
<c:forEach items=${ requestScope.arr } var="item">
${ item } <br />
</c:forEach>
<c:forEach items=${ requestScope.map } var="entry">
键:${ entry.key } ,值:${ entry.value } <br />
</c:forEach>
十、文件上传和下载
1、文件上传
步骤
● 导包
(0) 导入【commons-fileup.jar包】(它依赖于【commons-io.jar】),以便解析多段表单项内容。
● 前端
(1) 使用form标签:method属性设置为"post",encType属性设置为"multipart/form-data"。
(2) 在form标签内,使用input标签,input标签的type属性设置为"file"。
● 后端
(3) 在服务器端使用【ServletFileUpload】类来解析上传的数据,表单项被封装成【FileItem】。编写代码实现文件上传功能。
1、boolean static isMultipartContent(HttpServletRequest request)
判断当前上传的数据格式是否是多段表单项的格式
2、public List<FileItem> servletFileUpload对象.parseRequest(HttpServletRequest request)
解析上传的数据
3、boolean fileItem对象.isFormField()
判断是否为普通的表单项。如果是,则返回true;否则,是文件,返回false
4、String fileItem对象.getFieldName()
获得当前fileItem的name属性值
5、String fileItem对象.getString("编码方式(e.g UTF-8)")
获取当前fileItem的内容
6、String fileItem对象.getName()
获取上传的文件名
7、void fileItem对象.write(File file)
将上传的文件写入磁盘
2、文件下载
● 步骤
(1) 服务器端准备好对应的文件,用ServletContext对象获取资源并转化成二进制流。
ServletContext context = request.getServletContext();
InputStream is = context.getResourceAsStream("文件路径");
(2) 在response对象中,设置好响应头。相关属性有:【"Content-Type"、"Content-Disposition"】
● 其中content-Type可以通过context对象的getMimeType方法动态获取。
● content-disposition是为了告诉用户不要显示在浏览器,这是一个附件,应该下载;并给出下载时的文件名。
String mimeType = context.getMimeType("文件路径");
response.setContentType(mimeType);
response.setHeader("Content-Disposition", "attachment; filename=文件名");
⭕ 如果文件名是中文,需要进行编码。不同的浏览器支持的编码格式不同。
● 【可以通过request请求头的"User-Agent"来查看浏览器的信息】
I. IE或者谷歌支持URL编码。可以使用URLEncoder.encode("编码内容", "字符集")【java.net包】;
II. 早期的火狐不支持URL编码,支持BASE64编码【需要在编码后的字符串用=??=包裹,并告诉浏览器使用的是BASE64编码(B),即=?字符集?B?编码串?=】。可以使用base64Encoder对象.encode(byte[]);
(返回给浏览器时,浏览器会对应的解析。如果我们自己想解析看看,可以使用对应的decode("编码后内容")来查看)
if(request.getHeader("User-Agent").contains("Firefox")){
BASE64Encoder base64Encoder = new BASE64Encoder();
String filename = "=?" + base64Encoder.encode("中国.jpg".getBytes("utf-8")) + "?=";
}else{
String filename = URLEncoder.encode("中国.jpg", "utf-8");
}
(3) 从response中获取输出流,并将资源流写入输出流。这里可自己书写,也可使用common-io.jar包的IOUtils类API。
OuputStream os = response.getOutputStream();
IOUtil.copy(is, os);
十一、Cookie
Cookie是Servlet发送到Web浏览器的少量信息,浏览器将其保存下来,后续可发送回服务器。
(1) Cookie的值可以唯一的标识客户端,因此Cookie常用于【会话管理】。
(2) 当浏览器有Cookie值时,每次请求都会发送给服务器。
(3) 每个Cookie的大小不能超过4KB。
1、Cookie的使用
1.1 Cookie的创建
服务器端的web层的servlet程序负责创建。
● 构造器仅有一个,必须传入对应的键和值
Cookie cookie = new Cookie(String name, String value);
1.2 Cookie的添加
servlet程序通过reponse对象调用addCookie方法。就可以将Cookie发送给浏览器,让其保存。
response.addCookie(cookie);
1.3 Cookie的获取
当浏览器发送请求时,如果携带了Cookie信息,服务器的servlet程序就可以通过request对象调用getCookies方法获取Cookie数组。
Cookie[] cookies = request.getCookies();
⭕ 如果想要获取其中某一个特定的Cookie,一般来说都是遍历寻找。我们往往【编写一个CookieUtils工具类】,通过传入对应的Cookie键名,来获取该Cookie,如果没有就返回null。
CookieUtils.java
public class CookieUtils{
public static Cookie findCookieByName(String name, Cookie[] cookies){
if(name == null || "".equals(name) || cookies == null || cookies.length == 0) return null;
for(Cookie cookie : cookies){
if(name.equals(cookie.getName())){
return cookie;
}
}
return null;
}
}
1.4 Cookie的修改
(1) servlet中通过创建新的Cookie,并调用response对象的addCookie(cookie)方法来覆盖之前旧的Cookie。
(2) servlet先获取cookie数组,再找到对应的Cookie,并调用sertValue(String newValue)方法来修改旧值。
2、Cookie的属性
2.1 maxAge属性
该属性表示cookie对象的存活时间,单位:s。
(1) 当值为正数时,表示经过多少秒后该cookie失效;
(2) 当值为负数时,表示关闭浏览器时,cookie失效。【默认】
(3) 当值为零时,表示该cookie立即失效。
● 可以通过get方法读取,set方法写入。
2.2 path属性
该属性可以用于浏览器请求地址时,对cookie的过滤。即有些cookie可以被显示并使用,有一些被隐藏不能使用。
当浏览器的访问地址中【没有包含cookie的path】,那么该cookie就被过滤。
● 可以通过get方法读取,set方法写入。
十二、Session
接口【HttpSession】。Session就是会话,它用来维护一个客户端和服务器之间的关联。
(1) 每个客户端都会有自己的一个Session会话。
(2) 我们经常用Session会话保存用户登陆之后的信息。(而Cookie只是客户端请求携带过去的信息)
1、创建和获取Session
创建一个Session,和获取当前Session,API是一样的。
当还没有Session时,会创建Session会话;如果有会话了,会直接返回前面创建好的session。
● 可以使用【boolean session.isNew()】来判断是否是新创建的会话。
● 每一个Session都有一个唯一的标识,即ID,可以通过【session.getId()】获取。
HttpSession session = request.getSession();
boolean isNew = session.isNew();
String id = session.getId();
2、Session域数据的存取
session.setAttribute(String key, Object value);
Object value = session.getAttribute(String key);
3、Session的属性
3.1 maxInactiveInterval属性
该属性表示session对象的【指闲置的时间,两次请求访问的间隔最大时长】存活时间,单位:s。【默认:1800s,即30min】
()
(1) 当值为正数时,表示两次请求间隔多少秒后该session失效;
(2) 当值为负数时,表示session永不超时。(极少使用)
(3) 不能通过设置"0",让会话马上失效。但可以通过调用session的以下API,来使其立马失效。
session.invalidate();
<web-app ...>
<servlet>
...
</servlet>
<servlet-mapping>
...
</servlet-mapping>
<!-- 当前项目的session会话超时时间,单位:分钟。 -->
<session-config>
<session-timeout>20</session-timeout>
</session-config>
</web-app>
● 可以通过get方法读取,set方法写入。
如果只想修改个别session,而不是整个项目的,可以使用session.setMaxInactiveInterval("秒数");
4、浏览器和Session的关联
之前我们谈到,【关闭浏览器会使得一次会话结束】。
⭕ 当关闭浏览器时,如果Session还没有失效,为什么下一次打开浏览器,就拿不到之前的Session了呢?
十三、Filter
Filter过滤器是JavaEE的规范,即【接口】。它是JavaWeb的三大组件(Servlet、Listener、Filter)之一。
1、为什么需要Filter?
⭕ 现在有一个需求:
现在我们访问工程目录下的资源(如:html文件、jsp文件、jpg文件、mp4文件...)都可以直接访问。
而我们只希望,登录过的用户访问工程目录下的admin目录的资源文件。如何实现?——【Filter】进行控制。
● Filter的作用是:拦截请求【常用】、过滤响应。
拦截请求的常见应用场景:
(1) 权限检查
(2) 日记操作
(3) 事务管理
(4) ...等等
2、Filter的使用步骤
2.1 编写实现了Filter接口的myFilter类,并重写doFilter方法,处理自己的过滤逻辑。
public class AdminFilter implements Filter{
@Override
public boolean init(FilterConfig filterConfig) throws SevletException{
...
}
@Override
public boolean doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws SevletException{
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpSession session = req.getSession();
...
if(验证通过){
fileterChain.doFilter(servletRequest, servletResponse);
}else{
servletRequest.getRequestDispatcher("转发地址")forward(servletRequest, servletResponse);
}
}
@Override
public void destroy(){
...
}
}
● 重要方法:【filterChain.doFilter(servletRequest对象, servletResponse对象);】⭐
I. 如果有下一个Filter,则调用下一个Filter的doFilter方法继续进行验证;
II. 如果没有Filter,验证通过后,则进入资源文件。
2.2 在web.xml中配置filter类和拦截路径。 (
)
web.xml
<web-app ...>
<servlet>
...
</servlet>
<servlet-mapping>
...
</servlet-mapping>
<filter>
<filter-name>自定义Filter名字</filter-name>
<filter-class>实现了Filter接口的实现类的全类名</filter-class>
</filter>
<filter-mapping>
<filter-name>自定义Filter名字</filter-name>
<url-pattern>拦截的路径</url-pattern>
</filter-mapping>
</web-app>
(1) 拦截路径的三种写法:
a) 精确拦截:格式:【/资源】
b) 目录拦截:格式:【/目录/*】
c) 后缀名拦截:格式:【*.后缀】
e.g
a) <url-pattern>/test.jsp</url-pattern>
b) <url-pattern>/admin/*</url-pattern>
c) <url-pattern>*.html</url-pattern>
(2) 当要给同一个filter,配置多个拦截路径时,可以使用多个<url-pattern>标签。
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/path1/*</url-pattern>
<url-pattern>/path2/*</url-pattern>
<url-pattern>/path3/*</url-pattern>
</filter-mapping>
3、Filter的生命周期
3.1 构造器方法【web工程启动后】
3.2 init方法:进行初始化操作。【web工程启动后】
3.3 doFilter方法:进行拦截操作。【每次有需要被拦截的请求,就会执行】
3.4 destroy方法:进行Filter的销毁。【web工程停止】
4、FilterConfig类
FilterConfig是Filter的配置文件类。
Tomcat每次创建Filter的时候,会对应给它创建一个FilterConfig类,记录Filter的配置信息。
● 作用:
(1) 获取对应Filter的名字(在web.xml配置的)
String filterName = filterConfig.getFilterName();
(2) 获取在web.xml配置的Filter的初始参数。
web.xml
<web-app ...>
...
<filter>
...
<init-param>
<param-name>...</param-name>
<param-value>...</param-value>
</init-param>
<filter>
<filter-mapping>
...
</filter-mapping>
</web-app>
String value = filterConfig.getInitParameter("参数名");
(3) 获取servletContext对象
ServletContext context = filterConfig.getServletContext();
5、FilterChain类
过滤器链,即处理多个过滤器运转的类。
5.1 多个Filter使用的流程
5.2 多个Filter过滤器的特点
(1) 所有共同执行的filter和目标资源【默认】都执行在同一个线程中;
(2) 所有共同执行的filter和目标资源都使用同一个Request对象。
十四、JSON
JSON(JavaScript Object Notation)是一种轻量级(跟XML相比)的数据交换格式。
JSON采用完全独立于语言的文本格式,很多语言都提供了对json的支持,这就使得JSON成为理想的数据交换(指客户端和服务器的数据传递)语言。
1、在JavaScript中的格式【客户端】
1.1 定义
jsonObj = {
key1:value1,
key2:value2,
...
}
1.2 方法
(1) 获取
var value = jsonObj.key1;
(2) 设置
jsonObj.key1 = new_value1;
(3) 将当前json对象转化为json字符串
var jsonObjStr = JSON.stringify(json对象);
(4) 将当前json字符串转化为json对象
var jsonObj = JSON.parse(json字符串);
● 一般json对象是用来访问和存取数据的,而json字符串是用来做数据交换(数据传输)的。
2、在Java中的格式【服务器端】
导入json的jar包,并部署到项目中。
下面的示例以【gson-2.2.4.jar】为例。
2.1 JavaBean和json的相互转换
import com.google.gson.Gson;
Person person = new Person(1, "Mary");
Gson gson = new Gson();
String personJsonString = gson.toJson(person); // {"id":"1", "name":"Mary"}
Person newPerson = gson.fromJson(personJsonString, Person.class); // Person{id=1, name='Mary'}
2.2 List / Map 和json的相互转换
List
import com.google.gson.Gson;
// public class PersonListType extends TypeToken<ArrayList<Person>>{ // TypeToken是jar包中的类
// }
List<Person> personList = new ArrayList<>();
personList.add(new Person(1, "Mary"));
personList.add(new Person(2, "John"));
personList.add(new Person(3, "Tom"));
Gson gson = new Gson();
String personJsonString = gson.toJson(personList );
// List<Person> newPersonList = gson.fromJson(personJsonString, new PersonListType().getType());
List<Person> newPersonList = gson.fromJson(personJsonString, new TypeToken<ArrayList<Person>>().getType()); // 使用匿名内部类
Map
import com.google.gson.Gson;
// public class PersonMapType extends TypeToken<HashMap<Integer, Person>>{ // TypeToken是jar包中的类
// }
Map<Integer, Person> personMap = new HashMap<>();
personMap.put(1, new Person(1, "Mary"));
personMap.put(2, new Person(2, "John"));
personMap.put(3, new Person(3, "Tom"));
Gson gson = new Gson();
String personJsonString = gson.toJson(personMap);
Map<Integer, Person> newPersonMap = gson.fromJson(personJsonString, new TypeToken<HashMap<Integer, Person>>().getType()); // 使用匿名内部类
● 对象转化为json字符串,普通JavaBean、List、Map都可以直接转化;
● 但是如果是json字符串转化成对象:
● 普通JavaBean对象 = gson对象.toJson("Json字符串", JavaBean.class);
● List<T>或者Map<T>对象 = gson对象.toJson("Json字符串", 继承了TypeToken类(书写了泛型类型)的对象.getType());
十五、AJAX
AJAX(ASynchronized JavaScript And XML),异步JavaScript和XML(现在主要是JavaScript + Json联动做数据传输)。是一种创建交互式网页应用的网页开发技术。
1、AJAX请求的特点
(1) 异步请求
异步和同步是相对而言的。
● 【同步】是指下一个操作要等待上一个操作执行完毕后才能开始执行。
● 【异步】是指无需等待上一个操作执行完毕,继续往下执行。上一个操作执行完毕后,执行相应的回调函数。
(2) 局部更新页面
a) 原来地址栏的内容没有改变
b) 页面不做刷新操作,仅仅改变新的内容,没有更改的内容不发生变化。
没有使用ajax技术时,会将整个页面全部从服务器再次加载。
2、原生的AJAX请求步骤
2.1 js客户端中创建XMLHttpRequest对象
var xmlHttpRequest = new XMLHttpRequest();
2.2 xmlHttpRequest对象调用open方法,设置请求参数
xmlHttpRequest.open(请求的方法("GET"或"POST"), 资源路径(可以携带请求参数), 是否异步(true或false));
⭕ 注意:AJAX可以默认发送"GET"请求,一般我们发送"GET"、"POST"请求。而对于其他HTTP请求,如"PUT"、"DELETE"请求,部分浏览器不支持。
2.3 绑定xmlHttpRequest对象的onreadystatechang事件,获取服务器发送回来的数据,并处理请求完成后的操作
xmlHttpRequest.onreadystatechange = function(){
// ajax请求已经成功响应,状态200。且服务器已经将请求完成。
if(xmlHttpRequest.state == 200 && xmlHttpRequest.readyState == 4){
var jsonObj = JSON.parse(xmlHttpRequest.responseText); // 获取文本格式数据
document.getElementById("div01").innerHTML = "编号:" + jsonObj.id + ", 名字:" + jsonObj.name; //设置到标签元素中
}
}
2.4 xmlHttpRequest对象调用send方法,发送请求
xmlHttpRequest.send();
3、JQuery封装的AJAX请求
● 常用方法
3.1 ajax
$.ajax(
url:"资源路径",【不携带请求参数】
type:"GET"或"POST",
data:"请求参数", 【格式有两种,无需前置的"?"】
(1) "key1=value1&key2=value2..."
(2) {key1:value1, key2:value2...} (会自动帮我们解析成(1))
success:自定义回调函数,【可以处理成功或者失败的情况】
function(data){
必须写上参数名,装载了服务器回传的数据!如果dataType为"json",会帮我们自动转化成json对象,无需我们再手动处理。
}
dataType:预期服务器回传的数据
常用的有:"text"、"json"、"xml"
);
3.2 get / post
$.get/post(
url:"资源路径",
data:"请求参数",
callback:自定义回调函数,【仅处理成功情况!!!】
type:预期服务器回传的数据
);
3.3 getJSON
$.getJSON( 【请求方法:"GET"】
url:"资源路径",
data:"请求参数",
callback:自定义回调函数,【仅处理成功情况!!!】
);
3.4 serialize 【重要】
可以把表单中的所有表单项的内容都获取到,并且以name1=value1&name2=value2的形式帮我们拼接好了。
var formContent = $("表单选择器").serialize();
⭕ 为什么需要用到serialize?
● 传统做法是,通过表单中的submit提交按钮(依靠<form>标签action属性跳转),浏览器会自动将表单中的数据打包为 HTTP 请求并将其发送到服务器。服务器可以使用 request.getParameter() 方法来获取表单数据。
● 但是要想实现不刷新页面情况下异步更新页面,或者处理更复杂的表单逻辑时,服务器并不能感知客户端发送了AJAX请求,所以需要我们手动使用serialize方法,将表单数据发送给服务器。服务器再使用request.getParameter()方法来获取表单项得值。
贯穿始终的:(软件架构思想)MVC
MVC全称:model模型、View视图、Controller控制器。
● Model:将与业务逻辑相关的数据封装为具体的JavaBean类,其作用是处理数据——JavaBean / domain / entity / pojo。
● 实体类Bean:专门存储业务数据,如Student、User等...
● 业务处理Bean:指Service对象或Dao对象,专门用于处理业务逻辑和数据访问。
● View:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
● Controller:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。
⭕ 实际开发,优化思路:
(1) 一个模块,一个公共的servlet。
● 与之协作的html / jsp文件,使用input标签【type="hidden" name="action" value="..."】,以便公共servlet通过action来分辨哪一个功能。
(2) 若干个模块,有若干的公共servlet,抽取公共BaseServlet。
● 各个模块的公共servlet仅书写若干个功能方法。
● BaseServlet实现的功能是通过获取隐藏表单项的值,以此知道具体要调用哪个功能。我们约定,这个值和方法名相同,然后通过【反射】来调用对应的方法。
(3) 数据封装,并抽取BeanUtils。
功能方法中,往往会做相同的动作:从表单中获取参数值,并封装成JavaBean对象。
借助第三方工具包【commons-beanutils-1.8.0.jar,它的依赖包:commons-logging-1.1.1.jar】的BeanUtils类的populate(JavaBean, Map)方法,来帮助我们将获取的表单值,注入到对象中,返回给我们使用。我们可以封装成一个WebUtils工具类。
class WebUtils{
public static <T> T copyParamToBean(Map map, T bean){
try{
BeanUtils.populate(bean, map);
}catch(Exception e){
e.printStackTrace();
}
return bean;
}
}
e.g 用户模块中有功能:"登录"和"注册"。以往的做法是写两个Servlet,分别重写doPost()方法。
● 前端
<input type="hidden" name="action" value="login" />
<input type="hidden" name="action" value="register" />
● 后端
abstract class BaseServlet extends HttpServlet{
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp){
// 获取隐藏表单项的值,
String action = req.getParameter("action");
// 利用反射调用对应方法
Method method = this.class.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
}
}
class UserServlet extends BaseServlet{
// 登录
protected void login(HttpServletRequest req, HttpServletResponse resp){
User user = WebUtils.copyParamToBean(req.getParameterMap(), new User());
...
}
// 注册
protected void register(HttpServletRequest req, HttpServletResponse resp){
User user = WebUtils.copyParamToBean(req.getParameterMap(), new User());
...
}
}
⭐ 其他注意细节
1、web层的同一个servlet功能中,一般区分前台【普通用户】和后台【管理员】。我们往往在web.xml中的<servlet-mapping>标签中的<url-pattern>通过【/client/XXXServlet 和 /manager/XXXServlet】进行不同逻辑处理。
2、避免表单的重复提交!!!
⭕【表单重复提交】 常见三种情况:
● 情况一:提交完表单后。服务器使用请求转发进行页面跳转。这时,如果用户按下功能键F5,就会发起最后一次请求。造成表单重复提交的问题。**
>解决方案:使用【重定向】来进行跳转。
● 情况二:用户正常提交,但是由于网络延迟等原因,迟迟未收到服务器的响应。这个时候用户以为提交失败,多点了几次提交操作,也会造成表单重复提交。
>解决方案:使用【验证码】。
● 情况三:用户正常提交服务器,服务器也没有延迟。但是提交完成后,用户回退提交页面,重新提交。这也会造成表单重复提交。
>解决方案:使用【验证码】。
⭕ 验证码如何防止表单的重复提交?
a) 客户端第一次访问表单时(拉取表单页面内容),服务器就要给表单生成一个随机的验证码字符串,并把它保存到Session域中,同时要把验证码生成为图片一起发送给浏览器,显示在浏览器页面中。
b) 客户端填写表单和验证码,提交。
c) 服务器先获取Session域中的验证码,并在Session域中删除。然后与用户提交的验证码进行比对:如果相同,则正常执行;如果不同,则拒绝执行。
d) 当用户出现上面情况二、三的表单重复提交时,服务器依旧获取Session域中的验证码,此时为null,比对失败,拒绝执行。
3、在进行filter拦截时,既要对页面资源拦截,也要对配置的servlet地址拦截。(结合"细节1")
4、i18n国际化【了解】
国际化(Internationalization)指的是同一个网站可以支持多种不同的语言,以方便不同国家的用户进行访问。
由于拼写过长,所以简写成"i18n"。
a) 方法一:为不同的国际创建不同的网站。
比如苹果公司,英文网站是http://www.apple.com,而中国官网是http://www.apple.com/cn。
b) 方法二:Locale + Properties配置文件 + ResourceBundle
验证码的使用(使用谷歌API,它能够自动创建验证码图片,并把验证码内容保存到session)
(1) 导入谷歌验证码jar包,并加到工程的库中。【kaptcha-2.3.2.jar】
(2) 在web.xml中配置jar包中对应的servlet,并将<servlet-mapping>标签下的<url-pattern>路径后缀改为jpg!!!(因为我们只需要该servlet下的图片) ()
(3) 在表单页面中,加入验证码所需标签:<img src="上面的jpg路径" />。
(4) 在自己的servlet中获取session域中的验证码值,然后立即删除,和用户输入的验证码进行比对,如果成功,才处理对应的业务逻辑。
(5) 可以新增刷新验证码【是指重新向jar中的servlet发起请求,而不是刷新页面】的功能。
● 注意浏览器的缓存问题【每次发起请求,判断你的请求是否和之前一样(缓存里面找),一样的话,不走服务器,直接拿之前的数据】。这就导致了不会重新向jar中的servlet发起请求,验证码无法更新。 ()
● 解决方案:在发起请求中【加入随机参数,例如:时间戳,能保证每次都不一样】,使得与前一次请求不一样,必须从服务器获取。(因为我们只需要该servlet下的图片)
【事务安全】Filter + ThreadLocal
1、问题
在web应用程序中,Servlet容器通常使用【线程池的技术】来处理请求。
线程在被使用完成后,会被线程池回收,但在这种情况下,线程上下文的数据仍然会保留在线程栈中,线程池不会清空线程栈中的数据。()
● 【多用户情况下】:某一用户发起请求后,会从线程池中获取一个空闲的线程,进行请求处理。在使用前可能就已经被其他用户请求使用过了,它可以继续访问线程栈中的数据,产生数据安全问题。
● 【同一用户情况下】:可能会发起多次请求,也就是说2次请求有可能会获取同一个线程。尽管是同一个线程,第二次请求可以访问到第一次请求的数据,产生数据安全问题。
2、解决
在javaweb中,当我们需要保证一个业务是数据安全的,我们需要使用数据库的事务。
● 所以我们需要保证当前请求有自己的连接对象,并在访问数据库时始终用同一个,且该连接对象不能被其他请求影响。
(1) 无法使用request域来保存连接对象。
不同的web容器,在客户端发起请求时,获取HttpServletRequest对象和HttpServletResponse对象可能会不一样。
但一般都会【使用对象池来管理HttpServletRequest对象和HttpServletResponse对象】。在Tomcat中,例如,在处理HTTP请求时,Tomcat会从一个请求处理线程池中获取一个线程,然后将HTTP请求和响应对象传递给该线程来处理。如果没有空闲线程,则请求会被放入等待队列中,直到有线程可用。当一个请求被处理完成后,Tomcat会将其线程返回到线程池中,同时将其HttpServletRequest和HttpServletResponse对象返回到对象池中,以便可以在下一个请求中重用它们。因此,Tomcat会尽可能地重用对象,从而提高性能和效率。
假设现在我们使用request域存储连接对象,我们可以分以下两种情况讨论:
● 在多用户同时请求时,处理的线程是不一样的,这就意味着它们的request对象不会冲突(注意是同时请求),所以此时将连接对象放到request域中可以满足。
● 但是,对于同一用户的多次请求,是有可能用到同一个线程的,而线程在放回线程池时,是不会清除上一次请求的数据,所以下一次请求在使用时,就有可能取到上一次请求的数据。
因此在request域存储连接对象是数据不安全的。
(2) 无法使用session域来保存连接对象。
● session域是跨线程跨请求的,如果用它存储连接对象,类似request,存在线程不安全的问题。
● 并且session对象的存活周期长,会造成不必要的浪费。
(3) 将连接对象封装到ThreadLocal中使用!——【可解决】
ThreadLocal不仅可以解决多线程访问同一数据的安全问题,也可以解决web应用中同一线程的多次请求访问同一数据的安全问题!
⭕ ThreadLocal实现数据安全的原理
每个线程内都有一个ThreadLocalMap,即使是使用了同一线程的情况下,每一次新的请求时,都会创建新的ThreadLocal对象,作为该map的键,值对应于数据的副本。每次请求所对应的 ThreadLocal 对象和数据副本都是互相独立且线程安全的。
3、Filter在事务中的作用
在事务执行结束后,没有异常,说明成功,应该把当前connect对象的事务进行提交;反之,在事务执行过程中出现异常,应该把当前connect对象的事务进行回滚。
(1) 对每一个servlet中的doGet()/doPost/service()方法(对应每一个service的功能方法)对进行【try-catch块】的包裹。
try{
Connection connect = JDBCTransUtilsByDruid.getConnection(); // 获取连接对象
...
JDBCTransUtilsByDruid..commit(); // 提交
}catch(exception e){
JDBCTransUtilsByDruid.rollback(); // 回滚
throw new RuntimeException(e);
}finally{
JDBCTransUtilsByDruid.close(); // 关闭
}
(2) 由于访问servlet资源前可以先经过filter,我们利用filter的doFilter()方法,【一次性】实现这种包裹。⭐
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){
try{
filterChain.doFilter(servletRequest, servletResponse);
JDBCTransUtilsByDruid.commit(); // 提交
}catch(Exception e){
JDBCTransUtilsByDruid.rollback(); // 回滚
throw new RuntimeException(e);
}finally{
JDBCTransUtilsByDruid.close(); // 关闭
}
}