第二阶段(day11)servlet2

1.servlet线程特性

单实例 多线程

不要使用成员变量 成员变量会在多线程间共享 写操作不安全

之前讲servlet生命周期,servlet实例由web服务器自己创建,且只创建一次,故servlet实例只有一个。

为了满足多用户的同时访问,Tomcat服务器必须是多线程的。 多线程用同一个servlet对象(和类变量很类似,共享),是否有线程安全问题?若使用成员变量,会出现线程安全问题。

面试问题:servlet本身是不是线程安全的?

若加了成员变量,且对成员变量有写操作,绝对不安全。

不建立成员变量,也就不共享数据,也不会有线程安全问题。

故使用servlet时,一般不建立成员变量,基于方法做一些操作即可。若非要建成员变量,则要开启多实例模式。

2.servlet域对象

不是在代码层面数据共享,而是在服务器运行过程中,依据请求,响应和用户访问的过程,服务器自己创建几个对象,允许你在对象里传入或取出数据,从而达到数据共享的目的。

1.Servlet三大域对象介绍

1.request域

一个用户可有多个
每次请求产生一个
响应结束后对象失效
常用在页面间数据传递    

2.session域

一个用户一个,每一个浏览器都会产生一个,会话过期或者关闭浏览器对象消失,建议存放一些用户信息,不要把过多的信息存放在session里
​
同一个浏览器 多次访问间 使用同一个session对象(同一个浏览器多次访问间 可以共享数据)
session失效条件    
1.浏览器关闭
2.session超过有效期 有效非活动时间 (两次访问的间隔)
3.session.invalidate() 服务器设置session失效

3.servletContext域 (服务器对象)

全局只有一个,是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放。为了节省空间,提高效率,只存放少量的重要信息
​
服务器启动产生
服务器关闭销毁
全局共享  尽量只读

有效非活动时间指两次访问的间隔。

域对象统一方法:

voidsetAttribute(String key,Object value)以key/value的形式保存对象值
ObjectgetAttribute(String key)通过key获取对象值

2.Servlet三大域对象使用

2.1request域对象的使用

1.新建项目(Project Structrue->Modules->+->new Module.)(弹出的New Module界面,java Enterprise,勾选Web Application,下一步,起个新名字day2_servlet)

2.src目录下新建com.javasm.controller包,该包下新建ServletDemo1类

@WebServlet("/demo1")
public class ServletDemo1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //request的作用
        //1.请求报文相关信息封装在request里,且2.request还可做域对象
        req.setAttribute("mykey","myval");  //放置,这些对象只在服务器生效,浏览器请求过来,自动生效
        //Object mykey = req.getAttribute("mykey");  //根据key取值,默认返object,故根据自己要取东西的类型强转
        String mykey = (String)req.getAttribute("mykey");
        System.out.println(mykey);
​
    }
}

(其它两个域也是get和set方法)

3.配置服务器

(配置过一次,再新建项目时,Tomcat会自动将新建项目部署出来,同时创建处Web服务器)

右上角文本框,Deployment中已出现该项目,不用再添加。在其右边文本框添加/day2,作为模拟的根路径。

4.运行

弹出的页面输入http://localhost:8080/day2/demo1

控制台会出现myval

(这里最初报错:Address localhost:1099 is already in use

解决方案:(1)win+R,打开命令提示符框,输入cmd,进入命令提示符 ​ (2)输入netstat -aon | findstr 1099,找到占用1099端口的进程ID:PID ​ (3)输入taskkill -f -pid PID ​ (4)重启Tomcat )

5.新建ServletDemo2类

@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String mykey = (String)req.getAttribute("mykey");
        System.out.println(mykey);
    }
}

在ServletDemo2里仅仅取。重新部署启动后,在弹出的页面输入http://localhost:8080/day2/demo2,控制台输出null。再次输入http://localhost:8080/day2/demo1,还是myval。因为ServletDemo2的request域里没放东西。

(request域对象,每次请求都会产生一个)

2.2session域对象的使用

同2.1

(session域对象需要创建)

1.在ServletDemo1类:

@WebServlet("/demo1")
public class ServletDemo1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(); //获取session域对象,类型是HttpSession
        session.setAttribute("mykey","myval");
        String mykey = (String)session.getAttribute("mykey");
        System.out.println(mykey);
​
    }
}

2.在ServletDemo2类,仍然是只取:

@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(); //获取session域对象,类型是HttpSession
        String mykey = (String)session.getAttribute("mykey");
        System.out.println(mykey);
    }
}

3.启动服务器

先访问demo2,http://localhost:8080/day2/demo2,结果是null。(没地方放值,自然也就没值)

再访问demo1,http://localhost:8080/day2/demo1,结果为myval。

再次访问demo2,http://localhost:8080/day2/demo2,结果为myval。

(session域对象可在多次访问间共享数据,一旦有一个类将数据放入,其他类就可读取到)

4.关闭浏览器,再重新打开浏览器,服务器会重新创建session域对象,之前放入的东西没有了

访问demo2,http://localhost:8080/day2/demo2,结果是null。

(每一个浏览器的请求都可以使服务器创建一个session域对象,只要session域对象生效,同一个浏览器多次访问间,都可使用同一个session域对象,数据可共享。)

2.3servletContext域对象的使用

同2.1

1.在ServletDemo1类:

@WebServlet("/demo1")
public class ServletDemo1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取servletContext对象,可用当前实例获取,也可用请求获取
        ServletContext servletContext = req.getServletContext();  //servletContext就是服务器对象
        servletContext.setAttribute("mykey","myval");
        String mykey = (String)servletContext.getAttribute("mykey");
        System.out.println(mykey);
    }
}

2.在ServletDemo2类,仍然是只取:

@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        String mykey = (String)servletContext.getAttribute("mykey");
        System.out.println(mykey);
    }
}

3.启动服务器

先访问demo2,http://localhost:8080/day2/demo2,结果是null。(没人放过东西)

再访问demo1,http://localhost:8080/day2/demo1,结果为myval。

再次访问demo2,http://localhost:8080/day2/demo2,结果为myval。

4.关闭浏览器,再重新打开浏览器

访问demo2,http://localhost:8080/day2/demo2,结果为myval。

5.换个浏览器

访问demo2,http://localhost:8080/day2/demo2,结果为myval。

(一旦生成servletContext域对象,所有请求过来都可以访问到。是一个全局共享的数据)

3.Servlet中常用类和方法

3.1 ServeltContext接口

(常表示服务器对象。最主要的功能是当域对象使用,有setAttribute和getAttribute方法。getInitParameter方法拿初始化参数,昨天讲过)

方法说明
public void setAttribute(String name,Object object)绑定一个java对象和一个属性名,并存放到ServletContext中,参数name指定属性名,参数Object表示共享数据
pulbic Object getAttribute(String name)根据参数给定的属性名,返回一个Object类型的对象
public String getContextpath()返回当前web应用的URL入口
public String getInitParameter(String name)返回web应用范围内匹配的初始化参数值。在web.xml中,<web-app>元素中的<context-param>元素表示应用范围内的初始化参数

看一下getContextpath()方法:

1.新建ServletDemo3类:

@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        System.out.println(servletContext.getContextPath());
    }
}

2.重新部署,运行

访问demo3,http://localhost:8080/day2/demo3,结果是/day2。

(通过该方法可拼接一些项目要访问的地址)

3.2ServletConfig接口

方法说明
public String getInitParameter(String path)获取web.xml中指定Servlet的初始化参数值
public ServletContext getServletContext()获取ServletContext实例
public String getServletName()获取当前Servlet的名称

getInitParameter()方法是直接在servlet里调用,对应servlet里配置的初始化参数。

@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.getInitParameter("xxx");  //获取servlet上配置的初始化参数
        this.getServletContext();  //获取ServletContext对象的第二种方式
        this.getServletName();  //获取servlet配置的name
        
    }
}

对于String getServletName(),获取的是web.xml里的name

<servlet>
        <servlet-name>servlet3</servlet-name>   <!--获取的是servlet3-->
        <servlet-class>com.javasm.controller.ServletDemo3</servlet-class>
        <init-param>
            <param-name>mykey</param-name>
            <param-value>abc123</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

该接口方法不重要,了解即可。

3.3HttpRequest(很常用)

方法说明
public String getParameter(String name)获取页面提交指定名称的参数值
public String[] getParameterValues(String name)获取页面提交相同名称参数的数组值
public void setCharacterEncoding("UTF-8")设置字符编码格式
public Map getParameterMap()返回一个保存了请求的所有参数和值的Map对象
public void setAttribute(String name,Object obj)向request范围内设置属性,参数name为属性名,obj为属性值
public Object getAttribute(String name)返回一个请求中属性名为name的属性值
public String getContextPath()返回当前Web项目的根路径

(HttpRequest就是使用的request对象,每个请求里都会用到它,大部分方法都是针对请求报文数据的封装)

1.getParameter(),通过key取值。

@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String mykey = req.getParameter("mykey");
        System.out.println(mykey);
    }
}

若浏览器请求时不传参数,即http://localhost:8080/day2/demo3,读到的结果是null。

若浏览器请求时传参数,http://localhost:8080/day2/demo3?mykey=abc123,读到的结果是abc123。(传数据实际是客户端和服务端通过流做数据传输,故服务端拿到数据是没有对象的。拿到数据封装成对象是另一码事。数据本身只有字符串,故req.getParameter返回的数据类型都是字符串)

还有一种情况,参数有key,但没给value,如:http://localhost:8080/day2/demo3?mykey=&mykey2=xxx

结果是空字符串。

故,传值时空值有两种情况:一种是key不存在,后台拿到的是null;另一种是key存在,但没有值,后台拿到的是空字符串。

2.getParameterValues()

也是获取页面传过来的参数,获取同名的参数。

多选框就是用的相同的name属性,传值时就可能是http://localhost:8080/day2/demo3?hobby=1&hobby=3

 @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String[] hobbies = req.getParameterValues("hobby");
        System.out.println(hobbies[0]);
        System.out.println(hobbies[1]);
    }

结果是1 3

(该方法一般对应checkbox和多选select)

3.getParameterMap()

参数以键值对存在,键值对间用&隔开,服务器其实就是用&分割,将键值对存在Map集合里。

注:Map<String, String[]>,key是字符串,value是字符串数组

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Map<String, String[]> parameterMap = req.getParameterMap();
        System.out.println(parameterMap.get("mykey")[0]);
        System.out.println(parameterMap.get("hobby")[0]);
    }

运行后,浏览器输入http://localhost:8080/day2/demo3?mykey=abc123&hobby=1&hobby=3

结果是abc123 1

(1的getParameter()和2的getParameterValues()都是基于getParameterMap()又做了一次封装)

4.getInputStream()

获得的不是完整的请求报文,而是请求报文正文部分

ServletInputStream inputStream = req.getInputStream();

拿到一个输入流。request已经将请求报文解析过,按理说流只能读一次,这是干什么?

若使用post方式传数据,参数在请求正文部分,它可以将请求正文部分的内容不经过封装,原原本本地拿出来。

eg:

1.在web目录中新建myPage.xml,还是做一个注册页面,引入bootstrap,过程和昨天一样。这里将form里的action和method修改:

<form action="/day2/demo3" method="post" class="form-horizontal">

2.在后端,也就是ServletDemo3类中,想拿到浏览器传过来的原始数据处理:

@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        String s = br.readLine();
        System.out.println(s);
    }
}

3.重新部署,运行

弹出的浏览器输入http://localhost:8080/day2/myPage.html

输入用户名jack和密码123456,点击提交

结果:后台输出username=jack&password=123456

(注意:流只能读一次,用其他方法读了,这里就读不了了)

5.setCharacterEncoding()

昨天已讲,自动封装成Map的过程中,设置字符编码

处理请求乱码,需要在获取参数前执行:

req.setCharacterEncoding("utf-8");

6.当成域对象时使用的两个方法

setAttribute()

getAttribute()

7.getContextPath()

讲到ServeltContext接口时有一个getContextpath()

他们获取的东西一样

8.Request对象中封装了很多获取跟http相关的路径的方法

request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";

(request.getScheme()获得协议

request.getServerName()获得服务器名称

request.getServerPort()获得服务器端口

request.getContextPath()获得请求入口的路径)

结果:http://localhost:8080/day2 当前项目的根的绝对路径

request.getRemoteAddr() 客户端ip地址

(看一下谁来访问你)

request.getRealPath("/") 获取代码在Tomact里运行的真实目录

(该方法最初是放在request里,现在已过时。最终放在了ServletContext里)

 @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        System.out.println( servletContext.getRealPath("/"));
    }

弹出的浏览器输入http://localhost:8080/day2/demo3

运行后的结果:E:\workspace\two\javaEE\out\artifacts\day2_servlet_war_exploded\

(在服务器里做本地IO时会用到该路径)

request.getServletPath() 获得配置的当前servlet的访问路径

request.getRequestURI(); 获得请求路径

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getServletPath());
        System.out.println(req.getRequestURI());
        System.out.println(req.getRequestURL());
    }

弹出的浏览器输入http://localhost:8080/day2/demo3

运行后的结果:/demo3 ​ /day2/demo3 ​ http://localhost:8080/day2/demo3

(URI统一资源标识符,概念上的地址

URL统一资源定位符。实际地址

URL地址一定能访问到,URI一般是地址的一部分,用来指代当前要访问的资源)

3.4HttpResponse

方法说明
Public PrintWriter getWriter()获取响应的打印输出流对象
Public void setContentType("text/html;charset=UTF-8")设置响应内容的类型和字符编码格式

主要和输出相关。

setContentType()

使用之后,响应头会多加一句内容。

text/html表示返回的数据是什么格式。通过URL访问的资源除了html页面,还有图片,js,css等,不同资源,这个部分不同。虽然浏览器会自动解析,做适配,但如果把所有返回的数据都写成text/html是不行的。这里是返回文本格式的html页面格式
charset=utf-8告诉浏览器返回的数据用什么格式去解析。可用它处理响应的中文乱码。注意:若设置的是GBK,返回的数据也是GBK,这里也要写成GBK。(右下角可看到项目默认的编码格式)
​

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        //PrintWriter是封装后的输出流,之后用起来会很方便.
        PrintWriter writer = resp.getWriter();
        //writer.print()会包含各种类型,主要用它输出页面信息
        //也保留有原始的write()方法   writer.write();
        writer.print("xxxx");
        //流用完要刷新和关闭
        writer.flush();     //把流数据清空,看还有没有数据没有发出,没发的都推过去
        writer.close();     //close时还会自动做一次推流
    }

3.5HttpSession

1.session常用API

方法说明
setAttribute(String key,Object value)以key/value的形式保存对象值
getAttribute(String key)通过key获取对象值
getMaxInactiveInterval()获取session的有效非活动时间,以秒为单位
getId()获取session对象的编号
invalidate()设置session对象失效

session作为域对象使用的两个方法:setAttribute getAttribute

@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        String id = session.getId();
        System.out.println(id);
    }
}

重新部署,运行

弹出的浏览器输入 http://localhost:8080/day2/demo4

结果:00A5551ECBD2E66972F3430C363FAB0D

(当前使用的浏览器使用的session编号)

换个浏览器,会创建另一个单独的session对象,产生另一个session编号。

getMaxInactiveInterval()

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
​
        //设置最大有效非活动时间
        session.setMaxInactiveInterval(5);   //单位是秒
        //若两次访问时间超过5秒,服务器会重新创建session对象
        
        //读取最大有效非活动时间
         System.out.println(session.getMaxInactiveInterval());
        
    }

Tomcat也可通过配置文件修改最大有效非活动时间,默认值是30分钟

invalidate()

服务器可主动设置session失效

 session.invalidate();

会使当前session对象直接失效,现在再次访问,结果:

每次访问的都会创建新的session对象,session编号每次都不同。

总结session失效的三种办法:

1.关闭浏览器

2.超过有效时间(再次访问的间隔)

3.服务器主动设置session失效

在session生效的过程中,同一个用户(浏览器)多次访问间,可共享数据。

2.session常用场景:

1.数据共享

一个servlet里放入信息,同一个用户多次访问可访问到,其他用户不能访问到。不同用户间有隔离性。

前面的例子有。

2.同一个请求地址展示不同的界面

有如下四个servlet,分别是订单/用户/商品/登录服务。前两个必须登录后才能访问,可加逻辑判断。通过session里是否放入指定的值判断是否登陆过。

代码如下:

1.新建controller2包

2.在该包下新建LoginServlet类,读取用户名密码,判断用户名密码是否正确(这里先不连接数据库)。都正确则登陆成功,放session标记。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
​
        //假设正确的用户名是admin,正确的密码是abc123
        if ("admin".equals(username)&&"abc123".equals(password)){
            HttpSession session = req.getSession();  //放session前要先创建session对象
            session.setAttribute("loginUser",username);  //放session标记,这里值不重要,主要看是否有key
            System.out.println("登录成功");
        }
    }
}

3.新建UserServlet类,象征性写一下用户服务的代码

@WebServlet("/user")
public class UserServlet  extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行用户服务的代码");
    }
}

4.新建OrderServlet类

@WebServlet("/order")
public class OrderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行订单服务代码");
    }
}

5.为了使订单服务在登录成功后才能访问,增加逻辑判断

@WebServlet("/order")
public class OrderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
       
        if ("登陆过"){
            System.out.println("执行订单服务代码");
            writer.print("显示订单列表");
        }else{
            System.out.println("没有登陆过");
            writer.print("对不起 请先登录");
        }
        
        writer.flush();
        writer.close();
    }
}

这里直接将响应展示到页面上。

如何表示是否登录成功:

@WebServlet("/order")
public class OrderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
​
        HttpSession session = req.getSession();
        //Object loginUser = session.getAttribute("loginUser");
        String loginUser = (String)session.getAttribute("loginUser");
​
        if (loginUser!=null){
            System.out.println("执行订单服务代码");
            writer.print("显示订单列表");
        }else{
            System.out.println("没有登陆过");
            writer.print("对不起 请先登录");
        }
​
        writer.flush();
        writer.close();
    }
}

6.重新部署,运行

弹出的页面输入 http://localhost:8080/day2/order

页面直接显示 对不起 请先登录

输入http://localhost:8080/day2/login?username=admin&password=abc123

控制台显示 登录成功

再次访问 http://localhost:8080/day2/ord

页面显示 显示订单列表

换一个浏览器,输入 http://localhost:8080/day2/order

页面直接显示 对不起 请先登录

(不同用户拥有不同的session)

7.完善登录界面

还是用bootstrap(复制三个文件夹到web包,新建loginPage.html在web包下)

代码和昨天的差不多。

loginPage.html想做起始页面有两个方法:

1.在idea里配置

点击右上角文本框,选择Edit .....

在弹出的Run/Debug Configurations界面,在Tomcat 8.5.34选项,将URL对应的文本框中http://localhost:8080/day2/改为http://localhost:8080/day2/loginPage.html

2.在项目里设置

在web项目核心配置文件web.xml中,增加欢迎页面

    <welcome-file-list>
        <welcome-file>loginPage.html</welcome-file>
    </welcome-file-list>

此代码放在web-app标签里。

<?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">
    <welcome-file-list>
        <welcome-file>loginPage.html</welcome-file>
    </welcome-file-list>
</web-app>

loginPage.html最终的代码为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
    <script src="js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script>
    <script src="./js/bootstrap.js" type="text/javascript" charset="ytf-8"></script>
    <style>
        .mycls{
            width:180px;
            height:285px;
            border:1px solid gray;
            border-radius:10px;
            margin:200px auto;
            padding:50px;
        }
    </style>
</head>
<body>
    <div class="mycls">
        <h1>登录</h1>
        <hr/>
        <form action="/day2/login" method="post" class="form-horizontal">
            <div class="form-group">
                <label for="username" class="col-sm-2 control-label">用户名</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="username" name="username" placeholder="username">
                </div>
            </div>
            <div class="form-group">
                <label for="password" class="col-sm-2 control-label">密码</label>
                <div class="col-sm-10">
                    <input type="password" class="form-control" id="password" name="password" placeholder="Password">
                </div>
            </div>
            <hr/>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="submit" class="btn btn-default">登录</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

8.检查

将LoginServlet类修改

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
​
        System.out.println(username);
        System.out.println(password);
​
        /*//假设正确的用户名是admin,正确的密码是abc123
        if ("admin".equals(username)&&"abc123".equals(password)){
            HttpSession session = req.getSession();  //放session前要先创建session对象
            session.setAttribute("loginUser",username);  //放session标记,这里值不重要,主要看是否有key
            System.out.println("登录成功");
        }*/
    }

重新部署,运行

1.F12打开开发者工具。在页面输入用户名jack,密码abc123,看一下开发者工具中传输的参数是否为name=jack&&password=abc123。没问题则说明可以正常发出

2.在idea控制台看是否有jack和abc123。能接收到,则说明连通成功。

9.数据库效验

登录服务拿到用户名和密码,需要和数据库信息效验

①.打开Navicat,新建test库,在该库新建tb_users表

②.与数据库建立连接

先找jar包:mysql-connector-java-8.0.27.jar

Tomcat指定:在使用web项目时,jar包必须放在WEB-INF的lib目录里,它就从这里找。故先在WEB-INF新建lib目录,再将jar包粘贴进来

Tomcat也会在自己的运行环境找(就是安装Tomcat的地方。apache-tomcat-8.5.34-->lib)。两个地方都找不到,Tomcat会告知这个包不存在。

加载该jar包(目的是让idea找到该jar包),点击右上角Project Structure中,弹出的界面点击右边的加号。将放在web目录下的jar包加进来。

以上,不管idea还是Tomcat都可以找到该jar包了。

同理,将lombok.jar以同样方式加进来。

第一阶段将数据库连接池和DBUtil都用起来了,第三阶段会学数据库方面的框架,后面不用DBUtil,这里复习一下jdbc,也不用DBUtil。

第二阶段有三层结构:

控制层:放servlet代码,负责请求的接收,代码的调用,响应的反馈;

业务逻辑层:service层,写业务场景(业务逻辑)。场景主要有:

1.数据库出来的数据和要显示的格式不匹配,需要转换

2.数据库出来的数据,在数值或数据上,需要计算(比如加前后缀,加减操作等)

数据持久层:dao层(数据持久指内存的数据存到硬盘,数据库就是干这个事的),基本的增删改查操作。(与数据库交互,写入是添加,修改,删除;读取是查询)

③.在javasm包下新建dao包

该包下创建LoginDao接口。需要做的是:传入用户名,密码,返回用户对象。

public interface LoginDao {
    //通过用户名和密码寻找用户
    Users getUser(String username, String userpwd);
}

④.在javasm包下新建entity包,用来封装用户对象。

在该包下新建Users类。

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Users {
    private Integer userId;
    private String userName;
    private String userPwd;
    private String userPhone;
}

⑤.为了使用方便,通常会把数据封装成对象去传。对于③的username和password属于Users对象。故控制层通常接收到请求后,会将请求的参数(都是字符串)封装成对象。

在LoginServlet类中:

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
​
        Users inserUser = new Users(username,password);
    }

相应的在Users类中增加有参构造:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Users {
    private Integer userId;
    private String userName;
    private String userPwd;
    private String userPhone;
​
    public Users(String userName, String userPwd) {
        this.userName = userName;
        this.userPwd = userPwd;
    }
}

将LoginDao接口相应也修改:

public interface LoginDao {
    //通过用户名和密码寻找用户
    Users getUser(Users insertUser);
}

⑥.在dao包下新建impl包,在imp包下新建LoginDaoImpl类:

接下来与jdbc会有关,获得驱动,获得连接对象.....这些都是模式性的代码,故在Javasm包下新建util包,包下新建DBHelper类。同时在src包下新建配置文件jdbc.properties。

## \u914D\u7F6E\u4FE1\u606F
jdbc.user=root
jdbc.pass=root
jdbc.url=jdbc:mysql://localhost:3306/test1?useSSL=true&characterEncoding=utf-8
jdbc.driver=com.mysql.jdbc.Driver

DBHelper类:

public class DBHelper {
    static String username;
    static String pwd;
    static String url;
    static String drivername;
    
    static {
        Properties p = new Properties();
        try {
            //程序运行时 不一定在你的工程目录的编译目录下
            p.load(DBHelper.class.getResourceAsStream("/jdbc.properties"));
            username = p.getProperty("jdbc.user");
            pwd = p.getProperty("jdbc.pass");
            url = p.getProperty("jdbc.url");
            drivername = p.getProperty("jdbc.driver");
            Class.forName(drivername);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static Connection getConn() {
        Connection con = null;
        try {
            con = DriverManager.getConnection(url, username, pwd);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }
    
    public static void CloseConn(Connection conn,Statement stat,PreparedStatement psta,ResultSet rs){
            try {
                if(stat!=null)stat.close();
                if(psta!=null)psta.close();
                if(rs!=null)rs.close();
                if(conn!=null)conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
    }
}

回到LoginDaoImpl类:

public class LoginDaoImpl implements LoginDao {
    @Override
    public Users getUser(Users insertUser) {
        Connection conn = DBHelper.getConn();
        String sql = "";
        PreparedStatement preparedStatement = null;
        try {
            preparedStatement = conn.prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }
}

发现关键还是写对sql,故为了写对sql,无论哪个阶段,都要在Navicat的查询中写需要的sql语句,从而保证sql语句的正确性。

这里是带条件的查询,查用户名和密码,在Navicat的查询中新建查询,写出相应的sql:

结果是能查出来,说明该sql语句没问题。

(一般公司不建议用*,而是把需要用到的字段清楚地写出来。一般公司的表的字段会很多,而且表与表之间的关系也很复杂)

最终的sql语句:

select tu.user_id,tu.user_name,tu.user_pwd,tu.user_phone from tb_users tu where tu.user_name = 'jack' and tu.user_pwd = 'abc123'

回到LoginDaoImpl类,将sql填入,用户名和密码处用占位符:

String sql = "select tu.user_id,tu.user_name,tu.user_pwd,tu.user_phone from tb_users tu where tu.user_name = ? and tu.user_pwd = ?";

使用数据库时有prepareStatement预编译对象和Statement非预编译对象。区别就是:预编译对象在参数上可以用?去占位。用?占位的主要好处是防止注入攻击。(sql注入可百度)

完整的LoginDaoImpl类代码:

public class LoginDaoImpl implements LoginDao {
    @Override
    public Users getUser(Users insertUser) {
        Connection conn = DBHelper.getConn();
        String sql = "select tu.user_id,tu.user_name,tu.user_pwd,tu.user_phone from tb_users tu where tu.user_name = ? and tu.user_pwd = ?";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        Users loginUser = null;
        try {
            preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setString(1,insertUser.getUserName());
            preparedStatement.setString(2,insertUser.getUserPwd());
​
            resultSet = preparedStatement.executeQuery();
            if(resultSet.next()){
                int userId = resultSet.getInt("user_id");
                String userName = resultSet.getString("user_name");
                String userPsw = resultSet.getString("user_pwd");
                String userPhone = resultSet.getString("user_phone");
                loginUser = new Users(userId,userName,userPsw,userPhone);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            DBHelper.CloseConn(conn,null,preparedStatement,resultSet);
        }
​
        return loginUser;
    }
}

⑦.建一个测试类

开始项目前做的本地测试,将有问题的地方修改。

在javasm包下新建test包,该包下新建Mytest类:

public class Mytest {
    public static void main(String[] args) {
        LoginDao ld = new LoginDaoImpl();
        Users user = ld.getUser(new Users("jack", "abc123"));
        System.out.println(user);
    }
}

(运行出现问题,可能没勾选:File-->Settings-->Bulid,Execution,Deployment-->Complier-->Annotation Procession。选择day2_servlet,右上角勾选)

运行结果:控制台打印输出:Users(userId=1, userName=jack, userPwd=abc123, userPhone=14434343434)

⑧.dao层与service层连接

在javasm包下新建service包,新建LoginService接口:

public interface LoginService {
     Users getUsers(Users insertUser);
}

在service包下新建impl包,新建LoginServiceImpl类:

public class LoginServiceImpl implements LoginService {
    @Override
    public Users getUsers(Users insertUser) {
        LoginDao ld = new LoginDaoImpl();
        Users user = ld.getUser(insertUser);
        return user;
    }
}

这里发现service没什么业务逻辑,此时service层充当了通道。

也可强行添加业务逻辑:

public class LoginServiceImpl implements LoginService {
    @Override
    public Users getUsers(Users insertUser) {
        LoginDao ld = new LoginDaoImpl();
        Users user = ld.getUser(insertUser);
        if (user!=null){
            //名字前加了个亲爱的
            user.setUserName("my Dear!!!"+user.getUserName());
        }
        return user;
    }
}

⑨.与控制层的连接

控制层要做的事:1.接收参数,封装对象。前面已完成

2.调用业务逻辑

3.3.根据业务逻辑 层返回的Users对象决定返回什么页面

在LoginServlet类中:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收参数,封装对象。
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        Users inserUser = new Users(username,password);
​
        //2.调用业务逻辑
        LoginService ls = new LoginServiceImpl();
        Users user = ls.getUser(inserUser);  //调用service层,传入参数,并返回用户对象
​
        //3.根据Users对象决定返回什么页面
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        //这里查用户名和密码,若返回的Users对象为空,说明没查到
        if (user!=null){   //登录成功
            writer.print("恭喜你 登录成功");
        }else {  //用户名或密码错误
            writer.print("对不起 用户名或者密码错误");
        }
        //注:文案上不要用太生硬的词,比如:操作非法,输入非法等。公司里有专门做文案的东西,若没有参考京东淘宝的文案
        writer.flush();
        writer.close();
    }
}

⑩.测试

现在用服务器去运行,右上角文本框选择Tomcat 8.5.34,重新部署,运行

弹出登录页面,输入jack abc123

页面会显示 恭喜你 登录成功

这里输入中文有问题,在LoginServlet类重写方法的最上面加:

req.setCharacterEncoding("utf-8");

(有啥问题,自己一定要测出来。被专门的测试人员测出来就是她涨钱,你扣钱)

10.登录成功执行用户服务和订单服务

在LoginServlet类中登录成功,改成如下代码:

if (user!=null){   //登录成功
            HttpSession session = req.getSession();
            session.setAttribute("loginUser",inserUser);
            writer.print("恭喜你 登录成功 请使用服务<br/><a href='user'>用户服务</a><a href='order'>订单服务</a>");
        }else {  //用户名或密码错误
            writer.print("用户名或者密码错误 请重新<a href='/day2/loginPage.html'> 登录</a>");
        }

相应的OrderServlet类中:

Users loginUser = (Users)session.getAttribute("loginUser");

可用学过的知识把相对路径拼出来:

if (user!=null){   //登录成功
            HttpSession session = req.getSession();
            session.setAttribute("loginUser",user);
            writer.print("恭喜你 登录成功 请使用服务<br/><a href='"+req.getContextPath()+"/user'>用户服务</a><a href='"+req.getContextPath()+"/order'>订单服务</a>");
        }else {  //用户名或密码错误
            writer.print("用户名或者密码错误 请重新<a href='"+req.getContextPath()+"/loginPage.html'> 登录</a>");
        }

11.对用户服务做一些功能,重新来一次以上流程

①.完善UserServlet

@WebServlet("/user")
public class UserServlet  extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        Users loginUser = (Users) session.getAttribute("loginUser");
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        if(loginUser!=null){
            writer.print("显示用户列表");
        }else{
            writer.print("对不起 请先<a href='"+req.getContextPath()+"/loginPage.html'> 登录</a>");
        }
        writer.flush();
        writer.close();
    }
}

(下面做显示用户列表的操作)

②.dao包新建UserDao接口

public interface UserDao {
    //查询用户列表,返回的数据需要List集合
    List<Users> getAllUser();
}

③.dao.impl包下新建UserDaoImpl类:

该方法类似于登录

public class UserDaoImpl implements UserDao {
    @Override
    public List<Users> getAllUser() {
        Connection conn = DBHelper.getConn();
        String sql = "select tu.user_id,tu.user_name,tu.user_pwd,tu.user_phone from tb_users tu ";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<Users> listuser = new ArrayList<Users>();
        try {
            preparedStatement = conn.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                Integer userId = resultSet.getInt("user_id");
                String userName = resultSet.getString("user_name");
                String userPsw = resultSet.getString("user_pwd");
                String userPhone = resultSet.getString("user_phone");
                Users loginUser = new Users(userId,userName,userPsw,userPhone);
                listuser.add(loginUser);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            DBHelper.CloseConn(conn,null,preparedStatement,resultSet);
        }
​
        return listuser;
    }
}

④.service包新建UserService接口:

public interface UserService {
    List<Users> getAllUser();
}

⑤.service.impl包下新建UserServiceImpl类:

public class UserServiceImpl implements UserService {
    @Override
    public List<Users> getAllUser() {
        UserDao ud = new UserDaoImpl();
        List<Users> allUser = ud.getAllUser();
        return allUser;
    }
}

⑥.测试,在Mytest类中:

public class Mytest {
    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        System.out.println(us.getAllUser());
    }
}

结果:[Users(userId=1, userName=jack, userPwd=abc123, userPhone=14434343434), Users(userId=2, userName=rose, userPwd=abc123, userPhone=13343454544), Users(userId=3, userName=小明, userPwd=abc123, userPhone=15545454444), Users(userId=4, userName=小白, userPwd=abc123, userPhone=14567654454), Users(userId=5, userName=小亮, userPwd=abc123, userPhone=16656565656)]

没有问题

⑦.回到UserServlet类中:

调用service层:

​
@WebServlet("/user")
public class UserServlet  extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        Users loginUser = (Users) session.getAttribute("loginUser");
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        if(loginUser!=null){
            writer.print("显示用户列表");
            //调用service层
            UserService us = new UserServiceImpl();
            List<Users> allUser = us.getAllUser();
            //查到用户列表,在页面应该用table展示
            writer.print("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/bootstrap.css\">");
            writer.print("<table class='table'>");
            writer.print("<tr>");
            writer.print("<th>用户编号</th><th>用户名</th><th>密码</th><th>电话</th>");
            writer.print("</tr>");
            for (Users user:allUser){
                writer.print("<tr>");
                writer.print("<td>"+user.getUserId()+"</td>");
                writer.print("<td>"+user.getUserName()+"</td>");
                writer.print("<td>"+user.getUserPwd()+"</td>");
                writer.print("<td>"+user.getUserPhone()+"</td>");
                writer.print("</tr>");
            }
            writer.print("</table>");
        }else{
            writer.print("对不起 请先<a href='"+req.getContextPath()+"/loginPage.html'> 登录</a>");
        }
        writer.flush();
        writer.close();
    }
}
​

运行,输入jack abc123,结果页面输出:

显示用户列表

用户编号用户名密码电话
1jackabc12314434343434
2roseabc12313343454544
3小明abc12315545454444
4小白abc12314567654454
5小亮abc12316656565656

⑧.美化table

bootstrap已经引入,将table标签引入

在⑦的基础上加一句,并在table标签将class属性设置为table

 //查到用户列表,在页面应该用table展示
            writer.print("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/bootstrap.css\">");
            writer.print("<table class='table'>");

⑨.总结

和后台没什么关系的页面,可使用静态页面,即在web包下新建类似myPage.html的文件。

和后台有关系的页面,即有数据关联的页面,可用后台servlet去画页面。

请求到达servlet,servlet做一些数据处理,剩下就是显示的逻辑,控制给用户展示什么样的页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值