一、ContextLoaderListener
1.基本原理:
(1**)实现了servlet-api中的ServletContextListener,实现了ContextInitialized方法和contextDestroyed方法**
(2)每个Web都对应一个ServletContext,应用启动时创建,应用关闭时销毁,全局唯一
(3)ServletContextListener的使用举例,在web.xml中配置监听器即可,初始化会执行contextInitialize方法, 关闭时会执行ContextDestroyed方法
public class TestController implements ServletContextListener {
private ServletContext servletContext;
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//应用启动的时候,就会执行这里初始化的方
this.servletContext = servletContextEvent.getServletContext();
System.out.println(this.servletContext.getContextPath());
System.out.println(this.servletContext.getInitParameterNames());
InputStream inputStream = this.servletContext.getResourceAsStream("test.xml");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
<listener>
<listener-class>person.controller.TestController</listener-class>
</listener>
2.源码分析
(1)ServletContextListener中的contextInitialized
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
//ContextLoader中
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
......//不能存在多个ServletContextListener,即在web.xml中配置的spring监听器只能有一个,否则报错
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
try {
if (this.context == null) {
this.context = createWebApplicationContext(servletContext); //创建webApplicationContext实例
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//存在servletContext中,key是 org.springframework.web.context.WebApplicationContext.ROOT,value是一个webApplicationContext的实例
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
......//catch异常,以及各种日志
}
(2)ContextLoader中createWebApplicationContext创建webApplicationContext实例
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc); //这里是决定webApplicationContext接口库具体的实现类,见下面
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); //通过class.newInstance反射出对象实例
}
//ContextLoader中有静态域,加载properties文件,配置的默认的实现类为 XmlWebApplicationContext
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}......
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);//这里加载的是webxml中配置的WebApplicationcontext的实现类,通过context-param设置
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}.....catch异常
}
else {//如果没有配置,则去加载properties中的配置项,获得Class对象
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
}
(3) web.xml中的 param-name全部在ContextLoader中定义
<!--这里的 param-name全部在ContextLoader中定义的-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.GenericWebApplicationContext</param-value>
</context-param>
二、DispatcherServlet
1.dispatcherServlet的继承关系图
2.dispatcherSlervlet的初始化 即init()方法的重写,在父类HttpServletBean中重写了
(1)init()
public final void init() throws ServletException {
//解析<init-param>属性中的值并封装
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try { //将当前的Servlet即DispatcherSerlvet封装成spring的bean
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw); //钩子方法,留子类实现
bw.setPropertyValues(pvs, true); //属性注入
}
this.initServletBean(); //钩子方法,子类扩展用
}
ps init()方法主要做的事情:
将web.xml中配置的init-param封装成PropertyValue类
将当前的Servlet封装成spring的bean,目的是将属性能够注入进去
属性注入
调用子类复写的钩子方法//即FrameWorkServlet中实现的方法,对监听器中创建的WebApplicationContext的实例继续进行补充初始化
(2)FrameworkServlet中重写了父类的HttpServletBean的方法,对监听器中创建的WebApplicationContext的实例继续进行补充初始化
protected final void initServletBean() throws ServletException {
....//省略
this.webApplicationContext = this.initWebApplicationContext(); //继续初始化webApplicationContext实例
this.initFrameworkServlet();//钩子方法,留给子类实现
......//catch方法
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) { //不为空则是直接通过实例化FrameworkServlet注入的属性
wac = this.webApplicationContext;
this.configureAndRefreshWebApplicationContext(cwac); //配置webApplicationContext的属性
}
}
if (wac == null) { //如果依然为空,则找到之前在ContextLoaderListener中初始化实例的webapplicationContext实例
wac = this.findWebApplicationContext();
}
if (wac == null) { //如果还为空则通过反射的方式创建
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
this.onRefresh(wac); //这是个模板方法,在子类dispatcherServlet中重写,初始化WebApplicationContext 的很多全局变量
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
(3).onrefresh 在dispatcherServlet中重写的方法,初始化web应用很多功能需要的全局变量
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
//该方法调用的都是一些获取bean的方法
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context); //处理文件上传的bean
this.initLocaleResolver(context); //处理国际化的bean
this.initThemeResolver(context);//处理主题资源的bean,切换主题静态资源所在路径
this.initHandlerMappings(context);//初始化handlerMapping,默认会加载所有的实现了handlermapping接口的bean
this.initHandlerAdapters(context); //初始化HandlerAdapters
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
//默认情况下是没有配置该bean的,如果需要做文件上传,需要配置,配置如下,如果没有配置,
private void initMultipartResolver(ApplicationContext context) {
.....
try {
this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class);
}.......
}
在配置文件中要配置改bean
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSizePerFile" value="3"/>
</bean>
ps :init方法中获取以上的bean,由此验证bean的加载是在ContextLoaderListener监听器中进行的,监听器的加载在dispatcherServlet之前
(4) this.initRequestToViewNameTranslator(context);//当controller中没有返回view对象或逻辑视图名称,且未向response中写数据时,spring会以默认方式提供一个视图名 ,方式是实现接口RequestToViewNameTranslator的getNiewName,然后通过id=viewNameTranslator配置bean的方式,将实现类配置进去,spring默认的实现类是DefaultRequestToViewNameTranslator
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private static final String SLASH = "/";
private String prefix = "";//前缀
private String suffix = "";//后缀
private String separator = "/";
private boolean stripLeadingSlash = true;//首字符分隔符时默认去除
private boolean stripTrailingSlash = true;//结尾为分隔符则默认去除
private boolean stripExtension = true; //请求路径中包含扩展名时默认去除扩展名,eg:test.html会去掉.html
private UrlPathHelper urlPathHelper = new UrlPathHelper();
}
this.initViewResolvers(context);//初始化视图解析器
3.dispatcherServlet的逻辑处理
(1)doService中主要是一些准备工作,将init中初始化的一些类和listener中实例化的webapplicationcontext实例设置为request的属性
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
........
//init中初始化的东西
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());//默认是xmlwebapplicationcontext
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
this.doDispatch(request, response);//将包装好的request交给doDispatch,见下面
}..... }
(2)doDispatcher最终处理完成的请求逻辑
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request)
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try { //判断如果是MultipartContent类型的request则转换为multiPartHttpServletRequest,文件上传与普通request不一样
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest); //根据request信息寻找对应的handler,handler就是HandlerExecutionChain实例,见下面
if (mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response); //如果没有,则返回404,见下面
return;
}//寻找适配器,遍历选择类型合适的适配器,适配器很重要,里面包含了处理请求的所有要素
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
...... //拦截连的调用 ,见下面
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//处理handle并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
this.applyDefaultViewName(processedRequest, mv); //这里通过request处理需要视图名称添加前缀后缀的情况
mappedHandler.applyPostHandle(processedRequest, response, mv); //postHandle后处理方法
}//这里处理异常之后的的页面或者普通页面跳转,其中的render方法就是页面跳转
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) { //异常则处理触发器
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
}
}
}
ii.根据reqeust信息寻找对应的Handler,getHandler,系统启动时会将所有的映射类型的bean注册到handlerMappings中
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Iterator var2 = this.handlerMappings.iterator(); //对handlermapping实现类进行遍历
HandlerExecutionChain handler;
do {
if (!var2.hasNext()) {
return null;
}
HandlerMapping hm = (HandlerMapping)var2.next();
handler = hm.getHandler(request); //调用gethandler封装成HandlerExecutionChain,HandlerMapping的默认实现类是AbstractHandlerMapping
} while(handler == null);
return handler;
}
iii.关于handlerMappings 的说明,DispatcherServlet中有个属性private List handlerMappings;
关于HandlerMapping类的spring注释说明:这个接口被那些在beanName和request之间做映射的类实现,spring中有beanNameUrlHandlerMapping和defaultAnnotationHandlermapping两个实现类,一般不需要再开发;handlerMappings集合里一般也就是这两个实现类
iv.AbstractHandlerMapping中的getHandler实现
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = this.getHandlerInternal(request); //根据url找到匹配的Controller即Handler返回,见下面
if (handler == null) { //如果没有找到对应的Controller,那么会去查默认的处理器,spring中是可以配置默认的控制器的
handler = this.getDefaultHandler();
}
if (handler == null) { //如果还是为空,则说明没有找到匹配的controller
return null;
} else { //string类型,说明是配置的bean的名称,则根据beanName去找bean,
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.getApplicationContext().getBean(handlerName);
}
//封装并返回
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
CorsConfiguration config = globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig;
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
v.AbstractHandlerMapping中的是个抽象方法,方法实现在AbstractUrlHandlerMapping
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);//截取路径,即剔除根路径
Object handler = this.lookupHandler(lookupPath, request);//根据路径寻找Handler,这里的Handler一般是个HandlerEXecutionCChain对象,其中包括了请求的控制器,和请求的方法
if (handler == null) {
Object rawHandler = null;
if ("/".equals(lookupPath)) { //路径如果是/,则使用RootHandler
rawHandler = this.getRootHandler();
}//没有则去找默认的控制器,一般都为空
if (rawHandler == null) {
rawHandler = this.getDefaultHandler();
}
//String属于xml中配置的beanName
if (rawHandler != null) {
if (rawHandler instanceof String) {
String handlerName = (String)rawHandler;
rawHandler = this.getApplicationContext().getBean(handlerName);
}
this.validateHandler(rawHandler, request);
handler = this.buildPathExposingHandler(rawHandler, lookupPath, lookupPath, (Map)null);//将原生的handler进行充实,将handler封装成了HandlerExecutionChain
}
}
..............//日志
return handler;
}
vi. 在DispatcherServlet中noHandlerFound处理没有找到Handler的情况,一个请求对应一个handler(HandlerExecutionChain实例),一般开发人员可以配置默认的控制器,当没有找到handler则去找默认的,都没有则走该方法
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), (new ServletServerHttpRequest(request)).getHeaders());
} else {
response.sendError(404); //前台返回404
}
}