Spring的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,这个 Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区 与主题渲染等,甚至还能支持文件上传。处理器是你的应用中注解 了 @Controller 和 @RequestMapping 的类和方法,Spring为处理器方法提供了极其多样灵活的 配置。Spring 3.0以后提供了 @Controller 注解机制、 @PathVariable 注解以及一些其他的特 性,你可以使用它们来进行RESTful web站点和应用的开发。
“对扩展开放”是Spring Web MVC框架一个重要的设计原则,而对于Spring的整个完整框
架来说,其设计原则则是“对扩展开放,对修改闭合”。
Spring Web MVC核心类库中的一些方法被定义为 final 方法。作为开发人员,你不能
覆写这些方法以定制其行为。当然,不是说绝对不行,但请记住这条原则,绝大多数情 况下不是好的实践。
关于该原则的详细解释,你可以参考Seth Ladd等人所著的“深入解析Spring Web MVC与
Web Flow”一书。相关信息在第117页,“设计初探(A Look At Design)”一节。或者,你 可以参考:
Bob Martin所写的“开闭原则(The Open-Closed Principle)”(PDF)
你无法增强SpringMVC中的final 方法,比如AbstractController.setSynchronizeOnSession() 方法等。请参考10.6.1 理解AOP代理 一节,其中解释了AOP代理的相关知识,论述了为什么你不能对 final 方法进行增强。
在Spring Web MVC中,你可以使用任何对象来作为命令对象或表单返回对象,而无须实现一 个框架相关的接口或基类。Spring的数据绑定非常灵活:比如,它会把数据类型不匹配当成可 由应用自行处理的运行时验证错误,而非系统错误。你可能会为了避免非法的类型转换在表 单对象中使用字符串来存储数据,但无类型的字符串无法描述业务数据的真正含义,并且你 还需要把它们转换成对应的业务对象类型。有了Spring的验证机制,意味着你再也不需这么做 了,并且直接将业务对象绑定到表单对象上通常是更好的选择。
Spring的视图解析也是设计得异常灵活。控制器一般负责准备一个 Map 模型、填充数据、返 回一个合适的视图名等,同时它也可以直接将数据写到响应流中。视图名的解析高度灵活, 支持多种配置,包括通过文件扩展名、 Accept 内容头、bean、配置文件等的配置,甚至你还 可以自己实现一个视图解析器 ViewResolver 。模型(MVC中的M,model)其实是一 个 Map 类型的接口,彻底地把数据从视图技术中抽象分离了出来。你可以与基于模板的渲染 技术直接整合,如JSP、Velocity和Freemarker等,或者你还可以直接生成XML、JSON、 Atom以及其他多种类型的内容。 Map 模型会简单地被转换成合适的格式,比如JSP的请求属 性(attribute),一个Velocity模板的模型等。
Web.xml加载Spring的监听器。根据源码得知ContextLoaderListener类继承了父类。
调用了父类的initWebApplicationContext方法。 initWebApplicationContext方法是获取/创建一个spring web容器。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
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, etc
if (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;
}
}
Web.xml加载了另一个监听器。实现了ServletContextListener接口。使得Tomcat反复加载对象而产生框架并用时可能产生的内存泄漏。
内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
那么,Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
只知道servlet标准不允许在web容器内自行做线程管理,quartz的问题确实存在。
对于Web容器来说,最忌讳应用程序私自启动线程,自行进行线程调度,像Quartz这种在web容器内部默认就自己启动了10线程进行异步job调度的框架本身就是很危险的事情,很容易造成servlet线程资源回收不掉,所以我一向排斥使用quartz。
quartz还有一个问题就是不支持cluster。导致使用quartz的应用都没有办法做群集。
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListenerlistener-class>
listener>
public void contextInitialized(ServletContextEvent event) {
CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader());
Introspector.flushCaches();
}
Web.xml加载springmvc的配置文件。这里加载了springmvc的核心类DispatcherServlet。在开始监听的时候会把xml文件配置进入DispatcherServlet。DispatcherServlet类解读xml文件,获取相应的配置信息。
当你不指定的时候会默认加载一个任何字符开头以servlet结尾的xml文件。如果没有则会抛出not find file Exception。“/*”代表拦截所有请求
<servlet>
<description>spring mvc servletdescription>
<servlet-name>springMvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<description>spring mvc 配置文件description>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springMvcservlet-name>
<url-pattern>/*url-pattern>
servlet-mapping>
DispatcherServlet它负责把所有请求分发到控制器;同时提供其他web应用开发所需要的功能。不过Spring的中央处理器, DispatcherServlet能做的比这更多。它与Spring IoC容器 做到了无缝集成,这意味着,Spring提供的任何特性,在SpringMVC中你都可以使用。
下图展示了Spring Web MVC的 DispatcherServlet 处理请求的工作流。熟悉设计模式的朋友 会发现, DispatcherServlet应用的其实就是一个“前端控制器”的设计模式(其他很多优秀的 web框架也都使用了这个设计模式)。
核心方法就是加载bean(包括视图解析器/json转换器/文件/等等)进入ioc容器。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
Springmvc加载视图解析器:有两个接口在Spring处理视图相关事宜时至关重要,分别是视图解析器接口ViewResolver和 视图接口本身View。视图解析器ViewResolver负责处理视图名与实际视图之间的映射关系。视图接口View负责准备请求,并将请求的渲染交给某种具体的视图技术实现。
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
加载ViewResolver。而ViewResolver是InternalResourceViewResolver的顶级接口。所以是面向接口编程在xml文件里面把子类设置进去。而在构造方法中注入了viewClass类。而viewClass同样是在xml文件中配置。而prefix等属性(前缀后缀)则是在InternalResourceViewResolver的父类UrlBasedViewResolver中设置。这样完成后缀添加,前缀插入等操作。
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
}
}
}
public InternalResourceViewResolver() {
Class> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}