《Spring技术内幕》笔记-第四章 Spring MVC与web环境
上下文在web容器中的启动
1,IoC容器的启动过程
IoC的启动过程就是建立上下文的过程。该上下文是与ServletContext相伴。在Spring中存在一个核心控制分发器,DispatcherServlet,这是Spring的核心。在web容器启动Spring应用程序时。首先建立根上下文,然后ContextLoader建立WebApplicationContext。
Web容器中启动Spring步骤例如以下:

在web.xml中。已经配置了ContextLoadListener,该类实现了ServletLoaderListener接口。
ServletLoaderListener是在Servlet API中定义的接口,接口提供Servlet生命周期。而详细的IoC加载过程是ContextLoadListener交由ContextLoader完毕,ContextLoader是ContextLoadListener的父类。三者关系例如以下:

在ContextLoader中。完毕了两个IoC建立的基本过程,一是在Web容器中建立起双亲IoC容器,还有一个是生成对应的WebApplicationContext并将其初始化。
2。Web容器中的上下文设计。
Spring为Web提供了上下文扩展接口WebApplicationContext来满足启动过程的须要。
该接口主要提供getServletContext方法,通过这种方法能够获取容器的Web上下文。
/*** Return the standard Servlet API ServletContext for this application.* <p>Also available for a Portlet application, in addition to the PortletContext.*/ServletContext getServletContext();
在启动过程中,Spring使用XmlWebApplicationContext作为默认的实现。
XmlWebApplicationContext定义了一系列常量,定义了默认的配置文件,文件位置以及文件类型。
/** Default config location for the root context */public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";/** Default prefix for building a config location for a namespace */public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";/** Default suffix for building a config location for a namespace */public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
通时。该类的方法中。定义了通过xml启动IoC的过程:
@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// Create a new XmlBeanDefinitionReader for the given BeanFactory.XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's// resource loading environment.beanDefinitionReader.setEnvironment(getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);}
完毕对配置文件的读取:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = getConfigLocations();if (configLocations != null) {for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}}}
获取配置文件位置。
protected String[] getDefaultConfigLocations() {if (getNamespace() != null) {return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};}else {return new String[] {DEFAULT_CONFIG_LOCATION};}}
XmlWebApplicationContext类代码中能够看到,该类仅仅是实现了基础的基于XML文件的部分操作。详细的Servlet上下文,则默认取自其父类。
3。ContextLoader的设计与实现
ContextLoaderListener通过ContextLoader完毕IoC的初始化。
ContextLoaderListener的详细实现思路例如以下:ContextLoaderListener监听器启动根IoC容器并把它加载到Web容器的主要功能模块,这也是这个SpringWeb应用加载IoC的第一个地方。
从加载过程能够看到,首先从Servelt事件中得到ServletContext,然后能够读取配置在web.xml中的各个相关属性。记着ContextLoader会实例化WebApplicationContext,并完毕其加载和初始化过程。这个被初始化的第一个上下文作为根上下文存在,这个根上下文加载后。被绑定到Web应用程序的ServletCOntext上。怎样须要訪问根上下文的应用程序代码都能够通过WebApplicationContextUtils类的静态方法中得到。该根上下文存储至:ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
在ContextLoaderListener中,会监听ServletContext的详细变化,如创建和销毁,并再监听器中定义了对应的回调方法。
public ContextLoaderListener(WebApplicationContext context) {super(context);}/*** Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}/*** Close the root web application context.*/@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}
详细的调用。交由ContextLoader实现。例如以下。ContextLoader的初始化容器方法:
/*** Initialize Spring's web application context for the given servlet context,* using the application context provided at construction time, or creating a new one* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.* @param servletContext current servlet context* @return the new WebApplicationContext* @see #ContextLoader(WebApplicationContext)* @see #CONTEXT_CLASS_PARAM* @see #CONFIG_LOCATION_PARAM*/public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {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!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {//创建上下文this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.加载双亲上下文ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}
//配置各项參数。以及对应的初始化方法configureAndRefreshWebApplicationContext(cwac, servletContext);}}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);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;}}
createWebApplicationContext是详细的创建根上下文的方法。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class<?> contextClass = determineContextClass(sc);//Return the WebApplicationContext implementation class to useif (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);}
创建好上下文的后。调用了configureAndRefreshWebApplicationContext方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationString idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);wac.refresh();}
determineContextClass方法中。详细了详细实现什么上下文对象。默觉得XmlWebApplicationContext。
protected Class<?> determineContextClass(ServletContext servletContext) {
//配置參数
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);if (contextClassName != null) {try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);}}else {//没有额外配置则採用默认的配置contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());try {return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);}}}
以上就是IoC容器在Web容器中的启动过程,与应用中启动IoC基本同样,唯一不同的是须要考虑web容器特点。设置相应web容器须要的參数。
Spring MVC的设计与实现
Spring MVC出了配置ContextListener之类,还需配置核心分发器DispatcherServlet。DispatcherServlet作为一个前端控制器,全部web请求都要经过他来处理,其对请求进行转发。匹配,数据处理后。再由页面处理。DispatcherServlet是Spring MVC的核心。
1,概览
完毕ContextLoaderListener初始化之后,Web容器開始初始化DispatcherServlet。DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象。在建立自己的上下文时,会从ServletContext中得到根上下文作为DispatcherServlet持有的上下文的双亲上下文。有了根上下文。在对自己持有上下文进行初始化,最后把自己的上下文保存到ServletContex,供以后使用。
DispatcherServlet继承自FramworkServlet,FramworkServlet继承HttpServletBean。HttpServletBean继承HttpServlet,通过Servlet API对请求对应。
DispatcherServlet的工作大致分为两个:-1,初始化部分。由initServletBean启动。通过initWebApplicationContext()方法终于抵用DispatcherServlet的initStraegies方法,在这种方法里,DispatcherServlet对MVC的其它模块进行了初始化。比方handlerMapping,ViewResolver。-2,对HTTP请求进行响应。作为一个Servlet,Web容器会调用Servlet的doGet和doPost方法,在经过FraeWorkServlet处理后。会调用DispatcherServlet的doService()方法,这种方法中封装了doDispatch()方法,doDispatch()是DIspatcher实现MVC的主要部分。
2。DispatcherServlet的启动和初始化
DispatcherServlet的初始化方法的開始。是由HttpServletBean的init()方法開始的。该方法负责获取相应的各项配置參数:
/*** Map config parameters onto bean properties of this servlet, and* invoke subclass initialization.* @throws ServletException if bean properties are invalid (or required* properties are missing), or if subclass initialization fails.*/@Overridepublic final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug("Initializing servlet '" + getServletName() + "'");}// Set bean properties from init parameters.try {PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);throw ex;}// Let subclasses do whatever initialization they like.initServletBean();if (logger.isDebugEnabled()) {logger.debug("Servlet '" + getServletName() + "' configured successfully");}}
在该方法中。调用了子类的initServletBean()方法,即FrameworkServlet中的initServletBean()方法:
/*** Overridden method of {@link HttpServletBean}, invoked after any bean properties* have been set. Creates this servlet's WebApplicationContext.*/@Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {//初始化上下文this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +elapsedTime + " ms");}}
该方法中,通过initWebApplicationContext()完毕初始化上下文操作:
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.onRefresh(wac);}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +"' as ServletContext attribute with name [" + attrName + "]");}}return wac;}
通过WebApplicationContextUtils.getWebApplicationContext获取根上下文。
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);}public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {Assert.notNull(sc, "ServletContext must not be null");Object attr = sc.getAttribute(attrName);if (attr == null) {return null;}if (attr instanceof RuntimeException) {throw (RuntimeException) attr;}if (attr instanceof Error) {throw (Error) attr;}if (attr instanceof Exception) {throw new IllegalStateException((Exception) attr);}if (!(attr instanceof WebApplicationContext)) {throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);}return (WebApplicationContext) attr;}
建立好根上下文后,在通过createWebApplicationContext建立DispatcherServlet的上下文。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {Class<?> contextClass = getContextClass();if (this.logger.isDebugEnabled()) {this.logger.debug("Servlet with name '" + getServletName() +"' will try to create custom WebApplicationContext context of class '" +contextClass.getName() + "'" + ", using parent context [" + parent + "]");}if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());wac.setParent(parent);wac.setConfigLocation(getContextConfigLocation());configureAndRefreshWebApplicationContext(wac);return wac;}
再通过configureAndRefreshWebApplicationContext完毕各项配置以及初始化:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());}}wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}postProcessWebApplicationContext(wac);applyInitializers(wac);wac.refresh();//初始化}
以上完毕了DispatcherServlet的IoC容器的建立。
在Spring MVC DispatcherServlet的初始化过程中。以对HandlerMapping的初始化作为開始。该初始化过程。以HttpServletbean的init方法開始,到FrameWorkServlet的initServletBean,再到DispatcherServlet的onRefresh方法。最后到initStrategies(),启动整个MVC的初始化。
/*** This implementation calls {@link #initStrategies}.*/@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
HandlerMappings的初始化例如以下:
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());// We keep HandlerMappings in sorted order.OrderComparator.sort(this.handlerMappings);}}else {try {HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later.}}// Ensure we have at least one HandlerMapping, by registering// a default HandlerMapping if no other mappings are found.if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isDebugEnabled()) {logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");}}}
以上即为HandlerMappings的读取过程。
MVC处理HTTP分发请求
1。HandlerMapping的配置和设计原理
在初始化完毕时。在上下文环境中已经定义的全部HandlerMapping都已经被载入了,这些HandlerMapping被放入List并排序。存储着HTTP请求的相应映射数据。Spring提供了一系列HandlerMapping实现。SimpleUrlHandlerMapping为例。在SimpleUrlHandlerMapping中定义了一个Map持有一系列的映射关系,这些映射关系使SpringMVC能够相应到Controller中。
这些映射关系通过HandlerMapping接口封装。通过getHandler获取相应的HandlerExcutionChain,在HandlerExcutionChain中封装详细的Controller。
HandlerMapping接口的方法例如以下:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
此处是典型的命令模式。
HandlerExecutionChain中持有一个Inteceptor链和一个handler对象。该handler实际就是Controller对象。
public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);private final Object handler;private HandlerInterceptor[] interceptors;private List<HandlerInterceptor> interceptorList;private int interceptorIndex = -1;
构造函数:
/*** Create a new HandlerExecutionChain.* @param handler the handler object to execute* @param interceptors the array of interceptors to apply* (in the given order) before the handler itself executes*/public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {if (handler instanceof HandlerExecutionChain) {HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;this.handler = originalChain.getHandler();this.interceptorList = new ArrayList<HandlerInterceptor>();CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);}else {this.handler = handler;this.interceptors = interceptors;}}
HandlerExecutionChain中定义的Handler和Interceptor须要在定义HandlerMapping的时候配置好。
以SimpleUrlHandlerMapping为例。
在初始化时。会调用。
/*** Calls the {@link #registerHandlers} method in addition to the* superclass's initialization.*/@Overridepublic void initApplicationContext() throws BeansException {super.initApplicationContext();registerHandlers(this.urlMap);}/*** Register all handlers specified in the URL map for the corresponding paths.* @param urlMap Map with URL paths as keys and handler beans or bean names as values* @throws BeansException if a handler couldn't be registered* @throws IllegalStateException if there is a conflicting handler registered*/protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {if (urlMap.isEmpty()) {logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");}else {for (Map.Entry<String, Object> entry : urlMap.entrySet()) {String url = entry.getKey();Object handler = entry.getValue();// Prepend with slash if not already present.if (!url.startsWith("/")) {url = "/" + url;}// Remove whitespace from handler bean name.if (handler instanceof String) {handler = ((String) handler).trim();}registerHandler(url, handler);}}}
部分发放基于其父类,AbstractUrlhandlerMapping。
/*** Register the specified handler for the given URL path.* @param urlPath the URL the bean should be mapped to* @param handler the handler instance or handler bean name String* (a bean name will automatically be resolved into the corresponding handler bean)* @throws BeansException if the handler couldn't be registered* @throws IllegalStateException if there is a conflicting handler registered*/protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {Assert.notNull(urlPath, "URL path must not be null");Assert.notNull(handler, "Handler object must not be null");Object resolvedHandler = handler;// Eagerly resolve handler if referencing singleton via name.if (!this.lazyInitHandlers && handler instanceof String) {String handlerName = (String) handler;if (getApplicationContext().isSingleton(handlerName)) {resolvedHandler = getApplicationContext().getBean(handlerName);}}Object mappedHandler = this.handlerMap.get(urlPath);if (mappedHandler != null) {if (mappedHandler != resolvedHandler) {throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");}}else {if (urlPath.equals("/")) {if (logger.isInfoEnabled()) {logger.info("Root mapping to " + getHandlerDescription(handler));}setRootHandler(resolvedHandler);}else if (urlPath.equals("/*")) {if (logger.isInfoEnabled()) {logger.info("Default mapping to " + getHandlerDescription(handler));}setDefaultHandler(resolvedHandler);}else {this.handlerMap.put(urlPath, resolvedHandler);if (logger.isInfoEnabled()) {logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));}}}}
hadnlerMap保存了URL和controller的映射关系。
private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
2,使用HandlerMapping完毕请求的处理。
在HandlerExecutionChain的启动过程中,调用了getHandler方法,该方法就是完毕请求映射处理的地方。AbstractHadnlerMapping的getHandler方法例如以下:
/*** Look up a handler for the given request, falling back to the default* handler if no specific one is found.* @param request current HTTP request* @return the corresponding handler instance, or the default handler* @see #getHandlerInternal*/@Overridepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}return getHandlerExecutionChain(handler, request);}
/*** Build a {@link HandlerExecutionChain} for the given handler, including* applicable interceptors.* <p>The default implementation builds a standard {@link HandlerExecutionChain}* with the given handler, the handler mapping's common interceptors, and any* {@link MappedInterceptor}s matching to the current request URL. Subclasses* may override this in order to extend/rearrange the list of interceptors.* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a* pre-built {@link HandlerExecutionChain}. This method should handle those* two cases explicitly, either building a new {@link HandlerExecutionChain}* or extending the existing chain.* <p>For simply adding an interceptor in a custom subclass, consider calling* {@code super.getHandlerExecutionChain(handler, request)} and invoking* {@link HandlerExecutionChain#addInterceptor} on the returned chain object.* @param handler the resolved handler instance (never {@code null})* @param request current HTTP request* @return the HandlerExecutionChain (never {@code null})* @see #getAdaptedInterceptors()*/protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));chain.addInterceptors(getAdaptedInterceptors());String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {chain.addInterceptor(mappedInterceptor.getInterceptor());}}return chain;}
取得Handler的详细方法在getHandlerInternal()。该方法的详细实如今AbstractUrlHandlerMapping中:
/*** Look up a handler for the URL path of the given request.* @param request current HTTP request* @return the handler instance, or {@code null} if none found*/@Overrideprotected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//从request获取请求路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//请求路径与Handler匹配
Object handler = lookupHandler(lookupPath, request);if (handler == null) {// We need to care for the default handler directly, since we need to// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.Object rawHandler = null;if ("/".equals(lookupPath)) {rawHandler = getRootHandler();}if (rawHandler == null) {rawHandler = getDefaultHandler();}if (rawHandler != null) {// Bean name or resolved handler?if (rawHandler instanceof String) {String handlerName = (String) rawHandler;rawHandler = getApplicationContext().getBean(handlerName);}validateHandler(rawHandler, request);handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);}}if (handler != null && logger.isDebugEnabled()) {logger.debug("Mapping [" + lookupPath + "] to " + handler);}else if (handler == null && logger.isTraceEnabled()) {logger.trace("No handler mapping found for [" + lookupPath + "]");}return handler;}
/*** Look up a handler instance for the given URL path.* <p>Supports direct matches, e.g. a registered "/test" matches "/test",* and various Ant-style pattern matches, e.g. a registered "/t*" matches* both "/test" and "/team". For details, see the AntPathMatcher class.* <p>Looks for the most exact pattern, where most exact is defined as* the longest path pattern.* @param urlPath URL the bean is mapped to* @param request current HTTP request (to expose the path within the mapping to)* @return the associated handler instance, or {@code null} if not found* @see #exposePathWithinMapping* @see org.springframework.util.AntPathMatcher*/protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {// Direct match?Object handler = this.handlerMap.get(urlPath);if (handler != null) {// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}validateHandler(handler, request);return buildPathExposingHandler(handler, urlPath, urlPath, null);}// Pattern match?List<String> matchingPatterns = new ArrayList<String>();for (String registeredPattern : this.handlerMap.keySet()) {if (getPathMatcher().match(registeredPattern, urlPath)) {matchingPatterns.add(registeredPattern);}}String bestPatternMatch = null;Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);if (!matchingPatterns.isEmpty()) {Collections.sort(matchingPatterns, patternComparator);if (logger.isDebugEnabled()) {logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);}bestPatternMatch = matchingPatterns.get(0);}if (bestPatternMatch != null) {handler = this.handlerMap.get(bestPatternMatch);// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}validateHandler(handler, request);String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);// There might be multiple 'best patterns', let's make sure we have the correct URI template variables// for all of themMap<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();for (String matchingPattern : matchingPatterns) {if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);uriTemplateVariables.putAll(decodedVars);}}if (logger.isDebugEnabled()) {logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);}return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);}// No handler found...return null;}
经过一系列对HTTP请求进行解析和匹配handler的过程,得到了与请求对一个的handler处理器。在返回的handler中。已经完毕了在HandlerExecutionChain中的封装工作,为handler对HTTP请求的响应做好了准备。
3,Spring对HTTP请求的分发处理。
DispatcherServlet是Servlet的子类,通过doService来响应HTTP请求。
/*** Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}* for the actual dispatching.*/@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");}// Keep a snapshot of the request attributes in case of an include,// to be able to restore the original attributes after the include.Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<String, Object>();Enumeration<? > attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);try {doDispatch(request, response);}finally {if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {return;}// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}
doDispatch方法:
/*** Process the actual dispatching to the handler.* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.* @param request current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}try {// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());}finally {if (asyncManager.isConcurrentHandlingStarted()) {return;}}applyDefaultViewName(request, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Error err) {triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionmappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);return;}// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {for (HandlerMapping hm : this.handlerMappings) {if (logger.isTraceEnabled()) {logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");}HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}return null;}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {for (HandlerAdapter ha : this.handlerAdapters) {if (logger.isTraceEnabled()) {logger.trace("Testing handler adapter [" + ha + "]");}if (ha.supports(handler)) {return ha;}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}
HandlerAdapter的基本实现。SimpleControllerHandlerAddapter。
public class SimpleControllerHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof Controller);}@Overridepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);}@Overridepublic long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);}return -1L;}}
经过上面的处理。获取到Controller对象,開始调用Handler对象的HTTP响应动作。
运行完获取到视图,并将视图返回。
Spring MVC视图的呈现
1,DispatcherServlet视图呈现设计
在DIspatchServlet的doDispatch()方法中,获取到视图后调用了processDispatchResult()方法处理结果。视图的处理採用render方法。
/*** Render the given ModelAndView.* <p>This is the last stage in handling a request. It may involve resolving the view by name.* @param mv the ModelAndView to render* @param request current HTTP servlet request* @param response current HTTP servlet response* @throws ServletException if view is missing or cannot be resolved* @throws Exception if there's a problem rendering the view*/protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale = this.localeResolver.resolveLocale(request);response.setLocale(locale);View view;if (mv.isReference()) {// We need to resolve the view name.view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isDebugEnabled()) {logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");}try {view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +getServletName() + "'", ex);}throw ex;}}
resolveViewName通过对视图名称对象解析,获取视图。
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,HttpServletRequest request) throws Exception {for (ViewResolver viewResolver : this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}return null;}
ViewResolver的resolveViewName方法,BeanNameViewresolver是其经常使用实现。
@Overridepublic View resolveViewName(String viewName, Locale locale) throws BeansException {ApplicationContext context = getApplicationContext();if (!context.containsBean(viewName)) {// Allow for ViewResolver chaining.return null;}return context.getBean(viewName, View.class);}
Spring为了实现视图的灵活性,方便应用使用各种视图。在View接口下实现了不同View对象。各View对象依据其详细的使用,氛围不同的视图。
2,JSP视图的实现
使用jsp作为视图,Sprinng採用JstlView来作为View对象,而其render方法继承自父类AbstractView。
/*** Prepares the view given the specified model, merging it with static* attributes and a RequestContext attribute, if necessary.* Delegates to renderMergedOutputModel for the actual rendering.* @see #renderMergedOutputModel*/@Overridepublic void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isTraceEnabled()) {logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +" and static attributes " + this.staticAttributes);}Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);prepareResponse(request, response);renderMergedOutputModel(mergedModel, request, response);}
createMergedOutputModel方法:
/*** Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.* Dynamic values take precedence over static attributes.*/protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,HttpServletResponse response) {@SuppressWarnings("unchecked")Map<String, Object> pathVars = (this.exposePathVariables ?(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);// Consolidate static and dynamic model attributes.int size = this.staticAttributes.size();size += (model != null) ? model.size() : 0;size += (pathVars != null) ? pathVars.size() : 0;Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);mergedModel.putAll(this.staticAttributes);if (pathVars != null) {mergedModel.putAll(pathVars);}if (model != null) {mergedModel.putAll(model);}// Expose RequestContext?if (this.requestContextAttribute != null) {mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));}return mergedModel;}
renderMergedOutputModel,在InternalResourceView中完毕,InternalResourceView也属于JstlView的基类。
/*** Render the internal resource given the specified model.* This includes setting the model as request attributes.*/@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine which request handle to expose to the RequestDispatcher.HttpServletRequest requestToExpose = getRequestToExpose(request);// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, requestToExpose);// Expose helpers as request attributes, if any.exposeHelpers(requestToExpose);// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(requestToExpose, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(requestToExpose, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");}rd.include(requestToExpose, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");}rd.forward(requestToExpose, response);}}
AbstractView的exposeModelAsRequestAttributes()方法:
/*** Expose the model objects in the given map as request attributes.* Names will be taken from the model Map.* This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.* @param model Map of model objects to expose* @param request current HTTP request*/protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {for (Map.Entry<String, Object> entry : model.entrySet()) {String modelName = entry.getKey();Object modelValue = entry.getValue();if (modelValue != null) {request.setAttribute(modelName, modelValue);if (logger.isDebugEnabled()) {logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +"] to request in view with name '" + getBeanName() + "'");}}else {request.removeAttribute(modelName);if (logger.isDebugEnabled()) {logger.debug("Removed model object '" + modelName +"' from request in view with name '" + getBeanName() + "'");}}}}
/*** Prepare for rendering, and determine the request dispatcher path* to forward to (or to include).* <p>This implementation simply returns the configured URL.* Subclasses can override this to determine a resource to render,* typically interpreting the URL in a different manner.* @param request current HTTP request* @param response current HTTP response* @return the request dispatcher path to use* @throws Exception if preparations failed* @see #getUrl()*/protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)throws Exception {String path = getUrl();if (this.preventDispatchLoop) {String uri = request.getRequestURI();if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {throw new ServletException("Circular view path [" + path + "]: would dispatch back " +"to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +"(Hint: This may be the result of an unspecified view, due to default view name generation.)");}}return path;}
视图的解析过程大概就例如以下代码。
总而言之。Spring MVC的建立有下面几个过程:
-1。须要建立Controller和HTTP请求之间的映射关系。在HandlerMapping中封装HandlerExecutionChain对象完毕。而对Controller和HTTP请求关系的描写叙述是在Bean定义的描写叙述。并在IoC初始化时。通过初始化HandlerMapping来完毕,这些映射关系存储在handlerMap中。
-2,在MVC接收请求的时候,DispatcherServlet会依据详细的URL请求信息。在HandlerMapping中查询,从而得到HandlerExecutionChain。在HandlerExecutionChain中封装了Controller,这个请求的COntroller会完毕请求对应,以及生成须要的ModelAndView对象。
-3。得到ModelAndView对象后,DispatcherServlet将ModelAndView对象交给特定的视图对象。通过视图对象完毕数据呈现工作。
《Spring技术内幕》笔记-第四章 Spring MVC与web环境的更多相关文章
- Spring技术内幕笔记2--我懒不写了哈哈哈哈。
目录 1.1 关于IOC容器设计的线路区别 1.1.1 BeanFactory 1.1.2 ApplicationContext 2.1 FileSystemXmlApplicationContext ...
- Spring Boot实战笔记(四)-- Spring常用配置(事件Application Event)
一.事件(Application Event) Spring的事件为Bean和Bean之间的消息通信提供了支持.当一个Bean处理完一个任务之后,希望另一个Bean知道并能做相应的处理,这时我们就需要 ...
- Mysql技术内幕-笔记-第三章 查询处理
第三章 查询处理 逻辑查询处理:(8) SELECT (9) DISTINCT <select_list> (1) FROM <left_table> (3) <join ...
- Javac编译原理 《深入分析java web 技术内幕》第四章
javac编译的四个主要的流程: 词法分析器:将源码转换为Token流 将源代码划分成一个个Token(找出java语言中的关键字) 语法分析器:将Token流转化为语法树 将上述的一个个Token组 ...
- 《spring技术内幕》读书笔记(1)——什么是POJO模式
今天在看<spring技术内幕>,第一章中多次提到了使用POJO来完成开发,就百度了一下,在此保留 1. 什么是POJO POJO的名称有多种,pure old java obje ...
- Spring技术内幕:SpringIOC原理学习总结
前一段时候我把Spring技术内幕的关于IOC原理一章看完,感觉代码太多,不好掌握,我特意又各方搜集了一些关于IOC原理的资料,特加深一下印象,以便真正掌握IOC的原理. IOC的思想是:Spring ...
- Spring技术内幕:设计理念和整体架构概述(转)
程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...
- SQL Server技术内幕笔记合集
SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...
- Android群英传笔记——第四章:ListView使用技巧
Android群英传笔记--第四章:ListView使用技巧 最近也是比较迷茫,但是有一点点还是要坚持的,就是学习了,最近离职了,今天也是继续温习第四章ListView,也拖了其实也挺久的了,list ...
随机推荐
- mysql和mongodb的区别
1.mongodb的概括 MongoDB(文档型数据库):提供可扩展的高性能数据存储 2.mongodb的功能概括 (1)基于分布式文件存储 (2)高负载情况下添加更多节点,可以保证服务器性能 (3) ...
- iOS开发者中心重置设备列表
苹果开发者账号允许的测试设备为100台,如果你注册了,这台机器就算是一个名额,禁用也算一个名额,仍被计入机器总数,每年可以重置一次,那我们怎么重置机器数量呢? 我们需要给苹果发送申请: https:/ ...
- window下搭建Python3.7+selenium3.1.1+pycharm环境
1.安装Python3.7 1.1 下载 Python并安装 Python3.5 (勾选上 Add Python3.7 to PATH) 点击 Install Now,安装完成后将python路径加 ...
- 小A点菜 水题 dp 背包
基本上还是01背包,首先注意必须正好花光钱,所以初始化时除了dp[0]以外其他都要设置成inf,然后因为求方案数,所以基本方程为dp[i] = dp[i-x] + dp[i],再根据inf进行一些特殊 ...
- Java学习书目
Java学习书目 拜读了:http://www.importnew.com/26932.html#comment-636554
- 足球大数据:致足球怀疑论者-The Counter(s)-Reformation反教条改革
足球大数据:致足球怀疑论者-The Counter(s)-Reformation反教条改革 注:本序列文章都是本人对<The Numbers Game>书(豆瓣链接http://book. ...
- iOS设计模式之NSNotificationCenter 消息中心
消息中心模式和KVO模式有点相似,差别在于.KVO 模式是意图在于监听摸一个相应的值的变化.而去出发一个方法相应的动作.而消息中心在于,广播.它就像一个广播基站,发送一条消息,在全部的加入监听的地方 ...
- UVALive 6084 Happy Camper(数学题)
题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...
- POJ 2528 Mayor's posters 离散化和线段树题解
本题就是要往墙上贴海报,问最后有多少可见的海报. 事实上本题的难点并非线段树,而是离散化. 由于数据非常大,直接按原始数据计算那么就会爆内存和时间的. 故此须要把数据离散化. 比方有海报1 6 7 ...
- vijos - P1543极值问题(斐波那契数列 + 公式推导 + python)
P1543极值问题 Accepted 标签:[显示标签] 背景 小铭的数学之旅2. 描写叙述 已知m.n为整数,且满足下列两个条件: ① m.n∈1,2.-,K ② (n^ 2-mn-m^2)^2=1 ...