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 {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("context init");
}
@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 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 {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@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 {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter");
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下面的部分
|