多个请求路径映射一个servlet:
<servlet> <servlet-name>helloservlet</servlet-name> <servlet-class>com.guang.servlet.HelloServlet</servlet-class> </servlet> <!--第一种映射,添加一个url-pattern--> <servlet-mapping> <servlet-name>helloservlet</servlet-name> <url-pattern>/hello</url-pattern> <url-pattern>/hell</url-pattern> </servlet-mapping> <!--第二种映射,添加一个servlet-mapping--> <servlet-mapping> <servlet-name>helloservlet</servlet-name> <url-pattern>/llo</url-pattern> </servlet-mapping>
这两种方式都可以让路径映射到具体的servlet类来进行处理对应的请求。
也就是说url-pattern的配置方式一共有三种配置:
分别举几个例子来进行说明:
完全路径匹配:上面的xml文件中配置的都是完全路径匹配
目录匹配:
<!-- 目录匹配: 以/ 开始, 以*结尾 *表示后面的内容可以有,也可以没有--> <servlet-mapping> <servlet-name>hello02</servlet-name> <url-pattern>/hello02/*</url-pattern> </servlet-mapping>
扩展名匹配:
例如: *.action; 访问: aa.action, bb.action, c.action; 错误写法: /*.do, 不可以写*.jsp,*.html <!--后缀名匹配的方式 : 以*打头, 以后缀名结尾--> <servlet-mapping> <servlet-name>hello02</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping>
注意的地方:
一个路径只能对应一个servlet, 但是一个servlet可以有多个路径
tomcat获得匹配路径时,优先级顺序:完全路径匹配> 目录匹配 > 扩展名匹配
1、默认是在客户端第一次访问的时候,Tomcat根据客户端应用找到是哪个项目路径,然后根据url-pattern找到对应的servlet来进行处理
2、Tomcat会找到对应的servlet,然后Tomcat利用反射来创建对应的对象(单例对象);
3、在创建完成对象之后,会去调用init()方法来完成对该servlet的初始化操作;
4、Tomcat会针对每次到达的请求,都会从底层的线程池中获取得到新的线程,然后调用该servlet的service方法来进行处理;
5、在容器关闭阶段,会调用destroy方法来完成对servlet的销毁;
一共有是三个,在继承了Servlet接口中,五个方法中,有三个是生命周期方法:
void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy();
其中:
void init(ServletConfig var1) throws ServletException; void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; void destroy();
常规【重点】
(1):默认情况下, 来了第一次请求, 会调用init()方法进行初始化【调用一次】
**作用**:用于初始化.. 准备工作(创建流对象 | 准备数据库连接..)
(2):任何一次请求 都会调用service()方法处理这个请求
**作用**:用于接收请求、处理请求
(3):服务器正常关闭或者项目从服务器移除, 调用destory()方法进行销毁【调用一次】
**作用**:用于处理收尾的工作。 关流 | 释放连接
扩展
servlet是单例多线程的, 尽量不要在servlet里面使用全局(成员)变量,可能会导致线程不安全(如果要使用,要保证线程安全)
单例: 只有一个对象(init()调用一次, 创建一次)
多线程: 服务器会针对每次请求, 开启一个线程调用service()方法处理这个请求
因为是多线程的原因,才会导致如果使用了全局变量过程中会出现线程安全问题。
Servlet的配置对象, 可以使用用ServletConfig来获得Servlet的初始化参数,(在SpringMVC里面会遇到)
注意:可以在配置中修改servlet的创建时期,默认是第一次访问的时候加载实例;我们可以通过修改servlet配置,让其提前到在容器初始化的时候就加载该实例。
对应的xml配置文件如下所示:
<!--配置ServletConfigServlet--> <servlet> <servlet-name>servletconfig</servlet-name> <servlet-class>com.guang.servlet.ServletConfigServlet</servlet-class> <init-param> <param-name>password</param-name> <param-value>123456</param-value> </init-param> <!--表示的是在容器启动的时候就开始加载当前的servlet--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>servletconfig</servlet-name> <url-pattern>/servletconfig</url-pattern> </servlet-mapping>
对应的servlet代码是:
public class ServletConfigServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { // 获取得到serviceconfig对象 String password = servletConfig.getInitParameter("password"); System.out.println("在web.xml文件中配置的对当前servlet的初始化参数是:"+password); // 123456 } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
1、servlet的应用上下文,在一个servlet项目中,有且只有一个servletcontext对象。
2、作用:
3、获取方式:只要是servlet类,都是可以得到servlet上下文对象
对应的代码所示:
@WebServlet(name = "ServletContextOne", value = "/ServletContextOne") public class ServletContextOne extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); servletContext.setAttribute("data1",new String("hello,world")); } }
@WebServlet(name = "ServletContextTwo", value = "/ServletContextTwo") public class ServletContextTwo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object hello = this.getServletContext().getAttribute("data1"); System.out.println("对应的值是:"+hello.toString()); } }
首先访问ServletContextOne,然后再去访问ServletContextTwo,就可以获取得到在ServletContextOne中存入的数据即可。
1、在web.xml全局配置文件中来进行添加对应的全局初始化参数
<context-param> <param-name>companyname</param-name> <param-value>lig</param-value> </context-param>
2、编写一个servlet来统一进行验证:
@WebServlet(name = "ServletContextThree", value = "/ServletContextThree") public class ServletContextThree extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); String file1 = "a.txt"; String file2 = "a.mp3"; String mimeType1 = servletContext.getMimeType(file1); String mimeType2 = servletContext.getMimeType(file2); System.out.println(file1+"的资源类型是:"+mimeType1); System.out.println(file2+"的资源类型是:"+mimeType2); System.out.println("------------------------------"); System.out.println("------------------------------"); System.out.println("------------------------------"); String companyname = servletContext.getInitParameter("companyname"); System.out.println("全局初始化参数是:"+companyname); } }
对应的控制台显示:
a.txt的资源类型是:text/plain a.mp3的资源类型是:audio/mpeg ------------------------------ ------------------------------ ------------------------------ 全局初始化参数是:lig
注意: filepath:直接从项目的根目录开始写
对应的代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获得文件的绝对路径 getRealPath()这个方法的路径已经在项目下了, 已经到了web目录下了 String realPath = getServletContext().getRealPath("a.txt"); System.out.println("realPath="+realPath); //2.获得文件的输入流 getResourceAsStream(String path);这个方法的路径已经在项目下了, 已经到了web目录下了 //new FileInputStream(realPath); InputStream is = getServletContext().getResourceAsStream("a.txt"); System.out.println(is); }
控制台输出结果:
realPath=D:\hello\myproject\javaweb\out\artifacts\servlet_one_war_exploded\a.txt java.io.ByteArrayInputStream@44554de5
需求:统计网站一共被访问了多少次?
首先写两个servlet来进行功能区分:一个负责统计,另外一个负责显示
书写两个servlet:
@WebServlet(name = "LoginCountServlet", value = "/LoginCountServlet") public class LoginCountServlet extends HttpServlet { private static AtomicInteger count = new AtomicInteger(1); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } /** * 这里应该是通过一个唯一标识来表示登录的次数,过滤掉重复登录的情况。这里是简单,统计所有的情况即可 * @param request * @param response * @throws ServletException * @throws IOException */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); Object loginCount = servletContext.getAttribute("loginCount"); if (Objects.isNull(loginCount)) { servletContext.setAttribute("loginCount", count); } else { count.getAndDecrement(); servletContext.setAttribute("loginCount", count); } } }
负责展示的servlet:
@WebServlet(name = "ShowLoginServlet", value = "/ShowLoginServlet") public class ShowLoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); AtomicInteger loginCount = (AtomicInteger) servletContext.getAttribute("loginCount"); int currentCount = loginCount.get(); response.getWriter().println("您是第"+currentCount+"位登录用户"); } }
分别请求并进行展示即可
在使用javaweb进行开发过程中,每个请求都会被Tomcat服务器封装称为一个request请求对象,request请求对象封装了一个请求中的所有信息。
request代表请求对象. 原型是HttpServletRequest, 服务器创建好的, 以形参的方式存在doGet()/doPost()方法里面
request作用
实际上的操作,就只有获取。因为已经被Tomcat服务器默认给封装好了,所以不需要来对其进行修改。
获取客户机信息(操作请求行)
请求方式 请求路径(URI) 协议版本 GET /request/web/register.htm?username=zs&password=123456 HTTP/1.1
//method=GET String method = req.getMethod(); System.out.println("method=" + method); //uri=/request/request01 String uri = req.getRequestURI(); System.out.println("uri=" + uri); //url=http://localhost:8080/request/request01 StringBuffer url = req.getRequestURL(); System.out.println("url=" + url); //protocol=HTTP/1.1 String protocol = req.getProtocol(); System.out.println("protocol=" + protocol);
请求头: 浏览器告诉服务器自己的属性,配置的, 以key value存在, 可能一个key对应多个value。
getHeader(String name)
//2. 可以获取请求头 String ua = req.getHeader("User-Agent"); System.out.println("ua=" + ua); Enumeration<String> headerNames = req.getHeaderNames(); while(headerNames.hasMoreElements()){ String header = headerNames.nextElement(); String value = req.getHeader(header); System.out.println(header + "=" + value); }
操作请求体:最主要的是用来获取得到请求体中的参数来进行操作的。
对于get请求方式来说,是没有请求体的。所以这里就只针对的是post请求方式。
法名 | 描述 |
---|---|
String getParameter(String name) | 获得指定参数名对应的值。如果没有则返回null,如果有多个获得第一个。 例如:username=jack |
String[] getParameterValues(String name) | 获得指定参数名对应的所有的值。此方法专业为复选框提供的。 例如:hobby=抽烟&hobby=喝酒&hobby=敲代码 |
Map<String,String[]> getParameterMap() | 获得所有的请求参数。key为参数名,value为key对应的所有的值。 |
响应的代码展示:
@WebServlet(name = "Body1Servlet", value = "/Body1Servlet") public class Body1Servlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取得到单个参数的值,如果没有返回null String username = request.getParameter("username"); System.out.println("获取得到的参数是:"+username); System.out.println("----------------------"); // 获取得到指定参数的值,一个key对应的可以有多个value String[] hobbies = request.getParameterValues("hobby"); for (String hobby : hobbies) { System.out.println("hobby对应的爱好有:"+hobby); } System.out.println("----------------------"); // 获取得到所有的! Map<String, String[]> parameterMap = request.getParameterMap(); } }
浏览器请求:
http://localhost:8080/servlet_one_war_exploded/Body1Servlet?username=lig&hobby=1&hobby=2&hobby=3
对应的控制台显示:
获取得到的参数是:lig ---------------------- hobby对应的爱好有:1 hobby对应的爱好有:2 hobby对应的爱好有:3 ----------------------
但是如果参数过多,还要封装到javabean中去,那么调整好对应的参数之后,还需要调用对象之后来进行setXxx方法来进行操作。
非常麻烦,那么直接利用BeanUtils(Apache Commons组件),来简化JavaBean封装数据。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* //1. 获取数据 String username = req.getParameter("username"); String password = req.getParameter("password"); String address = req.getParameter("address"); String phone = req.getParameter("phone"); //2. 封装对象 User user = new User(username , password , address , phone); System.out.println("user=" + user); */ try { //1. 获取参数 还有一些特殊的地方,爱好,,有三个数据,??? Map<String, String[]> map = req.getParameterMap(); //2. 创建对象 User u = new User(); //3. 把参数封装到对象身上 BeanUtils.populate(u , map); //4. 打印一下 System.out.println("u=" + u); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
注意:JavaBean属性需要和Map的key一致,也就是说JavaBean属性需要和表单的name一致。
我们在输入一些中文数据提交给服务器的时候,服务器解析显示出来的一堆无意义的字符,就是乱码。
那么这个乱码是如何出现的呢?这是因为浏览器的编码格式和服务器的编码格式是不同的,只有把服务端和客户端的编码格式进行统一之后,那么才不会乱码。
在接收参数之前,来对参数进行处理
request.setCharacterEncoding("UTF-8");
请求转发的本质就是: 跳转 ,不能跳转到外部的资源,只能跳转到项目内部的资源
但是对于项目内部来说,仍然是一个请求,也就是请求还未处理完成。
request.getRequestDispatcher(url).forward(request, response); //转发
特点
来写一个例子:
@WebServlet(name = "ForwardServlet", value = "/ForwardServlet") public class ForwardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("处理完成请求之后"); request.getRequestDispatcher("/WEB-INF/hello.html").forward(request,response); } }
注意:对于WEB-INF下面的资源,客户端是无法直接来进行访问的,只能够通过转发的方式来进行请求访问到。
ServletContext: 可以存|取值,范围是整个应用程序, AServlet 存值, BServlet能取值 request范围: 一次请求内有效!!! 域对象是一个容器,这种容器主要用于Servlet与Servlet/JSP之间的数据传输使用的。
写一个案例来进行说明:
存数据的servlet:
@WebServlet(name = "StorageServlet", value = "/StorageServlet") public class StorageServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("store","hello,world"); request.getRequestDispatcher("/TakeOutServlet").forward(request,response); } }
取数据的servlet:
@WebServlet(name = "TakeOutServlet", value = "/TakeOutServlet") public class TakeOutServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String storeMsg = (String) request.getAttribute("store"); System.out.println("存储的数据是:"+storeMsg); response.getWriter().println(storeMsg); } }
发送对应的请求之后,然后浏览器上会显示:
hello,world
在Servlet API中,定义了一个HttpServletResponse接口(doGet,doPost方法的参数),它继承自ServletResponse接口,专门用来封装HTTP响应消息。由于HTTP响应消息分为响应行、响应头、响应体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码、响应头、响应体的方法
对应的格式:
HTTP/1.1 200
设置的API: response.setStatus(int code);
一般不需要设置, 可能302 重定向需要设置
常见的响应状态码
响应头: 是服务器指示浏览器去做什么
关注的方法: setHeader(String name,String value);
常用的响应头
Refresh:定时跳转 (eg:服务器告诉浏览器5s之后跳转到百度) Location:重定向地址(eg: 服务器告诉浏览器跳转到xxx) Content-Disposition: 告诉浏览器下载 Content-Type:设置响应内容的MIME类型(服务器告诉浏览器内容的类型)
对应的demo:
@WebServlet(name = "RefreshServlet", value = "/RefreshServlet") public class RefreshServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Refresh" , " 5; url=http://www.baidu.com"); } }
告知浏览器,在5秒钟后跳转到百度页面上。
@WebServlet(name = "LocationServlet", value = "/LocationServlet") public class LocationServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(302); response.setHeader("Location","/RefreshServlet"); // 还有一种更加简单的写法,直接省略上面两个步骤 // resp.sendRedirect("http://www.baicu.com"); // resp.sendRedirect("/RefreshServlet"); } }
告知浏览器重定向到RefreshServlet这个路径下来,重新发起对应的请求。
响应体影响着浏览器的显示。
解决字符流输出中文乱码问题
response.setContentType("text/html;charset=utf-8");
响应的内容类型是文本类型并以utf-8的形式将内容进行响应。
使用字节输出流输出中文乱码问题
//设置浏览器打开方式 response.setHeader("Content-type", "text/html;charset=utf-8"); //得到字节输出流 ServletOutputStream outputStream = response.getOutputStream(); outputStream.write("你好".getBytes("utf-8"));// 使用平台的默认字符(utf-8)集将此 String 编码为 byte 序列
注意:页面输出只能使用其中的一个流实现,两个流是互斥的.
为什么会出现乱码?
乱码解决
请求乱码
request.setCharacterEncoding("utf-8");
响应乱码
response.setContentType("text/html;charset=utf-8");
@WebServlet("/download") public class DownloadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 获取要下载的文件名称,通过参数传递进来的 String fileName = req.getParameter("file"); //a.jpg //2. 根据文件找到得到该文件的输入流 // FileInputStream fis = new FileInputStream(); InputStream fis = getServletContext().getResourceAsStream("download/"+fileName); if(fis == null){ System.out.println("文件路径错误!!!"); } //设置一下文件的类型,以及告诉浏览器要下载。 String mimeType = getServletContext().getMimeType(fileName); resp.setContentType(mimeType); resp.setHeader("Content-Disposition" , "attachment;filename="+fileName); //3. 使用字节流的方式写给浏览器 OutputStream os = resp.getOutputStream(); byte [] buffer = new byte[1024]; int len = 0 ; while( (len = fis.read(buffer)) != -1 ){ os.write(buffer , 0 , len); } os.close(); fis.close(); } }
对于来自客户端的请求来说,服务器端无所不应。
所以Tomcat服务器会将请求封装成为一个对象,将响应也封装成为一个对象。
对于一个http的请求来说,格式分为:请求行、请求头、请求体。这里都是获取的功能;
对于一个http的响应来说,格式分为:响应行、响应头、响应体。而响应我们可以通过response响应对象来进行响应的设置操作。
对于请求来说,我们可以知道从哪里请求来的,能够接收到什么
对于响应来说,服务器则是告知客户端需要来做些什么