Tomcat

直接创建项目

  1. tomcat文件夹–>webapp–>新建文件夹并进入
  2. 创建WEB-INF文件夹
  3. 将代码文件放在当前文件夹
  4. startup启动tomcat

IDEA中:

  1. 进入项目

  2. 右键项目

  3. 添加框架支持

  4. 选择web application

  5. 编辑配置 —>添加新配置

  6. Tomcat服务器 本地

  7. 服务器配置 应用程序服务器

    1. 部署 在服务器启动时部署 (修改Application context 应用程序上下文,可以仅设置为/)
    2. 服务器界面设置URL (运行时浏览器默认打开的URL,默认index.html,index.htm,index.jsp)
    3. (热部署: 执行更新操作:“重新部署” 切换出IDE时"更新类和资源")可以不改
  8. 项目设置–>模块设置添加依赖—>库—>应用程序服务器库

index.jsp web.xml 可以删除

  1. 项目结构 工件(artifacts)

控制台中文乱码:logging.properties java.util.logging.ConsoleHandler.encoding = GBK

Servlet

tomcat—>lib—>servlet-api.jar

设置默认页面

<!--自己项目或tomcat_config的web.xml文件中添加-->
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

设置编码

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //post方式下,需要在所有获取参数的语句之前,设置字符编码,防止乱码
    //response.setContentType("text/html;charset=UTF-8");
    request.setCharacterEncoding("UTF-8");
    //get方式.tomcat8之后无需考虑乱码
    /*(tomcat8之前)
    String fname = request.getParameter("fname");
    1.将字符串打散成字节数组
    byte[] bytes = fname.getBytes("ISO-8859-1");
    2.将字节数组按照设定的编码重新组装成字符串
    fname = new String(bytes,"UTF-8")
    */    
    String sname = request.getParameter("sname");
    String sidstr = request.getParameter("sid");
    Integer sid=Integer.parseInt(sidstr);
    String password=request.getParameter("password");
    System.out.println(sname+sid+password);
    Student student = new Student(sid, sname, password);
    System.out.println(student);

    StudentDao studentDao = new StudentDaoImpl();
    Connection conn=JDBCUtils.getConnection();
    studentDao.saveStudent(conn,student);
}

获取参数

<body>
<form action="add" method="post">
    学生:<input type="text" name="sname"/><br/>
    学号:<input type="text" name="sid"/><br/>
    密码:<input type="text" name="password"/><br/>
    教师:<input type="text" name="tname"/><br/>
    职工:<input type="text" name="tid"/><br/>
    分数:<input type="text" name="sgrad"/><br/>
    课程:<input type="text" name="cname"/><br/>
    课号:<input type="text" name="cid"/><br/>
    <input type="submit" value="添加">
</form>
</body>
public class AddServlet extends HttpServlet {
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String sname = request.getParameter("sname");
        String sidstr = request.getParameter("sid");
        Integer sid=Integer.parseInt(sidstr);
        String password=request.getParameter("password");
        System.out.println(sname+sid+password);
    }
}
<servlet>
    <servlet-name>AddServlet</servlet-name>
    <servlet-class>controller.AddServlet</servlet-class>
</servlet>
<servlet-mapping><!--映射-->
    <servlet-name>AddServlet</servlet-name>
    <url-pattern>/add</url-pattern>
    <!--<url-pattern>/add.html</url-pattern>-->
</servlet-mapping>
<!--
    1.用户发请求 action=add
    2.项目中 web.xml中找到url-pattern=/add
    3.寻找servlet-name=AddServlet
    4.找和servlet-mapping中和servlet-name一致的servlet
    5.找servlet-class=controller.AddServlet
    6.用户发送post请求 tomcat执行AddServlet中的doPost方法
	7.一个servlet可以对应多个servlet-mapping,即多个url为同一个servlet响应
-->

注解注册

@WebServlet("/stuselectservlet")
//@WebServlet(name = "StuSelectServlet",value = "/stuselectservlet")
public class StuSelectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StudentDao studentDao=new StudentDaoImpl();
        List<Student> student = studentDao.getStudent();
        System.out.println(student);
        //保存到session作用域
        HttpSession session = req.getSession();
        session.setAttribute("studentList",student);
    }
}
@WebServlet(urlPatterns={"/demo1","demo2"},
            initParams={
                @WebInitParam(name="hello",value="world"),
                @WebInitParam(name="hello1",value="world2"),
                @WebInitParam(name="hello1",value="world2")
            }
           )

继承关系

​ javax.servlet.Servlet接口
​ javax.servlet.GenericServlet抽象类
​ javax.servlet.http.HttpServlet抽象子类

相关方法

void init(config); 初始化方法
void service(request,response); 服务方法
void destory();  销毁方法    
/*
java.servlet.GenericServlet抽象类  :void service(request,response)仍是抽象的
java.servlet.http.HttpServlet抽象子类  :void service(request,response)不是抽象的

当有请求发送过来时,service方法自动响应,在HttpServlet中分析请求方式
service方法中 String method = req.getMethod(); 获取请求的方式
	if判断,根据请求方式不同,决定去调用不同的do方法,无对应方法,会报405错误
            if (method.equals("GET")) {
                this.doGet(req,resp);
            } else if (method.equals("HEAD")) {
                this.doHead(req, resp);
            } else if (method.equals("POST")) {
                this.doPost(req, resp);
            } else if (method.equals("PUT")) {
*/

生命周期

名称 时机 次数
创建对象 默认情况:接收到第一次请求 修改启动顺序后:Web应用启动过程中 一次
初始化操作 创建对象之后 一次
处理请求 接收到请求 多次
销毁操作 Web应用卸载之前 一次
  • 生命周期对应Servlet中的三个方法:init(),service(),destroy()
  • 默认情况下:
    • 第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
    • 从第二次请求开始,每一次都是服务
    • 当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法
  • Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.优点:提高系统的启动速度 。 缺点:第一次请求时,耗时长
  • 如果需要提高系统的启动速度,当前默认情况即可。如果需要提高响应速度,应该设置Servlet的初始化时机。
@WebServlet(name = "Hello", value = "/hello")
public class Hello extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("1");
        response.setContentType("text/html");
        PrintWriter out=response.getWriter();
        out.println("<h>Hello world!Hello world!Hello world!</h>");
    }

    @Override
    public void destroy() {
        super.destroy();
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("1");
        response.setContentType("text/html");
        PrintWriter out=response.getWriter();
        out.println("<h>Hello world!</h>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("1");
        response.setContentType("text/html");
        PrintWriter out=response.getWriter();
        out.println("<h>Hello world!</h>");
    }
}
public class AddServlet extends HttpServlet {
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //post方式下,需要在所有获取参数的语句之前,设置字符编码,防止乱码
        request.setCharacterEncoding("UTF-8");
        //get方式.tomcat8之后无需考虑乱码
        String sname = request.getParameter("sname");
        String sidstr = request.getParameter("sid");
        Integer sid=Integer.parseInt(sidstr);
        String password=request.getParameter("password");
        System.out.println(sname+sid+password);
        Student student = new Student(sid, sname, password);
        System.out.println(student);
        StudentDao studentDao = new StudentDaoImpl();
        Connection conn=JDBCUtils.getConnection();
        studentDao.saveStudent(conn,student);
    }
    @Override
    public void init() throws ServletException {
        System.out.println("正在初始化....");
    }
    @Override //重写service覆盖自定义的doPost()
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("正在服务....");
    }
    @Override
    public void destroy() {
        System.out.println("正在销毁....");
    }
}

初始化

可以通过<load-on-startup>来设置servlet启动的先后顺序,数字越小,启动越靠前,最小值0

<servlet>
    <servlet-name>AddServlet</servlet-name>
    <servlet-class>controller.AddServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><!--映射-->
    <servlet-name>AddServlet</servlet-name>
    <url-pattern>/add</url-pattern>
</servlet-mapping>

Servlet在容器中是:单例的、线程不安全的

  • 单例:所有的请求都是同一个实例去响应

  • 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化

  • servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:①不要修改成员变量的值 ②不要根据成员变量的值做一些逻辑判断

初始化参数

<servlet><!--添加学生信息-->
    <servlet-name>Find</servlet-name>
    <servlet-class>Servlet.FinderServlet</servlet-class>
    <init-param>
        <param-name>contacts</param-name>
        <param-value>1.xlsx,2.xlsx</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><!--映射-->
    <servlet-name>Find</servlet-name>
    <url-pattern>/find</url-pattern>
</servlet-mapping>
public void init() throws ServletException {
    String files = config.getInitParameter("contacts");
}

ServletContext

application值

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
//在初始化方法中: 
    ServletContxt servletContext = getServletContext();
//在服务方法中也可以通过request对象获取,也可以通过session获取:
    request.getServletContext(); session.getServletContext()
//获取初始化值:        
    servletContext.getInitParameter();

错误

500空指针

  • 检查JDBC连接设置,配置文件设置
  • jar包
  • 项目设置

405

  • 请求的方法如method=post 则Servlet必须对应doPost,否则405错误(默认为get)

500

  • Servlet方法权限是否足够调用

HTTP

  • HTTP 超文本传输协议

  • Http是无状态的

  • Http请求响应包含两个部分:请求和响应

    • 请求:
      请求包含三个部分: 1.请求行 ; 2.请求消息头 ; 3.请求主体

      • 请求行包含是三个信息: 1. 请求的方式 ; 2.请求的URL ; 3.请求的协议(一般都是HTTP1.1)
      • 请求消息头中包含了很多客户端需要告诉服务器的信息,比如:浏览器型号、版本、能接收的内容的类型、发的内容的类型、内容的长度等等
      • 请求体
        get方式,没有请求体,但是有一个queryString
        post方式,有请求体,form data
        json格式,有请求体,request payload
    • 响应:
      响应也包含三个部分: 1. 响应行 ; 2.响应头 ; 3.响应体

      • 响应行包含三个信息:1.协议 2.响应状态码(200) 3.响应状态(ok)

      • 响应头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)

      • 响应体:响应的实际内容(比如请求add.html页面时,响应的内容就是<html><head><body><form…)

会话

创建会话

Http是无状态的

  • HTTP 无状态 :服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的
    • 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱
  • 通过会话跟踪技术来解决无状态的问题。
    • 客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
    • 下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端
public class SessionDemo extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取session,如果获取不到,创建一个新的
        HttpSession session = request.getSession();
        System.out.println("session ID:"+session.getId());
    }
}
<servlet>
    <servlet-name>SessionDemo</servlet-name>
    <servlet-class>controller.SessionDemo</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><!--映射-->
    <servlet-name>SessionDemo</servlet-name>
    <url-pattern>/sessiontest</url-pattern>
</servlet-mapping>
@WebServlet("/stuselectservlet")
//@WebServlet(name = "StuSelectServlet",value = "/stuselectservlet")
public class StuSelectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StudentDao studentDao=new StudentDaoImpl();
        List<Student> student = studentDao.getStudent();
        System.out.println(student);
        //保存到session作用域
        HttpSession session = req.getSession();
        session.setAttribute("studentList",student);
    }
}

常用API

request.getSession(); -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true); -> 效果和不带参数相同
request.getSession(false); -> 获取当前会话,没有则返回null,不会创建新的
session.getId(); -> 获取sessionID
session.isNew(); -> 判断当前session是否是新的
session.getMaxInactiveInterval(); -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval();->设置默认激活时长
session.invalidate(); -> 强制性让会话立即失效

保存作用域

  • session保存作用域是和具体的某一个session对应的
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
    //获取session,如果获取不到,创建一个新的
    HttpSession session = request.getSession();
    System.out.println("session ID:"+session.getId());
    int maxInactiveInterval = session.getMaxInactiveInterval();//获取非激活间隔时长
    System.out.println("获取非激活间隔时长"+maxInactiveInterval);

    session.setAttribute("username","lina");//向session保存作用域保存一个一对key-value
    System.out.println(session.getAttribute("username"));//从当前session保存作用域获取指定的key对应的value
}
void session.setAttribute(k,v)//设置
Object session.getAttribute(k)//获取
void removeAttribute(k)//删除

服务器内部转发 客户端重定向

1) 服务器内部转发 : request.getRequestDispatcher("...").forward(request,response);
  - 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的
  - 地址栏没有变化
2) 客户端重定向: response.sendRedirect("....");
  - 两次请求响应的过程。客户端肯定知道请求URL有变化
  - 地址栏有变化
<!-- 配置上下文参数 -->
<context-param>
    <param-name>view-prefix</param-name>
    <param-value>/</param-value>
</context-param>
<context-param>
    <param-name>view-suffix</param-name>
    <param-value>.html</param-value>
</context-param>
//servlet3.0开始支持注解方式注册
@WebServlet("/stuselectservlet")
public class StuSelectServlet extends ViewBaseServlet{
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        StudentDao studentDao=new StudentDaoImpl();
        List<Student> student = studentDao.getStudent();
        System.out.println(student);
        //保存到session作用域
        HttpSession session = req.getSession();
        session.setAttribute("studentList",student);
        //此处视图名称是index
        //thymeleaf会将这个逻辑视图名称studentviewtest  对应到物理视图名称  view-prefix+ 逻辑视图名称 +view-presuffix
        super.processTemplate("studentviewtest",req,resp);
    }
}
<table id="tbl_stu">
    <tr>
        <th class="w20">课程代码</th>
        <th class="w20">课程名称</th>
        <th class="w20">学分</th>
        <th class="w20">学号</th>
        <th class="w20">学生</th>
        <th class="w20">绩点</th>
        <th class="w20">成绩</th>
    </tr>
    <tr th:if="${#lists.isEmpty(session.classstus)}">
        <td colspan="7">没有对应的信息!</td>
    </tr>
    <tr th:unless="${#lists.isEmpty(session.classstus)}" th:each="c1: ${session.classstus}">
        <td class="w20" th:text="${c1.cid}"></td>
        <td class="w20" th:text="${c1.cname}"></td>
        <td class="w20" th:text="${c1.credit}"></td>
        <td class="w20" th:text="${c1.sid}"></td>
        <td class="w20" th:text="${c1.sname}"></td>
        <td class="w20" th:text="${c1.sgrad}/20"></td>
        <td class="w20" th:text="${c1.sgrad}"></td>
    </tr>
</table>

保存作用域

原始情况下,保存作用域有4个

  • page(页面级别,现在几乎不用,jsp)
  • request(一次请求响应范围)
  • session(一次会话范围)
  • application(整个应用范围)

request

// /d1请求1
request.setAttribute("usrname","Lili");
response.sendRedirect("/d2")//客户端重定向 请求2无法获取请求1的数据
request.getRequestDispatcher("/d2").forward(request,response);//服务器内部转发 可以获取数据 

session

// /d1请求1
request.getSession().setAttribute("usrname","Lili");
response.sendRedirect("/d2")//客户端重定向 请求2可以获取请求1的数据
request.getRequestDispatcher("/d2").forward(request,response);//服务器内部转发 可以获取数据 

application

客户端切换不同浏览器能访问,但是一旦断开连接,所有客户端都无法访问

// /d1请求1
application.setAttribute("usrname","Lili");
response.sendRedirect("/d2")//客户端重定向 请求2可以获取请求1的数据
request.getRequestDispatcher("/d2").forward(request,response);//服务器内部转发 可以获取数据 
//application.getAttribute("usrname");


ServletContext application =request.getServletContext();//获取Servlet上下文
application.getAttribute("usrname");

路径问题

所有路径都是以上下文路径为基础,后续自动拼接

<base href="http://localhost:8080/" />
<link rel="stylesheet" href="css/index.css">
<link rel="stylesheet" th:href="@{/css/index.css}">

MVC

MVC : Model(模型)、View(视图)、Controller(控制器)
视图层:用于做数据展示以及和用户交互的一个界面
控制层:能够接受客户端的请求,具体的业务功能还是需要借助于模型组件来完成
模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件

IOC

耦合/依赖

指代码间的依赖关系 , 设置原则高内聚低耦合

IOC - 控制反转 / DI - 依赖注入

控制反转:

  1. 之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl();
    这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
    如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别
  2. 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中
    因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转

依赖注入:

  1. 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
    那么,控制层和service层存在耦合。

  2. 之后,我们将代码修改成FruitService fruitService = null ;
    然后,在配置文件中配置:

    <bean id="fruit" class="FruitController">
         <property name="fruitService" ref="fruitService"/>
    </bean>```
    

示例

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();

    public ClassPathXmlApplicationContext(){
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
            //1.创建DocumentBuilderFactory
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            //2.创建DocumentBuilder对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
            //3.创建Document对象
            Document document = documentBuilder.parse(inputStream);

            //4.获取所有的bean节点
            NodeList beanNodeList = document.getElementsByTagName("bean");
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                Node beanNode = beanNodeList.item(i);
                if(beanNode.getNodeType() == Node.ELEMENT_NODE){
                    Element beanElement = (Element)beanNode ;
                    String beanId =  beanElement.getAttribute("id");
                    String className = beanElement.getAttribute("class");
                    Class beanClass = Class.forName(className);
                    //创建bean实例
                    Object beanObj = beanClass.newInstance() ;
                    //将bean实例对象保存到map容器中
                    beanMap.put(beanId , beanObj) ;
                    //到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
                }
            }
            //5.组装bean之间的依赖关系
            for(int i = 0 ; i<beanNodeList.getLength() ; i++){
                Node beanNode = beanNodeList.item(i);
                if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beanElement = (Element) beanNode;
                    String beanId = beanElement.getAttribute("id");
                    NodeList beanChildNodeList = beanElement.getChildNodes();
                    for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
                        Node beanChildNode = beanChildNodeList.item(j);
                        if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                            Element propertyElement = (Element) beanChildNode;
                            String propertyName = propertyElement.getAttribute("name");
                            String propertyRef = propertyElement.getAttribute("ref");
                            //1) 找到propertyRef对应的实例
                            Object refObj = beanMap.get(propertyRef);
                            //2) 将refObj设置到当前bean对应的实例的property属性上去
                            Object beanObj = beanMap.get(beanId);
                            Class beanClazz = beanObj.getClass();
                            Field propertyField = beanClazz.getDeclaredField(propertyName);
                            propertyField.setAccessible(true);
                            propertyField.set(beanObj,refObj);
                        }
                    }
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}

applicationContext.xml

<?xml version="1.0" encoding="utf-8"?>
<beans>
    <bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl">
        <!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet{

    private BeanFactory beanFactory ;

    public DispatcherServlet(){
    }

    public void init() throws ServletException {
        super.init();
        beanFactory = new ClassPathXmlApplicationContext();
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("UTF-8");
        //假设url是:  http://localhost:8080/pro15/hello.do
        //那么servletPath是:    /hello.do
        // 我的思路是:
        // 第1步: /hello.do ->   hello   或者  /fruit.do  -> fruit
        // 第2步: hello -> HelloController 或者 fruit -> FruitController
        String servletPath = request.getServletPath();
        servletPath = servletPath.substring(1);
        int lastDotIndex = servletPath.lastIndexOf(".do") ;
        servletPath = servletPath.substring(0,lastDotIndex);

        Object controllerBeanObj = beanFactory.getBean(servletPath);

        String operate = request.getParameter("operate");
        if(StringUtil.isEmpty(operate)){
            operate = "index" ;
        }

        try {
            Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
            for(Method method : methods){
                if(operate.equals(method.getName())){
                    //1.统一获取请求参数

                    //1-1.获取当前方法的参数,返回参数数组
                    Parameter[] parameters = method.getParameters();
                    //1-2.parameterValues 用来承载参数的值
                    Object[] parameterValues = new Object[parameters.length];
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        String parameterName = parameter.getName() ;
                        //如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了
                        if("request".equals(parameterName)){
                            parameterValues[i] = request ;
                        }else if("response".equals(parameterName)){
                            parameterValues[i] = response ;
                        }else if("session".equals(parameterName)){
                            parameterValues[i] = request.getSession() ;
                        }else{
                            //从请求中获取参数值
                            String parameterValue = request.getParameter(parameterName);
                            String typeName = parameter.getType().getName();

                            Object parameterObj = parameterValue ;

                            if(parameterObj!=null) {
                                if ("java.lang.Integer".equals(typeName)) {
                                    parameterObj = Integer.parseInt(parameterValue);
                                }
                            }

                            parameterValues[i] = parameterObj ;
                        }
                    }
                    //2.controller组件中的方法调用
                    method.setAccessible(true);
                    Object returnObj = method.invoke(controllerBeanObj,parameterValues);

                    //3.视图处理
                    String methodReturnStr = (String)returnObj ;
                    if(methodReturnStr.startsWith("redirect:")){        //比如:  redirect:fruit.do
                        String redirectStr = methodReturnStr.substring("redirect:".length());
                        response.sendRedirect(redirectStr);
                    }else{
                        super.processTemplate(methodReturnStr,request,response);    // 比如:  "edit"
                    }
                }
            }

            /*
            }else{
                throw new RuntimeException("operate值非法!");
            }
            */
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Filter

拦截 过滤 放行

创建Filter

  • 要点1:实现javax.servlet.Filter接口
  • 要点2:在doFilter()方法中执行过滤
  • 要点3:如果满足过滤条件使用 chain.doFilter(request, response);放行
  • 要点4:如果不满足过滤条件转发或重定向请求
    • 附带问题:Thymeleaf模板渲染。这里我们选择的解决办法是跳转到一个Servlet,由Servlet负责执行模板渲染返回页面。
public class Target01Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 1.打印一句话表明Filter执行了
        System.out.println("过滤器执行:Target01Filter");

        // 2.检查是否满足过滤条件
        // 人为设定一个过滤条件:请求参数message是否等于monster
        // 等于:放行
        // 不等于:将请求跳转到另外一个页面
        // ①获取请求参数
        String message = request.getParameter("message");

        // ②检查请求参数是否等于monster
        if ("monster".equals(message)) {

            // ③执行放行
            // FilterChain对象代表过滤器链
            // chain.doFilter(request, response)方法效果:将请求放行到下一个Filter,
            // 如果当前Filter已经是最后一个Filter了,那么就将请求放行到原本要访问的目标资源
            chain.doFilter(request, response);

        }else{

            // ④跳转页面
            request.getRequestDispatcher("/SpecialServlet?method=toSpecialPage").forward(request, response);

        }

    }

    @Override
    public void destroy() {

    }
}
<!-- 配置Target01Filter -->
<filter>
    <!-- 配置Filter的名称 -->
    <filter-name>Target01Filter</filter-name>
    <!-- 配置Filter的全类名,便于Servlet容器创建Filter对象 -->
    <filter-class>com.atguigu.filter.filter.Target01Filter</filter-class>
</filter>

<!-- 配置Filter要拦截的目标资源 -->
<filter-mapping>
    <!-- 指定这个mapping对应的Filter名称 -->
    <filter-name>Target01Filter</filter-name>

    <!-- 通过请求地址模式来设置要拦截的资源 -->
    <url-pattern>/Target01Servlet</url-pattern>
</filter-mapping>

生命周期

和Servlet生命周期类比,Filter生命周期的关键区别是:在Web应用启动时创建对象

生命周期阶段 执行时机 执行次数
创建对象 Web应用启动时 一次
初始化 创建对象后 一次
拦截请求 接收到匹配的请求 多次
销毁 Web应用卸载前 一次

匹配规则

精准匹配

<!-- 配置Filter要拦截的目标资源 -->
<filter-mapping>
    <!-- 指定这个mapping对应的Filter名称 -->
    <filter-name>Target01Filter</filter-name>
    <!-- 通过请求地址模式来设置要拦截的资源 -->
    <url-pattern>/Target01Servlet</url-pattern>
</filter-mapping>

模糊匹配

在我们配置了url-pattern为/user/*之后,请求地址只要是/user开头的那么就会被匹配。

<filter-mapping>
    <filter-name>Target02Filter</filter-name>

    <!-- 模糊匹配:前杠后星 -->
    <!--
        /user/Target02Servlet
        /user/Target03Servlet
        /user/Target04Servlet
    -->
    <url-pattern>/user/*</url-pattern>
</filter-mapping>

极端情况:/*匹配所有请求

<filter>
    <filter-name>Target04Filter</filter-name>
    <filter-class>com.atguigu.filter.filter.Target04Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>Target04Filter</filter-name>
    <url-pattern>*.png</url-pattern>
</filter-mapping>

包含png就匹配<img th:src="@{/./images/img025.png}"/><br/>

错误规则: <url-pattern>/*.png</url-pattern>

匹配Servlet名称

<filter-mapping>
    <filter-name>Target05Filter</filter-name>

    <!-- 根据Servlet名称匹配 -->
    <servlet-name>Target01Servlet</servlet-name>
</filter-mapping>

过滤器链

  • 多个Filter的拦截范围如果存在重合部分,那么这些Filter会形成Filter链
  • 浏览器请求重合部分对应的目标资源时,会依次经过Filter链中的每一个Filter。
  • Filter链中每一个Filter执行的顺序是由web.xml中filter-mapping配置的顺序决定的。./images
<filter-mapping>
    <filter-name>TargetChain03Filter</filter-name>
    <url-pattern>/Target05Servlet</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>TargetChain02Filter</filter-name>
    <url-pattern>/Target05Servlet</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>TargetChain01Filter</filter-name>
    <url-pattern>/Target05Servlet</url-pattern>
</filter-mapping>

执行顺序 3 - 2 - 1

过滤器链
1)执行的顺序依次是: A B C demo03 C2 B2 A2
2)如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
3)如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序

Listener

观察者模式

  • 观察者:监控『被观察者』的行为,一旦发现『被观察者』触发了事件,就会调用事先准备好的方法执行操作。
  • 被观察者:『被观察者』一旦触发了被监控的事件,就会被『观察者』发现。

监听器

​ 监听器:专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。 Servlet监听器:Servlet规范中定义的一种特殊类,它用于监听Web应用程序中的ServletContext,HttpSession 和HttpServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。

./images

  • 域对象监听器
  • 域对象的属性域监听器
  • Session域中数据的监听器

监听器列表

ServletContextListener

作用:监听ServletContext对象的创建与销毁

方法名 作用
contextInitialized(ServletContextEvent sce) ServletContext创建时调用
contextDestroyed(ServletContextEvent sce) ServletContext销毁时调用

ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。

HttpSessionListener

作用:监听HttpSession对象的创建与销毁

方法名 作用
sessionCreated(HttpSessionEvent hse) HttpSession对象创建时调用
sessionDestroyed(HttpSessionEvent hse) HttpSession对象销毁时调用

HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象。

ServletRequestListener

作用:监听ServletRequest对象的创建与销毁

方法名 作用
requestInitialized(ServletRequestEvent sre) ServletRequest对象创建时调用
requestDestroyed(ServletRequestEvent sre) ServletRequest对象销毁时调用

ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象。

ServletContextAttributeListener

作用:监听ServletContext中属性的创建、修改和销毁

方法名 作用
attributeAdded(ServletContextAttributeEvent scab) 向ServletContext中添加属性时调用
attributeRemoved(ServletContextAttributeEvent scab) 从ServletContext中移除属性时调用
attributeReplaced(ServletContextAttributeEvent scab) 当ServletContext中的属性被修改时调用

ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletContext() 获取ServletContext对象

HttpSessionAttributeListener

作用:监听HttpSession中属性的创建、修改和销毁

方法名 作用
attributeAdded(HttpSessionBindingEvent se) 向HttpSession中添加属性时调用
attributeRemoved(HttpSessionBindingEvent se) 从HttpSession中移除属性时调用
attributeReplaced(HttpSessionBindingEvent se) 当HttpSession中的属性被修改时调用

HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getSession() 获取触发事件的HttpSession对象

ServletRequestAttributeListener

作用:监听ServletRequest中属性的创建、修改和销毁

方法名 作用
attributeAdded(ServletRequestAttributeEvent srae) 向ServletRequest中添加属性时调用
attributeRemoved(ServletRequestAttributeEvent srae) 从ServletRequest中移除属性时调用
attributeReplaced(ServletRequestAttributeEvent srae) 当ServletRequest中的属性被修改时调用

ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletRequest () 获取触发事件的ServletRequest对象

HttpSessionBindingListener

作用:监听某个对象在Session域中的创建与移除

方法名 作用
valueBound(HttpSessionBindingEvent event) 该类的实例被放到Session域中时调用
valueUnbound(HttpSessionBindingEvent event) 该类的实例从Session中移除时调用

HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取当前事件涉及的属性名
getValue() 获取当前事件涉及的属性值
getSession() 获取触发事件的HttpSession对象

HttpSessionActivationListener

作用:监听某个对象在Session中的序列化与反序列化。

方法名 作用
sessionWillPassivate(HttpSessionEvent se) 该类实例和Session一起钝化到硬盘时调用
sessionDidActivate(HttpSessionEvent se) 该类实例和Session一起活化到内存时调用

HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。

示例

创建监听器类

public class AtguiguListener implements ServletContextListener {
    @Override
    public void contextInitialized(
            // Event对象代表本次事件,通过这个对象可以获取ServletContext对象本身
            ServletContextEvent sce) {
        System.out.println("Hello,我是ServletContext,我出生了!");

        ServletContext servletContext = sce.getServletContext();
        System.out.println("servletContext = " + servletContext);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Hello,我是ServletContext,我打算去休息一会儿!");
    }
}

注册监听器

<!-- 每一个listener标签对应一个监听器配置,若有多个监听器,则配置多个listener标签即可 -->
<listener>
    <!-- 配置监听器指定全类名即可 -->
    <listener-class>com.atguigu.listener.AtguiguListener</listener-class>
</listener>

ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

用处:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息。

4、数据库连接,Session会话管理。

public class ThreadLocalTest01 {
    public static void main(String[] args) {
        //新建一个ThreadLocal
        ThreadLocal<String> local = new ThreadLocal<>();
        //新建一个随机数类
        Random random = new Random();
        //使用java8的Stream新建5个线程
        IntStream.range(0, 5).forEach(a-> new Thread(()-> {
            //为每一个线程设置相应的local值
            local.set(a+"  "+random.nextInt(10));   
            System.out.println("线程和local值分别是  "+ local.get());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start());
    }
}
/*线程和local值分别是  0  6
  线程和local值分别是  1  4
  线程和local值分别是  2  3
  线程和local值分别是  4  9
  线程和local值分别是  3  5 */
//获取ThreadLocal的值
public T get() { }
//设置ThreadLocal的值
public void set(T value) { }
//删除ThreadLocal
public void remove() { }
//初始化ThreadLocal的值
protected T initialValue() { }

set()

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。

get()

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

remove()

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

内存泄露

ThreadLocalMap的为null,且线程一直存在(比如线程池),后续继续使用这个线程,ThreadLocalMap会不断增大,继而发生内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况

创建Cookie

//创建一个 Cookie 对象:您可以调用带有 cookie 名称和 cookie 值的 Cookie 构造函数,cookie 名称和 cookie 值都是字符串
Cookie cookie = new Cookie("key","value");
//设置最大生存周期 (以秒为单位)
cookie.setMaxAge(60*60*24); 
//发送 Cookie 到 HTTP 响应头
response.addCookie(cookie);

Servlet Cookie 处理需要对中文进行编码与解码,方法如下:

// 编码
Cookie cookie = new Cookie("Chinese", URLEncoder.encode("中文", "utf-8"));
// 解码
System.out.println(URLDecoder.decode(cookie.getValue(), "utf-8"));
String   str   =   java.net.URLEncoder.encode("中文","UTF-8");            //编码
String   str   =   java.net.URLDecoder.decode("编码后的字符串","UTF-8");   // 解码
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
    // 为名字和姓氏创建 Cookie      
    Cookie name = new Cookie("name",
                             URLEncoder.encode(request.getParameter("name"), "UTF-8")); // 中文转码
    Cookie url = new Cookie("url",
                            request.getParameter("url"));

    // 为两个 Cookie 设置过期日期为 24 小时后
    name.setMaxAge(60*60*24); 
    url.setMaxAge(60*60*24); 

    // 在响应头中添加两个 Cookie
    response.addCookie( name );
    response.addCookie( url );

    // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();
    String title = "设置 Cookie 实例";
    String docType = "<!DOCTYPE html>\n";
    out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<ul>\n" +
                "  <li><b>站点名:</b>:"
                + request.getParameter("name") + "\n</li>" +
                "  <li><b>站点 URL:</b>:"
                + request.getParameter("url") + "\n</li>" +
                "</ul>\n" +
                "</body></html>");
}

读取Cookie

要读取 Cookie,您需要通过调用 HttpServletRequestgetCookies( ) 方法创建一个 javax.servlet.http.Cookie 对象的数组。然后循环遍历数组,并使用 getName() 和 getValue() 方法来访问每个 cookie 和关联的值。

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
    Cookie cookie = null;
    Cookie[] cookies = null;
    // 获取与该域相关的 Cookie 的数组
    cookies = request.getCookies();

    // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();
    String title = "Delete Cookie Example";
    String docType = "<!DOCTYPE html>\n";
    out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" );
    if( cookies != null ){
        out.println("<h2>Cookie 名称和值</h2>");
        for (int i = 0; i < cookies.length; i++){
            cookie = cookies[i];
            if((cookie.getName( )).compareTo("name") == 0 ){
                cookie.setMaxAge(0);
                response.addCookie(cookie);
                out.print("已删除的 cookie:" + 
                          cookie.getName( ) + "<br/>");
            }
            out.print("名称:" + cookie.getName( ) + ",");
            out.print("值:" +  URLDecoder.decode(cookie.getValue(), "utf-8") +" <br/>");
        }
    }else{
        out.println(
            "<h2 class=\"tutheader\">No Cookie founds</h2>");
    }
    out.println("</body>");
    out.println("</html>");
}

删除Cookie

  • 读取一个现有的 cookie,并把它存储在 Cookie 对象中。
  • 使用 setMaxAge() 方法设置 cookie 的年龄为零,来删除现有的 cookie。
  • 把这个 cookie 添加到响应头。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
    Cookie cookie = null;
    Cookie[] cookies = null;
    // 获取与该域相关的 Cookie 的数组
    cookies = request.getCookies();

    // 设置响应内容类型
    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();
    String title = "删除 Cookie 实例";
    String docType = "<!DOCTYPE html>\n";
    out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" );
    if( cookies != null ){
        out.println("<h2>Cookie 名称和值</h2>");
        for (int i = 0; i < cookies.length; i++){
            cookie = cookies[i];
            if((cookie.getName( )).compareTo("url") == 0 ){
                cookie.setMaxAge(0);
                response.addCookie(cookie);
                out.print("已删除的 cookie:" + 
                          cookie.getName( ) + "<br/>");
            }
            out.print("名称:" + cookie.getName( ) + ",");
            out.print("值:" + cookie.getValue( )+" <br/>");
        }
    }else{
        out.println(
            "<h2 class=\"tutheader\">No Cookie founds</h2>");
    }
    out.println("</body>");
    out.println("</html>");
}