美文网首页技术干货
Java Servlet,JSP,Filter,Listener

Java Servlet,JSP,Filter,Listener

作者: 两分与桥 | 来源:发表于2018-08-18 11:03 被阅读33次

    一、WEB项目的演变

    1.发展规律
       - 由单机向网络发展
       - 由CS向BS发展
       
    2.CS和BS的区别
       - CS
       -- Client Server
       -- 客户端服务器程序
       -- 客户端需要单独开发,用户需要单独下载并安装
       
       - BS
       -- 浏览器服务器程序
       -- 客户端不用单独开发,用户不用单独安装
    

    二、Servlet介绍

    1.服务器如何保存并返回一个网页?
        - 静态网页
        -- 无论谁看其内容都一样
        -- 服务器直接存储HTMl,直接返回HTML
        
        - 动态网页
        -- 不同的人看到的内容有差异
        -- 服务器保存一个组件,动态给每个用户动态生产网页
        -- 在Java语言中这个组件就是Servlet
        -- 组件:满足规范的对象
        
    2.Servlet的特点
        -- 是服务器端的组件
        -- 满足sun的规范
        -- 可以动态拼接资源(HTML/IMG等)
        -- 术语:处理HTTP协议
        
    3.什么是Servlet?
    -- 是sun推出的用于在服务器端处理HTTP协议的组件
    

    三、服务器

    1.名称
        - Java服务器
        - WEB服务器
        - Java WEB服务器
        - Servlet容器
        
    2.本质
        - 是一个软件
        - 它和浏览器是平级的关系
    
    3.举例
        - Tomcat(Apache)
        - JBoss
        - WebLogic
        - WebSphere
    

    四、Tomcat的使用方式

    - 绿色版软件,解压缩(释放)可用
    
    1.单独使用(项目上线时)
        -- 配置好JAVA_HOME
    
    2.通过Eclipse调用(开发时)
    
    
    使用:
    修改Tomcat端口
        - 打开server.xml,在65行修改tomcat的端口,修改后重启
    

    五、Servlet开发步骤

    1.创建WEB项目
        - 必须具备标准的WEB目录
        - /webapp/WEB-INF/web.xml
        - eclipse 右击出现Generate Deployment Descriptor Stub自动生成
    
    2.导入jar包
        -- 使用maven搜索javaee
        -- 在结果中选择javaee-api
        
        -- 使用Tomcat自带的包
        -- 选择项目,右键点击properties
        -- 弹出框里在左侧选择Targeted Runtimes
        -- 在右侧勾选Apache Tomcat
        -- Apply
    
    3.开发Servlet
        - 编写Servlet
        -- 创建package,创建一个类,名为XXXServlet
        -- 继承HttpServlet,从而间接实现了
        
        - 配置Servlet
        -- 先声明类,并给它取别名--ts
        -- 再通过别名引用此类,
        
        部署(拷贝)
        -- 在Servers视图下,选择tomcat7
        -- 右键点击Add and Remove
        -- 在弹出框内将左边的待部署项目移动到右侧
        -- 启动tomcat即可
        
        
        访问
        -- 格式:项目名/别名
        -- http:localhost:8080/servlet1/ts
    

    web.xml 文件添加的内容

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>web.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/ts</url-pattern>
    </servlet-mapping>
    

    项目中的TestServlet类

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Random;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class TestServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            res.setContentType("text/html");
            PrintWriter out = res.getWriter();
            Random rand = new Random();
            int num = rand.nextInt(100);
            out.println("<h1>"+ num +"</h1>");
            out.close();
        }
    }
    

    效果是在访问中可以看到返回一个随机数,访问路径就是 项目名/别名


    2.png

    获取请求消息头数据,迭代遍历取值

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;
    import java.util.Random;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class TestServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            
            System.out.println("请求方式:" + req.getMethod());
            System.out.println("请求路径:" + req.getServletPath());
            System.out.println("协议类型:" + req.getProtocol());
            
             * 消息头(键值对)
             * Enumeration是老版本的迭代器,作用及用法和Iterator类似
            Enumeration<String> e = req.getHeaderNames();
            while(e.hasMoreElements()) {
                String key = e.nextElement();
                String value = req.getHeader(key);
                System.out.println(key + " : " + value);
            }
            
             * 消息头中的大部分数据由服务器填充,但返回的内容格式需要自己指定
            
            res.setContentType("text/html");
            PrintWriter out = res.getWriter();
            Random rand = new Random();
            int num = rand.nextInt(100);
            out.println("<h1>"+ num +"</h1>");
            out.close();
        }
    }
    
    打印:
    请求方式:GET
    请求路径:/ts
    协议类型:HTTP/1.1
    host : localhost:8080
    connection : keep-alive
    cache-control : max-age=0
    upgrade-insecure-requests : 1
    user-agent : Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
    accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8
    accept-encoding : gzip, deflate, br
    accept-language : zh-CN,zh;q=0.9
    cookie : csrftoken=cTqd6QXbuKfzXZt4KqoEGJ1fHVxquYXCxgUQS42DGqH1Q870NgiKZs68KD4R1dAp; pgv_pvi=6176773120; Pycharm-10f6446f=8a4b4f57-683d-4839-bd3d-7b42b1b1a034
    

    解决请求数据的中文乱码

    1.get/post
    - 将乱码后的字符串,安装ISO还原成byte
    - 再将byte按照UTF-8编码为字符串
    优点:万能    缺点:麻烦
    
    2.get
    - 修改tomcat配置文件
    - 在server.xml第65行(不一定是65行),加URIEncoding="utf-8",修改路径传参编码
    优点:简单    缺点:只对get有效
    修改成如下
        <Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
    
    3.post
    - 在接收参数前,加 req.setCharacterEncoding("utf-8")
    - 声明实体内容的编码为utf-8
    优点:简单    缺点:只对post有效
    
    一般将第二种和第三种相结合解决get和post方式的乱码
    

    解决返回数据的中文乱码

    out.println输出中文时,Servlet默认会使用"iso-8859-1",该编码不支持中文,故不管如何都会乱码
    
    res.setCharacterEncoding("utf-8");
    res.setContentType("text/html");
    
    或者写成一句
    res.setContentType("text/html;charset=utf-8");
    

    一个注册的小例子

    web.xml文件

    <servlet>
        <servlet-name>reg</servlet-name>
        <servlet-class>web.RegServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>reg</servlet-name>
        <url-pattern>/reg</url-pattern>
    </servlet-mapping>
    

    register.html 文件,只需要放在webapp目录下,不需要配置

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <form action="reg" method="post">
        <p>账号:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password"></p>
        <p>
            性别:
            <input type="radio" name="sex" value="man">
            <input type="radio" name="sex" value="woman">
        </p>
        <p>
            兴趣:
            <input type="checkbox" name="interest" value="sport">运动
            <input type="checkbox" name="interest" value="music">音乐
            <input type="checkbox" name="interest" value="movie">电影
        </p>
        <p><input type="submit" value="提交"></p>
    </form>
    </body>
    </html>
    

    请求路径,项目名/register.html


    1.png

    RegServlet.java文件

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Random;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class RegServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            req.setCharacterEncoding("utf-8");
            
            String username = req.getParameter("username");
            
            String password = req.getParameter("password");
            System.out.println("username = " + username + ", password = " + password);
            String sex = req.getParameter("sex");
            String[] interests = req.getParameterValues("interest");  --- 获取多个数据
            System.out.println("sex = " + sex);
            if(interests != null) {
                System.out.print("interests =");
                for(String i:interests) {
                    System.out.print(" " + i);
                }
                System.out.println();
            }
            res.setContentType("text/html;charset=utf-8");   --解决中文乱码
            PrintWriter out = res.getWriter();
            out.println("<h1>注册成功!!!<h1>");
            out.close();
        }
    }
    
    2.png


    控制台输出
    3.png

    JavaBean - 满足如下规范的类:

    - 有包
    - 有默认构造器
    - 实现序列化接口
    

    URI和URL的区别

    1.狭义
        -- 只在Java项目中的URI和URL
        -- URI:绝对路径
        -- URL:完整路径
         > 从表面上看URI包含了URL
         
    2.广义
        - 在任意的WEB项目中理解URI和URL
        -- URI:资源的名称
        -- URL:资源的真名
         > URI包含了URL
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        System.out.println("请求方式:" + req.getMethod());
        System.out.println("项目名:" + req.getContextPath());
        System.out.println("请求的后部分路径:" + req.getServletPath());
        System.out.println("URI:" + req.getRequestURI());
        System.out.println("URL:" + req.getRequestURL());
    }
    
    打印:
    请求方式:GET
    项目名:/servlet3
    请求的后部分路径:/abc
    URI:/servlet3/abc
    URL:http://localhost:8080/servlet3/abc
    

    重定向

    解决一个网站内2个独立组件之间的跳转
    一般增加、修改、删除后重定向到查询
    
    res.sendRedirect("[别名]");
    

    建议:

     * 1.尽量使用封装类型,因为它比基本类型多了null
     * 2.使用java.sql包下的日期,因为JDBC支持这样的日期类型。
    

    设置访问路径的三种方式

    所谓的访问路径都是针对部署代码而言的
    1.静态资源(HTML/IMG/CSS/JS等)
        访问路径就是它在Tomcat内存放的位置
    2.动态资源(Servlet)
        访问路径就是web.xml中声明的别名
        
    在Servlet中如果没有写返回响应信息,浏览器会看到一片空白,并不会报错。
             
    配置Servlet访问路径:
        1.精确匹配
        -- 举例:/abc
        -- 只有/abc才能访问此Servlet
        -- 此Servlet只能处理这一个请求
         > 适合规模很小的项目
         
        2.通配符
        -- 举例:/*
        -- 所有的路径都能访问此Servlet
        -- 此Servlet能处理所有u请求
         > 适合一个项目只写一个Servlet
         
         3.后缀
         -- 举例:*.hi
         -- 所有以hi为后缀的请求都能访问此Servlet
         -- 此Servlet能处理多个请求
          > 适合一个项目写少量的几个Servlet
    

    例子,查询员工,通配路径 *.do,在Servlet中配置特定路径访问
    web.xml文件配置匹配路径

    <servlet>
        <servlet-name>main</servlet-name>
        <servlet-class>web.MainServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>main</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    

    Main.Servlet文件

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import dao.EmpDao;
    import dao.EmpDaoImpl;
    import entity.Emp;
    
     * 路径规范:
     * 查询员工:/findEmp.do
     * 增加员工:/addEmp.do
    
    public class MainServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            String path = req.getServletPath();
            if("/findEmp.do".equals(path)) {
                findEmp(req,res);
            }else if("/addEmp.do".equals(path)) {
                addEmp(req,res);
            }else {
                 * 该异常抛给服务器,服务器可以统一处理异常
                throw new RuntimeException("找不到了");
            }
        }
    
        protected void findEmp(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            EmpDaoImpl empdaoimp = new EmpDaoImpl();
            List<Emp> list = empdaoimp.findAll();
            
            res.setContentType("text/html;charset=utf-8");
            PrintWriter out = res.getWriter();
            out.println("<a href=\"addemp.html\">添加员工</a>");
            out.println("<table border=\"1\">");
            out.println("<td>" + "编号" + "</td>");
            out.println("<td>" + "名称" + "</td>");
            out.println("<td>" + "工作" + "</td>");
            out.println("<td>" + "薪水" + "</td>");
            if(list != null) {
                for(Emp emp:list) {
                    out.println("<tr>");
                    Integer no = emp.getEmpno();
                    String name = emp.getEname();
                    String job = emp.getJob();
                    Double salary = emp.getSalary();
                    System.out.println("no =" + no + ", name = " + name + ", job = " + job + ", salary = " + salary);
                    out.println("<td>" + no + "</td>");
                    out.println("<td>" + name + "</td>");
                    out.println("<td>" + job + "</td>");
                    out.println("<td>" + salary + "</td>");
                    
                    out.println("</tr>");
                }
            }
            out.println("</table>");
            out.println("输出结束");
            
            out.close();
        }
        
        protected void addEmp(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            req.setCharacterEncoding("utf-8");
            String username = req.getParameter("username");
            String password = req.getParameter("password");
            String salary = req.getParameter("salary");
            System.out.println("username = " + username + ", password = " + password + ", salary = " + salary);
            
            Emp emp = new Emp();
            emp.setEname(username);
            emp.setJob(password);
            if(salary != null && !salary.equals("")) {
                emp.setSalary(Double.valueOf(salary));
            }
            EmpDao dao = new EmpDaoImpl();
            dao.save(emp);
            
            res.sendRedirect("findEmp.do");
        }
    }
    

    HiServlet的生命周期,init(),destroy()

    1.默认首次访问Servlet时Tomcat会实例化HiServlet
    2.可以改为启动Tomcat时就实例化HiServlet
    3.HiServlet从头到尾只实例化一次,所以HiServlet是单例的(一个)
    4.每次请求都调用处理执行
    5.意义:当需求规定在什么时刻做什么处理时,开发者便于处理
    
    修改实例化Servlet的顺序
    <servlet>
        <servlet-name>hi</servlet-name>
        <servlet-class>web.HiServlet</servlet-class>
        <!- 启动时加载此Servlet,中间的数字是加载的顺序。 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    

    HiServlet.java文件

    package web;
    
    import java.io.IOException;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class HiServlet extends HttpServlet{
    
        public HiServlet() {
            System.out.println("实例化");
        }
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
            System.out.println("初始化HiServlet");
        }
        
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            System.out.println("调用HiServlet处理请求");
        }
    
        @Override
        public void destroy() {
            super.destroy();
            System.out.println("销毁HiServlet");
        }
    }
    

    ServletConfig和ServletContext,配置初始化参数

    1.作用
        使用web.xml做配置文件,使用config或context来读取该文件中的参数
    
    2.区别
        - config
        -- 和Servlet是1对1的关系
        -- Tomcat在初始化每个Servlet前会给它创建一个config
        -- 调用HelloServlet.init()前给它创建一个config
        -- 如果想给某个Servlet预置数据,使用config
        
        - context
        -- 和Servlet是1对多的关系
        -- Tomcat在启动时就创建唯一的一个context
        -- 所有的Servlet都可以共享这个对象中的数据
        -- 如果想给多个Servlet预置数据,使用context
    

    config使用,init()初始化之前创建一个config
    web.xml文件

    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>web.LoginServlet</servlet-class>
         <!- 给此Servlet预置参数  -->
        <init-param>
            <param-name>maxOnline</param-name>
            <param-value>3000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    

    读取config参数
    LoginServlet.java文件

    package web;
    
    import java.io.IOException;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class LoginServlet extends HttpServlet{
    
         * Tomcat创建Servlet的逻辑:
         * LoginServlet s = new LoginServlet();
         * ServletConfig c = new ServletConfig();
         * c.加载数据();   从web.xml加载数据
         * s.init(c);
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
            String maxOnline = config.getInitParameter("maxOnline");
            System.out.println(maxOnline);
        }
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
             * 此config就是init()传入的那个
            ServletConfig cfg = getServletConfig();
            System.out.println("正在登录·····"+cfg.getInitParameter("maxOnline"));
        }
    }
    
    打印:   --- 获取到数据了
    3000
    正在登录·····3000
    

    context使用,读取常量
    web.xml 中配置

    context:
        - 软件内有很多的查询功能,都带有分页
        - 每页显示的行数size是常量,并且可配置
        - 该数据在多个查询功能之间共用,使用context读取
    
     在标签外配置的参数是给所有Servlet公用的参数, 它们都可以通过context读取该参数
    <context-param>
        <param-name>size</param-name>
        <param-value>20</param-value>
    </context-param>
    
    public class TextServlet extends HttpServlet{
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
             * Tomcat启动时就会创建唯一的context,并且会调用它的方法
             * 加载web.xml中的公用参数,context是全局的,任何Servlet都可以使用
            ServletContext ctx = getServletContext();
            String size = ctx.getInitParameter("size");
            System.out.println(size);
        }
    }
    
    打印:
    20
    

    context可以读写变量

    - 用该对象读写的变量是可以被所有的Servlet共用的
    - setAttribute()/getAttribute()
    

    web.xml 配置文件

    <servlet>
        <servlet-name>init</servlet-name>
        <servlet-class>web.InitServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
        <servlet-mapping>
        <servlet-name>init</servlet-name>
        <url-pattern>/init</url-pattern>
    </servlet-mapping>
    

    初始化设置变量,在所有的Servlet中都可以读写Context中的变量

     * 用来在Tomcat启动时初始化数据,一般WEB项目都有1-2个这样的Servlet。
    
    public class InitServlet extends HttpServlet{
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
    
             * Tomcat启动时会优先创建Context,然后创建Servlet
            ServletContext ctx = getServletContext();
            ctx.setAttribute("count", 0);
        }
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ServletContext ctx = getServletContext();
            Integer count = (Integer) ctx.getAttribute("count");
            ctx.setAttribute("count", ++count);
            System.out.println("count = " + count);
        }
    }
    
    打印:
    count = 1
    count = 2
    count = 3
    count = 4
    count = 5
    
    2.png

    Servlet线程安全问题

    1.同时修改局部变量
     - 局部变量存于栈内
     - 每个线程有自己的栈帧
     - 此时没有线程安全问题
     
    2.同时修改成员变量
     - 成员变量存于堆中
     - 所有线程共享这样的数据
     - 此时存在线程安全问题
     
    3.解决方案
     - 加锁 
    

    jsp

    jsp本质上就是Servlet


    3.png

    jsp生成Servlet过程


    4.png
    jsp隐含(内置)对象
    1.request
    - HttpServletRequest
    
    2.response
    - HttpServletResponse
    
    3.out
    - JSPWriter
    - 类似于PrintWriter
    
    4.config
    - ServletConfig
    
    5.application
    - ServletContext
    
    6.exception
    - Throwable
    - jsp翻译成的Servlet所抛出的异常
    
    7.session
    - HttpSession
    
    8.page
    - Object
    - 相当于this,用来指代翻译成的那个Servlet
    
    9.pageContext
    - PageContext
    - 是管理者,通过它可以获得其他8个隐含对象
    
    如何在jsp上使用隐含对象
    - <%Object user = session.getAttribute("user"); %>
    

    time.jsp文件

    pageEncoding:声明此文档的编码
    contentType:声明输出的内容格式
    
    此jsp被其他网页引用
    
    <%@ page pageEncoding="utf-8" contentType="text/html" import="java.text.SimpleDateFormat,java.util.Date"%>
    <%
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(date);
    %>
    <p><%=now %></p>
    

    hello.jsp文件,引入上面的time.jsp文件

    <%@page pageEncoding="utf-8"%>
    <html>
    <head>
    <meta charset="uft-8">
    <title>测试</title>
    </head>
    <%!
        public double bb(double d){
            return d*100;
        }
    %>
    <body>
        <ul>
            <% 
                for(int i=0; i<10; i++){
            %>
                <li><%=bb(Math.random()) %></li>
            <%
                }
            %>
        </ul>
        <!-- 引入一个jsp -->
        <%@include file="time.jsp" %>
    </body>
    </html>
    
    打印:
    十个随机数
    

    jsp转发例子

    package web;
    
    import java.io.IOException;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import dao.EmpDao;
    import dao.EmpDaoImpl;
    import entity.Emp;
    
    public class FindEmpServlet extends HttpServlet{
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            EmpDao dao = new EmpDaoImpl();
            List<Emp> emplist = dao.findAll();
            
             * 转发到jsp,让jsp继续完成这个请求
             * 1.将数据绑定到request上
             * 2.将请求提交给jsp让它继续处理
             * 同时要将request、response给jsp
             * 当前:/jsp2/findEmp
             * 目标:/jsp2/emp_list.jsp
    
            req.setAttribute("emplist", emplist);
            req.getRequestDispatcher("emp_list.jsp").forward(req, res);
        }   
    }
    

    被转发的emp_list.jsp文件

    <%@ page pageEncoding="utf-8" import="entity.Emp,java.util.*" %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Insert title here</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <td>编号</td>
                <td>姓名</td>
                <td>职位</td>
                <td>月薪</td>
            </tr>
            <%  
                List<Emp> emplist = (List<Emp>) request.getAttribute("emplist");
                if(emplist != null){
                    for(Emp e:emplist){
                    %>
                    <tr>
                        <td><%=e.getEmpno() %></td>
                        <td><%=e.getEname() %></td>
                        <td><%=e.getJob() %></td>
                        <td><%=e.getSalary() %></td>
                    </tr>       
                    <%
                    }
                }
            %>
        </table>
    </body>
    </html>
    
    5.png

    MVC

    引入了MVC设计模式,对代码分层,降低代码的耦合度。
    MVC是经典的设计模式,是代码的分层思想:
    M:Model,业务层,用来处理业务
    V:View,视图层,用来展现数据
    C:Controller,控制层,用来进行调度,是业务层和视图层的桥梁
    其目的是要将代码解耦,便于团队开发及维护
    

    转发和重定向

    1.转发和重定向的相同点
    - 都是解决WEB组件之间的跳转问题
    - WEB组件:Servlet/JSP
    
    转发的特点:
    1.一次请求
    2.地址不变
    3.一个请求只有一个request,组件可以通过它共享数据
    4.只能转发到项目内部的资源
    
    重定向的特定:
    1.二次请求
    2.地址改变
    3.两个请求有两个request,组件无法通过它共享数据
    4.可以重定向到项目外部的资源
    
    建议:
    一般是这样
    - 查询时用转发
    - 增加、删除、修改后重定向到查询
    

    jsp例子
    Course.java类

    package entity;
    
    import java.io.Serializable;
    
    public class Course implements Serializable{
        private Integer courseId;
        private String name;
        private Integer days;
    
         * 1.与get/set对应的属性叫Bean属性
         * 2.通过get/set反应出来的我们以为的属性
         * 3.去掉get并将首字母小写的单词是Bean属性
         * Bean属性通常和对象属性一致,也可以修改为不一致
    
        public Integer getId() {
            return courseId;
        }
        public void setId(Integer courseId) {
            this.courseId = courseId;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getDays() {
            return days;
        }
        public void setDays(Integer days) {
            this.days = days;
        }
    }
    

    Student.java类,调用上面的Course

    package entity;
    
    import java.io.Serializable;
    
    public class Student implements Serializable{
        private String name;
        private Integer age;
        private String sex;
        private String[] interests;
        private Course course;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        public String[] getInterests() {
            return interests;
        }
        public void setInterests(String[] interests) {
            this.interests = interests;
        }
        public Course getCourse() {
            return course;
        }
        public void setCourse(Course course) {
            this.course = course;
        }
    }
    

    FindStudentServlet.java处理逻辑,并转发到jsp

    package web;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import entity.Course;
    import entity.Student;
    
    public class FindStudentServlet extends HttpServlet {
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            Student stu = new Student();
            stu.setName("libai");
            stu.setAge(23);
            stu.setSex("M");
            stu.setInterests(new String[] {"a","b","c","d"});
            Course c = new Course();
            c.setId(1);
            c.setName("java");
            c.setDays(80);
            stu.setCourse(c);
            
            req.setAttribute("stu", stu);
            req.getRequestDispatcher("find_student.jsp").forward(req, res);
        }   
    }
    

    find_student.jsp的运算和取值,获取请求中返回的数据

    <%@page pageEncoding="utf-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>El和JSTL</title>
    </head>
    <body>
        <h1>El</h1>
        <!-- 等价于request.getAttribute("stu").getName() -->
        <p>姓名:${stu.name}</p>
        <p>年龄:${stu["age"]}</p>
        <!-- 等价于request.getAttribute("stu").getCoures().getId() -->
        <p>课程:${stu.course.id }</p>
    
        <!-- EL的取值范围:
        1.EL默认从如下4个对象中依次取值
        page,request,session,application
        这4个隐含对象是EL默认的取值范围
        2.也可以指定取值范围
        requestScope.stu.name
        sessionScope.stu.name
        3.设计默认取值的目的,是为了开发者不用经常写
        范围,是为了简化EL表达式
         -->
        <p>性别:${requestScope.stu.sex }</p>
    
        <!-- EL支持运算 -->
        <p>运算:${stu.age+10 }</p>
        <p>是否在某个区间:${stu.age>20 && stu.age<30}</p>
        <p>是否为空:${empty stu.age }</p>
    
        <!-- 获取请求参数,我的请求路径为 /jsp3/findStudent?p=123 -->
        <p>参数:${param.p }</p>
    </body>
    </html>
    
    6.png

    JSTL
    maven中导入包

    <dependencies>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>
    

    其他的文件不变,就修改jsp文件,引入标签库,重命名为c,调用if,choose,forEach方法

    <%@page pageEncoding="utf-8"%>
    <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>JSTL</title>
    </head>
    <body>
        <p>性别:
            <c:if test="${stu.sex=='M' }">男</c:if>
            <c:if test="${stu.sex=='F' }">女</c:if>
        </p>
        <p>
            性别:
            <c:choose>
                <c:when test="${stu.sex=='M' }">男</c:when>
                <c:otherwise>女</c:otherwise>
            </c:choose>
        </p>
        <!-- items:声明遍历的数据,var:每次循环赋值数据 -->
        <p>
            兴趣:
            <c:forEach items="${stu.interests}" var="interest">
            <span>${interest } </span>
            </c:forEach>
        </p>
    </body>
    </html>
    
    4.png

    JSTL标签原理上还是调用类实现的


    3.png

    note

     * java.sql包下的日期类型:
     * java.sql.Date 年月日
     * java.sql.Time 时分秒
     * java.sql.Timestamp 年月日时分秒
     * 它们都是java.util.Date的子类
    

    一个Dao的例子,注意,其中的数据库连接池的类DBUtils在我之前文章JDBC中有

    package dao;
    
    import java.io.Serializable;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    
    import util.DBUtils;
    import entity.Cost;
    
    public class CostDaoImpl implements CostDao,Serializable{
    
        public List<Cost> findAll() {
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                Statement st = conn.createStatement();
                String sql = "select * from zhizhang order by cost_id";
                ResultSet rs;
                rs = st.executeQuery(sql);
                List<Cost> list = new ArrayList<Cost>();
                while(rs.next()) {
                    Cost cost = new Cost();
                    cost.setCostId(rs.getInt("cost_id"));
                    cost.setName(rs.getString("name"));
                    cost.setBaseDuration(rs.getInt("base_duration"));
                    cost.setBaseCost(rs.getDouble("base_cost"));
                    cost.setUnitCost(rs.getDouble("unit_cost"));
                    cost.setStatus(rs.getString("status"));
                    cost.setDescription(rs.getString("description"));
                    cost.setCreatime(rs.getTimestamp("creatime"));
                    cost.setStartime(rs.getTimestamp("startime"));
                    cost.setCostType(rs.getString("cost_type"));
                    
                    list.add(cost);
                }
                return list;
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("查询失败", e);
            } finally {
                DBUtils.close(conn);
            }
        }
    
         * setInt()/setDouble()不允许传入null,
         * 但实际业务中这些字段确实有null的情况,
         * 表里也支持null,此时需要将他们当作对象来处理
         * 都改成setObject()就行了    
        
        public void save(Cost c) {
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                String sql = "insert into zhizhang (name,base_duration,base_cost,unit_cost,status,description,creatime,startime) "
                        + "values(?,?,?,?,?,?,?,NULL)";
                PreparedStatement st = conn.prepareStatement(sql);
                st.setString(1, c.getName());
                st.setObject(2, c.getBaseDuration());
                st.setObject(3, c.getBaseCost());
                st.setObject(4, c.getUnitCost());
                st.setString(5, c.getDescription());
                st.setString(6, c.getCostType());
                st.executeUpdate();
                
            } catch(Exception e) {
                e.printStackTrace();
                throw new RuntimeException("增加失败");
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    

    MainServlet.java

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import dao.CostDao;
    import dao.CostDaoImpl;
    import entity.Cost;
    
    public class MainServlet extends HttpServlet{
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            String path = req.getServletPath();
            System.out.println("path = " + path);
            if("/findCost.do".equals(path)) {
                findCost(req, res);
            }else if("/toAddCost.do".equals(path)) {
                toAddCost(req, res);
            }else if("/addCost.do".equals(path)) {
                addCost(req, res);
            }else if("/toLogin.do".equals(path)){
                toLogin(req, res);
            }else if("/toIndex.do".equals(path)){
                toIndex(req, res);
            }else {
                throw new RuntimeException("查无此页");
            }
        }
        
        protected void toLogin(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            req.getRequestDispatcher("WEB-INF/cost/login.jsp").forward(req, res);
        }
        protected void toIndex(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            req.getRequestDispatcher("WEB-INF/cost/index.jsp").forward(req, res);
        }
        
        protected void findCost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            CostDao dao = new CostDaoImpl();
            List<Cost> list = dao.findAll();
            req.setAttribute("list", list);
            req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
        }
        
        protected void toAddCost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);
        }
    }
    

    自定义错误页面

    配置错误页面:
    1.tomcat是所有服务端代码调用的入口,它在调用代码时会尝试捕获异常。
    2.默认捕获到异常会自动转发到对应的错误页面,如404.html、500.html。
    3.我们可以通过配置改变这个默认的行为,明确告诉
    tomcat哪个异常去哪个错误页面,这件事本事解决不了异常,仅仅是看起来更友好。
    4.声明错误页面时需要写绝对路径,但由于是转发过去,只能访问此项目内部的资源,所以tomcat自动帮我们加上项目名
    
    <!-- 1.通过异常类型配置 -->
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/WEB-INF/error.jsp</location>
    </error-page>
    <!-- 2.通过异常编号(404/405/500)进行配置 -->
    
    JSTL:(JSP Standard Tag Library)JSP标准标签库
    
    WEB-INF具有保护作用,放在它内部的资源受保护,
    无法直接访问,必须通过转发来访问。
    
    1.若jsp没放到WEB-INF下,就可以直接访问它
    2.在MVC模式下,直接访问jsp会使其丢失数据来源,导致报错
    3.将其放到WEB-INF下,就是包含它不被直接访问,从而避免报错。
    
    静态资源不要放在WEB-INF下。
    

    Cookie

    cookie的特点:
    - cookie保存在浏览器上
    - 多个请求可以共用一组cookie
    - 多个Servlet可以共用一组cookie
    
    在/main/login路径下创建的cookie,它只对/main及其下级路径有效
    
    EL默认的取值范围是:
    page->request->session->application
    EL也可以从cookie中取值,语法为:
    cookie.key.value
    

    设置cookie

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class LoginServlet extends HttpServlet{
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            String username = req.getParameter("username");
    
             * 检查账号密码,通过后将账号存入cookie
             * 每个cookie对象只能存一条数据
             * key,value都是字符串
             * 将cookie发送给浏览器,浏览器接收后会自动保存
    
            Cookie cookie = new Cookie("username", username);
    
             * 声明cookie的生存时间
             * >0时cookie保存在客户端的硬盘上
             * =0时cookie被浏览器删除
             * 默认<0,保存在内存中
    
            cookie.setMaxAge(600000); //秒数
            cookie.setPath("/jsp4");  //设置cookie的有效路径,对整个项目都有效
            
            res.addCookie(cookie);
            res.setContentType("text/html;charset=utf-8");
            PrintWriter out = res.getWriter();
            out.println("设置cookie, name = " + cookie.getName() + ", value = " + cookie.getValue());
            out.close();
        }   
    }
    

    cookie取值

    package web;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class IndexServlet extends HttpServlet{
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    
             * 获取登陆时存入的cookie的值,检测是否过登陆了
            PrintWriter out = res.getWriter();
            Cookie[] cs = req.getCookies();
            if(cs != null) {
                res.setContentType("text/html;charset=utf-8");
                for(Cookie c:cs) {
                    out.println(c.getName() + " = " + c.getValue() + "<br>");
                }
            }
            out.close();
        }   
    }
    

    Session

    - 浏览器第一次访问服务器时,服务器会给它创建1个session
    - 服务器使用cookie将session的SID返回给浏览器
    - 浏览器下次访问该服务器时会带上SID
    - 多个请求可以共用同一个session
    - 多个Servlet可以共用同一个session
    
    * session是存储在服务器上的对象,它的内部可以存储任意类型的数据
    String username = req.getParameter("username");
    HttpSession session = req.getSession();
    session.setAttribute("username", username);
    
    * 响应时服务器会自动创建cookie,将session的ID通过cookie发送给浏览器,类似如下
    * Cookie c = new Cookie("JSESSIONID",session.getId());
    
    HttpSession session = req.getSession();
    String username = (String)session.getAttribute("username");
    
    如果cookie被禁用了,可以使用URL重写解决问题,也就是把SessionID加到地址后面
    

    过滤器

    1.过滤器的作用
    - 用来处理项目中的公共需求
    - 举例:记录日志、过滤敏感词、权限检查
    - 公共的需求:很多请求都包含的业务
    
    2.实现步骤:
    - 创建一个类,实现过滤器接口Filter
    - 在web.xml中配置这个类
    - 过滤器由服务器自动调用
    

    两个简单的业务

    public class FindCostServlet extends HttpServlet{
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            System.out.println("查询");
            res.setContentType("text/html;charset=utf-8");
            PrintWriter out = res.getWriter();
            out.println("查询");
            out.close();
        }
    }
    
    public class AddCostServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
            System.out.println("添加");
            res.setContentType("text/html;charset=utf-8");
            PrintWriter out = res.getWriter();
            out.println("添加");
            out.close();
        }
    }
    

    一个日志过滤器,在执行各个Servlet之前,之后记录日志

    package web;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    public class LogFilter implements Filter{
         * 关闭tomcat时自动调用此方法
        public void destroy() {
            System.out.println("Log过滤器的销毁方法");
        }
    
         * 该方法是处理公共业务的方法
         * Filter相当于是Servlet的管家,tomcat在调用Servlet之前会将请求
         * 提交给Filter,Filter有权让请求继续,也可以让请求终止
         * tomcat就是调用doFilter方法让Filter统一处理请求的,调用它之前会
         * 创建好request和response并传入,创建的类型:RequestFacade和ResponseFacade
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("Log chain 前");
    
             * 请求跳转到各个Servlet中继续执行
            chain.doFilter(request, response);
            System.out.println("Log chain 后");
        }
    
         * tomcat启动时会自动实例化Filter,然后调用
         * 其init()来初始化Filter,调用此方法时会传入config,
         * 该对象和Filter是1对1的关系,可以给Filter预置参数(web.xml).
         * 该对象和ServletConfig用法完全一样
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("Log过滤器的初始化方法");
        }
    }
    

    检查是否登陆,没有登陆跳转到特定的页面

    package web;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    
    public class LoginFilter implements Filter{
    
        public void destroy() {
            System.out.println("Login过滤器的销毁方法");
        }
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("Login chain 前");
            String[] paths = new String[] {
                    "/find"
            };
            HttpServletRequest req = (HttpServletRequest)request;
            HttpServletResponse res = (HttpServletResponse)response;
            String path = req.getServletPath();
            for(String p:paths) {
                if(p.equals(path)) {
                    chain.doFilter(request, response);
                    System.out.println("Login chain 后");
                    return;
                }
            }
            HttpSession session = req.getSession();
            String username = (String) session.getAttribute("username");
            if(username != null && username != "") {   // 没有登陆时,username为null
                chain.doFilter(request, response);
                System.out.println("Log chain 后");
            }else {
                res.sendRedirect("/jsp5/find");
            }
        }
    
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("Login过滤器的初始化方法");
        }   
    }
    

    配置web.xml文件,url-pattern中声明需要过滤的请求

    <filter>
        <filter-name>log</filter-name>
        <filter-class>web.LogFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>log</filter-name>
        <!-- 声明此Filter可以过滤哪些请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>login</filter-name>
        <filter-class>web.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>login</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    最终的结果,当你访问 /jsp5/add (jsp5是我的项目名),会跳转到 /jsp5/find页面

    Log chain 前
    Login chain 前
    查询
    Login chain 后
    Log chain 后
    

    各个过滤器之间是一层套一层的关系,而他们的执行顺序是按照<filter-mapping>标签的位置来确定的

    过滤器上也可以设置特定的参数

    <filter>
        <filter-name>test</filter-name>
        <filter-class>web.TestFilter</filter-class>
        <init-param>
            <param-name>city</param-name>
            <param-value>北京</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>test</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    获取设置的参数
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println(filterConfig.getInitParameter("city"));
        }
    

    监听器,可以监听HttpSession,HttpRequest,ServletContext三个对象的创建和销毁,还能监听各个对象的增加、删除、替换值的事件

    package web;
    
    import javax.servlet.ServletRequestAttributeEvent;
    import javax.servlet.ServletRequestAttributeListener;
    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    
    public class MyListener implements ServletRequestListener,ServletRequestAttributeListener {
    
         * tomcat销毁request前自动调用此方法
        public void requestDestroyed(ServletRequestEvent e) {
            System.out.println("销毁request");
        }
    
         * tomcat创建request后自动调用此方法
        public void requestInitialized(ServletRequestEvent e) {
            System.out.println("创建request");
            System.out.println(e.getServletRequest());
        }
    
        public void attributeAdded(ServletRequestAttributeEvent arg0) {
            System.out.println("向request内添加一个值");
        }
    
        public void attributeRemoved(ServletRequestAttributeEvent arg0) {
        }
    
        public void attributeReplaced(ServletRequestAttributeEvent arg0) {
        }
    }
    

    web.xml中添加配置

    <listener>
        <listener-class>web.MyListener</listener-class>
    </listener>
    

    打印结果

    创建request
    org.apache.catalina.connector.RequestFacade@63e1bdfd
    Log chain 前
    Login chain 前
    查询
    Login chain 后
    Log chain 后
    销毁request
    

    相关文章

      网友评论

        本文标题:Java Servlet,JSP,Filter,Listener

        本文链接:https://www.haomeiwen.com/subject/tdtfiftx.html