• Servlet学习总结(Request请求与转发,Response响应,Servlet生命周期、体系结构、执行流程等...)


    @Override 是Java中的注解(Annotation),它用于告诉编译器该方法是覆盖(重写)父类中的方法。当我们使用@Override注解时,编译器会检查当前方法是否正确地覆盖了父类中的方法,如果没有覆盖成功,编译器将会报错。

    @Param("参数名") 作用是指定传入SQL语句中的参数名称,使得SQL语句中的占位符(#{参数名})可以获取到正确的参数值。

    1. @Select("select * from tb_user where username=#{username} and password=#{password}")
    2. User select(@Param("username")String username,@Param("password") String password);

    @Select表示将下面定义的方法作为一个查询操作,执行注解中指定的SQL语句。

    @Insert表示往数据库里添加值

    1. @Insert("insert into tb_user value(null,#{username},#{password})")
    2. void Add(User user);

    一、Servlet快速入门

    Servlet是Java提供的一门动态Web资源开发技术

    Servlet是JavaEE规范之一,是一个接口,将来我们需要定义Servlet类来实现Servlet借口,并由web服务器运行Servlet。

    Servlet快速入门:

    1.创建web项目,导入Servlet依赖坐标(scope的值要配置成privided的,在编译和测试环境有效,在运行环境无效,因为war包里自己会有这个jar包,如果再有会冲突)

    1. <dependencies>
    2. <dependency>
    3. <groupId>javax.servlet</groupId>
    4. <artifactId>javax.servlet-api</artifactId>
    5. <version>3.1.0</version>
    6. <scope>provided</scope>
    7. </dependency>
    8. </dependencies>

    2.创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话(注意是输出到控制台,表名这段代码有执行)

    1. public class ServletDemo1 implements Servlet{
    2. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    3. System.out.println("servlet hello world!!");
    4. }
    5. }

    3.配置:在类上使用@WebServlet注解,配置该Servlet的访问路径

    1. @WebServlet("/demo1")
    2. public class ServletDemo1 implements Servlet{}

    4.访问:启动Tomcat,浏览器输入URL访问该Servlet

    http://localhost:8080/web-demo/demo1

    二、Servlet执行流程和生命周期

    2.1 执行流程

    Servlet由谁创建:Servlet由web服务器创建。

    Servlet方法由谁调用:Servlet方法由web服务器调用。

    服务器怎么知道Servlet中一定有service方法:因为Servlet接口中有service方法,当我们自定义Servlet时,必须实现Servlet接口并复写其方法。

    2.2 生命周期

    Servlet对象是由Tomcat自动帮我们创建的。

    Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

    1.加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象。

    2.初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次。

    3.请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。

    4.服务终止:当需要释放内存或者容器关闭时,容器会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。

    获取ServletConfig对象:ServletConfig getServletConfig()。

    获取Servlet信息:String getServletInfo()。

    三、Servlet体系结构

    将来只有service方法比较常用,但总共有5个方法,都需要复写一遍很麻烦,学习体系结构就是为了简化开发。

    我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会继承HttpServlet,doGet方法是对Get请求方式进行处理,doPost方法是对Post请求方式进行处理。

    @webServlet()注解用于配置访问路径。

    action指定的是跳转的目标路径:

    点击提交即会去访问demo4的资源,而且是以post的方式,会在控制台输出post...

    下面的MyHttpServlet就是包自带的HttpServlet的原理,讲的是为什么要有HttpServlet这个接口供我们继承:

    因为doGet和doPost方法将来要被别人复写,因此用protected的修饰符,将来子类可以看到方法。

    service代码如下:

    1. @Override
    2. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    3. //根据请求方式的不同,进行分别的处理
    4. HttpServletRequest request = (HttpServletRequest) req;
    5. //1. 获取请求方式
    6. String method = request.getMethod();
    7. //2. 判断
    8. if("GET".equals(method)){
    9. //get方式的处理逻辑
    10. doGet(req,res);
    11. }else if("POST".equals(method)){
    12. //post方式的处理逻辑
    13. doPost(req,res);
    14. }
    15. }

     将上面的所有代码封装在一个MyHttpServlet中:

    然后让ServletDemo5去继承MyHttpServlet即可,重写其中的doGet和doPost方法:

    1. @WebServlet("/demo5")
    2. public class ServletDemo5 extends MyHttpServlet {
    3. @Override
    4. protected void doGet(ServletRequest req,ServletResponse res){
    5. System.out.println("get。。。.");
    6. }
    7. @Override
    8. protected void doPost(ServletRequest req,ServletResponse res){
    9. System.out.println("post。。。.");
    10. }
    11. }

    四、Servlet urlPattern配置

     urlPattern就是我们servlet对应的访问路径,所以Serblet要想被访问,必须配置其访问路径urlPattern。

    一个Servlet可以配置多个urlPattern:

     urlPattern的配置规则:1.精确匹配。2.目录匹配。3.拓展名匹配。4.任意匹配。

    4.1一个Servlet可以配置多个访问路径

    1. @WebServlet(urlPatterns = {"/demo7","/demo8"})
    2. public class ServletDemo7 extends HttpServlet{
    3. @Override
    4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    5. System.out.println("demo7 get...");
    6. }
    7. @Override
    8. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}
    9. }

    4.2精确匹配

    4.3目录匹配

    4.4拓展名匹配(注意不能加斜杠开头)

    4.5任意匹配

    /:当我们的项目中的Servlet配置了“/”,会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时会走这个Servlet(就访问不了静态页面和静态资源了)

    或者/*意味着配置任何访问路径。

    4.6优先级

    精确路径>目录路径>拓展名路径>/*>/

    五、XML配置方式编写Servlet

    3.0版本之前都是用XML配置方式配置,3.0版本之后才支持注解配置。

    先配置Servlet的全类名(配置类):

    1. <!--Servlet 全类名-->
    2. <servlet>
    3. <servlet-name>demo13</servlet-name>
    4. <servlet-class>com.itheima.web.ServletDemo13</servlet-class>
    5. </servlet>

     然后配置Servlet的访问路径(配置路径):

    1. <!--Servlet 访问路径-->
    2. <servlet-mapping>
    3. <servlet-name>demo13</servlet-name>
    4. <url-pattern>/demo13</url-pattern>
    5. </servlet-mapping>

    六、Request

    6.1 Request的简单介绍

    Request用于获取请求数据。

    request.getParameter("name")的意思:比如url?name=zhangsan,那么会直接返回zhangsan。 

    response.getWriter().write()就是用于返回内容

    1. @WebServlet("/demo3")
    2. public class ServletDemo3 extends HttpServlet {
    3. @Override
    4. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    5. //使用request对象 获取请求数据
    6. String name = request.getParameter("name");//url?name=zhangsan
    7. //使用response对象 设置响应数据
    8. response.setHeader("content-type","text/html;charset=utf-8");
    9. response.getWriter().write("

      "+name+",欢迎您!

      "
      );
    10. }
    11. @Override
    12. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    13. System.out.println("Post...");
    14. }
    15. }

    6.2 Request继承体系

    因为Tomcat需要解析请求数据,封装为request对象,并且创建request对象传递到service方法中,所以Tomcat来定义实现类。

    6.3 Request获取请求数据

    请求行:

    获取请求方式:GET。String getMethod()。

    获取虚拟目录(项目访问路径):/request-demo。String getContextPath()。

    获取URL(统一资源定位符):http://localhost:8080/request-demo/req1。StringBuffer getRequestURL()。

    获取URI(统一资源标识符):/request-demo/req1。String getRequestURI()。

    获取请求参数(GET方式):username=zhangsan&password=123。String getQueryString()。

    请求头:

    获取请求头名称、获取值。String getHeader(String name)。(其中name在这里就是user-agent)

    请求体:

    只有post请求才有请求体,会把表单的参数放到请求体提交。

    采用流的方式,有字节输入流和字符输入流。

    获取字符输入流。BufferedReader getReader()。

    获取字节输入流(比如:图片或者文件)。ServletInputStream getInputStream()。

    下面我们将做一个实验,先创建一个req.html的表单,模拟输入username和password,

    action属性用于指定表单数据提交的目标URL。当用户点击提交按钮时,表单中的数据将被发送到action属性指定的URL。在这个例子中,表单数据将被发送到/request-demo/req1这个URL。

    下面是req.html代码:

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Title</title>
    6. </head>
    7. <body>
    8. <form action="/request-demo/req1"method="post">
    9. <input type="text" name="username">
    10. <input type="password" name="password">
    11. <input type="submit">
    12. </form>
    13. </body>
    14. </html>

    下面是RequestDemo1代码:

    1. @WebServlet("/req1")
    2. public class RequestDemo1 extends HttpServlet {
    3. @Override
    4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    5. //获取post 请求体:请求参数
    6. //1. 获取字符输入流
    7. BufferedReader br = req.getReader();
    8. //2. 读取数据
    9. String line = br.readLine();
    10. System.out.println(line);
    11. }
    12. }

    6.4Request通用方式获取请求参数

    GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样。可以提供一种统一的请求参数的方式。

    Request用Map集合来存储结果,键是用String类型来存储,值使用String数组的形式存储。

    获取所有参数Map集合。Map getParameterMap()。

    1. //1. 获取所有参数的Map集合
    2. Map<String,String[]> map = req.getParameterMap();
    3. for(String key : map.keySet()){
    4. //username:zhangsan
    5. System.out.print(key+":");
    6. //获取值
    7. String[] values = map.get(key);
    8. for(String value:values){
    9. System.out.print(value+" ");
    10. }
    11. }

    根据名称获取参数值(数组)。String[] getParameterValues(String name)。

    1. //2.根据key获取参数值,
    2. String[] hobbies = req.getParameterValues("hobby");
    3. for(String hobby:hobbies){
    4. System.out.println(hobby);
    5. }

    根据键获取参数值(单个值)。String getParameter(String name)。

    1. //3.根据key 获取单个参数值
    2. String username = req.getParameter("username");
    3. String password = req.getParameter("password");
    4. System.out.println(username);
    5. System.out.println(password);

    6.4 Servlet模板

    6.4 Request解决乱码

    先要更改下面的action属性:

    <form action="/request-demo/req4"method="post">

    对于post方法,是通过字符输入流来获取数据,底层默认iso-8859-1所以在读中文数据乱码,所以置需要将编码格式设置为utf-8即可,加入下面这段代码(记得刷新一下Maven):

    1. //1. 解决乱码:POST,getReader()
    2. request.setCharacterEncoding("UTF-8");//设置字符输入流的编码

    对于get方法,通过getQueryString获取参数,没有涉及到流,不能用上面方法解决。浏览器用UTF-8的格式编码发送URL编码,将URL编码发送到Tomcat,因为Tomcat解码时是用ISO-8859-1的格式解码。/ 所以乱码的原因是:编解码使用的字符集不一样。/ URL编码是将字符串按照编码方式转为二进制,一个汉字占3个字节,每个字节转为2个16进制数并在前边加上%。/ 但它们底层的字节都是一样的。/ 解决思路:先将utf-8编码转为字节码,然后再将字节码转化为汉字即可。

     将username字符串重新以utf-8的形式进行URL编码:String encode = URLEncoder.encode(username,"utf-8");

     将username字符串以ISO-8859-1的形式进行URL解码:String decode = URLDecoder.decode(encode,"ISO-8859-1");

     将上面以ISO-8859-1编码的decode字符串转化为字节:byte[] bytes = decode.getBytes("ISO-8859-1");

     将字节码bytes的内容以utf-8的格式重新编码为字符串:new String(bytes,"utf-8");

    1. byte[] bytes = decode.getBytes("ISO-8859-1"); //编码
    2. System.out.println(new String(bytes,"utf-8")); //解码

    下面是正式修改的代码:

    1. byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
    2. username = new String(bytes,StandardCharsets.UTF_8);
    3. System.out.println("解决乱码后:"+username);

    6.5 Request请求转发

    请求转发:一种在服务器内部的资源跳转方式。

    下面代码卸载资源A里,调用getRequestDispatcher()方法,将要跳转的路径作为参数传入,request.getRequestDispatcher("资源B路径").forward(request,response);

    request.getRequestDispatcher("/req6").forward(request,response);

    请求转发资源间共享数据:使用Request对象。

    name相当于给传入的数据命名,方便提取,o是传入的数据,存储数据到request域中:void setAttribute(String name,object o)。

    通过之前的命名name取出o,根据key获取值:Object getAttribute(String name)。

    根据key删除该键值对:void removeAttribute(String name)。

    特点:1.路径不发生变化。2.只能转发到服务器内部的资源。3.一次请求和一次响应。

    将hello字符串数据以msg为代号,存储到request中:request.setAttribute("msg","hello");

    将以msg为代号的数据从request中取出,存储到Object变量msg中:Object msg = request.getAttribute("msg");

    七、Response

    使用response对象来设置响应数据。Response用于获取响应数据。

    7.1 Response设置响应数据功能介绍

    响应行:协议:HTTP/1.1 响应状态码:200 状态码描述:OK

    void setStatus(int sc):设置响应状态码。

    响应头:Content-Type:text/html

    void setHeader(String name,String value):设置响应头键值对。

    响应体:

    PrintWriter getWriter():获取字符输出流。

    ServletOutputStream getOutputStream():获取字节输出流。

    7.2 Response完成重定向

    重定向(Redirect):一种资源跳转方式。

    浏览器请求资源A,资源A响应说:我处理不了,找别人处理(给出响应状态码302),那个人的位置是xxx(响应头location:xxx),浏览器根据位置信息去请求资源B。

    response.setStatus(302);

    response.setHeader("location","资源B的路径");

    下面是一般写法:

    1. //1.设置响应状态码 302
    2. response.setStatus(302);
    3. //2. 设置响应头 Location
    4. response.setHeader("Location","/request-demo/resp2");

    下面是简化写法:

    response.sendRedirect("/request-demo/resp2");

    重定向的特点:1.浏览器地址栏路径发生变化。2.可以重定向到任意位置的资源(服务器内部、外部均可)。3.两次请求,不能在多个资源使用request共享数据。 

    7.3 资源路径问题

    浏览器使用:需要加虚拟目录(项目访问路径)。重定向(路径是给浏览器使用,要加虚拟目录)。

    服务端使用:不需要加虚拟目录。转发(服务器内部使用,不加虚拟目录)。

    动态获取虚拟目录:

    request.getContextPath()可以获取虚拟路径

    1. String contextPath = request.getContextPath();
    2. response.sendRedirect(contextPath+"/resp2");

    7.3 Response响应字符数据

    通过Response对象获取字符输出流:PrintWriter writer = resp.getWriter();

    写数据:writer.write("aaa");

    设置请求头的类型,用html解析:response.setHeader("content-type","text/html");

    1. PrintWriter writer = response.getWriter();
    2. response.setHeader("content-type","text/html");
    3. writer.write("

      aaa

      "
      );

     流不需要主动关闭

    response.setContentType("text/html;charset=utf-8");

    7.4 Response响应字节数据

    通过Response对象获取字符输出流:ServletOutputStream outputStream = resp.getOutputStream();

    写数据:outputStream.write(字节数据);

    下面是读取一张图片的字节码,并将图片显示的常规方法:

    1. //1. 读取文件
    2. FileInputStream fis = new FileInputStream("e://a.jpg");
    3. //2. 获取response字节输出流
    4. ServletOutputStream os = response.getOutputStream();
    5. //3. 完成流的copy
    6. byte[] buff = new byte[1024];
    7. int len = 0;
    8. while((len=fis.read(buff))!=-1){
    9. os.write(buff,0,len);
    10. }
    11. fis.close();

     下面是快速方法,先导入下面依赖:

    1. <dependency>
    2. <groupId>commons-io</groupId>
    3. <artifactId>commons-io</artifactId>
    4. <version>2.6</version>
    5. </dependency>

     代码如下:

    1. //1. 读取文件
    2. FileInputStream fis = new FileInputStream("e://a.jpg");
    3. //2. 获取response字节输出流
    4. ServletOutputStream os = response.getOutputStream();
    5. //将字节流进行输出:
    6. IOUtils.copy(fis,os);
    7. fis.close();

     IOUtils工具类使用:IOUtils.copy(输入流,输出流);

    7.5 案例

    7.5.1 用户登录

    用户登录:流程说明:1.用户填写用户名和密码,提交到LoginServlet。2.在LoginServlet中使用MyBatis查询数据库,验证用户名和密码是否正确。3.如果正确,响应登录成功,如果错误,响应登录失败。

    操作:

    1.复制资料中的静态页面到项目的webapp目录下:

    2.创建db1数据库,创建tb_user表,创建User实体类

     3.导入MyBatis坐标,MySQL驱动坐标

     4.创建mybatis-config.xml核心配置文件,UserMapper.xml映射文件,UserMapper接口

     

    然后通过Navicat查询模块创建表,创建pojo包将User数据类放在下面,导入下面的mybatis和mysql依赖:

    1. <dependency>
    2. <groupId>org.mybatis</groupId>
    3. <artifactId>mybatis</artifactId>
    4. <version>3.5.5</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>mysql</groupId>
    8. <artifactId>mysql-connector-java</artifactId>
    9. <version>5.1.34</version>
    10. </dependency>

    java下的mapper目录下的UserMapper用于书写SQL语句:

    1. public interface UserMapper {
    2. @Select("select * from tb_user where username=#{username} and password=#{password}")
    3. User select(@Param("username")String username,@Param("password") String password);
    4. }

    resources下mapper目录下的UserMapper.xml文件用于与映射UserMapper,应该是告诉Spring有这么一个SQL语句文件:

    1. "1.0" encoding="UTF-8" ?>
    2. mapper
    3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    5. <mapper namespace="com.itheima.mapper.UserMapper"/>

    java下的web目录下的LoginServlet用于接收用户名和密码,然后调用MyBatis完成查询,并且判断成员是否在数据库中输出登录成功或失败与否。

    ps1:getParameter方法接收的参数是html标签中的name属性值:

    1. String username = request.getParameter("aaa");
    2. String password = request.getParameter("bbb");
    7.5.2 用户注册 

     在UserMapper文件下创建查询和新增语句:

    1. @Select("select * from tb_user where username=#{username}")
    2. User selectByUsername(String username);
    3. @Insert("insert into tb_user value(null,#{username},#{password})")
    4. void Add(User user);

     register.html更改表单的action值和method值:

    1. @WebServlet("/registerServlet")
    2. public class RegisterServlet extends HttpServlet {
    3. @Override
    4. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws SecurityException, IOException{
    5. //1.接收用户数据
    6. String username = request.getParameter("username");
    7. String password = request.getParameter("password");
    8. //2.封装用户对象
    9. User user = new User();
    10. user.setUsername(username);
    11. user.setPassword(password);
    12. //3.调用mapper,根据用户名查询用户对象
    13. //2.1 获取SqlSessionFactory对象
    14. String resource = "mybatis-config.xml";
    15. InputStream inputStream = Resources.getResourceAsStream(resource);
    16. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    17. //2.2 获取SqlSession对象
    18. SqlSession sqlSession = sqlSessionFactory.openSession();
    19. //2.3 获取Mapper
    20. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//注意是先创建一个userMapper对象,然后再去调用里面的SQL方法
    21. //2.4调用方法
    22. User u = userMapper.selectByUsername(username); //调用查询方法
    23. //3.判断用户对象是否为Null
    24. if(u == null){
    25. //用户名不存在可以添加用户
    26. userMapper.Add(user); //调用新增方法
    27. //提交事务
    28. sqlSession.commit(); //提交事务
    29. sqlSession.close();
    30. }else{
    31. //用户名存在,给出提示信息
    32. response.setContentType("text/html;charset=utf-8");
    33. response.getWriter().write("用户名已存在");
    34. }
    35. }
    36. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws SecurityException, IOException{
    37. this.doGet(request,response); //注意是Post方法要执行这句
    38. }
    39. }
    7.5.3 存在问题与优化

    第1个问题是下面这段代码写了多遍;第2个问题是SqlSessionFactory是工厂,既然是工厂就不要创建多次了,每个SqlSessionFactory里面都绑定一个连接池,多个工厂就有多个连接池,资源消耗大。

    结局思路:把这几行代码放在一个工具类里面,静态代码块只能执行一次,防止执行多次。

     创建一个java目录下util目录下SqlSessionFactoryUtils文件,写入如下代码:

    1. public class SqlSessionFactoryUtils {
    2. private static SqlSessionFactory sqlSessionFactory;
    3. static //静态代码块会随着类的加载自动执行,且只执行一次
    4. {
    5. try {
    6. String resource = "mybatis-config.xml";
    7. InputStream inputStream = Resources.getResourceAsStream(resource); //需要抛异常因此用try-catch包裹
    8. sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    9. } catch (IOException e) {
    10. throw new RuntimeException(e);
    11. }
    12. }
    13. public static SqlSessionFactory getSqlSessionFactory(){
    14. return sqlSessionFactory;
    15. }
    16. }

    执行原理是,在LoginServlet等类中加载类,会自动加载静态代码块,然后会给全局变量赋值,然后调用方法返回对象。静态代码块已经执行过便不会再执行,会直接return。

    上面这行不能抽取到工具类因为本质是连接数据库,如果公用会产生相互间的影响。

  • 相关阅读:
    零基础学Python之循环语句的使用(手把手带你做牛客网python代码练习题)
    第一章 创建 REST 服务简介
    【Java Web】Kafka,构建TB级异步消息系统
    SQL注入【SQLi-LABS Page-1(Basic Challenges Less1-Less22)】
    Greenplum的并发控制概述
    深度学习-07-反向传播的自动化
    【Android进阶】9、用 RecyclerView 显示列表
    Day10:基础入门-HTTP数据包&Postman构造&请求方法&请求头修改&状态码判断
    小程序面试题:js、vue、uni、小程序的页面传参方式区别
    mysql只保留数字排序
  • 原文地址:https://blog.csdn.net/RuanFun/article/details/132611822