Java Web 开发:Request 与 Response
前言
学习目标
- 理解Request、Response和HTTP报文之间的关系
- 掌握通过Request能够获得的信息
- 请求URL、URI、请求协议
- 请求头、客户机和主机
- 请求参数
- 掌握通过Response能够完成的设置
- 响应中文乱码问题
- 响应(Json)字符串、图片(文件)
- 了解特殊的响应头
- 逐步通过反射能够解决一些通用问题
前置知识准备
- URL统一资源位置和URI统一资源标识符
- 比如请求http://localhost:8080/demo7/hello.jsp
- url:http://localhost:8080/demo7/hello.jsp
- uri:/demo7/hello.jsp
- HTTP 请求报文和响应报文
- 请求报文和响应报文分别包含哪些部分
- 请求报文
- 请求行 请求方法 请求URL 请求协议
- 请求头 key=value
- (空行)
- 请求体 username=root {“username”:“root”} 字节数据
- 响应报文
- 响应行 协议 响应状态码
- 响应头 key=value
- (空行)
- 响应体 字符 字节数据
- 反射reflect
- 获得Class对象
- 获得成员变量并给成员变量赋值
- 获得方法并且能够调用指定方法
- MyBatis的基本使用
- 通过form表单分别构造get请求和post请求,能构造文件上传的请求
- 解析字符串获得其中的信息
请求报文
请求报文的组成部分
- 请求行
- 请求方法
- 请求的URL http://localhost:8080/hello
- 请求协议
- 请求头
- 格式是key:value
- 比较特殊的请求头:
- Content-Type 由浏览器提供给服务器的正文类型
- Accept 浏览器期望从服务器获得正文的类型( 服务器提供给浏览器的正文类型)
- Host 主机ip
- (空行)
- 请求正文
- 普通的Get请求和form表单提供的Get/Post请求 如果携带了参数它的格式是 key1=value1&key2=value2&key3=value3等
- 携带Json数据的post请求 {}或[]
POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1Host: 101.43.69.31:8083Connection: keep-aliveContent-Length: 45Accept: application/json, text/plain, */*User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78Content-Type: application/json;charset=UTF-8Origin: http://101.43.69.31:8080Referer: http://101.43.69.31:8080/Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
{"username":"admin123","password":"admin123"}响应报文
响应报文的组成
- 响应行
- 协议
- 状态码
- 响应头
- 格式是key=value
- Content-Type 服务器提供给浏览器的正文类型,后面通常会跟charset,比如application/json;charset=utf-8
- (空行)
- 响应正文
- 如果是json,Content-Type中通常是application/json
HTTP/1.1 200Vary: accept-encoding,origin,access-control-request-headers,access-control-request-method,accept-encodingSet-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Sun, 12-Feb-2023 06:51:56 GMTSet-Cookie: JSESSIONID=24287278-5ebb-407d-a3f7-56b74782c4c7; Path=/; HttpOnlyAccess-Control-Allow-Origin: *Content-Type: application/json;charset=UTF-8Date: Mon, 13 Feb 2023 06:51:56 GMTContent-Length: 200
{"errno":0,"data":{"adminInfo":{"nickName":"admin123","avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"},"token":"24287278-5ebb-407d-a3f7-56b74782c4c7"},"errmsg":"成功"}反射
获得Class对象
获得Class对象的方式
定义了一个类,这个类,处于com.cskaoyan.service包下,类名为UserServiceImpl,我们想要获得这个Class对象,如何获得呢?
-
UserServiceImpl.class
-
UserServiceImpl userService = new UserServiceImpl();
useService.getClass();
-
Class.forName(“com.cskaoyan.service.UserServiceImpl”)
Class<UserServiceImpl> clazz1 = UserServiceImpl.class;
UserServiceImpl userService = new UserServiceImpl();Class<? extends UserServiceImpl> clazz2 = userService.getClass();
Class<?> clazz3 = Class.forName("com.cskaoyan.service.UserServiceImpl");如果我们想要使用一些通用性的代码,我们用哪种方式?
Class.forName
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { InputStream inputStream = DriverDemo.class.getClassLoader().getResourceAsStream("parameter.properties"); Properties properties = new Properties(); properties.load(inputStream);
String driver = (String) properties.get("driver");
Class<?> driverClass = Class.forName(driver);
Object instance = driverClass.newInstance();}# driver=com.cskaoyan.service.UserServiceImpldriver=com.mysql.jdbc.Driver我们使用的mysql版本是5.7,对应的驱动是com.mysql.jdbc.Driver
如果使用mysql版本8,对应的驱动com.mysql.cj.jdbc.Driver
获得成员变量并给成员变量赋值
// 通过class能够获得实例// 可以直接使用newInstance方法Object instance1 = clazz3.newInstance();// 可以先获得构造器(构造方法),通过构造方法实例化Constructor<?> constructor = clazz3.getDeclaredConstructor();Object instance2 = constructor.newInstance();
// 利用反射方式获得Field,通过反射的方式复制Field usernameField = clazz3.getDeclaredField("username");usernameField.setAccessible(true);usernameField.set(instance2,"zhangsan");// instance2的username这个成员变量赋值zhangsan
// 我们并不建议直接通过反射的方式给成员变量赋值,我们建议使用set方法做赋值获得方法并且能够调用方法
//UserServiceImpl userServiceImpl = (UserServiceImpl) instance1;//userServiceImpl.setUsername("lisi");// 通过反射的方式实现通用性的设置
Properties properties = new Properties();properties.load(ReflectExecution.class.getClassLoader().getResourceAsStream("parameter.properties"));String className = (String) properties.get("className");String method = (String)properties.get("method");String value = (String)properties.get("value");
Class<?> clazz = Class.forName(className);Object instance = clazz.newInstance();//instance的username成员变量是否是lisiMethod declaredMethod = clazz.getDeclaredMethod(method, String.class);Object invoke = declaredMethod.invoke(instance, value);Object invoke = method.invoke(instance,args)
| 变量名 | 类型 | 含义 |
|---|---|---|
| method | Method → clazz.getDeclaredMethod | 通过反射获得的Method |
| instance | Object 1. new XXX ; 2.反射,比如clazz.newInstance | 就是实例(对象) |
| args | Object[] | 参数,把所有的参数封装为数组 |
| invoke | Object | instance对象执行method的返回值 |
/** * 通过反射的方式执行UserServiceImpl中的sayHello */public static void main(String[] args) throws Exception{ Properties properties = new Properties(); properties.load(ReflectExecution.class.getClassLoader().getResourceAsStream("parameter.properties")); String className = (String) properties.get("className"); String method = (String)properties.get("method"); String value = (String)properties.get("value"); //String parameter1 = (String)properties.get("parameter1"); //String parameter2 = (String)properties.get("parameter2"); Object parameter1 = properties.get("parameter1"); Object parameter2 = properties.get("parameter2");
Class<?> clazz = Class.forName(className); Object instance = clazz.newInstance();
Method setUsername = clazz.getDeclaredMethod("setUsername", String.class); setUsername.invoke(instance, value);
//Method declaredMethod = clazz.getDeclaredMethod(method, String.class,String.class); Method declaredMethod = clazz.getDeclaredMethod(method, parameter1.getClass(),parameter2.getClass()); //执行sayHello方法 Object result = declaredMethod.invoke(instance, new Object[]{parameter1, parameter2});}MyBatis
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));// 在整个应用程序中维护这个对象,想要在多个Servlet中共享这个对象// ServletContext// 提供一个Servlet,loadOnStartup为正数,且数值比较小,这个Servlet在应用程序启动过程中就会率先开始初始化,我们就可以利用生命周期的init方法去初始化SqlSessionFactory实例,并且将其放入到ServletContext中概述
Request 请求
Response 响应
先看我们之前开发的Servlet,在Servlet中的service以及HttpServlet的doGet、doPost方法中的形参
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}在Tomcat的时候就会产生Request,同时也会产生Response
可以这样子理解,当我们通过浏览器(或客户端)发送请求,在服务器应用中整个过程中的信息流通都是通过Request、Response流通的
Request:获得提供的信息,主要使用的是其getXXX方法
Response:设置信息提供给客户端(浏览器),主要使用的是其setXXX方法



每次发起请求,其实产生一组新的Request和Response对象
记住一个点:使用Request主要为了输入,使用Response主要为了输出
服务器将请求报文封装为Request对象,方便我们通过Request提供的方法来直接获得请求报文中的信息;服务器将响应的信息封装为Response对象,当我们通过Response对象来完成各种设置之后,服务器响应给浏览器的时候将Response对象转换为响应报文。
为了提供统一的规范,提供统一的规范接口ServletRequest和ServletResponse,为什么提供的是接口,接口能够提供统一的规范
Request
HttpServletRequest 获得HTTP请求报文中携带的信息
POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1Host: 101.43.69.31:8083Connection: keep-aliveContent-Length: 45Accept: application/json, text/plain, */*User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78Content-Type: application/json;charset=UTF-8Origin: http://101.43.69.31:8080Referer: http://101.43.69.31:8080/Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
{"username":"admin123","password":"admin123"}请求报文的组成
- 请求行
- 请求头
- (空行)
- 请求体
请求行
POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1GET http://localhost:8080/demo3/hello?username=zhangsan HTTP/1.1| 信息 | 内容 | 方法 | 返回值 | 说明 |
|---|---|---|---|---|
| 请求方法 | POST、GET | getMethod() | String | 获得请求方法 |
| URL | http://101.43.69.31:8083/admin/auth/login | getRequestURL() | StringBuffer | 获得请求URL |
| URI | /admin/auth/login、/demo3/hello | getRequestURI() | String | 获得请求URI |
| context-path | /demo3 | getContextPath() | String | 获得上下文 |
| 服务器ip | 101.43.69.31、localhost | getLocalAddr() | String | 获得ip |
| 端口号 | 8083、8080 | getLocalPort() | int | 获得端口号 |
| QueryString | username=zhangsan | getQueryString() | String | 获得Get请求的查询字符串 |
| 协议 | HTTP/1.1 | getProtocol() | String | 获得通讯协议 |
代码
@WebServlet("/line")public class LineServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { // http://localhost:8080/demo3/line?username=zhangsan // http://192.168.0.180:8080/demo3/line?username=zhangsan // 192.168.0.180可以修改为自己的ip
// 请求方法 String method = request.getMethod(); // 请求URL和URI String url = request.getRequestURL().toString(); String uri = request.getRequestURI();
// 服务器本地的IP和端口号 String localAddr = request.getLocalAddr(); int localPort = request.getLocalPort();
// 应用的上下文路径 String contextPath = request.getContextPath();
String queryString = request.getQueryString();//username=zhangsan
// 请求协议 String protocol = request.getProtocol();
}}请求头
Host: 101.43.69.31:8083Connection: keep-aliveContent-Length: 45Accept: application/json, text/plain, */*User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78Content-Type: application/json;charset=UTF-8Origin: http://101.43.69.31:8080Referer: http://101.43.69.31:8080/Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6请求头中的信息,都是key:value的形式
- 可以知道有哪一些请求头 Key → 获得所有的请求头
- 也可以知道这些请求头当中的值是什么 Value → 获得特定Key对应的Value
| 方法 | 返回值 | 说明 |
|---|---|---|
| getHeaderNames() | Enumeration<String> | 获得所有的请求头,可以通过遍历的方式来使用,使用方式类似于Iterator |
| getHeader(String) | String | 传入的是请求头的Key,返回的是请求头的value |
构造一个请求,请求报文
GET http://192.168.0.180:8080/demo4/header HTTP/1.1Host: 192.168.0.180:8080Connection: keep-aliveUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.41Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6代码
@WebServlet("/header")public class HeaderServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { Enumeration<String> headerNames = request.getHeaderNames(); System.out.println("所有的请求头"); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); System.out.println(headerName); }
// 想要获得Host请求头的值 String hostValue = request.getHeader("Host"); System.out.println("hostValue = " + hostValue); }}控制台输出的结果
所有的请求头hostconnectioncache-controlupgrade-insecure-requestsuser-agentacceptaccept-encodingaccept-languagehostValue = 192.168.0.180:8080可以直接输入对应的key-value
Enumeration<String> headerNames = request.getHeaderNames();System.out.println("所有的请求头");while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); String headerValue = request.getHeader(headerName); System.out.println(headerName + ":" + headerValue);}小练习:能够判断,是否包含某个请求头呢?如果包含,请把对应值打印出来
注意事项:请求头大小写不敏感
// 想要获得Host请求头的值String hostValue = request.getHeader("Host");String hostValue2 = request.getHeader("host");String hostValue3 = request.getHeader("HOST");请求体
form表单
<form action="/demo1/body1" method="post"> <input name="username" type="text"><br> <input type="submit"></form>Postman构造的

请求报文是
POST http://localhost:8080/demo1/body HTTP/1.1User-Agent: PostmanRuntime/7.29.2Accept: */*Host: localhost:8080Accept-Encoding: gzip, deflate, brConnection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 17
username=zhangsan请求体的部分
username=zhangsan
字节流InputStream、字符流Reader
| 内容 | 方法 | 返回值 | 描述 |
|---|---|---|---|
| 字节流 | getInputStream() | ServletInputStream | 获得字节流 |
| 字符流 | getReader() | BufferedReader | 获得字符流 |
字节流
@WebServlet("/body2")public class BodyServlet2 extends HttpServlet {
@Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { ServletInputStream inputStream = request.getInputStream(); File file = new File("D:\\tmp", "1.txt"); FileOutputStream outputStream = new FileOutputStream(file);
int length = 0; byte[] bytes = new byte[1024]; while ((length = inputStream.read(bytes)) != -1) { outputStream.write(bytes,0,length); } outputStream.close(); inputStream.close(); }}字符流
@WebServlet("/body1")public class BodyServlet1 extends HttpServlet {
@Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { BufferedReader reader = request.getReader(); File file = new File("D:\\tmp", "2.txt"); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); String str = null; while ((str = reader.readLine()) != null) { bufferedWriter.write(str); } bufferedWriter.flush(); bufferedWriter.close(); reader.close(); }}在同一个方法中,不能同时使用字符流和字节流,原因是里面有标记,使用其中的一个流会导致标记后移,另外一个流无法使用
特殊信息
客户机和服务器主机信息(了解)
请求是从客户机发到服务器的,在服务器中处理信息的获得,那么对于服务器,本地local是服务器,远程remote是客户机
我们在Servlet中可以获得本地的IP和Port,也可以获得远程IP和Port
| 信息 | 方法 | 返回值 |
|---|---|---|
| 本地(服务器)IP | getLocalAddr() | String |
| 本地(服务器)端口号 | getLocalPort() | int |
| 远程(客户机)IP | getRemoteAddr() | String |
| 远程(客户机)端口号 | getRemotePort() | int |
@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获得服务器端的信息 String localAddr = req.getLocalAddr(); int localPort = req.getLocalPort();
// 获得客户机端的信息 String remoteAddr = req.getRemoteAddr(); int remotePort = req.getRemotePort();
System.out.println("由" + remoteAddr + ":" + remotePort + "发送到" + localAddr + ":" + localPort);}★请求参数
请求参数的场景:
-
请求参数 在请求行中
- getQueryString()
-
请求参数 在请求体中
- getInputStream()/getReader()
指的是key=value&key=value
@WebServlet("/origin")public class OriginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }
@Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { String parameterString = null; if ("GET".equals(request.getMethod())) { parameterString = request.getQueryString(); }else { //ServletInputStream inputStream = request.getInputStream(); //byte[] bytes = new byte[1024]; //inputStream.read(bytes); //parameterString = new String(bytes); BufferedReader reader = request.getReader(); parameterString = reader.readLine(); } Map<String,String> parameterMap = executeMap(parameterString); String username = parameterMap.get("username");
System.out.println(username); }
private Map<String, String> executeMap(String parameterString) { HashMap<String, String> map = new HashMap<>(); if (parameterString == null || "".equals(parameterString)) { return map; } //parameterArray[0] = "username=zhangsan" //parameterArray[1] = "password=lisi" String[] parameterArray = parameterString.split("\\&"); for (String parameter : parameterArray) { int index = parameter.indexOf("="); String key = parameter.substring(0,index); String value = parameter.substring(index + 1, parameter.length()); map.put(key, value);
} return map; }}直接封装
但是实际开发中并不需要我们自己这么做,因为Request已经帮我们封装了可以直接使用的方法了
request.getParameterXXX这样的一些方法
| 方法 | 返回值 | 说明 |
|---|---|---|
| getParameterNames() | Enumeration<String> | 获得所有的key |
| getParameterMap() | Map<String,String[]> | 获得所有的请求参数 |
| getParameter(String) | String | 获得第一个值 |
| getParameterValues(String) | String[] | 获得所有值 |
username=zhangsan&password=lisi&hobby=sing&hobby=dance&hobby=rap&hobby=basketball
request对于请求参数的封装实际上,封装为一个Map<String,String[]>

/** * GET http://localhost:8080/demo7/parameter?key1=a1&key1=a2&key1=a3&key2=b1&key2=b2&key2=b3&key3=c1&key3=c2&key3=c3 * key1: a1,a2,a3 * key2: b1,b2,b3 * key3: c1,c2,c3 * @author stone * @date 2023/02/14 16:49 */@WebServlet("/parameter")public class ParameterServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }
@Override protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { Map<String, String[]> parameterMap = request.getParameterMap(); Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String parameterName = parameterNames.nextElement(); System.out.println("parameterName = " + parameterName); }
String key1 = request.getParameter("key1"); System.out.println("key1 = " + key1);
String[] key1s = request.getParameterValues("key1"); System.out.println("key1s = " + Arrays.asList(key1s)); }}引用类型
GET http://localhost:8080/demo7/register?username=root&password=123456&age=20&birthday=2000-10-15 HTTP/1.1将请求参数封装为一个引用类型的对象,比如User
-
个性化(并不是一个褒义词)
-
User user = new User();user.setUsername(request.getParameter("username"));user.setPassword(request.getParameter("password"));
-
-
反射
-
BeanUtils.transfer(instance,parameterMap);//通过自己写的工具类,进行转换
-
public class WdBeanUtils {@SneakyThrowspublic static <T> T transfer(Class<T> clazz, Map<String, String[]> parameterMap) {// 先创建clazz对应的实例T instance = clazz.newInstance();// 给其成员变量赋值 →// 反射直接给成员变量赋值 field.set(instance,value)// 反射通过set方法间接给成员变量赋值 setMethod.invoke(instance,value)Iterator<String> iterator = parameterMap.keySet().iterator();// 反射直接给field赋值// giveFieldValue(clazz, parameterMap, instance, iterator);// 反射直接调用set方法,给set方法传入形参,通过形参给field赋值giveMethodValue(clazz, parameterMap, instance, iterator);// 赋值完成之后给其返回去return instance;}private static <T> void giveMethodValue(Class<T> clazz, Map<String, String[]> parameterMap, T instance, Iterator<String> iterator) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {// 请求参数的名称while (iterator.hasNext()) {String fieldName = iterator.next();// 拼接set方法名// username → setUsername// password → setPassword// fieldName 首字母大写char[] chars = fieldName.toCharArray();chars[0] -= 32;String upperFirst = new String(chars);String setMethodName = "set" + upperFirst;// 获得形参的类型 → 成员变量的类型Field field = clazz.getDeclaredField(fieldName);Class<?> type = field.getType();Method method = clazz.getDeclaredMethod(setMethodName, type);//set方法String[] value = parameterMap.get(fieldName); // username=zhangsan → value=[zhangsan] → setUsername("zhangsan")if (field.getType().isArray()) {method.invoke(instance, (Object) value);} else {//method.invoke(instance, value);method.invoke(instance, value[0]); // user.setUsername("zhangsan")}}}private static <T> void giveFieldValue(Class<T> clazz, Map<String, String[]> parameterMap, T instance, Iterator<String> iterator) throws NoSuchFieldException, IllegalAccessException {while (iterator.hasNext()) {// 请求参数名 → 成员变量名String fieldName = iterator.next();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);// 判断field对应的成员变量的类型是否是数组,// 如果是数组,给其的值是value// 如果不是数组,给其的值是value[0]String[] value = parameterMap.get(fieldName);if (field.getType().isArray()) {field.set(instance, value);} else {field.set(instance, value[0]);}}}}
-
-
BeanUtils
-
引入依赖commons-beanutils
-
BeanUtils.copyProperties(instance, parameterMap);//直接使用其提供的copyProperties方法
-
Post请求请求参数乱码
乱码问题:编解码不一致
构造一个form表单,一个Get请求,一个是Post请求,分别去获得请求参数,查看通过Get和Post请求获得的请求参数
<h1>GET请求</h1><form action="/demo8/parameter" method="get"> <input name="username"><input type="submit"></form><h1>POST请求</h1><form action="/demo8/parameter" method="post"> <input name="username"><input type="submit"></form>@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); System.out.println("username = " + username);}文件上传
在实际的开发过程中,有些场景需要做文件上传,比如上传头像,上传商品的描述图片等。
我们首先来看文件上传的请求的form表单如何构造
然后来看其对应的请求报文是什么样子的,然后我们在做进一步的分析
创建了一个form表单如下
注册用户信息<form action="/demo1/parameter/file" enctype="multipart/form-data" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="text" name="password"><br> 年龄:<input type="text" name="age"><br> 头像<input type="file" name="avatar"><br> <input type="submit"></form>显示效果如下

接下来发送请求,并且通过fiddler来抓取请求报文
POST http://localhost:8080/parameter/file HTTP/1.1Host: localhost:8080Connection: keep-aliveContent-Length: 21336Cache-Control: max-age=0sec-ch-ua: "Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1Origin: http://localhost:8080Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydR2cL54pRAC57iDsUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: same-originSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentReferer: http://localhost:8080/demo1/file.htmlAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
------WebKitFormBoundarydR2cL54pRAC57iDsContent-Disposition: form-data; name="username"
zhangsan------WebKitFormBoundarydR2cL54pRAC57iDsContent-Disposition: form-data; name="password"
123456------WebKitFormBoundarydR2cL54pRAC57iDsContent-Disposition: form-data; name="age"
25------WebKitFormBoundarydR2cL54pRAC57iDsContent-Disposition: form-data; name="avatar"; filename="ikun.jpeg"Content-Type: image/jpeg
JFIF H H C
")$+*($''-2@7-0=0''8L9=CEHIH+6OUNFT@GHE C
!!E.'.EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE W " W !1AQaq "R 2Br #4b $35S %&CTUcs 6E '7DF t ( !1A"2Qqa#3R ? 6Y M~ 4& 8 +6Y]n 1 /64~p \ 7m E8qo 8 +Λp 8 4 8= oJ_ v?9 {_# sC@ x ?u R - V 9 &`a .$ + 5p h{C ; Q :Y IM p ͍ \ ִ EOM## h-; Y )' # u U a l4 ;$ >J jz S 7 } u; $k ZA߷ b u KECH :(Gk d v ͦ<树-Y . n6 2} N?PP2 9| % c; $ n PԳT ޡ u[ .k D z~O uuׂ ^ =͌/u `.% U K_d 6 [-0 cZ z Ӊ !5 W>' M&x"a{ 2pJ _i5 D & 9 < ދ䭎 \ 7 s J / OJ3 s <v d!g % W FV ( i_L$p gc VN 7On ffMQ < s hշ @ ( $ csA8 r 5e 2 :f:l c r _ 7 ; / #o =˶ p D# o Լ j 8E}e 5 cFO LEQ # m%k@ z S^)' U $ $ / c q lG ,W* & BBh@ $& Bh@ $& Bh@ $& Bh@ $!II4+e Ogޣ,#5 N gޣ 5' W ? S bPZ a : UR Wl .y J ' 6 J [I5 x AN G ˳ ڻk 皤 %8 T ? N U >L 9+ K! F YVk %7 * nW iZ d, 0 iH hB F Ae Tce Q `skI!ǐq= W[ x Zx q y.^I [ hi.r : | M F Y} } S O qt By cH Z} .^ dsi 3 n ެz^; v K * o<{ w%X 2G% < i r R Ω)@ i Z * :JK n? Ԯ pcK @rI 5 Y # $ f =ǯ ӫ .1 O#Y)'Fy 9g p (: I 颏Ҡ Qc N կ.ƒ 9 Y G 8ګ V <d:B ۍ m è/G QMr 5;_W BN ۲ 乙Ϫ i +lVH( :P8 p O; r jw k d yc k j: ж(̕ q rs P # * 덿 r %սZ5,T K, dC O < pǵIi a X( 0 O w?iP ]IUjմs l ho ' q ݅[5l -E /w#W ZV 5 c bO mp K . @PRy5 P % j oR ѐ nTV ) > hrx q ML 1 Y(q r㫥 E ]t-ࡖ?7 [lO ؽ mp? 1 Y7ɵx; 1 yzN@ ^+ M RCyy 6 v2 o ^ kt5m߂P# JV j9 #4 4༎ Iv i ՜ oQ o o z * B )8 Ӟ- z r o|# Z붢 تa4 k\x Gh ^ f履 ; 1 7NŌ Y 7 .F k c S6 O 9 } .z C L p 9 U = ! q; ±/= ^VB BB HM$HB$ V ?O~ ' Wu >!pi > X m Q ܵ b ʁ _ E v ^X lUn Gdg { < Wt d 9 48 tή/M ~q !]G v> qi Bߛ { }R V S VK !7 * h 8|Oܳ ֑Mͦ aHT / u : K%ѢY1 s | pZo #}* O } X u,v #):g 2O _ N ' E,|M #=^ h Ci Z ĕ P; & s \ti f . R̀ a Xܭ E[ ]l $ ? m ~L ⴻGڏ.U 8 M)n $ ` nJuQ =Ck X\ MR _ ݗ ET %{2iT-B 3Ψ3 SN # */_ }/ ڭ E q٣ީ: sQt < . gZ[.W m # .y yA٤m { Yy8 : N a e a T} Z e 1 A0 hcG`Q+ m? L^ _ ( U Q;H (# > ; _+X( b + [WK$/ 0 ` h+ PJ G+ \ QrEmTTT TT<Gm.s PNg T ȡ e y n7 rƺlN{ 9 3 6i^=K C ` = 'ī^ 2 f < ( r? S uZ ; a s =Ƿ @ i )] x] Y +ZPI=9 \r 1 .o` # º*F ӕ e ͘ D7n >Ф4 \=( =g) eI E 9 6 <$ # ] Z X 26 s% Y R Y ֥ -) 5 DIsC ^r7۹[u& [ |"*ʲ0m =* _KUp ř x" ~ : `^ ? P 6 w~ Y V : TuʧDTy ◦ s 3T }\½ n6۬BJ) v7# \ |si EI Y3 ! > ] | 4 a ~ B$! BBBBBBB Bh@ MI I4 *我们来分析一下上面的请求报文,首先能够进入眼帘的是,响应报文中出现了一堆未知的字符
这些内容其实包含了图片的字节数据

另外在请求信息之间出现了这样的一块内容,出现了两次
------WebKitFormBoundary1pgACDbBGFGBPZYi
这块内容其实就是分隔符,用来分割普通的请求数据和文件的请求数据的,在上面的请求头Content-Type的值中也可以看到。如果我们拿到请求体中的内容,通过分隔符,也就可以拿到图片的字节数据,拿到字节数据的话就可以通过OutputStream做写出了
但是呢,这个过程如果我们自己来完成的话,非常的复杂。在Servlet3.0之前,我们可以借助第三方工具来封装,比如commons-fileupload(FileUpload – Using FileUpload (apache.org)),但是这个过程仍然是非常繁琐。
Servlet3.0提供了对文件上传的支持,通过@MultipartConfig标注和HttpServletRequest提供的方法可以完成文件部分的获取,我们当前可以使用Request中的getPart方法直接拿到其图片部分
Part getPart(String var1) throws IOException, ServletException;该方法的参数需要传入一个字符串值,该值是请求参数名,如果拿的是上面请求中图片文件的信息,则需要传入 avatar
<!--intput标签中的name属性值其实就是请求参数名,分隔符中name对应的值也是这个-->头像:<input type="file" name="avatar"><br>获得Part对象,通过Part中提供的方法可以获得其他具体的信息
| 方法 | 返回值 | 说明 |
|---|---|---|
| getInputStream() | InputStream | 获得字节输入流,可以读取字节数据将其保存下来 |
| getContentType() | String | 获得正文类型,比如png图片,其值为image/png |
| getSize() | long | 获得文件字节大小 |
| getName() | String | 获得请求参数名(这里就是avatar) |
| getSubmittedFileName() | String | 获得上传的原始文件名(这里是ikun.jpeg) |
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part avatar = req.getPart("avatar"); InputStream inputStream = avatar.getInputStream(); String contentType = avatar.getContentType(); long size = avatar.getSize(); String name = avatar.getName(); String submittedFileName = avatar.getSubmittedFileName();}如果要将图片以原始文件名保存在web资源根目录,我们可以写这样的代码
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part avatar = req.getPart("avatar"); InputStream inputStream = avatar.getInputStream(); String submittedFileName = avatar.getSubmittedFileName();
byte[] bytes = new byte[1024]; int length = 0; FileOutputStream outputStream = new FileOutputStream(new File(getServletContext().getRealPath(submittedFileName))); while ((length = inputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, length); } inputStream.close();}Request做请求转发(了解)
实际开发过程中,基本不会再涉及到,当前基本上不再使用JSP技术,JSP技术使用过程中会做请求转发。
JSP,而JSP是一种特殊的Servlet
但是jsp目前已经没有什么使用场景了。目前架构主要是前后端分离。所以关于转发了解即可。

如果是jsp的话,jsp其实也是一个servlet

可以通过IDEA中的Tomcat的CATALINA_BASE看一下其生成的jsp 相关的java文件

请求转发,其实就是在一个Servlet处理业务,处理完业务,继续由另外一个Servlet处理业务。
其中一个关注点,转发的两个请求之间的数据共享
Request域,转发的请求之间数据共享
- request.setAttribute
- request.getAttribute
Response
响应报文的封装,设置响应报文
HTTP/1.1 200Vary: accept-encoding,origin,access-control-request-headers,access-control-request-method,accept-encodingSet-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Sun, 12-Feb-2023 06:51:56 GMTSet-Cookie: JSESSIONID=24287278-5ebb-407d-a3f7-56b74782c4c7; Path=/; HttpOnlyAccess-Control-Allow-Origin: *Content-Type: application/json;charset=UTF-8Date: Mon, 13 Feb 2023 06:51:56 GMTContent-Length: 200
{"errno":0,"data":{"adminInfo":{"nickName":"admin123","avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"},"token":"24287278-5ebb-407d-a3f7-56b74782c4c7"},"errmsg":"成功"}响应报文的组成
- 响应行
- 响应头
- (空行)
- 响应体(正文)
响应行
协议就不设置了,设置一下响应状态码
| 方法名 | 参数 | 说明 |
|---|---|---|
| setStatus(int) | 参数就是状态码 | 设置响应状态码 |
@WebServlet("/line")public class LineServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { response.setStatus(302); }}发送请求后接收到的响应报文
HTTP/1.1 302Content-Length: 0Date: Wed, 15 Feb 2023 03:20:18 GMTKeep-Alive: timeout=20Connection: keep-alive响应头
响应头也是key:value的格式,提供了通用的方法,可以设置响应头的key和value;也提供了一些特定的方法,特定的方法做的事情,就是设置特定响应头的值
| 方法 | 参数 | 说明 |
|---|---|---|
| setHeader(String,String) | 参数1提供key,参数2提供value | 通用的方法 |
@WebServlet("/header")public class HeaderServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { // 第一个参数是key,第二个参数是value response.setHeader("custom-header","abcdef"); }}HTTP/1.1 200custom-header: abcdefContent-Length: 0Date: Wed, 15 Feb 2023 03:28:04 GMTKeep-Alive: timeout=20Connection: keep-alive响应体
响应体(正文)
可以使用字符流,也可以使用字节流。
| 方法 | 返回值 | 描述 |
|---|---|---|
| getWriter() | PrintWriter | 字符流 |
| getOutputStream() | ServletOutputStream | 字节流 |
字符流
@WebServlet("/body1")public class BodyServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //resp.getWriter().println("hello world"); //resp.getWriter().append("hello world"); resp.getWriter().write("hello world"); }}@WebServlet("/body2")public class BodyServlet2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletOutputStream outputStream = resp.getOutputStream(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("dlrb.jpg"); byte[] bytes = new byte[1024]; int length = 0; while ((length = inputStream.read(bytes)) != -1) { outputStream.write(bytes,0,length); } inputStream.close(); }}场景:
字符流:响应文本数据,最主要的场景就是前后端分离之后,通过字符流响应Json数据
字节流:响应图片、文件,也通常在文件下载的场景下使用
特殊响应头
特殊的几个响应头
refresh → 定时刷新、跳转
content-type → 限定响应的正文(也可以解决中文乱码问题)
content-disposition → 文件下载
location → 重定向
refresh
private void refreshAndForward(HttpServletResponse resp) { resp.setHeader("refresh","3;url=/demo12/hello");}
private void refreshPerSecond(HttpServletResponse resp) throws IOException { resp.setHeader("refresh","1"); Date date = new Date(); String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); resp.getWriter().write(dateStr);}Content-Type
通常不需要设置
比如我们响应Json数据给前端,我们可以设置Content-Type:application/json
我们要在这里做字符集的设置,如果没有做有可能出现中文乱码
比如我们响应Json,想要设置字符集为utf-8
Content-Type
@WebServlet("/contenttype")public class ContentTypeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 这里不是json而是普通字符,就设置了text/html了 resp.setHeader("content-type","text/html;charset=utf-8"); resp.getWriter().write("你好"); }}也可以直接使用Response来调用其setContentType方法
content-disposition
下载的场景会使用
content-disposition: attachment;filename=1.jpg
以1.jpg来下载正文
@WebServlet("/disposition")public class ContentDispositionServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setHeader("content-disposition","attachment;filename=1.jpg"); ServletOutputStream outputStream = resp.getOutputStream(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("dlrb.jpg"); byte[] bytes = new byte[1024]; int length = 0; while ((length = inputStream.read(bytes)) != -1) { outputStream.write(bytes,0,length); } inputStream.close(); outputStream.close(); }}location
重定向
@WebServlet("/location")public class LocationServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("访问到LocationServlet"); resp.setStatus(302); resp.setHeader("location","http://localhost:8080/demo12/hello"); }}案例
请求分发案例
场景:有多个请求
Http://localhost:8080/user/login → 登录
http://localhost:8080/user/create → 注册
http://localhost:8080/user/info → 查看用户信息
按照我们之前的写法,我们需要写3个Servlet,如果不想写3个Servlet,只写一个UserServlet可以不?
Servlet的url-pattern的合法写法 /xxx/*,我们在这个UserServlet上可否写一个/user/*
@WebServlet("/user/*")public class UserServlet extends HttpServlet{
}获得请求的URI,根据uri 的user后面的值的不同做不同的处理
@WebServlet("/user/*")public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String requestURI = req.getRequestURI(); // 截取URI字符串,举例 /demo13/user/login int index = requestURI.lastIndexOf("/"); String operation = requestURI.substring(index + 1, requestURI.length());
resp.setHeader("content-type","text/html;charset=utf-8"); // 请求URI的最后一级 if ("login".equals(operation)) { method1(req,resp); } else if ("create".equals(operation)) { method2(req,resp); } else if ("info".equals(operation)) { method3(req,resp); } //resp.getWriter().write(requestURI); }
private void method1(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().write("访问登录"); } private void method2(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().write("访问注册");
} private void method3(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().write("访问查看信息");
}
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }}可以通过反射的方式,实现其通用性
public class DispatchUtil {
public static void dispatch(String operation, HttpServletRequest request, HttpServletResponse response, HttpServlet instance) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method method = instance.getClass().getDeclaredMethod(operation,HttpServletRequest.class,HttpServletResponse.class); method.setAccessible(true); method.invoke(instance,new Object[]{request,response}); }}/** * localhost:8080/demo2/user2/login * localhost:8080/demo2/user2/register * localhost:8080/demo2/user2/info * localhost:8080/demo2/user2/remove * localhost:8080/demo2/user2/logout * @author stone * @date 2023/03/31 16:57 *///@WebServlet({"/user/login","/user/register","/user/info"})@WebServlet("/user2/*")public class UserServlet2 extends HttpServlet {
@SneakyThrows @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); }
private void process(HttpServletRequest request, HttpServletResponse resp) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String operation = null; // /demo2/user/login String requestURI = request.getRequestURI(); operation = requestURI.substring(requestURI.lastIndexOf("/") + 1); DispatchUtil.dispatch(operation,request,resp,this); }
private void modify(HttpServletRequest request, HttpServletResponse resp) { System.out.println("modify"); } private void logout(HttpServletRequest request, HttpServletResponse resp) { System.out.println("logout"); }
private void remove(HttpServletRequest request, HttpServletResponse resp) {
}
private void register(HttpServletRequest request, HttpServletResponse resp) { }
private void login(HttpServletRequest request, HttpServletResponse resp) {
} private void info(HttpServletRequest request, HttpServletResponse resp) {
}
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { process(req, resp); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }}登录案例
请求:先访问登录请求,登录如果成功,那么接着 先提示登录成功,然后过两秒访问到info请求
这个请求由登录页面提供,我们可以通过html提供一个登录表单,该表单会发出请求
http://localhost:8080/user/login → Servlet → 检查用户名和密码是否正确(使用一下MyBatis) →
- 如果正确,那么就提示登录成功
- 如果错误,那么刷新登录页面
任务拆解:
- 包含登录表单的 login.html文件,放在webapp目录下
- 开发UserServlet
- /user/login对应的处理方法,使用MyBatis做查询
- /user/info对应的处理方法
- 整合MyBatis,在应用程序中维护SqlSessionFactory实例
/** * Servlet如果它的loadOnStartup为正数,则应用程序启动的时候初始化; * 如果为负数,则访问其URL-Pattern时才初始化 * 想让其应用程序启动的时候就初始化 → 提供一个SqlSessionFactory的实例,然后共享该实例 * @author stone * @date 2023/02/16 16:16 */@WebServlet(value = "/mybatis/init",loadOnStartup = 1)public class MyBatisInitializationServlet extends HttpServlet { @SneakyThrows @Override public void init() throws ServletException { ServletContext servletContext = getServletContext(); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml")); servletContext.setAttribute("sqlSessionFactory",sqlSessionFactory); }}@WebServlet("/user/*")public class UserServlet extends HttpServlet { SqlSessionFactory sqlSessionFactory; @Override public void init() throws ServletException { sqlSessionFactory = (SqlSessionFactory) getServletContext().getAttribute("sqlSessionFactory"); }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String operation = URIUtil.fetchOperation(req); switch (operation) { case "login": login(req, resp); break; case "info": info(req, resp); break; default: resp.setHeader("content-type","text/html;charset=utf-8"); resp.getWriter().write("请求有误,请联系管理员"); }
}
private void login(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException { // 判断用户名和密码是否符合要求 //Object sqlSessionFactory = getServletContext().getAttribute("sqlSessionFactory"); SqlSession sqlSession = sqlSessionFactory.openSession(true); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); CskaoyanUser cskaoyanUser = userMapper.selectByUsernameAndPassword(username, password); if (cskaoyanUser == null) { resp.setStatus(302); resp.setHeader("location","/demo14/login.html"); }else { //先把user信息暂存一下 getServletContext().setAttribute("user",cskaoyanUser); resp.setHeader("refresh","2;/demo14/user/info"); } } private void info(HttpServletRequest req, HttpServletResponse resp) throws IOException { CskaoyanUser user = (CskaoyanUser) getServletContext().getAttribute("user"); System.out.println("user = " + user); resp.setHeader("content-type","text/html;charset= utf-8"); resp.getWriter().write("欢迎你," + user.getUsername()); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }}localhost:8080/user → 把数据库cskaoyan_user里的所有的user都查出来打印到控制台
localhost:8080/userdetail →把数据库cskaoyan_user_detail里的所有的userdetail都查出来打印到控制台
保存文件到指定位置
位置由配置文件定义
pic.path=d://tmp2提供 一个Servlet,读取配置文件中的值,并且将其放在ServletContext
@WebServlet(value = "/properties",loadOnStartup = 1)public class PropertiesInitializationServlet extends HttpServlet { @SneakyThrows @Override public void init() throws ServletException { ServletContext servletContext = getServletContext(); Properties properties = new Properties(); properties.load(PropertiesInitializationServlet.class.getClassLoader().getResourceAsStream("application.properties")); String value = properties.getProperty("pic.path"); servletContext.setAttribute("picPath", value); }}保存过程,需要获得保存路径,通过ServletContext去获取,原始的文件名submittedFileName
@MultipartConfig@WebServlet("/parameter/file")public class ParameterFileServlet extends HttpServlet {
@Override protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { Part usernamePart = request.getPart("username"); Part avatarPart = request.getPart("avatar");
InputStream inputStream = avatarPart.getInputStream(); String contentType = avatarPart.getContentType(); System.out.println("contentType = " + contentType); long size = avatarPart.getSize(); System.out.println("size = " + size); String name = avatarPart.getName(); System.out.println("name = " + name); String submittedFileName = avatarPart.getSubmittedFileName(); System.out.println("submittedFileName = " + submittedFileName);
//保存在d:/tmp/ikun2.jpeg //FileOutputStream fileOutputStream = new FileOutputStream("d://tmp/ikun2.jpeg");
//保存在指定位置,上传时的文件名是什么,保存的时候就叫什么文件名 String picPath = (String) getServletContext().getAttribute("picPath"); File file = new File(picPath, submittedFileName); FileOutputStream fileOutputStream = new FileOutputStream(file);
int length = 0; byte[] bytes = new byte[1024]; while ((length = inputStream.read(bytes)) != -1) { fileOutputStream.write(bytes, 0, length); } fileOutputStream.close(); inputStream.close();
}}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!