1、context容器是什么?
直观上面看,context容器就是tomcat的webapps下面的一个应用
java的web应用,你是不是一下就想到了servlet,filter,listener这三大神器。context容器就是管理它们的。
这篇博文就是了解context如何管理它们的
从功能是我们回顾一下
servlet:处理能匹配到的请求的类
fiter:针对指定请求必须经过的类
listener:发生特定时间通知到的类
2、Servlet管理
之前我们有讲过Wrapper容器就是Servlet的一个包装类。主要负责的就是实例化和调用service。
我们知道web.xml的Servlet配置有一个loadOnStartUp,如果这个参数配置大于1,就会在启动tomcat的时候就实例化这个Servlet,这个工作就是在Context中管理完成的。
StandardContext中的StartInternal有这么一段
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
public boolean loadOnStartup(Container children[]) {
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
// children中保存的就是web.xml中配置的所有的Servlet对应的Wrapper容器
// 这里就是遍历所有的容器,查看其loadOnStartUp
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0)
continue;
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
// 如果loadOnStartUp大于0,加入Map中
list.add(wrapper);
}
// 遍历map,也就是所有符合启动就加载的Wrapper
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
// 实例化Wrapper中的Servlet
wrapper.load();
}
}
}
return true;
}
Context在启动的时候就会找到所有满足启动需要加载的Servlet,也就是Wrapper中配置的loadOnStartup参数大于0,实例化这个Servlet。
3 Filter管理
在Wrapper中涉及到Filter的业务操作,但是我并没有选择在那一篇中讲解Filter,毕竟真正管理Filter的是Context容器。
我们看看Context容器是怎么管理Filter
下面代码是StandardContext启动器中的一段
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
synchronized (filterConfigs) {
filterConfigs.clear();
// filterDefs在解析web.xml的时候生成
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
// 创建一个FilterConfig
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
}
}
这里创建了FilterConfig,我们来看看filterConfig是些什么内容
比如web.xml中配置了一个名为filter1的过滤器,全限定名为com.yanlink.filter.Filter1,匹配模式为/hello,配置如下
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.yanlink.filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/hello</url-pattern>
</filter-mapping>
先看看filterDef保存的什么
filterDef{
filterClass=com.yanlink.filter.Filter1
filterName=filter1
}
再看看filterConfig
filterConfig{
context:当前容器对象
filterDef:上面的对象
filter:对应的filter对象
}
我们还是来看看new ApplicationFilterConfig做了些什么
ApplicationFilterConfig(Context context, FilterDef filterDef)
throws ClassCastException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
ServletException, InvocationTargetException, NamingException {
super();
// 保存容器上下文
this.context = context;
// 保存过滤器定义
this.filterDef = filterDef;
if (filterDef.getFilter() == null) {
// 创建过滤器
getFilter();
} else {
this.filter = filterDef.getFilter();
getInstanceManager().newInstance(filter);
initFilter();
}
}
Filter getFilter() throws ClassCastException, ClassNotFoundException,
IllegalAccessException, InstantiationException, ServletException,
InvocationTargetException, NamingException {
// Return the existing filter instance, if any
if (this.filter != null)
return (this.filter);
// 获取类的全限定名
String filterClass = filterDef.getFilterClass();
// 实例化过滤器对象
this.filter = (Filter) getInstanceManager().newInstance(filterClass);
// 初始化过滤器对象也就是调用filter的init
initFilter();
//
return (this.filter);
}
我们再来看看filter在Wrapper容器中的使用
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// 必须需要一个servlet,没有servlet的过滤器没意义
if (servlet == null)
return null;
// Create and initialize a filter chain object
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
// 获取wrapper对应的Context容器
StandardContext context = (StandardContext) wrapper.getParent();
// 上下文中获取filterMap,这个是用于filter与请求的uri匹配的,找到合适的filter
FilterMap filterMaps[] = context.findFilterMaps();
String servletName = wrapper.getName();
// 过滤器匹配
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
// 在context中根据过滤器的名字找到对应的filterConfig
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
// 找到filterConfig加入到过滤器链
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
context中创建的filterConfig在这里就有用到,上面的代码是由工厂方法创建了一个FilterChain,这个filterChain包含了一个servlet和一个filter数组。
servlet是必须有的,没有目标的servlet的过滤器链是没有意义的,所有如果servlet为null,返回null
从context中找一个filterMap,字面意思这是个映射器,我们看看保存的是什么
filterMap{
filterName=filter1,
urlPatterns:["/hello"]
}
从数据结构可以看出来,这个映射器是通过请求的uri来匹配对应的filter名称
然后在context中找到之前配置的filterConfigs,根据filterName匹配到对应的filterConfig,加入filterChain中
接下来看看filterChain的调用
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
//
if (pos < n) {
// 从过这个pos定位执行到的filter
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
// 并且把自己的对象传过去,在这里为什么没有for,关键点在于传入的这个this
filter.doFilter(request, response, this);
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
// 过滤器执行完毕再执行service
servlet.service(request, response);
}
}
看完这个大家是不是存在一些疑惑,没有for循环,怎么执行完所有的filter的。我们结合下应用使用filter的代码
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("filter1 dofilter");
// 这里通过chain再次调用了doFilter
chain.doFilter(request, response);
}
看到chain.doFilter没,这个chain就是上面的this,也就是把自己这个对象传过去,应用的时候需要靠开发人员加入这个chain.doFilter如果没有则访问不到servlet,宏观层面看看,这个是不是和pipeline一样还是一个责任链的模式,只是使用了另一种实现方式
4、Listener管理
这里的listener我针对的是ServletContextListener ,我们来看看其接口
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization
** process is starting.
** All ServletContextListeners are notified of context
** initialization before any filter or servlet in the web
** application is initialized.
*/
public void contextInitialized ( ServletContextEvent sce );
/**
** Notification that the servlet context is about to be shut down.
** All servlets and filters have been destroy()ed before any
** ServletContextListeners are notified of context
** destruction.
*/
public void contextDestroyed ( ServletContextEvent sce );
}
我们来看看关于监听器的代码
public boolean listenerStart() {
// Instantiate the required listeners
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
// listener是监听器的全限定名
String listener = listeners[i];
// 实例化listener
results[i] = getInstanceManager().newInstance(listener);
}
ArrayList<Object> eventListeners = new ArrayList<>();
ArrayList<Object> lifecycleListeners = new ArrayList<>();
for (int i = 0; i < results.length; i++) {
// 加入lifecycleListeners
lifecycleListeners.add(results[i]);
}
getServletContext();
// 获取当前关注容器状态的监听者
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
// 创建事件,也就是把context对象带过去,这里是一个外观类
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextListener listener = (ServletContextListener) instances[i];
// 执行listener的容器初始化
listener.contextInitialized(event);
return (ok);
}
这里也就是一个典型的观察者模式,容器中注册 了所有的对context当前状态感兴趣的对象,context在启动和销毁的时候会给这些对象发送通知。
我们看看ServletContextListener接口中的参数ServletContextEvent ,在tomcat中创建这个事件的时候传入了一个容器对象,这个容器对象并不是this,而是一个外观类。这样保护了context类,免得一些不安全的方法开放给了开发者。
5、基础阀
再看看context与wrapper管道对接的基础阀StandardContextValve
// Disallow any direct access to resources under WEB-INF or META-INF
Wrapper wrapper = request.getWrapper();
if (wrapper == null || wrapper.isUnavailable()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
wrapper.getPipeline().getFirst().invoke(request, response);
这块逻辑还是相对比较简单的,找到Wrapper容器,调用Wrapper的管道的第一个阀的invoke,这里找到Wrapper是在request中实现的,我们可以大概猜测一下,Wrapper容器中是有Name的这个name也就是,我们在请求的uri中去掉ContextPath部分,剩下的部分做wrapper匹配,找到匹配度最高的Wrapper。