Servlet学习笔记
Servlet生命周期
Servlet 接口,java中的一个包,Server Applet 服务端小程序,javax.servlet
功能 根据客户端提交的请求,调用服务器端Java代码,完成对请求的处理和运算
生命周期 从创建到被销毁的过程
实例化 --> 初始化 --> 服务 --> 销毁
请求 --> 服务器 --> URI --> 映射为Servlet --> 开启生命周期
1.被创建:执行init方法,只执行一次
Servlet什么时候被创建?
--默认情况下,第一次被访问时,Servlet被创建,然后执行init方法;
--可以配置执行Servlet的创建时机;
2.提供服务:执行service方法,执行多次
3.被销毁:当Servlet服务器正常关闭时,执行destroy方法,只执行一次
注册Servlet类和映射关系
<servlet>
<servlet-name>some-servlet</servlet-name>
<servlet-class>Someservlet</servlet-class>
</servlet>
--映射,url
<servlet-mapping>
<servlet-name>some-servlet</servlet-name>
<url-pattern>/some</url-pattern>
</servlet-mapping>
Servlet特征
1.Serlvet是单例多线程的
一个Servlet实例只会执行一次无参构造器与init()方法,并在第一次访问时执行
用户没提交一次对当前Servlet请求,就会执行一次service()方法
一个Servlet实例只会去执行一次destory()方法,在应用停止时执行
忧郁Servlet是单例多线程的,所以为了保证其安全性,一般情况下不为Servlet类定义可修改的成员变量。因为每个线程均可修改这个成员变量,会出现线程安全问题
默认情况下,Servlet在Web容器启动时不会被实例化。
Web容器启动时创建Servlet实例
<!--在servlet下面添加,n代表优先级,越小越高-->
<load-on-stattup>n</load-on-stattup>
Servlet map 两个map
第一个map key--uri value--Servlet引用 第二个map key--uri value--Servlet class
请求解析出的uri访问第一个map,根据key找到value,找不到就访问第二个map,找到Servlet class,创建Servlet实例,并添加到第一个map中//所以Servlet是单例。
ServletInfo() 对当前Servlet进行的说明,版本/作者/信息等
ServletContext
ServletConfig() Servlet配置对象,Servlet容器传递信息到Servlet初始化,就是获取Servlet注册信息
ServletContext 通过ServletConfig.getServletContext()取得。getInitParameterNames()获取init param枚举对象
ServletContext 可以代表整个应用
<!--放在servlet里面-->
<init-param>
<param-name>name</param-name>
<param-value>value</param-value>
</init-param>
...
ServletContext sc = config.getServletContext();
getInitParameterNames() ServletContext中获取初始化参数名
Enumeration<String> names = sc.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = sc.getInitParameter(name);
}
setAttribute(name,value) ServletContext中设置域属性
作用域
sc.setAttribute("email","qianzi@163.com");
sc.setAttribute("address","wuhan");
getAttribute(name) ServletContext中获取域属性,全局获取
String str = (String)sc.getAttribute("email");//返回的是一个Object,要转型
getContextPath() ServletContext中获取应用名称
String path = sc.getContextPath();
getRealPath(value) ServletContext中获取本地文件路径
String realPath = sc.getRealPath("/images");//获取图片images的本地绝对路径
欢迎界面和URL-PATTERN
welcome-file-list 欢迎页面--可以有多个,从上往下查找,没有设置在tomcat中有默认的欢迎页面index
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
url-pattern 在servlet-mapping配置的定位url
精确路径模式
访问主页+/xxx时响应Servlet。可以配置多个
<servlet-mapping>
<servlet-name>servletname</servlet-name>
<url-pattern>/x/xxx/xxx/xxx/x</url-pattern>
<url-pattern>/x/xxx/xxx</url-pattern>
<url-pattern>/x/xxx</url-pattern>
<url-pattern>/xxx</url-pattern>
</servlet-mapping>
通配符路径模式
*表示通配符,在/*后面无论是什么目录都拦截
<servlet-mapping>
<servlet-name>servletname</servlet-name>
<url-pattern>/x/xxx/xxx/xxx/*</url-pattern>
<url-pattern>/x/xxx/*</url-pattern>
<url-pattern>/x/*</url-pattern>
<url-pattern>/xxx</url-pattern>
</servlet-mapping>
全路径匹配
/*表示所有路径都拦截
/也表示所有路径都拦截,但是不拦截动态资源请求
<servlet-mapping>
<servlet-name>servletname</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>servletname</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
后缀模式
*.xxx 表示拦截.xxx结尾的路径,后缀模式不能和其他的混用
<servlet-mapping>
<servlet-name>servletname</servlet-name>
<url-pattern>*.xxx</url-pattern>
</servlet-mapping>
匹配原则
越精确就越先匹配谁,而不匹配其他的路径
精确路径优先匹配原则
最长路径优先匹配原则
精确路径 > 长路径带通配符 > 短路径带通配符 > 后缀匹配
Servlet核心
自定义GenericServlet类 在开发中不需要去实现Servlet接口,因此定义一个GenericServlet类实现Servlet接口,然后再定义Servlet类的时候继承GenericServlet类,重写service等函数即可,其他方法空实现。这是缺省适配器设计模式
缺省适配器设计模式 让一个类去空实现一个接口,然后让子类去实现接口的部分方法
模板方法设计模式 父类的方法让子类去实现,子类只用实现父类留给子类的方法就可以实现父类中复杂的方法
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;
//缺省适配器设置模式,实现Servlet和ServletConfig接口,创建Servlet类时更方便使用。
public class GenericServlet implements Servlet,ServletConfig {
private ServletConfig config;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.config = config;
this.init();
}
//模板方法设计模式
private void init() {
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
@Override
public String getServletName() {
return config.getServletName();
}
@Override
public ServletContext getServletContext() {
return config.getServletContext();
}
@Override
public String getInitParameter(String name){
return config.getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
系统自带GenericServlet类 只需要继承系统自带的类就OK了,不需要自己写
自定义HttpServlet类 区分Get和Post提交,写出doPost和doGet函数让子类去对POST和GET去处理
public class HttpServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取请求提交方式
String method = request.getMethod();
if ("POST".equals(method)) {
doPost(request, response);
} else if ("GET".equals(method)) {
doGet(request, response);
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) {
}
public void doPost(HttpServletRequest request, HttpServletResponse response) {
}
}
系统也自带HttpServlet 功能更加强大,而且识别了Http协议和版本。
请求的生命周期 HttpServletRequest实例对象什么时候创建和销毁
服务器接收Htpp请求协议的格式对象,进行解析,同时服务器会创建HttpServletRequest的实现类和RequestFacade的对象,即请求对象,调用set方法,将数据封装到对象中,此时HttpServletRequest实例就创建完成了。通过HttpServletRequest对象去执行相应的操作,执行完后向客户端发送响应结束后,HttpServletRequest实例被销毁。一次请求对应一个请求对象,不同的请求之间没有关系,HttpServletRequest生命周期很短暂。
请求参数 request获取请求参数,参数名称,参数值可以为多个
getParameter(name) HttpServletPequest中获取指定名称的参数值,一个参数若是有多个值,获取其第一个值
String name = request.getParameter("name");
getParameterNames() 获取全部参数名称的枚举集合
Enumeration<String> names = request.getParameterNames();
while(names.haMoreElements()){
String eleName = names.nextElement();
String eleValue = request.getParameter(eleName);
}
getParameterValues(name) 获取指定参数的所有值
String[] values = request.getParameterValues("name");
getParameterMap() 获取全部参数,返回map,key表示参数名称,value表示参数所有值
Map<String,String[]> map = request.getParameterMap();
for(String key : map.keySet()){
String[] values = map.getValue(key);
}
请求的域属性 Attribute,请求域,只在一次请求中有效
setAttribute(name,value) HttpServletRequest中设置域属性
request.setArrtribut("name","value");
转发请求 getRequestDispatcher().forward
request.getRequestDispatcher("\OtherServlet").forward(request,response);
getAttribute(name) 获取指定名称的域属性
String value = request.getAttribute("name");
getAttributeNames() 获取所有域属性的名称
Enumeration<String> names = request.getAttributeNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = request.getAttribute(name);
}
removeAttribute(name) 从请求中删除指定的域属性
request.removeAttribute("name");
HttpServletRequest常用方法 request
getRequestURL() return StringBuffer 获取request的url url:http://xxx
StringBuffer requestURL = request.getRequestURL();
getRequestURI() return String 获取request的uri,本地相对路径
String requestURI = request.getRequestURI();
getContextPath() return String 获取当前Servlet的根路径
String requestContextPath = request.getContextPath();
getMethod() return String 获取请求方式,是GET还是POST
String requestMethod = request.getMethod();
getRemoteAddr() return String 获取客户端IP
String clientIP = request.getRemoteAddr();
getServletPath() return String 获取Servlet匹配的精确部分路径
//Servlet url-pattern /xxx/xx/x/*
//访问 /xxx/xx/x/111/2
String servletPath = request.getServletPath();
// 输出 /xxx/xx/x
getPathInfo() return String 获取Serlvet匹配的通配符部分路径
String pathInfo = request.getPathInfo();
//输出 /111/2
// 不存在通配符部分。pathInfo为null
中文乱码问题 http协议基于TCP协议,传输的形式基于字节流
字节流 以%开头以16进制出现
产生原因 程序界面和服务器的编码方式不同
GET与POST GET在Tomcat9中自动解决了乱码问题
setCharacterEncoding(“编码方式”) 设置了请求正文中的字符编码,解决了post提交的中文乱码,解决不了get方案
request.setCharacterEncoding("UTF-8");
完美解决方案 对需要解决乱码的参数设置编码方式,服务器接收到的字符编码为ISO8859-1
String name = request.getParameter("name");
name = new String(name.getBytes("ISO8859-1"),"UTF-8");
Response 响应对象
PrintWriter response.getWriter()
响应对象会把要响应的数据放入getWriter中,这是一个标准输出流,服务器会直接发送该对象到客户端
PrintWriter out = response.getWriter();
写入流的方式 append print write
out.append("text");
out.print("text");
out.write("text");
关闭流 不要直接关闭PrintWriter流,当响应结束了,流会由服务器自动关闭
解决乱码问题 setCharacterEncoding() setContentType
setCharacterEncoding(“charset”) 该方法要写在response.getWriter()之前
response.setCharacterEncoding("UTF-8");
//并不能解决乱码问题,该方法只是设置MIME字符编码,要先设置ContentType才能解决乱码
setContentType(“type”) 设置响应的格式
response.setContentType("text/html;charset=UTF-8");
//可以指定MIME的字符编码即响应体的字符编码
请求转发与重定向 通过HttpServletRequest获取RequestDispatcher对象的forward方法,完成请求转发,通过HttpServletResponse的sendRedirect方法,完成重定向功能。
请求转发 请求发送到服务器访问资源1,但是又用到资源2,所以资源1将请求转发到资源2,资源2可以是WEB-INF中的资源,请求转发成为服务器内跳转
重定向 请求发送的服务器访问资源1,但是又用到了资源2,资源1响应请求后,接受响应后再次请求资源2,这就是重定向,资源2不可以是WEB-INF中的资源,重定向成为服务器外跳转
response.sendRedirect("other");
//将请求重定向到other
重定向无法传递数据 解决方案,加?和参数
response.sendRedirect("other?name=qianzi&address=wuhan");
重定向传输数据乱码 发送请求的编码根据TCP IP协议,是字节流数据
//URLEncoder URLDecoder编码解码库
name = URLEncoder.encode(name,"UTF-8");//编码
pname = URLDecoder.decode(pname,"UTF-8");//解码
重定向到其他应用
response.sendRedirect("/otherWebapp");
重定向作用 重定向到其他应用,请求转发不行,防止表单重复提交
请求转发和重定向 跳转到其他应用,对于表单数据的处理,或者请求会消耗大量服务器资源,使用重定向,如果只是数据传递,使用请求转发,两者都能使用的时候,建议使用重定向
请求转发的forword()和include()的区别 资源1请求转发到资源2,其实是内部的资源请求,而两次请求会被合并成一个请求。转发的资源2的请求会被增强。
forword和include的区别在于response forword是在资源2得到请求后才开启响应输出流,资源1的响应输出流不会合并到资源2,include是在资源1得到请求后转发时开启响应输出流,资源1的响应将会被合并到资源2的响应输出流中
访问路径问题
URL 统一资源定位符
格式 协议,主机,端口,路径
绝对路径 = 参照路径 + 相对路径 服务器或浏览器会自动生成参照路径,所以不用自己写参照路径
以斜杠开头的相对路径 前台路径和后台路径
前台路径 出现在jsp中,加/表示以服务器路径为根路径 无/表示以当前路径为根路径,查找资源
后台路径 出现在xml,java类中,以项目路径为根路径,标识出资源
以路径名开头的相对路径 当前访问路径的资源路径为根路径
Servlet线程安全
线程安全 多线程并发访问,存在可修改的共享数据
JVM可能存在的线程安全问题的数据分析
栈内存数据分析 栈内是多例的,jvm会为每个线程创建一个栈,不存在数据共享,方法中的局部变量存放在Stack的栈针中,执行完毕,出栈,所以不会导致数据共享。所以栈内存中不存在线程安全问题。
堆内存数据分析 一个jvm只存在一个堆,堆内存是共享的。被创建的对象是存放在堆内存中,成员变量是存在堆内存中,堆内存的数据是多线程共享的,堆内存存在线程安全问题
方法数据分析 一个jvm只有一个方法区,静态变量与常量存放在方法区,方法区是多线程共享的。常量是不能被修改的,不存在线程安全问题。静态变量是多线程共享的,所以静态变量存在线程安全问题。
线程安全问题解决方案
对于一般性的类,不要定义为单例,除非特殊需要或者类创建时消耗大量系统资源
尽量少使用静态变量
单例类尽量不使用成员变量
使用synchronized实现线程同步。
Servlet线程安全解决问题
servlet类变量最好放在方法中。
synchronized 同步,这样不好,一次只能有一个访问
synckronized (this){
username = request.getParameter("name");
....
}
参考资料
Servlet 经典实战视频教程_动力节点
https://www.bilibili.com/video/BV1yx411s7zC