Servlet
一、软件架构
在软件发展过程中,有两种基本的软甲架构。CS(客户端/服务器)和BS(浏览器/服务器)。
1.1 CS架构
Client/Server,客户端/服务器程序
优点:
- 图像化的效果较好(3D等)
缺点:
- 需要下载客户端
- 升级不仅需要升级服务器,而且还需要客户升级客户端
1.2 BD架构
Brower/Server,浏览器/服务器程序
优点:
- 无需下载客户端,任意系统只要有浏览器就可使用
- 更新比较方便,只需要升级服务器即可
缺点:
- 图像化效果不好
二、服务器
静态资源:HTML、CSS、JS
动态资源:Servlet、JSP
在Java中,动态资源的开发技术统称为Java Web。
Web服务器:用来运行和发布Web应用的容器
web项目必须放到Web容器(Web服务器)中才能运行,才能使网络中的用户使用浏览器访问
常用的Web服务器:
- 开源免费
- Tomcat
- Jetty
- Resin
- 收费
- WebLogic(BEA,后来被Oracle收购)
- WebSphere(IBM)
- 功能强大,耗资源
三、Tomcat服务器
Tomcat是Apache的一个核心项目。目录结构如下:
bin:可执行文件
conf:配置文件
lib:第三方库,jar包
logs:日志文件
temp:临时文件
webapps:开发的代码打包后放的位置
- ROOT:默认项目
work:JSP转译成servlet时,源代码和字节码的位置
启动(两种方式):
- 解压后打开bin目录,双击
startup.bat
文件(会启动一个新的cmd界面来启动服务器)- 在bin目录中,在路径框输入cmd,进入cmd后输入
catalina.bat run
后启动服务器(会在当前cmd启动服务器)访问:
- 启动后,打开浏览器,输入:
http://localhost:8080
,就可以访问首页(ROOT项目),也可以输入http://ip地址:8080
访问他人的服务器停止:
- 强制关闭
cmd
框- 使用bin目录下的
shutdown.bat
停止修改端口号:
打开conf文件夹,打开
server.xml
,修改以下内容<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
将
port
修改成想要的端口号,只要端口不冲突就可,修改后需要重启服务才有效
404:报错信息,找不到页面(可能是服务器启动错误)
四、Servlet概述
是指运行在服务器端的一段程序
- 动态网页技术
- 是JavaWeb的基础
- 用来处理客户端的请求,并完成响应
作用:
- 处理客户端的请求
- 在服务端动态生成网页,并返回给客户端
- 直接跳转已经存在的网页
五、开发步骤
- 导入包servlet-api.jar。(创建Java Enterprise工程时,会自动导入tomcat的包)
- 编写Servlet
- 在web.xml中配置Servlet的访问路径
- 将程序部署到tomcat中,并运行
客户端如何发送请求:
- 表单提交(action属性设置服务器的路径)
- 链接
- 使用js的localtion.href
服务端响应:
- 服务端处理:使用Java代码进行相应处理(业务逻辑,数据库操作)
- 服务器返回信息:
- 动态生成网页,并显示
- 跳转到已经存在的网页
5.1 配置环境
配置IDEA中Tomcat
需要先将该项目变成web项目
配置IDEA中Servlet
5.2 基本使用
现在我们编写时,不提交给数据库,分为三步
- 编写前端网页文件(.html文件)
- 编写servlet的实现类,因为tomcat会调用servlet接口的实现类(.class文件)
- 编写servlet接口的实现类和提交地址的映射,让其前端能够提交给正确的地址。(web.xml中编写)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--这里提交时,地址不加/-->
<form action="a.lmz" method="post">
<input type="text" name="username" placeholder="用户名"><br>
<input type="password" name="pwd" placeholder="密码"><br>
<input type="submit" value="提交" />
</form>
</body>
</html>
LoginDemo1.java,需要实现Servlet接口
//需要实现Servlet接口才能在Tomcat启动时被调用
public class LoginDemo1 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {}
@Override
public ServletConfig getServletConfig() {return null;}
//这个方法是核心方法(只需要重写这一个方法)
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
//request是前端请求,response表示后端响应
//得到请求中name=username的值
String username = request.getParameter("username");
//得到请求中name=pwd的值
String pwd = request.getParameter("pwd");
//此处可以先不写,把其他的写了运行一遍看,会出现后端响应给前端的文字乱码,所以这里是将响应的字符编码改为utf-8
response.setContentType("text/html;charset=utf-8");
//获得响应的输出流(可以将响应输出给前端)
PrintWriter out = response.getWriter();
if (username.equals("zhangsan")&&pwd.equals("123456")){
//这个是打印到控制台
System.out.println("登录成功success");
out.println("登录成功success");
}else{
System.out.println("登录失败fail");
out.println("登录失败fail");
}
}
@Override
public String getServletInfo() {return null;}
@Override
public void destroy() {}
}
web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 这里需指向编写的Servlet,设置一个url-pattern作为地址给前端提交-->
<servlet>
<!-- 这里名称可以随意,与下面mapping中的name一致即可,这两个相关联,即将这个类对应/a.lmz地址-->
<servlet-name>lmz</servlet-name>
<!-- 指向哪个Servlet-->
<servlet-class>com.lmz.day1.LoginDemo1</servlet-class>
</servlet>
<!-- 类映射地址-->
<servlet-mapping>
<!-- 这里name与上面一致表示映射-->
<servlet-name>lmz</servlet-name>
<!-- 注意:这里的地址前面必须加/,否则会报错,这里地址给前端请求时使用-->
<url-pattern>/a.lmz</url-pattern>
</servlet-mapping>
</web-app>
六、HTTP协议
超文本传输协议。运行于TCP的协议之上
- 基于请求响应模式
- 无状态协议
- HTTP1.1
请求报文。(发送请求时的格式)
- 请求头
- 方法:GET或者POST
- 请求url(包含GET提交的表单信息)
- 协议的版本HTTP1.1
- IP地址和端口号
- 请求正文
- POST提交的表单信息
响应报文
- 响应头
- 协议版本
- 状态行,例如:200(状态码) OK(描述)
- 返回类型。(text/html;charset=utf-8)
- 内容长度
- 响应正文
- 内容
常见的状态码:
- 200 ,OK,成功
- 400,参数类型格式转换错误(在springMVC框架中比较常见)
- 403,Forbidden,接收到请求,但是不提供服务
- 404,Not Found,页面未找到
- 500,服务器代码出错
七、Servlet核心接口和类
Servlet接口
init
:初始化数据getServletConfig
:获取servlet配置信息getServletinfo
:获取servlet描述信息service
:核心方法,作用是用来处理请求和返回响应destroy
:销毁前
执行(这里的销毁是关闭服务器)
GeneicServlet抽象类
实现了Servlet接口
- 将除了service方法以外的其他方法进行近乎空的实现,作用是可以更容易编写Servlet(因为只有service为抽象方法,所以
继承该抽象类只需要重写service方法
即可)
HTTPServlet抽象类
:
继承了GenericServlet类,并实现了service方法,该方法将HTTP协议加入了
- 并在service方法中,将请求的不同种类进行了分支,get请求执行doGet方法,post请求执行doPost方法等。(可查看底层源码)
- 提供doGet、doPost等方法,并在这些方法中写了发送错误的代码,在子类中如果不发送响应的请求,可以不重写该方法,但是如果发送了请求而没有重写该方法,则会报错。
综上所述,一般情况下,编写
Servlet应该继承HTTPServlet
,并重写doGet、doPost等方法
经典面试题:
如果在继承了HttpServlet的类中,重写了doGet和doPost以及service方法,那么发送一个get请求,会调用哪个方法?
- service方法。(因为请求过来会调用service方法,没有重写service方法的情况下,父类的service方法将请求不同种类进行了分支;当你重写了service方法后,父类的service方法就不会被执行,就会执行重写后的方法)(底层源码可见)
八、Servlet的配置
- 使用web.xml进行配置
- 在配置文件中配置,可以兼容旧版本
- 可以在生产环境下修改
- 可以对第三方写的Servlet进行配置
<!-- 给servlet起个名字-->
<servlet>
<servlet-name>lmz</servlet-name>
<servlet-class>com.lmz.day1.LoginDemo1</servlet-class>
</servlet>
<!-- 给对应名字的servlet一个访问路径,路径必须加/-->
<servlet-mapping>
<servlet-name>lmz</servlet-name>
<url-pattern>/a.lmz</url-pattern>
</servlet-mapping>
- 使用注解配置
- 配置简单
- 只有Servlet3.0以上的版本才可以使用
注意:
- 当使用第一种方法进行配置后,就不需要使用第二种方法配置,当两种方法都配置且路径一样时,就会起不起来服务。
- 当使用第二种方法配置后,括号中必须写路径,否则日志中也会报错,就算该Servlet没有被使用
//继承HttpServilet类,不需要重写service方法,因为底层帮我们重写该方法了且区分了请求类型,只需要重写请求类型的方法即可
//此处使用注解来配置Servlet
@WebServlet("/login")
public class LoginDemo1 extends HttpServlet {
//即使你没有使用get请求,避免被使用,可以将该请求转给dopost
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
//因为表单使用的post请求,所以需要重写post方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
//响应的字符编码改为utf-8
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
if (username.equals("zhangsan")&&pwd.equals("123456")){
System.out.println("登录成功success");
out.println("登录成功success");
}else{
System.out.println("登录失败fail");
out.println("登录失败fail");
}
}
}
注意:1.Servlet配置路径时,在配置的地方一定要加
/
。否则会出现Invalid <url-pattern> ls.do in servlet mapping
错误,导致项目无法启动
- 两个Servlet配置的路径不能相同。否则会出现
The servlet named [com.lmz.day1.LoginDemo1] and [com.lmz.day1.LoginDemo2] are both mapped to the url-pattern [/login] which is not permitted
,导致项目无法启动- 如果在控制台中没有报错信息,应该点击旁边的两个Tomcat相关的Log窗口查看错误信息
- 在页面发送该路径请求时不要加
/
,否则会出现无法访问或404.
Servlet默认是在第一次访问时由容器创建对象。仅创建一个对象,但是一致持续提供服务。
load-on-startup默认为-1,表示第一次访问时才创建对象,可以设置为0或者正整数,以使容器启动时就可以创建对象。数字越小,优先级越高(先创建)。
<!-- 给servlet起个名字-->
<servlet>
<servlet-name>lmz</servlet-name>
<servlet-class>com.lmz.day1.LoginDemo1</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- 给对应名字的servlet一个访问路径,路径必须加/-->
<servlet-mapping>
<servlet-name>lmz</servlet-name>
<url-pattern>/a.lmz</url-pattern>
</servlet-mapping>
@WebServlet(value = "/login",loadOnStartup = 0)
public class LoginDemo1 extends HttpServlet {}
九、GET和POST的区别
GET:
- 将表单数据放到url上,使用?后面拼接参数,以键值对的方式蛮多个参数使用&连接
- 数据不安全
- url有长度限制,所以get不能提交大量的数据信息
- 不能用来上传文件
- 效率相对高
- 链接分享时,作为邮件发送时,一般用get方式
POST
- 将表单数据放到请求正文中
- 数据相对安全
- post数据长度理论来说没有限制
- 可以用来上传文件
- 效率相对低
注意:为什么javaweb中没有main方法?
main方法在Tomcat中,当启动Tomcat,相当于启动了main方法
十、request对象和response对象
10.1 request对象
封装用户的请求。
常用方法:
getParameter("key")
:获取请求参数getParameterValues("key")
:获取一组相同name 的请求参数,返回一个数组(例如:复选框,可以选择多个值 ,就需要使用该方法)setCharacterEncoding("utf-8")
:设置POST请求字符集
10.2 response
用来封装响应对象
setHeader("")
:设置响应头信息,一般情况下不用设置,主要是下载时需要设置setContentType("")
:设置响应的格式setCharacterEncoding("")
:设置响应编码。在响应格式中可以设置编码,不需要通过此代码设置getWriter()
:获取页面输出流响应输出乱码问题解决:
response.setContentType("text/html;charset=utf-8");
10.3 案例
- 在数据库中建立一个Student表,表中字段有id,name,password,sex,age,hobby,phone字段,且id可以设置为自动增长,用户注册时不用输入id。
- 前端网页让用户输入用户信息,提交后保存到Student表中,提交后显示提交成功(可以将用户输入的信息响应给用户看)
10.3.1 前端
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>用户注册</h2>
<form action="register" method="post">
用户名:<input type="text" name="username" placeholder="用户名"><br>
密 码: <input type="password" name="pwd" placeholder="密码"><br>
年 龄: <input type="text" name="age" value="18" /><br>
性 别: <input type="radio" name="sex" value="男" checked>男 <input type="radio" name="sex" value="女">女<br>
爱 好: <input type="checkbox" name="hobby" value="吃饭">吃饭
<input type="checkbox" name="hobby" value="睡觉">睡觉
<input type="checkbox" name="hobby" value="打豆豆">打豆豆<br>
电 话: <input type="text" name="phone" placeholder="电话"><br>
<input type="submit" value="提交" /> <input type="reset" value="重置" />
</form>
</body>
</html>
10.3.2 后端
10.3.2.1 Servlet
//此处使用注解配置Servlet
@WebServlet("/register")
public class LoginServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收请求参数时需要设置字符集,否则会乱码
request.setCharacterEncoding("utf-8");
//响应字符集也需要设置(如果响应有文字
response.setContentType("text/html;charset=utf-8");
//当请求只有一个参数时,使用getParameter
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
String phone = request.getParameter("phone");
//当请求可能有多个参数时,使用getParameterValues
String[] hobby = request.getParameterValues("hobby");
//得到请求数据后,需要将这些数据交给Service层,然后Service层调用DAO层存到数据库
//将数据封装成对象
Student student = new Student();
student.setName(username);
student.setPwd(pwd);
student.setAge(age);
student.setSex(sex);
student.setPhone(phone);
//因为hobby是字符串数组,所以需要转成字符串
student.setHobby(String.join(",",hobby));
StudentServiceImpl studentService = new StudentServiceImpl();
boolean save = studentService.save(student);
if (save){
response.getWriter().println("注册成功");
}else{
response.getWriter().println("注册失败");
}
}
}
10.3.2.2 Utils
常量接口
和数据库连接
public interface Constants {
String STUDENTSAVE = "insert into student(name,`password`,age,sex,hobby,phone) VALUES (?,?,?,?,?,?)";
}
public class DBConnection {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal();
private static final Properties PROPERTIES = new Properties();
private static DataSource dataSource;
static {
InputStream inputStream = DBConnection.class.getResourceAsStream("/druid.properties");
try {
PROPERTIES.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES);
} catch (Exception e) {
e.printStackTrace();
}
}
public static DataSource getDataSource() {
return dataSource;
}
public static Connection getConnection() throws SQLException {
Connection connection = threadLocal.get();
if (connection == null || connection.isClosed()) {
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
}
public static void close() throws SQLException {
Connection connection = threadLocal.get();
if (connection != null && !connection.isClosed()) {
connection.close();
threadLocal.remove();
}
}
}
10.3.2.3 entity
//实例类
public class Student {
private int id;
private String name;
private String pwd;
private String age;
private String sex;
private String hobby;
private String phone;
//此处省略有参构造,无参构造,toString,getter和setter方法
}
10.3.2.4 Service
接口和接口的实现类
public interface StudentService {
boolean save(Student student);
}
public class StudentServiceImpl implements StudentService {
StudentDAO studentDAO = new StudentDAOImpl();
@Override
public boolean save(Student student) {
try {
return studentDAO.add(student);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return false;
}
}
10.3.2.5 DAO
DAO接口和DAO接口的实现类
public interface StudentDAO {
boolean add(Student student) throws SQLException;
}
public class StudentDAOImpl implements StudentDAO {
//我们没有使用事务,所以直接将连接传给queryRunner来管理,所以不需要关闭
private QueryRunner queryRunner = new QueryRunner(DBConnection.getDataSource());
@Override
public boolean add(Student student) throws SQLException {
//name,`password`,age,sex,hobby,phone
return queryRunner.update(Constants.STUDENTSAVE,
student.getName(),student.getPwd(),student.getAge(),student.getSex(),student.getHobby(),
student.getPhone()) > 0;
}
}
十一、转发和重定向
跳转页面
经典面试题:转发和重定向的区别
11.1 请求转发
- 使用请求对象
- 会将请求对象传到下一个路径,不会丢失
请求路径不会发生改变
- 只能站内转发
- 当需要将数据放入到请求中,且在下一个路径将数据取出,可以使用
request.setAttribute("key",value)
:设置数据,键值对方式,key只能是String,值可以是任意类型request.getAttribute("key")
:获取数据,通过key获取。返回object类型,需要强转request.removeAttribute("key")
:删除request中的数据。语法:
request.getRequestDispatcher("路径").forward(request,response);
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
System.out.println("a.do=====" + username);
//可以在转发时设置数据让转发后的路径取值
req.setAttribute("password","123");
//通过getRequestDispatcher将参数传递给b.do
req.getRequestDispatcher("b.do").forward(req,resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的参数
String username = req.getParameter("username");
//获取设置的数据
String password = (String)req.getAttribute("password");
System.out.println("b.do=====" + username);
System.out.println("b.do=====" + password);
//删除请求中设置的数据
req.removeAttribute("passsword");
}
11.2 重定向
使用响应对象
相对于a链接
丢失请求的所有信息
会显示新的路径
可以重定向到其他网站(例如:百度等)
如果要实现数据传递,可以使用get方式,将此参数放到url中(相当于在url中用?拼接数据),但是只能是基本数据类型和string,然后新的路径通过
request.getParameter()
获取。不推荐使用此方式语法:
response.sendRedirect("路径");
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//重定向到b.do并传递数据给b.do,因为重定向会丢失所有信息,所以我们可以使用get方式拼接信息
resp.sendRedirect("b.do?id=1");
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
System.out.println("b.do====" + id);
}
十二、Servlet生命周期
是指Servlet从创建到销毁过程中的状态
- 创建:对象的创建过程,只会执行一次,默认为第一次访问时执行
- 初始化:init方法,只会执行一次
- 服务:service方法,用来处理用户的请求和返回响应。核心方法,会执行多次
- 销毁:destroy,只会执行一次,在对象被销毁前执行
十三、Servlet特性
经典面试题:简述Servlet的单实例多线程
- servlet对象由容器创建,只会创建一个,默认第一次访问时创建,可以通过设置load-on-startup的值来让servlet在容器启动时就创建
- 只有一个对象,却提供多个请求同时访问的效果,说明它是多线程访问的
- 如果在servlet中设置属性,则该属性是多线程共享,可能会出现线程安全问题
- 应该尽量避免上述情况发生,如果有,可以通过线程同步的方式来解决该问题,但是可能因此而降低性能
十四、状态管理
HTTP协议是无状态的,服务器不记录客户端的信息。
状态管理是指服务器能够记录客户端的信息。将一个客户端的多次操作关联起来
有两种方式来解决此问题:
客户端技术
:在客户端记录用户信息,每次给服务器发送请求时,将客户端记录的信息一起发送。cookie技术,在客户端以文本明文的方式记录信息,不安全,大小有限制(4kb),类型只能是字符串。服务器技术
:session技术。在服务器记录用户信息,但是将用户身份编码发送到客户端,客户端将该id记录在cookie中,每次给服务器发送请求时,将客户端记录的id一起发送,服务器接收id后,查找该id对应的信息,相对安全,大小理论没有限制,类型没有限制。
14.1 cookie的使用
@WebServlet("/login.do")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
//判断是否登录成功
if ("admin".equals(username) && "123456".equals(pwd)) {
//如果成功,就将信息保存到cookie中
//创建cookie
Cookie cookie = new Cookie("username", username);
//如果是中文,就需要进行编码(因为此处我们用的固定的用户名和密码,所以这里使用中文来测试)
username = URLEncoder.encode("张三", "utf-8");
System.out.println(username);
//将username变成cookie对象,键值对形式,且都必须是字符串
cookie = new Cookie("username",username);
//设置路径PATH
cookie.setPath("/");
//设置生命周期
//单位为秒,表示该cookie保存的时长
//如果设置0,表示立即失效,即删除
//如果为-1,表示浏览器关闭后就会失效
cookie.setMaxAge(2 * 60);//两分钟
//响应到客户端(用response将cookie添加)
response.addCookie(cookie);
//使用转发和重定向都可
response.sendRedirect("home.do");
} else {
request.setAttribute("fail", "账号或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
@WebServlet("/home.do")
public class HomeServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = null;
//获取所有cookie
Cookie[] cookies = req.getCookies();
if (cookies != null){
//在cookie中获取key为username对应的value
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())){
//默认取值为英文
username = cookie.getValue();
//如果是中文,需要反编译(解码)
username = URLDecoder.decode(username,"utf-8");
}
}
}
//当没有cookie中没有对应的value时,就跳转到login.html登录界面(当cookie中没有username信息,且直接网址进入home.do时,就为true)
if (username == null){
resp.sendRedirect("login.jsp");
}else {
//否则就直接使用cookie保存的用户名登录主页(当直接网址进入home.do时,且cookie中有信息,就可以直接进入home.jsp)
req.setAttribute("username",username);
//因为这里使用的转发,所以网址不会跳转到home.jsp
req.getRequestDispatcher("home.jsp").forward(req,resp);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>欢迎进入,${username}</h1>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<div style="color: red">${fail}</div>
<form action="login.do" method="post"><br>
用户名:<input type="text" name="username" placeholder="用户名"><br>
密码:<input type="password" name="pwd" ><br>
<input type="submit" value="登录"><br>
</form>
</body>
</html>
优点:
- 可配置到期时间
- 简单,使用键值对操作
- 数据持久,即使关机后,再开机还保存
缺点:
- 大小受限制,根据浏览器不同,有4k和8k的限制
- 可能会被用户禁用
- 存在风险,可能会被篡改
14.2 sesson使用
session称为会话,一次会话表示使用同一个浏览器发送多次请求,浏览器关闭,则结束会话。
创建session对象:
//创建Session对象,获取Session
HttpSession session = request.getSession();
//存数据,这里key和value,key是字符串,value可以是任意对象
session.setAttribute("key",value);
//获取数据,根据key获取value
session.getAttribute("key");
//删除数据
session.removeAttribute("key");
作用域:
request作用域
:作用在同一个请求之内,(可以是使用请求转发的多个路径)session作用域
:在同一个浏览器多次发送请求时,所有路径均有效,除非浏览器关闭,或者主动销毁session
- 失效方式:
- 关闭浏览器
- 超时:session默认有效时间为30分钟,可以通过
session.setMaxInactiveInterval(second)
来设置。当一直没有向服务器发送请求,时间超过了超时时间,会失效。
- 注销:
request.getSession().invalidate();
经典面试题:
removeAttribute与invalidate的区别?
removeAttribute是清空当前session中指定的属性,下个request中的sessionID是不变的,session还是原来的session;
invalidate就是销毁此session对象,session对象中绑定的那些对象值也都不存在了,session改变;
@WebServlet("/login.do")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
//判断是否登录成功
if ("admin".equals(username) && "123456".equals(pwd)) {
//创建Session对象,获取Session
HttpSession session = request.getSession();
//存数据,这里key和value,value可以是任意对象
session.setAttribute("username","张三");
//可以使用重定向或者转发,因为session是任何地方都可以访问
response.sendRedirect("home.jsp");
} else {
request.setAttribute("fail", "账号或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
@WebServlet("/logout.do")
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//注销session(注销后cookie中session编号就会改变,就需要重新登录)
request.getSession().invalidate();
//注销结束跳转到登录界面
response.sendRedirect("login.jsp");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>欢迎进入,${username}</h1>
<a href="logout.do">注销</a>
</body>
</html>
注意:
session中保存的数据会较为长久的驻留在服务器内存中
,不应该将大量数据保存在session中,所以如果有大量数据需要传递,应该使用request对象,session一般用来保存用户身份信息、权限信息等。
14.3 ServletContext对象
ServletContext
接口的对象,一般会被命名为application
对象。该对象也可以存储数据。
//类似于request存数据,只不过是一直有效,除非服务器重启或者删除
ServletContext application = request.getServletContext();
//存数据,这里key和value,key是字符串,value可以是任意对象
application.setAttribute("key",value);
//获取数据,根据key获取value
application.getAttribute("key");
//删除数据
application.removeAttribute("key");
在整个服务器上有效,意味着常驻内存。而且对所用用户有效。除非重启服务器或者通过removeAttribute删除,否则一直存在。所以一般用来保存服务器的设置信息等。
//访问count.html会执行
@WebServlet("/count.html")
public class CountServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取
ServletContext application = req.getServletContext();
//获取count的值
Integer count = (Integer) application.getAttribute("count");
//如果第一次进入,则count就为null,所以赋值为0
if (count == null) count = 0;
//次数+1
count++;
//设置到application中
application.setAttribute("count", count);
//设置打印的格式
resp.setContentType("text/html;charset=utf-8");
//获取打印流,直接打印,不传递到网页中
PrintWriter out = resp.getWriter();
//在浏览器中打印次数
out.println("<h1>此页面被刷新了" + count + "次</h1>");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
十五、Filter(过滤)
在JavaWeb中,用来拦截用户请求,并根据用户的请求信息判定是否合法,如果不合法,可以停止该请求过程,将其跳转到其他路径。
步骤:
- 实现Filter接口,并重写doFilter方法
- 对其拦截路径进行配置。(类似Servlet)
常用场景:
- 转码(当需要将一些请求内容进行转码时,可以将其设置到Filter中过滤,此时所有请求都会被过滤)
- 权限拦截
两种配置方式:
- web.xml
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.lmz.demo1.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<!-- 表示对所有请求都进行过滤-->
<url-pattern>/*</url-pattern>
</filter-mapping>
- 使用注解
@WebFilter("/*")
public class EncodingFilter implements Filter{}
过滤器链:
多个过滤器在项目中形成一个过滤器链,应该依次拦截请求进行过滤,通过以下代码执行。
//让过滤器链上其他过滤器执行,(放行)注意,此代码必须写在最后一行
filterChain.doFilter(servletRequest,servletResponse);
编码过滤器:
//使用注解配置
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//该方法过滤用户的请求,并进行相应的处理
//转码过滤
servletRequest.setCharacterEncoding("utf-8");
//让过滤器链中的其他过滤器继续执行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
权限过滤器
//使用注解配置
@WebFilter("/*")
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//权限过滤,当用户没有登录时,让其无法访问home.jsp或者其他界面,只有登录后才让其访问
//将请求和响应转为HttpServletRequest,才能获得session存放的用户信息,因为用户信息存放在session中
// 网页中都可以访问session中的信息,且关闭浏览器后就需要重新登录
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取session中用户信息,因为用户登录成功后会将信息存入session,那么每次发送请求就去获取
//若没有登录就访问其他的网页那么这里的username就为null
Object username = request.getSession().getAttribute("username");
//获取用户访问的url地址(网页地址)
String requestURI = request.getRequestURI();
//获取当前工程名称
String path = request.getContextPath();
//当访问的地址是登录界面或首页,或直接访问的网站,或各种资源(例如css、js、Ajax请求等等)就不进行过滤操作
//这里可以将不需要过滤的资源全部列出,那么这些资源就不会被过滤掉
if (requestURI.endsWith("/login.do") || requestURI.endsWith("/login.jsp")
|| requestURI.endsWith("/index.jsp")
|| requestURI.endsWith(path) || requestURI.endsWith(path + "/")
|| requestURI.endsWith(".css") || requestURI.endsWith(".js")
|| requestURI.endsWith(".jpg") || requestURI.endsWith(".png")) {
} else {
//过滤,判断用户是否直接登录的其他网页,是则直接让其跳转到登录页面,提示用户需要先登录才能访问此网页
if (username == null) {
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("login.jsp").forward(request, response);
//一定要return退出,否则他还会继续往后面执行,那么就没有达到过滤的效果
return;
}
}
//进行链式过滤,如果后面没有过滤条件了就会执行对应的Servlet
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
@WebServlet("/login.do")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println(111111);
//判断是否登录成功
if ("admin".equals(username) && "123456".equals(pwd)) {
//创建Session对象,获取Session
HttpSession session = request.getSession();
//存数据,这里key和value,value可以是任意对象
session.setAttribute("username",username);
//可以使用重定向或者转发,因为session是任何地方都可以访问
response.sendRedirect("home.jsp");
} else {
request.setAttribute("msg", "账号或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
@WebServlet("/logout.do")
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//注销session(注销后cookie中session编号就会改变,就需要重新登录)
request.getSession().invalidate();
//注销结束跳转到登录界面
response.sendRedirect("login.jsp");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
}
<%--index.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="login.jsp">登录</a>
</body>
</html>
<%--login.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<div style="color: red">${msg}</div>
<form action="login.do" method="post"><br>
用户名:<input type="text" name="username" placeholder="用户名"><br>
密码:<input type="password" name="pwd" ><br>
<input type="submit" value="登录"><br>
</form>
</body>
</html>
<%--home.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>欢迎进入,${username}</h1>
<a href="logout.do">注销</a>
</body>
</html>