先附上JavaEE官方文档。
之前课堂上学过jsp,jsp的每次修改之后不需要重启服务器,而Servlet修改一次需要重启一次。
servlet和jsp本质都是servlet,运行时都是运行.class文件。但是它们的部署方式不一样。
servlet是先编译后部署,修改完以后,IDE进行编译,然后部署.class文件到servlet容器中。如果web服务器已启动,则之前class已被servlet容器加载,可能修改后的class文件不会被servlet容器执行。
而jsp是web服务器进行编译。tomcat可以设置为监视jsp文件的改动,改动之后则重新编译、执行。
一、入门Servlet
概念:运行在服务器端的小程序
本质:Servlet就是一个接口,定义了java类被浏览器访问到(Tomcat识别到)的规则。
1.1 快速入门
- 创建JavaEE项目
- 创建类,实现Servlet接口
- 实现接口中的抽象方法
- 配置Servlet
配置Servlet,修改WEB-INF下的web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>demo01</servlet-name> <servlet-class>demo01_servlet.Demo01Servlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>demo01</servlet-name> <url-pattern>/demo01</url-pattern> </servlet-mapping> </web-app>
|
Demo01Servlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public class Demo01Servlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException {
}
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("Hello Servlet"); }
@Override public String getServletInfo() { return null; }
@Override public void destroy() {
} }
|
访问localhost:8080/demo01,然后控制台就会输出Hello Servlet
之前一直在用eclipse,最近改用idea,不得不说,真香!
二、详解Servlet
2.1 执行原理
浏览器访问localhost:8080/demo01,tomcat从web.xml找资源路径/demo01,通过url找到配对的servlet-name,然后找到类文件(看到全类名,首先应该想到反射)
tomcat将全类名对应的字节码文件加载进内存Class.forName
,创建对象class.newInstance
,调用方法service
,这些过程都是在服务器完成的。
总结
- 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
- 查找web.xml,是否有对应的
<url-pattern>
便签体内容 - 如果有,则再找到对应的
<servlet-class>
找到全类名,Tomcat将字节码文件加载进内存,并创建其对象 - 调用方法
2.2 生命周期
- 被创建:执行init方法,只执行一次。
- 默认情况下,第一次访问时,Servlet被创建
- 可以指定Servlet启动的时机为启动服务器时,Servlet被创建,一般用于加载资源
- 提供服务:执行service方法,执行多次
- 每次访问Servlet时,service都会被调用一次
- 被销毁:执行destroy方法(服务器正常关闭时),只执行一次
- Servlet被销毁时执行。如果服务器不是正常关闭,不会执行。destroy可以理解为“临终遗言”,在Servlet销毁之前执行。一般用于释放资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class Demo02Servlet implements Servlet {
@Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("init..."); }
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("service..."); }
@Override public String getServletInfo() { return null; }
@Override public void destroy() { System.out.println("destory..."); } }
|
配置Servlet启动的时机
- 第一次被访问时创建。
<load-on-startup>
为负数,一般为-1 - 在服务器启动时创建。
<load-on-startup>
为0或正整数,一般为0-10
1 2 3 4 5 6 7 8 9 10
| <servlet> <servlet-name>demo02</servlet-name> <servlet-class>demo01_servlet.Demo02Servlet</servlet-class>
<load-on-startup>1</load-on-startup> </servlet>
|
Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的。
多个用户同时访问时,可能存在线程安全问题。就比方说买票,容易数据错乱
解决方法:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对其赋值和修改值。
2.3 注解配置
在Servlet3.0之前,需要通过配置文件web.xml来配置。但是,太麻烦。
在Servlet3.0之后,可以通过注解来配置,web.xml可以不需要了。
从JavaEE6之后,才开始支持Servlet3.0。
步骤
- 创建JavaEE项目,选择Servlet的版本为3.0以上,可以不创建web.xml
- 定义一个类,实现Servlet接口
- 实现接口中的抽象方法
- 在类上使用注解@WebServlet
@WebServlet("资源路径")
,当然@WebServlet还可以配置别的内容,具体参照其源码
参照文章注解
如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略(可以参考@SuppressWarnings的源码)
数组赋值时,用{}包裹。如果其中只有一个值的时候,{}可以省略不写
2.4 编译器与其Tomcat相关配置
如果直接使用tomcat的bin目录下脚本打开服务器,他的配置文件就放在服务器根目录下了。
对于编译器,他们会给集成到自身的tomcat单独建立配置文件。eclipse和idea这两款编译器,我都用过。
如果使用eclipse,那么eclipse会给他单独配置一个路径
同样地,idea也会单独配置路径来存储配置文件
项目分为工作空间目录和tomcat部署的Web项目
- tomcat真正访问的是tomcat部署的Web项目,而tomcat会将编译后的工作空间目录内容的字节码文件放到WEB-INF下的classes中
- WEB-INF下的文件不能直接被浏览器访问
- 断点调试:不能直接运行服务器,需要通过Debug模式来运行服务器
2.5 Servlet体系结构
每次都像上面那样实现Servlet接口,却只用到了service方法,多余!那就需要通过一个实现类来帮助完成。Servlet自身有这种体系结构。
HttpServlet继承自GenericServlet,GenericServlet实现Servlet接口。看文档或者源码
GenericServlet:将Servlet接口中的其他方法做了默认空实现,只将service()方法作为抽象
- 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可。其他方法可以自选重写
HttpServlet:对http协议的一种封装,简化操作
- 定义类继承HttpServlet
- 重写doGet和doPost方法
在实际使用中,继承HttpServlet的方法会更好
2.6 Servlet相关配置
urlPattens:Servlet访问路径
- 一个Servlet可以定义多个访问路径:@WebServlet({“/d6”,”/dd6”,”/demo06”})
- 路径定义规则:如
/xxx
、/xxx/xxx
(多层路径,目录结构)、/*
(*
的优先级很低,如果在访问已经配置的路径时,则会访问已配置的路径)、*.do
(如果写*.do
是会报错的,这个只是换了一个扩展名而已)
在注解中,如果属性值是value,那么value可以省略。
在配置访问路径的时候,是走的value,而不是urlPattens。我就在想,那么实现类是如何将value转换成url的呢?
找了半天,百度,和官网源码,也没找到@WebServlet的实现类。
后来再文档中看到说,value跟urlPatterns是一个东西。WTF!可能官方是为了让书写更加简洁?
三、HTTP请求协议
概念:Hyper Text Transfer Protocol 超文本传输协议
传输协议:定义了客户端和服务端通信时,发送数据的格式
特点
- 基于TCP/IP的高级协议
- 默认端口号:80
- 基于请求/响应模型的:一次请求对应一次响应
- 无状态的协议:每次请求之间相互独立
历史版本
- 1.0:每一次请求响应都会建立新的连接
- 1.1:复用连接
具体的各版本的区别
3.1 请求消息数据格式
- 请求行
- 请求方式 请求url 请求协议/版本。如GET /login.html HTTP/1.1
- 请求头
- 请求空行:作分割作用
- 请求体(正文):get方式是没有请求体的。用来封装post请求消息的请求参数的
字符串格式的请求数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST http://meethigher.top/admin/login.php HTTP/1.1 Host: meethigehr.top Connection: keep-alive Content-Length: 35 Cache-Control: max-age=0 Origin: http://baixiu-dev.top Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://meethigher.top/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=cdtnkpj4apnarjhtqugqegsfmn
email=admin@qq.com&password=admin
|
请求方式
Http协议中有7种请求方式,常用的有2种
- get
- 请求参数在请求行中(也可以理解在url中)
- 请求的url长度是有限制的
- 相对不安全
- post
- 请求参数在请求体中
- 请求的url长度是没有限制的
- 相对安全
请求头
常见的请求头
- User-Agent:浏览器访问服务器内容时,使用的浏览器版本信息
- Referer:告诉服务器,当前请求从哪里来
- Connection:keep-alive一直连接,表示可复用
Referer有两个作用
- 防盗链(盗取超链接)
- 统计功能
比如看斗罗大陆,一般是通过选了集数,跳转到腾讯播放页观看,这是用户正常的播放流程。但是如果,用户通过自己建的网页,直接指向腾讯播放页,这个过程就叫做盗链。
3.2 响应消息数据格式
- 响应行
- 响应头
- 响应空行
- 响应体
字符串格式的响应数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| HTTP/1.1 302 Found Date: Tue, 09 Jun 2020 01:43:26 GMT Server: Apache/2.4.34 (Win64) PHP/7.2.10 X-Powered-By: PHP/7.2.10 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Location: /admin/index.php Content-Length: 2508 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8
<html>返回一套html代码,此处省略</html>
|
响应状态码
服务器告诉客户端浏览器本次请求和响应的状态,状态码都是三位数字
状态码分类
- 1xx:服务器接收客户端消息,但是没有接收完成,等待一段时间后,发送1xx多状态码
- 2xx:表示成功。最常见的就是200
- 3xx:表示重定向。最常见的302(重定向),304(访问缓存)
- 4xx:客户端错误。最常见的404(代表请求路径没有对应的资源),405(请求方式没有相应的方法,比方说在Servlet中,没有doGet方法或者doPost方法)
- 5xx:服务器端错误 。最常见的500(代表服务器内部错误)
响应头
常见的响应头
- Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
- Content-disposition:服务器告诉客户端以什么格式打开响应体数据
- in-line:默认值(在当前页面内打开)
- attachment;filename=xxx:以附件形式打开响应体。在文件下载中,会使用这个,文件名是xxx
响应体
响应体就是真实的传输的数据
四、Request
Request对象和Response对象的原理
- request和response是由服务器来创建,开发者来使用
- request获取请求消息,response设置响应消息
4.1 Request对象继承体系结构
tomcat里面的RequestFacade
类实现了继承自ServletRequest
接口的实现类HttpServletRequest
具体的实现可以在Tomcat的源码中查看。
4.2 Request功能
- 获取请求消息
- 获取请求行数据(标❤比较常用)
- 获取请求方式:String getMethod()
- ❤获取虚拟目录:String getContextPath()
- 获取Servlet路径:String getServletPath()
- 获取get请求参数:String getQueryString()
- ❤获取请求URI:String getRequestURI()和String getRequestURL()
- 获取协议以及版本:String getProtocol()
- 获取客户机ip地址:String getRemoteAddr()
- 获取请求头数据(标❤比较常用,请求头是不区分大小写的)
- ❤String getHeader(String name):通过请求头的名称,获取请求头的值
- EnumerationgetHeaderNames():获取所有的请求头名称
- 获取请求体数据:只有post请求方式,才有请求体,在请求体中封装了post请求的请求参数
- 获取流对象
- BufferedReader getReader():获取字符输入流,只能操作字符
- ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据。用于文件上传比较好
- 流对象中拿数据
- ❤其他功能
- 获取请求参数通用方式:不论get或是post请求,都可以使用下面这些方法
- String getParameter(String name):根据参数名称获取参数值
- String[] getParameterValues(String name):根据参数名称获取参数值数组,多用于复选框
- EnumerationgetParameterNames():获取所有请求的参数名称
- Map<String,String[]> getParameterMap():获取所有参数的map集合
- 请求转发:一种在服务器内部的资源跳转方式
- 共享数据
- 获取ServletContext对象
获取请求消息
获取请求行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @WebServlet("/demo01request") public class Demo01Request extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(request.getMethod()); System.out.println(request.getContextPath()); System.out.println(request.getServletPath()); System.out.println(request.getQueryString()); System.out.println(request.getRequestURI()+"---"+request.getRequestURL()); System.out.println(request.getProtocol()); System.out.println(request.getRemoteAddr()); } }
|
URL 统一资源定位符
URI 统一资源标识符
URI代表的范围更大一些(并不是取决于路径的长度的),URL限制了指定的服务器,而URI是没有限制的,应该这样理解
获取请求头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @WebServlet("/demo02request") public class Demo02Request extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String str = headerNames.nextElement(); if ("user-agent".equals(str)) { String agent = request.getHeader(str); if (agent.contains("Chrome")) { System.out.println("Chrome"); } else if (agent.contains("Firefox")) { System.out.println("Firefox"); }
} }
} }
|
获取请求体
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @WebServlet("/demo03request") public class Demo03Request extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); BufferedReader br=request.getReader(); String line=null; while((line=br.readLine())!=null){ System.out.println(line); } } }
|
这个部分出了点问题,就是它输出的内容,将字符串以url编码形式打印出来了。而后面的getParameter是直接原样输出的。那这两个的区别是啥?我尝试查看HttpServletRequest这个接口的实现类,最后通过request.getClass().getName()输出全类名,发现实现类是在tomcat里面。
结果里面的代码,也是超级简洁,我也没咋看明白两者的不同之处,惆怅。
4.3 其他功能
获取请求参数通用方式
这个方式既适用于post也适用于get,所以说是通用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @WebServlet("/demo04request") public class Demo04Request extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8");
System.out.println("奥利给");
Map<String, String[]> parameterMap = request.getParameterMap(); Set<String> strings = parameterMap.keySet(); for(String key:strings){ System.out.println(key+"↓↓↓↓↓↓"); String[] strings1 = parameterMap.get(key); for (String value:strings1) { System.out.println(value); } System.out.println("------"); }
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
|
请求转发
步骤
- 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
- 使用RequestDispatcher对象进行转发:void forward(ServletRequest request, ServletResponse response)
特点
- 浏览器地址栏路径没有发生变化
- 只能转发到当前的服务器内部中
- 转发是一次请求
共享数据
域对象:一个有作用范围的对象,可以在范围内共享数据
request域:代表一次请求的范围。一般用于请求转发的多个资源中共享数据
方法
- setAttribute(String name,Object object):存储数据
- Object getAttribute(String name):通过键获取值
- removeAttribute(String name):通过键移除值
1 2 3 4 5 6 7 8 9 10 11 12 13
| @WebServlet("/demo05request") public class Demo05Request extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo05request"); request.setAttribute("女神","胡列娜"); RequestDispatcher requestDispatcher = request.getRequestDispatcher("/demo06request"); requestDispatcher.forward(request,response); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
|
demo05将请求转发到demo06,同时在request域内共享数据
1 2 3 4 5 6 7 8 9 10 11
| @WebServlet("/demo06request") public class Demo06Request extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo06request"); System.out.println(request.getAttribute("女神")); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
|
获取ServletContext
ServletContext getServletContext()
4.4 中文乱码问题
get方式:Tomcat8已经将get方式乱码解决了
post方式:会乱码。解决方式是在获取参数前,将request的编码设置为utf-8
1
| request.setCharacterEncoding("utf-8")
|
五、用户登录案例
5.1 需求
- 编写登录页面,账号密码
- 使用Druid数据库连接技术
- 使用JDBCTemplate技术封装JDBC
- 登录成功,跳转SuccessServlet,提示登录成功
- 登录失败,跳转到FailServlet,提示登录失败
5.2 实现
- 创建项目、登录界面,配置文件以及数据库,导入jar包(在Web项目下,jar包一般是放在WEB-INF的lib目录下)
- 创建包domain,包下建User表,对应着数据库表
- 创建包dao,包下建UserDao,提供Login方法
- 创建包utils,包下建JDBCUtils(连接池工具类)
- 创建包servlet,包下建响应的Servlet
- 创建包test,通过Junit进行单元测试
代码
login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="loginServlet" method="post"> 名:<input type="text" name="user"><br> 密:<input type="password" name="password"><br> <input type="submit" value="提交"> </form> </body> </html>
|
dao层下的UserDao.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class UserDao { private JdbcTemplate temp=new JdbcTemplate(JDBCUtils.getDs());
public User login(User loginUser){ try { String sql="select * from user where user=? and password=? limit 0,1"; User user = temp.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), loginUser.getUser(),loginUser.getPassword()); return user; } catch (DataAccessException e) { e.printStackTrace(); return null; } } }
|
domain层的User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public class User { private int id; private String user; private String password;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; } }
|
utils层下的JDBCUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
public class JDBCUtils { private static DataSource ds;
static { try { Properties pro = new Properties(); InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); ds = DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
}
public static DataSource getDs() { return ds; }
public static Connection getConnection() throws SQLException { return ds.getConnection(); } }
|
test层下的UserDaoTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class UserDaoTest { @Test public void testLogin(){ User u=new User(); u.setUser("胡列娜"); u.setPassword("123f"); UserDao udao=new UserDao(); udao.login(u); } }
|
servlet层下的LoginServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @WebServlet("/example/loginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String user = request.getParameter("user"); String password = request.getParameter("password"); User loginUser = new User(); loginUser.setUser(user); loginUser.setPassword(password); User login = new UserDao().login(loginUser); if(login!=null){ request.setAttribute("user",login); request.getRequestDispatcher("/example/successServlet").forward(request,response); }else{ request.getRequestDispatcher("/example/failServlet").forward(request,response); }
} }
|
servlet层下的SuccessServlet和FailServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @WebServlet("/example/successServlet") public class SuccessServle extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); User user = (User) request.getAttribute("user"); response.getWriter().write(user.getUser()+"登录成功"); } }
@WebServlet("/example/failServlet") public class FailServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); response.getWriter().write("登录失败"); } }
|
BeanUtils工具类
JavaBean:标准的Java类
JavaBean要求
- 类必须被public修饰
- 必须提供空参的构造方法
- 成员变量必须使用private修饰
- 提供公共的getter和setter方法
- 一般在项目中放在domain或者entity下
功能:简化数据封装
概念
- 成员变量
- 属性:setter和getter方法截取后的产物,比方说getUser(),对应的属性就是user
方法
- setProperty()
- getProperty()
- populate()
我个人的理解,其实属性跟成员变量是一回事。只不过有的库在封装(比如BeanUtils)的时候,需要拆分出方法的设置或获取的属性,由此产生属性这个概念。
其实,从这个角度来想,好多东西,都是人规定的,因为你整的东西好,用的人多了,自然而然也就成了规定。就像js,php,这种叫做弱类型语言,底层可能还是通过基本数据类型来实现,但是我也可以说,他没有数据类型,因为在用的时候,根本不需要去定义数据类型。
1 2 3 4 5 6 7 8 9
| public class BeanUtilsTest { @Test public void test() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { User user = new User(); BeanUtils.setProperty(user,"user","胡列娜"); String user1 = BeanUtils.getProperty(user, "user"); System.out.println(user+"--"+user1); } }
|
优化
登录功能只有两条数据需要封装成User对象。获取数据,创建对象,赋值,6步完成。如果是一个很多条数据,要获取十几次数据,赋值十几次,岂不是很麻烦。这就需要优化一下了。
一次能获取所有数据,一次赋完所有值。可以通过下载BeanUtils来完成数据的封装
优化后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @WebServlet("/example/loginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap(); User loginUser = new User(); try { BeanUtils.populate(loginUser,parameterMap); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } User login = new UserDao().login(loginUser); if(login!=null){ request.setAttribute("user",login); request.getRequestDispatcher("/example/successServlet").forward(request,response); }else{ request.getRequestDispatcher("/example/failServlet").forward(request,response); }
} }
|
六、Response
6.1 Response功能
设置响应消息
- 设置响应行
- 设置响应头:setHeader(String name,String value)
- 设置响应体
- 获取输出流
- 字符输出流:PrintWriter getWriter();
- 字节输出流:ServletOutputStream getOutputStream()
- 使用输出流,将输出流输出到客户端
6.2 重定向
重定向:资源跳转的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @WebServlet("/demo01Response") public class Demo01Response extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo01Response...");
System.out.println(response.getClass().getName()); response.sendRedirect("/demo02Response"); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
转发的特点(forward)
- 浏览器地址栏路径没有发生变化
- 只能转发到当前的服务器内部中
- 转发是一次请求,可以使用request来共享数据
重定向的特点(redirect)
- 地址栏发生变化
- 可以访问其他站点资源
- 重定向是两次请求,不能使用request来共享数据
6.3 路径
分类
- 相对路径:通过相对路径,不可以确定唯一资源
- 如./index.html(当前路径下的index.html,./是可以省略的),../index.html(上一级的index.html,上上级之类的以此类推)
- 不以/开头,以.开头路径;./表示当前路径,当然,这个是可以省略的。
- 规则:要使用相对路径,要确定当前的资源和目标资源之间的相对位置关系
- 绝对路径:通过绝对路径,可以确定唯一的资源
规则:判断定义的路径是给谁用的
- 给客户端浏览器使用:需要加虚拟目录(项目的访问路径),比方说重定向
- 给服务器使用:不需要加虚拟目录,比方说请求转发
在实际项目中,虚拟目录的路径不能写死,可以通过request.getContextPath()
动态获取虚拟目录
6.4 服务器输出数据到客户端
获取流对象
- 获取字符输出流:PrintWriter getWriter()
- 获取字节输出流:ServletOutputStream getOutputStream()
字符输出流
步骤
- 获取字符输出流
- 输出数据
注意:乱码问题,客户端的默认编码,与服务端返回消息体的编码不一致
response.getWriter()获取的流的默认编码是ISO-8859-1,我们需要设置该流的默认编码
response获取的流,不需要刷新和关闭。因为response在一次响应结束之后,自动销毁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @WebServlet("/demo03Response") public class Demo03Response extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter pw = response.getWriter(); pw.write("Hello Response"); pw.write("胡列娜 我女神"); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
字节输出流
步骤
- 获取字节输出流:response.getOutputStream()
- 输出数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @WebServlet("/demo04Response") public class Demo04Response extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); ServletOutputStream os = response.getOutputStream(); os.write("胡列娜 我女神".getBytes("utf-8")); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
6.5 案例-验证码
本质:图片
目的:防止表单恶意注册
关于Graphics的常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @WebServlet("/checkCodeServlet") public class CheckCodeServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width=500; int height=300; BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); Graphics g=image.getGraphics(); g.setColor(Color.PINK); g.fillRect(0,0,width,height); g.setColor(Color.RED); g.drawRect(0,0,width-1,height-1); System.out.println("奥利给"); String str="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; Random r=new Random(); for (int i = 0; i < 4; i++) { g.drawString(str.charAt(r.nextInt(str.length()))+"",width/5*(i+1),height/2); } g.setColor(Color.green); for (int i = 0; i < 10; i++) { int x1=r.nextInt(width); int y1=r.nextInt(height); int x2=r.nextInt(width); int y2=r.nextInt(height); g.drawLine(x1,y1,x2,y2); } ImageIO.write(image,"jpg",response.getOutputStream()); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
|
通过html对齐进行访问以及单击修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <img src="/checkCodeServlet"> <a href="javascript:void(0)">看不清换一张</a> <script> let $img=document.querySelector("img"); $img.onclick = function () { let url = "/checkCodeServlet"; let date=new Date().getTime(); this.setAttribute("src", url+"?"+date) }; document.querySelector("a").onclick=()=>{ let url = "/checkCodeServlet"; let date=new Date().getTime(); $img.setAttribute("src", url+"?"+date) } </script> </body> </html>
|
七、ServletContext对象
概念:代表整个web应用,可以和程序的容器(服务器)来通信
获取
- 通过request获取:request.getServletContext()
- 通过HttpServlet获取:getServletContext
其实通过这两个方式获取的都是同一个对象。因为本身HttpServletRequest就是HttpServlet的子类,调用的就是其父类的方法
功能
- 获取MIME类型
- MIME类型:在互联网通信过程中定义的一种文件数据类型
- 格式:大类型/小类型 如:htm或者html对应text/html、jpg对应image/jpeg
- 获取:String getMimeType(String filename)
- 域对象:共享数据
- setAttribute(String name,Object value)
- getAttribute(String name)
- removeAttribute(String name)
- 获取文件的真实路径(服务器完整路径)
- String getRealPath(String path)
7.1 获取MIME类型
具体的MIME的相关的映射,都在tomcat的web.xml里面配置着。Tomcat的web.xml相当于是所有集成到ide的web.xml的父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @WebServlet("/demo02ServletContext") public class Demo02ServletContext extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = getServletContext(); String name="a.jpg"; String mimeType = servletContext.getMimeType(name); System.out.println(mimeType); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
7.2 域对象
ServletContext对象范围:所有用户所有请求的数据(范围最大的)
在一个servlet设置属性
1 2 3 4 5 6 7 8 9 10 11 12 13
| @WebServlet("/demo03ServletContext") public class Demo03ServletContext extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = getServletContext(); servletContext.setAttribute("fairy","胡列娜");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
在另外的servlet访问属性
1 2 3 4 5 6 7 8 9 10 11 12 13
| @WebServlet("/demo04ServletContext") public class Demo04ServletContext extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = getServletContext(); Object fairy = servletContext.getAttribute("fairy"); System.out.println(fairy.toString()); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
一般用这个,也比较谨慎,所有用户都是可以共享和操作的。而且,生命周期也非常长,服务器启动,它就创建了,服务器关闭,才会销毁。驻留在内存的时间非常久,造成的内存的压力会很大。
7.3 获取文件真实路径(服务器完整路径)
- 获取web目录下文件
- 获取WEB-INF目录下文件
- 获取src目录下文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @WebServlet("/demo05ServletContext") public class Demo05ServletContext extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = getServletContext(); String realPath = servletContext.getRealPath("/example/login.html"); System.out.println(realPath); String realPath1 = servletContext.getRealPath("/WEB-INF/web.xml"); System.out.println(realPath1); String realPath2 = servletContext.getRealPath("/WEB-INF/classes/a.html"); System.out.println(realPath2); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
八、文件下载与上传案例
8.1 文件下载
需求
- 页面显示超链接
- 点击超链接后弹出下载框
- 完成图片文件下载
HTML中超链接指向的资源如果能够被浏览器解析,则在浏览器中展示;若不能解析,就会进行下载到本地。当然,我们也可以通过添加download属性,直接让它所有的都下载。但是这个是没有提示框的。
我们可以使用响应头设置资源打开的方式
content-disposition:attachment;filename=xxx这个表示已附件的形式打开,文件名为xxx
步骤
- 定义页面
- 定义Servlet
- 获取文件名称
- 用字节输入流加载进内存
- 设置响应头为附件形式打开
- 将内存写出到响应输出流
中文名称的文件下载时会乱码,改成utf-8的即可,垃圾ie浏览器已经没救了,兼容不了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @WebServlet("/downloadServlet") public class DownloadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String filename = request.getParameter("filename"); ServletContext sc = getServletContext(); String realPath = sc.getRealPath("/壁纸5.jpg"); FileInputStream fis = new FileInputStream(realPath); String mimeType = sc.getMimeType(filename);
response.setContentType(mimeType); filename= URLEncoder.encode(filename,"utf-8"); response.setHeader("content-disposition","attachment;filename="+filename); ServletOutputStream sos = response.getOutputStream(); byte[] bytes = new byte[1024 * 8]; int len=0; while((len=fis.read(bytes))!=-1){ sos.write(bytes,0,len); } fis.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
|
html中的代码
1 2 3 4 5 6 7 8 9 10
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="downloadServlet?filename=壁纸5.jpg">图片下载</a> </body> </html>
|
8.2 文件上传
我一开始以为文件上传跟文件下载一样,获取请求的传过来的数据,然后写。
但是,一直写出数据都是0kb,我就很纳闷了,我的代码是没有问题的。想了一会,搞明白了,request获取的输入流,是获取到的请求体的数据,而不是获取上传文件的内容