javaweb之servlet、request和response
Servlet
Javaweb中使用的tomcat服务器,利用servlet来接收和处理来自客户端的动态请求。
1、简单说明
首先通过简单的案例来进行实现。
既然是通过servlet来接收并处理请求,那么利用到的肯定有servlet的内容,如何和servlet来进行建立联系?因为servlet是接口,是一种规范,所以实现接口中的方法即可。
public class ServletHelloWorld implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("初始化方法");
System.out.println("servletConfig对应的值是:"+servletConfig);
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello,world");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("销毁方法");
}
}
然后如何让用户和服务器端的程序建立起来联系?一般来说,客户在浏览器端输入路径,发送请求给服务器端,然后让服务器端来进行响应,所以路径和servlet之间应该建立起来关联:
在web.xml中来进行信息配置:
<servlet>
<servlet-name>servletHelloWorld</servlet-name>
<servlet-class>com.guang.quickstart.ServletHelloWorld</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletHelloWorld</servlet-name>
<!--只要是hello的请求都将会交给ServletHelloWorld来进行处理-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
从上面的配置信息中可以很明显的理解到,如果访问的是/hello这个后缀的,那么这个请求将会交给com.guang.quickstart.ServletHelloWorld这个类来进行处理,这样子来描述是比较准备的。servlet和servlet-mapping形成映射。
当ServletHelloWorld第一次被访问的时候,会执行其生命周期方法。
上面写的ServletHelloWorld实现servlet接口中的五个方法中,有三个是生命周期方法。
分别是init、service和destroy方法,分别对应的是初始化、服务、销毁方法。
当ServletHelloWorld第一次被访问,会首先执行init方法,进行一些操作,这个方法只会执行一次;
当ServletHelloWorld无论是在第一次还是在第N次被访问的时候,都将会执行service方法;
在服务器进行关闭的时候执行destroy方法。
当然,通常来说,我们会将init方法的执行提前到容器加载的时候,也就是说可以配置servlet类的初始化时机。
销毁的时候也不会做一些其他的操作。
2、路径配置
从名字上可以看到是路径匹配,和正则表达式有点类似。
在上面的web.xml中的url-pattern标签下,我们配置了/hello后缀的路径交由com.guang.quickstart.ServletHelloWorld这个类来进行处理。
对于这里的路径有多种配置方式,后面可能会用到,所以在这里来进行详细的说明下:
url-pattern路径的配置一共有三种:
1、完全路径匹配;2、目录匹配;3、扩展名匹配
完全路径匹配:以/开头
<!--完全路径匹配: 以 / 打头,以定义的路径结尾-->
<servlet-mapping>
<servlet-name>hello0</servlet-name>
<url-pattern>/hello0</url-pattern>
</servlet-mapping>
目录匹配:以/开头,以*结尾
<!-- 目录匹配: 以/ 打头, 以*结尾 *表示后面的内容可以有,也可以没有-->
<servlet-mapping>
<servlet-name>hello02</servlet-name>
<url-pattern>/hello02/*</url-pattern>
</servlet-mapping>
扩展名匹配:不能够以/开头,而是以*开头的
<!--后缀名匹配的方式 : 以*打头, 以后缀名结尾-->
<servlet-mapping>
<servlet-name>hello02</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
url-pattern的访问优先级顺序是:完全路径匹配>目录匹配>扩展名匹配
3、servlet的创建
在我们实现了servlet接口之后,实现了其必要的方法,可以看到的是,这里的servlet实现类并非是我们自己来进行创建的,而是由tomcat服务器来进行创建的。tomcat容器给我们创建的是servlet类实例是单例的,如果说多个线程来同时操作这个单例对象,如果在当前的servlet实现类中使用成员变量,那么将会导致线程安全问题的发生,所以这点应该要做到避免。线程安全问题参考多线程章节。
多线程指的是每次请求都会创建出来一个新的线程来对请求来进行处理,最终都会调用单例对象的service方法。
4、ServletConfig
从名字中可以看出来这个对象是servlet的配置的意思。
servlet的配置信息可以通过注解WebServlet来进行查看:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
// servlet的名字,这里也就是在web.xml中配置的servlet的名字
String name() default "";
// 默认值
String[] value() default {};
// 路径匹配。从这里可以看到一个servlet可以对应的多个路径
// 额外说明下,一个路径对应着一个servlet,多个路径可以对应着一个servlet
String[] urlPatterns() default {};
// 加载启动时机,-1默认第一次访问的时候启动。如果是1,2,3,数字越大代表优先级越小
int loadOnStartup() default -1;
// 携带的初始化参数
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
从上面摘几个分析下:
@WebServlet(urlPatterns = {"/one"},name = "UrlPatternServletOne",initParams = {@WebInitParam(name = "a",value = "b")})
public class UrlPatternServletOne implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
// 获取得到里面配置的值
String a = servletConfig.getInitParameter("a");
System.out.println(a); // name = "a",value = "b"中的b
String servletName = servletConfig.getServletName();
System.out.println(servletName); // name = "UrlPatternServletOne"中的name
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello,world");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
除此之外,还可以来进行配置启动时机:
@WebServlet(urlPatterns = {"/one"},loadOnStartup = 1)
public class UrlPatternServletOne implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("hello,one");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello,world");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
可以看到如果配置了这里的启动时机是大于0的,那么在本地项目中启动了之后就可以从控制台上显示出来。
但是如果说没有配置大于0的值,而是默认的,那么是在第一次访问的时候来进行加载的。
5、Servlet的体系结构
查看下官方文档说明:
定义所有 servlet 都必须实现的方法。
servlet 是运行在 Web 服务器中的小型 Java 程序。servlet 通常通过 HTTP(超文本传输协议)接收和响应来自 Web 客户端的请求。
要实现此接口,可以编写一个扩展 javax.servlet.GenericServlet 的一般 servlet,或者编写一个扩展 javax.servlet.http.HttpServlet 的 HTTP servlet。
此接口定义了初始化 servlet 的方法、为请求提供服务的方法和从服务器移除 servlet 的方法。这些方法称为生命周期方法,它们是按以下顺序调用的:
构造 servlet,然后使用 init 方法将其初始化。
处理来自客户端的对 service 方法的所有调用。
从服务中取出 servlet,然后使用 destroy 方法销毁它,最后进行垃圾回收并终止它。
除了生命周期方法之外,此接口还提供了 getServletConfig 方法和 getServletInfo 方法,servlet 可使用前一种方法获得任何启动信息,而后一种方法允许 servlet 返回有关其自身的基本信息,比如作者、版本和版权。
从官方API中可以获取得到一些重要的信息:
1、servlet是运行在服务器端的小型java程序;
2、tomcat容器已经实现了HTTP协议的封装和解析,并对每个用户的请求和响应做好了封装。
3、想要写一个servlet,推荐使用两种方式。继承GenericServlet或者是HttpServlet
4、生命周期方法以及生命周期方法的调用顺序;
5、另外的两个生命周期方法的作用。
那么关于GenericServlet和HttpServlet如何来进行选择,继续 根据API文档来进行查看:
GenericServlet说明:
定义一般的、与协议无关的 servlet。要编写用于 Web 上的 HTTP servlet,请改为扩展 javax.servlet.http.HttpServlet。
GenericServlet 实现 Servlet 和 ServletConfig 接口。servlet 可以直接扩展 GenericServlet,尽管扩展特定于协议的子类(比如 HttpServlet)更为常见。
GenericServlet 使编写 servlet 变得更容易。它提供生命周期方法 init 和 destroy 的简单版本,以及 ServletConfig 接口中的方法的简单版本。GenericServlet 还实现 log 方法,在 ServletContext 接口中对此进行了声明。
要编写一般的 servlet,只需重写抽象 service 方法即可。
从这里可以看到:如果要是编写web上的servlet,推荐使用HttpServlet;
因为GenericServlet已经实现了Servlet 和 ServletConfig 接口接口,我们自己写的可以直接来进行扩展GenericServlet
这里说明了,即使GenericServlet已经提供了servlet原始方法中的简单版本,但是依然推荐使用HttpServlet。
再看下HttpServlet的说明:
提供将要被子类化以创建适用于 Web 站点的 HTTP servlet 的抽象类。HttpServlet 的子类至少必须重写一个方法,该方法通常是以下这些方法之一:
doGet,如果 servlet 支持 HTTP GET 请求
doPost,用于 HTTP POST 请求
doPut,用于 HTTP PUT 请求
doDelete,用于 HTTP DELETE 请求
init 和 destroy,用于管理 servlet 的生命周期内保存的资源
getServletInfo,servlet 使用它提供有关其自身的信息
几乎没有理由重写 service 方法。service 通过将标准 HTTP 请求分发给每个 HTTP 请求类型的处理程序方法(上面列出的 doXXX 方法)来处理它们。
同样,几乎没有理由重写 doOptions 和 doTrace 方法。
servlet 通常运行在多线程服务器上,因此应该意识到 servlet 必须处理并发请求并小心地同步对共享资源的访问。共享资源包括内存数据(比如实例或类变量)和外部对象(比如文件、数据库连接和网络连接)。有关在 Java 程序中处理多个线程的更多信息
这里可以看到HttpServlet是一个抽象类,子类中必须重写其中的一个doXxx方法。
但是这里应该有一个问题,那就是为什么原生的servlet中的接口中的声明方法这里没有?而是重写了doXxx方法?
通过上面的一句话来看出来:
service 通过将标准 HTTP 请求分发给每个 HTTP 请求类型的处理程序方法(上面列出的 doXXX 方法)来处理它们。
也就是说对于每个请求来说,都将会请求分发出去对应的处理程序方法,也就是doXxx方法处理,那么看下对应的方法调用关系:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 通过强制类型转换,转换成http类型的
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
// 然后进行了方法重载
this.service(request, response);
}
再看下这个方法,可以看到在这个重载的service方法中调用了doXxx方法:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取得到请求方式
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
这里也就是上面为什么说几乎没有理由重写 service 方法的原因了。
但是对于一般的web开发来说,只需要实现doPost方法和doGet方法即可。
所以现在的开发方式对于我们来说,推荐使用的是继承HttpServlet类,重写其中的doGet方法和doPost方法:
在idea中直接使用快捷键即可:
@WebServlet(name = "HelloServlet", value = "/HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
可以看到自己写的servlet类继承了HttpServlet类之后,直接重写了doGet和doPost方法,而不是去重写service方法了。
6、ServletContext
servlet上下文对象,在一个servlet项目中,这个对象有且只有一个。也就是说在一个web项目中,都会有这样一个servletContext对象的存在,这个对象代表的就是这个项目的,而不是属于某一个servlet的。
这个对象的作用是什么?如何来进行获取?
这个servletContext对象作用分类:
1、可以存放数据和获取得到数据,实现servlet之间的数据共享;
2、获取得到文件的MIME;
3、获取得到文件资源;
4、获取得到全局配置文件;
因为第一个用的最多,所以就拿第一个来举例子来进行说明,后面的后续用到了再说:
存取数据
新建两个servlet类
@WebServlet(name = "HelloServletOne", value = "/HelloServletOne")
public class HelloServletOne extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.getServletContext().setAttribute("hello",new String("hello,world"));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
@WebServlet(name = "HelloServletTwo", value = "/HelloServletTwo")
public class HelloServletTwo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object hello = this.getServletContext().getAttribute("hello");
System.out.println("hello");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
先访问One,再访问Two,可以看到控制台,输出了对应的内容。
但是需要注意的是:如果没有存入值,就直接来进行获取,那么将会出现NPE异常。
7、Request
在Servlet API中,定义了一个HttpServletRequest接口,它继承自ServletRequest接口,专门用来封装HTTP请求消息。由于HTTP请求消息分为请求行、请求头和请求体三部分,因此,在HttpServletRequest接口中定义了获取请求行、请求头和请求消息体的相关方法。Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。
request作用:
- 操作请求行、头、体的中的内容
- 请求转发
- 存储数据,能够像servletContext一样存储数据
7.1、操作请求行
获取客户机信息
请求方式 请求路径(URI) 协议版本
http://localhost:8080/servlet02_war_exploded/HelloServletThree?username=zs&password=123456
-
getMethod();获取请求方式
-
getRemoteAddr() ;获取客户机的IP地址(知道是谁请求的)
-
getContextPath();获得当前应用工程名(部署的路径);
-
getRequestURI();获得请求地址,不带主机名
-
getRequestURL();获得请求地址,带主机名
-
getServerPort();获得服务端的端口
-
getQueryString();获的请求参数(get请求的,URL的?后面的. eg:username=zs&password=123456)
编写代码来实现一下:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();
System.out.println("请求方式是:----"+method);
String requestURI = request.getRequestURI();
System.out.println("请求的URI是:----"+requestURI);
StringBuffer requestURL = request.getRequestURL();
System.out.println("请求的URL是:----"+requestURL);
String remoteAddr = request.getRemoteAddr();
System.out.println("请求的远程地址是:----"+remoteAddr);
String contextPath = request.getContextPath();
System.out.println("请求上下文路径是:----"+contextPath);
int serverPort = request.getServerPort();
System.out.println("请求端口号是:----"+serverPort);
String queryString = request.getQueryString();
System.out.println("get请求方式参数是:----"+queryString);
}
查看控制台输出:
请求方式是:----GET
请求的URI是:----/servlet02_war_exploded/HelloServletThree
请求的URL是:----http://localhost:8080/servlet02_war_exploded/HelloServletThree
请求的远程地址是:----0:0:0:0:0:0:0:1
请求上下文路径是:----/servlet02_war_exploded
请求端口号是:----8080
get请求方式参数是:----username=zs&password=123456
获取得到请求体中的参数的时候,因为tomcat>=8的时候,乱码问题已经处理好了,而留给我们的只是处理post方式的。
因为客户端提交给服务器端的是一些中文数据,而服务器端在进行解析的时候,因为解码和编码方式不同,导致了服务器端拿到的是乱码,所以一般来说这些都是交友给我们来设置的。
req.setCharacterEncoding("UTF-8");
7.2、请求转发
请求转发是在服务器内部来进行请求调用的,也就是说只能够在服务器内部来进行使用,不能够跳转到外部去。
request.getRequestDispatcher(url).forward(request, response); //转发
下面来通过静态资源和动态资源分别来举个例子:
/**
* 将这个请求转发到PictureServlet去
*/
@WebServlet(name = "HelloServletFour", value = "/HelloServletFour")
public class HelloServletFour extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("请求转发");
// 相对路径
request.getRequestDispatcher("PictureServlet").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
/**
* 通过请求转发,访问到一个静态页面
*/
@WebServlet(name = "PictureServlet", value = "/PictureServlet")
public class PictureServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("我在这里等着请求,等着关门打狗");
// 来进行响应一下
request.getRequestDispatcher("/WEB-INF/pic/git.png").forward(request,response);
// request.getRequestDispatcher("/WEB-INF/en.html").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
可以看到,对于浏览器端来说:http://localhost:8080/servlet02_war_exploded/HelloServletFour
请求的是HelloServletFour这个servlet,但是因为请求转发交由给PictureServlet来进行处理,然后由服务器端来进行响应。
从这里可以看到浏览器端地址是没有发生变化的。
7.3、操作请求体
主要是用来获取得到请求参数的。
获得请求参数
法名 | 描述 |
---|---|
String getParameter(String name) | 获得指定参数名对应的值。如果没有则返回null,如果有多个获得第一个。 例如:username=jack |
String[] getParameterValues(String name) | 获得指定参数名对应的所有的值。此方法专业为复选框提供的。 例如:hobby=抽烟&hobby=喝酒&hobby=敲代码 |
Map<String,String[]> getParameterMap() | 获得所有的请求参数。key为参数名,value为key对应的所有的值。 |
现在我们已经可以使用request对象来获取请求参数,但是,如果参数过多,我们就需要将数据封装到对象。
以前封装数据的时候,实体类有多少个字段,我们就需要手动编码调用多少次setXXX方法,因此,我们需要BeanUtils来解决这个问题。
BeanUtils是Apache Commons组件的成员之一,主要用于简化JavaBean封装数据的操作。
使用步骤:
- 导入jar
commons-beanutils-1.8.3.jar 和 commons-logging-1.1.1.jar
- 使用BeanUtils.populate(user,map)
通过一个例子来进行说明:
/**
* 获取得到参数,将其转账到对象中去
*/
@WebServlet(name = "ParamterServlet", value = "/ParamterServlet")
public class ParamterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取得到的参数
String id = request.getParameter("id");
String username = request.getParameter("username");
System.out.println("对应的id是:"+id);
System.out.println("对应的username是:"+username);
System.out.println("--------------------");
System.out.println("--------------------");
System.out.println("--------------------");
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
String key = stringEntry.getKey();
String[] value = stringEntry.getValue();
for (String s : value) {
System.out.println("对应的key是"+key+",对应的value是"+s);
}
}
// 但是这种使用方式太过于麻烦,所以来使用API来进行操作
User user = new User();
try {
BeanUtils.populate(user,parameterMap);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(user);
}
}
查看控制台输出:
对应的id是:2
对应的username是:liguang
--------------------
--------------------
--------------------
对应的key是id,对应的value是2
对应的key是username,对应的value是liguang
User{id=2, username='liguang'}
7.4、存取值
这个也是request使用最多的地方
ServletContext: 可以存和取值,范围是整个应用程序, AServlet 存值, BServlet能取值
request范围: 一次请求内有效!!!
域对象是一个容器,这种容器主要用于Servlet与Servlet/JSP之间的数据传输使用的。
Object getAttribute(String name)
void setAttribute(String name,Object object)
void removeAttribute(String name)
可以看下这里的操作
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//存值
req.setAttribute("username" , "张三");
//自己取值
String username = (String) req.getAttribute("username");
System.out.println("FirstServlet::username=" + username);;
System.out.println("first::hashcode" + req.hashCode());
System.out.println("使用请求转发跳转到SecondServlet" );
//使用请求转发的方式跳转到SecondServlet
req.getRequestDispatcher("second").forward(req,resp);
}
}
8、Response
在Servlet API中,定义了一个HttpServletResponse接口(doGet,doPost方法的参数),它继承自ServletResponse接口,专门用来封装HTTP响应消息。由于HTTP响应消息分为响应行、响应头、响应体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码、响应头、响应体的方法
response的作用
相对request来说,response的功能就比较简单了。
用来操作响应行、响应头和响应体
操作响应行中,API提供的一个最重要的方法就是操作状态码。所以一定要记熟常见的状态码。
200:成功
302:重定向
304:访问缓存
404:客户端错误
500:服务器错误
等等比较常见的状态码。
8.1、操作响应头
这里用来告知客户端需要对服务器端相应的流来做些什么操作。web开发中就是告知浏览器端需要做一些什么事情。
关注的方法: setHeader(String name,String value);
常用的响应头
Refresh:定时跳转 (eg:服务器告诉浏览器5s之后跳转到百度)
Location:重定向地址(eg: 服务器告诉浏览器跳转到xxx)
Content-Disposition: 告诉浏览器下载
Content-Type:设置响应内容的MIME类型(服务器告诉浏览器内容的类型)
8.2、操作响应体
响应体中封装的内容影响这客户端的展示情况,所以这里也是非常重要的。
可以看下API文档,有两种方式来进行操作:
ServletOutputStream getOutputStream()
Returns a ServletOutputStream suitable for writing binary data in the response.
PrintWriter getWriter()
Returns a PrintWriter object that can send character text to the client.
但是这两种方式获取得到的流是互斥的,也就是说只能够来选择其中的一种来进行使用。
8.3、解决乱码
解决字符流输出中文乱码问题
response.setContentType("text/html;charset=utf-8");
使用字节输出流输出中文乱码问题
//设置浏览器打开方式
response.setHeader("Content-type", "text/html;charset=utf-8");
//得到字节输出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write("你好".getBytes("utf-8"));// 使用平台的默认字符(utf-8)集将此 String 编码为 byte 序列
可以看到对于响应这一步来说,相对于请求来说,麻烦了一步。请求是直接设置即可。
让其和请求数据对比:
请求乱码:
request.setCharacterEncoding("utf-8");
响应乱码:
response.setContentType("text/html;charset=utf-8");
8.4、重定向
重定向是两次请求,也就是说第一次请求的时候,服务器端响应给客户端的是另外一个请求的地址;
第二次客户端重新发起请求,去寻找目标资源;
在这其中,浏览器端的地址栏会发生变化。
request域对象中存放的数据不能够获取得到,因为这个时候第二次的请求就相当于是一次新的请求了,无法再次访问得到上次的数据了。
这个时候会有两种情况的发生:
1、如果请求的是外界资源,那么什么都不用做;
2、但是如果重定向后请求的是内部资源,那么重定向不能够访问到web-inf文件夹下的资源。
直到今天才想明白tomcat就是一个最基本的jar包而已,只是说其可以用来处理web请求和响应的。
是因为其支持这种操作,支持对应的处理而已。
文件下载:
/**
* 下载文件三部曲:设置两个头和一个流
*
* 需要利用到输出流和输入流来结合进行使用
*/
@WebServlet(name = "DownloadServlet", value = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String fileName = "commons-beanutils-1.8.3.jar";
// 获取得到流对象
InputStream resourceAsStream = servletContext.getResourceAsStream("/WEB-INF/lib/"+fileName);
String mimeType = servletContext.getMimeType(fileName);
// 设置两个头
response.setContentType(mimeType);
response.setHeader("Content-Disposition" , "attachment;filename="+fileName);
ServletOutputStream outputStream = response.getOutputStream();
byte [] buffer = new byte[1024];
int len = 0 ;
while( (len = resourceAsStream.read(buffer)) != -1 ){
outputStream.write(buffer , 0 , len);
}
// 关不关无所谓,框架底层都会关的
outputStream.close();
resourceAsStream.close();
}
}
9、总结
servlet规范:生命周期方法、单例多线程、访问路径和体系结构(如何正确选取使用哪种servlet)
request操作请求行、请求头和请求体:
response操作响应行、响应头和响应体:
乱码产生原因和处理方式