Java学习 day56_Web组件

Web组件

Listener

监听器。

在EE规范早期,其实只有servlet一个组件,后面又引入了listener、filter等另外两个组件。

JavaEE中有三大组件:Servlet(开发动态web资源)、Listener(监听器)、Filter(过滤器)


web中的监听器:

被监听者:比如ServletContext对象

监听者:编写的监听器

监听事件:ServletContext对象的创建和销毁

触发事件:调用监听器里面对应的方法


使用(掌握)

1.编写一个类实现ServletContextListener接口

2.配置该Listener(web.xml 注解)

<listener>
    <listener-class>com.cskaoyan.listener.MyServletContextListener</listener-class>
</listener>

或者注解

package com.cskaoyan.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyServletContextListener implements ServletContextListener {

    /**
     * 在servletContext被创建的时候执行该代码逻辑
     * @param servletContextEvent
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("context init");
    }

    /**
     * 在servletContext对象被销毁的时候执行该代码逻辑
     * @param servletContextEvent
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("context destroy");
    }
}

不需要通过访问任何地址就可以执行。

有什么用呢?

其实我们可以将之前写在servlet的init方法里面,然后设置load-on-startup=1的这些代码逻辑放到listener里面来写。更为恰当一些。

如果之前想设置一段代码,该代码的运行和当前servlet是否被访问无关,那么可以设置一个init ,随着应用加载而运行。将这块代码逻辑写在init里面。

这段代码逻辑其实和当前servlet其实关系并不是特别的紧密。比如购物车案例,实例化商品。可以写在indexServlet中,也可以写在GoodsServlet中,只要设置init load-on-startup=1即可。

这段代码其实可以认为是一个全局性的代码。全局性的代码最好不要写在某个serlvet中。因为如果你写在某个serlvet中,别人很容易认为该段代码逻辑是和当前servlet是密切相关的。

而应该写在listener里面。

你可以认为最开始时,只有servlet,只能将这些全局性代码写在sevlet init中, 后面版本中出现了listener,建议就将这些代码逻辑写到listenner中。


原理(不做统一要求)

listener是如何实现的?

ServletCotontext对象被创建的这段代码应该是早就已经写好了的,我们编写的监听器是后来写的,如何做到十几年前的老代码调用你写的代码的呢?

宝宝哭,哄宝宝

爸爸干什么

妈妈干什么

package com.cskaoyan.listener.test;

public class Baby {
    private Dad dad;

    private Mom mom;

    public Baby(Dad dad, Mom mom) {
        this.dad = dad;
        this.mom = mom;
    }

    public void cry(){
        dad.boil();
        mom.feed();
    }
}
package com.cskaoyan.listener.test;

public class Dad {

    public void boil(){
        System.out.println("爸爸热奶");
    }
}
package com.cskaoyan.listener.test;

public class Mom {

    public void feed(){
        System.out.println("妈妈喂奶");
    }
}
package com.cskaoyan.listener.test;

public class MainTest {

    public static void main(String[] args) {
        Baby baby = new Baby(new Dad(),new Mom());
        baby.cry();
    }
}

但是爸爸妈妈都是独生子,也要工作,工作日就无人照看孩子,爷爷奶奶来照看

但是Baby里面维护的引用是爸爸和妈妈,怎么办?

周末还需要将爷爷奶奶变回来

爷爷和爸爸出去干活,奶奶和妈妈在家

package com.cskaoyan.listener.test.update;

public interface Family {

    void action();
}
package com.cskaoyan.listener.test.update;

public class Dad implements Family {
    @Override
    public void action() {
        System.out.println("爸爸热奶");
    }
}
package com.cskaoyan.listener.test.update;

public class Mom implements Family {
    @Override
    public void action() {
        System.out.println("妈妈喂奶");
    }
}
package com.cskaoyan.listener.test.update;

public class Grandpa implements Family {
    @Override
    public void action() {
        System.out.println("爷爷哄宝宝");
    }
}
package com.cskaoyan.listener.test.update;

public class Grandma implements Family {
    @Override
    public void action() {
        System.out.println("奶奶抱宝宝");
    }
}
package com.cskaoyan.listener.test.update;

import java.util.ArrayList;
import java.util.List;

public class Baby {

    private List<Family> members = new ArrayList<>();

    public void add(Family family){
        this.members.add(family);
    }

    public void cry(){
        for (Family member : members) {
            member.action();
        }
    }
}
package com.cskaoyan.listener.test.update;

public class MainTest {

    public static void main(String[] args) {
        Baby baby = new Baby();
//        baby.add(new Dad());
//        baby.add(new Mom());
        baby.add(new Grandma());
        baby.add(new Grandpa());
        baby.cry();
    }
}

在这里插入图片描述

ServletContextListener接口就是Family

MyServletContextListener

就是一个实现类

Baby其实就是tomcat里面当前应用的的ServletContext

ServletContext{
	List<ServletContextListener> listeners;
	
	add()----{listeners.add(listener)}
	
	
	init(){
		listeners.for{listener.contextInitialized()}
	}
	
	destroy(){
		listners.for{listener.contextDestroyed()}
	}


}

包装设计模式

监听器设计模式

代理设计模式

本质来说都是多态


Filter

概念

过滤器、拦截器。

打游戏的时候 发消息 我***

比如古城市,城门,进出城都需要经过城门。城门就类似于filter

filter位于servlet的前面,在请求到达servlet之前,会先经过filter

响应返回给客户端之前,要再次经过filter出去

过滤器关联的servlet


开发使用

1.编写一个类,实现Filter接口,实现方法

2.声明该Filter

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/filter1")
public class FirstFilter implements Filter {

    /**
     * 直接随着应用的启动而加载
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init");
    }

    /**
     * 类似于servlet的service方法,每当访问一次filter
     * 那么就会执行一次该方法
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter");
    }

    /**
     * 应用被卸载、服务器被关闭
     */
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

通过filter的介绍,我们清楚请求到达servlet之前会先经过filter,我们创建了一个servlet,访问该servlet

但是发现请求并没有经过filter。

说明了fitler此时并没有和servlet产生关联。

filter如何和servlet产生关联呢?

最简单的方式就是通过url-pattern,最简单的方式就是将servlet的url-pattern给filter。


Filter的注意事项

1.filter可以设置和servlet相同的url-pattern,这是完全允许的。

默认情况下,filter执行的是拦截操作,如果希望filter能够放行请求,需要设置一行代码

package com.cskaoyan.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/servlet1")
public class FirstFilter implements Filter {

    /**
     * 直接随着应用的启动而加载
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init");
    }

    /**
     * 类似于servlet的service方法,每当访问一次filter
     * 那么就会执行一次该方法
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter");
        //这行代码对于请求的进一步放行非常重要,如果没有这行代码,那么filter执行的就是拦截操作
        //如果有这行代码,那么filter执行的就是放行操作
        //比如未登录的情况下访问个人主页
        //这行代码的逻辑的比较复杂:其实是采用了递归的形式去调用下一个组件
        // 管道设计模式、责任链设计模式(不做要求)
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 应用被卸载、服务器被关闭
     */
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

2.filter和filter之间可不可以设置相同的url-pattern呢?

完全可以。

*3.filter可不可以设置/

完全可以。

其实filter设置/*还是很有必要的,假设设置编码格式的代码写在filter中,filter如果设置了/ *,那其实只需要写一处该代码即可,后面所有的servlet均无需重新设置。

4.既然filter可以设置相同的url-pattern,并且也都会加入到特定请求的处理中,那么执行的先后顺序是如何呢?

比如一个filter设置了/* 一个filter设置了/servlet1

那么当我访问/servlet1时,两个filter均进入到了该请求的执行中来,那么执行的先后顺序应该是什么样的呢?

如果多个filter加入到了一个请求中,那么执行的先后顺序按照如下顺序

1.如果是web.xml,按照filter-mapping声明的先后顺序

<filter-mapping>
    <filter-name>filter2</filter-name>
    <url-pattern>/servlet1</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>filter1</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2.如果是注解,按照类名首字母的ASCII先后顺序来

注意:和url-pattern优先级,覆盖范围没有任何关系。

有什么实际使用意义呢?

比如有一个后台管理系统(管理员使用的 管理员去维护数据的地方),有一个前台系统(用户使用的系统 jd.com)。

分别设置两个filter,这两个filter分别对应的是后台管理系统的filter和前台用户系统的filter

除此之外还可以设置一个全局性编码格式的filter

CharacterEncodingFilter /*

AdminFilter /admin/*

MallFilter /mall/*

CharacterEncodingFilter 、AdminFilter

假设AdminFilter里面需要有一个读取请求参数的逻辑,如果AdminFilter在前,CharacterEncodingFilter 在后,那么CharacterEncodingFilter 里面设置编码格式的代码逻辑还有效果吗?

CharacterEncodingFilter、 MallFilter


引入Filter之后的请求处理流程

以访问http://localhost/app/servlet1为例,阐述请求的执行流程

1.输入对应的地址,域名解析,建立TCP连接,发送HTTP请求报文,发送到目标机器之后

2.被监听80端口号的Connector程序接收到,将其解析成为reqeust对象,同时提供一个response对象

3.进一步交给engine、host,host去选择一个叫做/app的应用,如果找到,则将这两个对象交该该应用来处理

4.应用拿到的有效路径为/servlet1,首先先在当前应用下寻找是否有合适的filter可以处理该请求(/ /servlet1),发现有两个filter可以同时处理该请求,那么会将这两个filter按照filter先后顺序加入到一个链表中;filter寻找完毕,接下来去寻找有没有合适的servlet(/servlet1)可以处理该请求,将可以处理该请求的这个servlet加入到链表的后面(最终只会找到一个servlet,只会找到一个优先级最高的,如果没有找到servlet,会交给缺省Servlet,最终肯定有一个servlet)*

5.依次去调用链表上面的每一个组件,依次去执行filter的doFilter方法以及servlet的service方法,方法需要一个request、response对象,恰好有这两个对象,将这两个对象作为参数传递给对应的方法去执行

6.当这些组件的所有方法执行完毕,整个请求处理结束,最终Connector读取response里面的数据生成响应报文。

其实整个请求处理流程就是不同组件依次去操作request、response的过程。


案例

登录案例:

用户通过表单页面提交用户名、密码,登录成功,进入到info页面。info页面有一个特点:登录以后可以自由访问,没登录之前直接跳转到登录页面去。

问题:

既然通过图示,我们可以看出,请求进入servlet时会经过一次filter,然后返回给客户端时,又会经过一次filter,那么理应filter的doFilter方法里面的log应该打印两遍才对?为什么只打印了一遍?

doFilter before—filter doFilter方法之前
info-----servlet
doFilter after-----filter doFilter方法之后

为什么会这样呢?原因就是因为递归调用下一个组件。

你可以认为请求从客户端发送到servlet时,走的是doFilter方法上面的部分

从servlet响应回客户端时,代码走的是doFilter下面的部分

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值