SpringMVC原理
1 框架搭建(xml方式)
1 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
2 webapp/WEB-INF/web.xml:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--1、配置Spring的容器路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 初始化spring ioc 容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--Spring MVC 框架会根据 servlet-name 配置,找到/WEB-INF/dispatcherServlet-servlet.xml 作为配置文件载入 Web 工程中-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--Servlet拦截-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
3 dispatcherServlet-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 使用注解驱动 -->
<mvc:annotation-driven/>
<!--定义扫描装载得包 -->
<context:component-scan base-package="com.spring" use-default-filters="false">
<!--只扫描控制器。 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--配置视图解析器,方便页面返回 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 将springmvc不能处理的请求交给tomcat -->
<mvc:default-servlet-handler/>
</beans>
4 applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
5 TestController:
@Controller
public class TestController {
@RequestMapping("/test")
public ModelAndView index(){
ModelAndView mv = new ModelAndView ();
mv.setViewName("index");
return mv ;
}
}
2框架搭建(JavaConfig方式)
1 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
2 MyWebApplicationInitializer:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
/**
* web容器在启动时会去调用onStartup
* servlet 3.0版本后提出新规范SPI
* 当项目里某些类或某些方法要在启动时被web容器(tomcat)调用的话
* 要在项目目录的META-INF/services/建一个文件javax.servlet.ServletContainerInitializer,
* 里面定义类实现ServletContainerInitializer接口,定义@HandlesTypes({WebApplicationInitializer.class})把WebApplicationInitializer.class接口的所有实现全部扫描
* @param servletCxt
*/
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("*.do");
}
}
3 AppConfig:
@Configuration
@ComponentScan("com")
public class AppConfig {
}
4 TestController:
@Controller
public class TestController {
@RequestMapping("/test")
public ModelAndView index(){
System.out.println("test");
ModelAndView mv = new ModelAndView();
mv.setViewName("index");
return mv ;
}
}
3原理
3.1 概述
SpringMVC是Spring为展现层提供的基于MVC设计理念的Web框架,SpringMVC 中重要组件 :
--DispatcherServlet : 前端控制器,接收所有请求
--HandlerMapping: 解析请求格式的.判断希望要执行哪个具体的方法.
--HandlerAdapter: 负责调用具体的方法
--Controller注册方式不同,具体怎么实现方法也不同,需要适配器进行适配。
--注册controller的三种方式:@Controller(通过反射)、实现HttpRequestHandler接口(直接调用接口的实现类)、实现Controller接口.
--ViewResovler:视图解析器.解析结果,准备跳转到具体的物理视图
3.2 流程
3.2.1 初始化阶段
--扫描整个项目
--拿到所有加了@Controller注解的类
--遍历类中所有方法对象
--判断方法是否加了@RequestMapping注解
--把@RequestMapping注解的value作为handlerMap的key,把controller对象作为value放入handlerMap(容器初始化时在AbstractDetectingUrlHandlerMapping中进行)
3.2.1.1 AbstractDetectingUrlHandlerMapping#detectHandlers()
protected void detectHandlers() throws BeansException {
//.......
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
//得到controller对应的所有mapping
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
//初始化handlerMap
registerHandler(urls, beanName);
}
else {
//........
}
}
}
3.2.1.2上个方法中的determineUrlsForHandler
@Override
protected String[] determineUrlsForHandler(String beanName) {
ApplicationContext context = getApplicationContext();
Class<?> handlerType = context.getType(beanName);
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
if (mapping != null) {
// @RequestMapping found at type level
this.cachedMappings.put(handlerType, mapping);
Set<String> urls = new LinkedHashSet<String>();
String[] typeLevelPatterns = mapping.value();
if (typeLevelPatterns.length > 0) {
// @RequestMapping specifies paths at type level
String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
for (String typeLevelPattern : typeLevelPatterns) {
if (!typeLevelPattern.startsWith("/")) {
typeLevelPattern = "/" + typeLevelPattern;
}
boolean hasEmptyMethodLevelMappings = false;
for (String methodLevelPattern : methodLevelPatterns) {
if (methodLevelPattern == null) {
hasEmptyMethodLevelMappings = true;
}
else {
String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
//将controller类上的mapping和方法上的mapping合并
addUrlsForPath(urls, combinedPattern);
}
}
//......
}
3.2.1.3 registerHandler()
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.
//......
//判断handler是否已经存在
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 {
//请求地址作为key
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
3.2.2 客户端发请求
--根据用户发送的请求拿到请求中的URI
--使用请求的uri作为handlerMap的key,去handlerMap里面get看看是否有返回值,得到handler后,遍历handler的所有方法,在Map<Method, RequestMappingInfo>mppings中(mppings在获得HandlerAdapter时将方法和RequestMappingInfo put进去,并将其封装为resolver对象,先从缓存取resolver对象,若没有再进行put)找到与uri匹配RequestMappingInfo,从而定位具体执行的Method。
--通过url定位的controller(handler)和Method执行Method方法
发送请求进入DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//....
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//1 通过url找到handler(controller)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 2 获得HandlerAdapter,将方法和RequestMappingInfo put进Map<Method, RequestMappingInfo>mppings中,封装resolver对象(或缓存没有)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
//......
// Actually invoke the handler.
//3 执行对应的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//.....
}
}
3.2.2.1 获取handler的函数getHandler()
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据controller的注册方式选择HandlerMapping
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;
}
3.2.2.2 真正根据url寻找handler的代码在hm.getHandler(request)里的getHandlerInternal(request)
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据url寻找handler
Object handler = lookupHandler(lookupPath, request);
3.2.2.3执行方法的函数invokeHandlerMethod()
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取handler
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
//遍历hanler的所有方法,获取于请求地址对应要执行的方法
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
//执行方法
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}