1 Star 0 Fork 4

随随 / OZSpringMvcDemo

forked from Orange_Zh / OZSpringMvcDemo 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 36.68 KB
一键复制 编辑 原始数据 按行查看 历史
Orange_Zh 提交于 2020-11-26 18:51 . update README.md.

介绍

OZSpringDataDemo:(作业2)

实现登录页面(简易版即可),实现登录验证功能、登录之后跳转到列表页,查询出 tb_resume 表【表数据和课上保持一致】的所有数据(列表不要求分页,在列表右上方有“新增”按钮,每一行后面有“编辑”和“删除”按钮,并实现功能),如果未登录就访问url则跳转到登录页面,用户名和密码固定为admin/admin

技术要求:根据SSM整合的思路,进行SSS整合(Spring+SpringMVC+SpringDataJPA),登录验证使用SpringMVC拦截器实现

OZSpringMvcDemo:(作业1)

手写MVC框架,实现简单的Spring的Ioc容器,包含容器初始化,Bean实例化,实现@Service、@Autowired、@Transactional、@Bean、@Value等注解

###作业思路 ####题目1:

1)定义注解@Security(有value属性,接收String数组),该注解用于添加在Controller类或者Handler方法上,表明哪些用户拥有访问该Handler方法的权限(注解配置用户名)
2)访问Handler时,用户名直接以参数名username紧跟在请求的url后面即可,比如http://localhost:8080/demo/handle01?username=zhangsan
3)程序要进行验证,有访问权限则放行,没有访问权限在页面上输出
解决思路:
1、	首先创建@Security注解类
2、	在IOC容器初始化完成后再解析@Controller相关的类,将@Security的值加入到Handler类Set<String> securityUser;中进行保存
3、	当用户发起请求时,判断securityUser是否为空,为空就表示没有权限限制;如果不为空,就检查是否包含此用户,包含则放行

主要代码: com.ozdemo.mvcdemo.framework.servlet.DispatcherServlet#initHandlerMapping


@Secrity解析

@Secrity权限判断

示例链接:

http://localhost:8080/demo/demo1?username=user1
http://localhost:8080/demo/demo1?username=user3
http://localhost:8080/demo/demo1?username=user4
http://localhost:8080/demo/demo2?username=user3
http://localhost:8080/demo/demo2?username=user3
http://localhost:8080/demo/demo2?username=user4

####题目2:

实现登录页面(简易版即可),实现登录验证功能、登录之后跳转到列表页,查询出 tb_resume 表【表数据和课上保持一致】的所有数据(列表不要求分页,在列表右上方有“新增”按钮,每一行后面有“编辑”和“删除”按钮,并实现功能),如果未登录就访问url则跳转到登录页面,用户名和密码固定为admin/admin
技术要求:根据SSM整合的思路,进行SSS整合(Spring+SpringMVC+SpringDataJPA),登录验证使用SpringMVC拦截器实现
解决思路:
1、	首先引入必要的spring-webmvc包
2、	创建登录拦截器并加入到配置文件中
3、	实现resume的增删查改操作

SpringMvc应用

MVC架构(三层机构)
  • 表现层
也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使⽤http 协议请求web 层,web 需要接收 http 请求,完成 http 响应
表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示
表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端
表现层的设计⼀般都使⽤ MVC 模型(MVC 是表现层的设计模型,和其他层没有关系)
  • 业务层
service 层,它负责业务逻辑处理,web 层依赖业务层,但是业务层不依赖 web 层
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性。(事务应该放到业务层来控制)
  • 持久层
dao 层,负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进⾏持久化的载体,数据访问层是业务层和持久层交互的接⼝,业务层需要通过数据访问层将数据持久化到数据库中,持久层就是和数据库交互,对数据库表进⾏增删改查的

MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀种⽤于设计创建 Web 应⽤程序表现层的模式

MVC 中每个部分各司其职:

  • Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业务
  • View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的,通常视图是依据模型数据创建的
  • Controller(控制器): 是应⽤程序中处理⽤户交互的部分,作⽤⼀般就是处理程序逻辑的

MVC提倡:每⼀层只编写⾃⼰的东⻄,不编写任何其他的代码,分层是为了解耦,解耦是为了维护⽅便和分⼯协作

SpringMvc是什么?

SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品

Spring MVC请求处理流程

流程说明:

  • 第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
  • 第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
  • 第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截器(如果 有则⽣成)⼀并返回DispatcherServlet
  • 第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
  • 第五步:处理器适配器执⾏Handler
  • 第六步:Handler执⾏完成给处理器适配器返回ModelAndView
  • 第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个底层对 象,包括 Model 和 View
  • 第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
  • 第九步:视图解析器向前端控制器返回View
  • 第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
  • 第⼗⼀步:前端控制器向⽤户响应结果

SpringMvc九大组件:

  • HandlerMapping(处理器映射器)
HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是⽅法;⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler;Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor
  • HandlerAdapter(处理器适配器)
HandlerAdapter 是⼀个适配器,因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可,但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是
doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责
  • HandlerExceptionResolver
HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况,它的作⽤是根据异常设置ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯
  • ViewResolver
ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀个resolveViewName()⽅法;从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View;View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件;
ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板和所⽤的类型(其实也就是找到视图的类型,如JSP)并填⼊参数;默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的
  • RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName,因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置ViewName,便要通过这个组件从请求中查找 ViewName
  • LocaleResolver
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale,LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域,这个组件也是 i18n 的基础
  • ThemeResolver
ThemeResolver 组件是⽤来解析主题的,主题是样式、图⽚及它们所形成的显示效果的集合;
Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。
  • MultipartResolver
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现。
MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。
  • FlashMapManager
FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

SpringMvc源码分析

请求流程源码分析

1、进入源码前准备 demo demo目录结构 SpringMvc Demo目录结构.png

DemoController

@Controller
@RequestMapping("/demo")
public class DemoController  {
    @RequestMapping("/handle01")
    public String handle01(String name,Map<String,Object> model) {
		System.out.println("++++++++handler业务逻辑处理中....");
        Date date = new Date();
        model.put("date",date);
        return "success";
    }
}

springmvc.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"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
">
    <!--开启controller扫描-->
    <context:component-scan base-package="com.ozdemo.test.controller"/>

    <!--配置springmvc的视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:springmvc.xml</param-value>
		</init-param>

	</servlet>
	
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

2、断点查看请求到Handler的调用栈

SpringMvc请求流程调用栈

由上图我们可以看到整个过程调用的方法栈

3、org.springframework.web.servlet.DispatcherServlet#doDispatch 关建方法,SpringMVC处理请求的流程就在这个方法

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 {
				//1、检查是否是⽂件上传的请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				//2、获取到能够处理当前请求的执⾏链 HandlerExecutionChain(Handler+拦截器)
				//取得处理当前请求的Controller,这⾥也称为Handler,即处理器这⾥并不是直接返回 Controller,⽽是返回 HandlerExecutionChain 请求处理链对象该对象封装了Handler和Interceptor
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					// 如果 handler 为空,则返回404
					noHandlerFound(processedRequest, response);
					return;
				}

				//3、 获取处理请求的处理器适配器 HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// 处理 last-modified 请求头
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 4、实际处理器处理请求,返回结果视图对象ModelAndView
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				//结果视图对象的处理
				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			//5、跳转⻚⾯,渲染视图
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			//最终会调⽤HandlerInterceptor的afterCompletion ⽅法
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			//最终会调⽤HandlerInterceptor的afterCompletion ⽅法
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

4、org.springframework.web.servlet.DispatcherServlet#getHandler 获取到能够处理当前请求的执⾏链 HandlerExecutionChain(Handler+拦截器)

5、org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter 获取处理请求的处理器适配器 HandlerAdapter

6、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal 实际处理器处理请求,返回结果视图对象ModelAndView

@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// 如果需要,在synchronized块中执行invokeHandlerMethod,同步执行
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				//当前session生成一个唯一的锁key
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					//handlerMethod参数适配,并调用目标handler
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// 没有HttpSession可用->不需要互斥
				//handlerMethod参数适配,并调用目标handler
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// 不需要同步调用
			//handlerMethod参数适配,并调用目标handler
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod handlerMethod参数适配,并调用目标handler
  • org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle 对请求参数进行处理,并调用目标handlerMethod,并将返回值封装成一个ModelAndView对象
  • org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest 对目标handler进行参数处理,并调用
  • org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues 将request中的参数转换成handler的参数形式
  • org.springframework.web.method.support.InvocableHandlerMethod#doInvoke 使用反射进行调用

7、org.springframework.web.servlet.DispatcherServlet#processDispatchResult 跳转⻚⾯,渲染视图

  • org.springframework.web.servlet.DispatcherServlet#render 完成视图的渲染
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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// 视图解析器解析出View视图对象
			view = resolveViewName(viewName, 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.isTraceEnabled()) {
			logger.trace("Rendering view [" + view + "] ");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
                        //封装View视图对象之后,调⽤了view对象的render⽅法,渲染数据
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}
    • org.springframework.web.servlet.DispatcherServlet#resolveViewName 视图解析器解析出View视图对象
      • org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName 视图解析器解析出View视图对象,里面有视图缓存逻辑
        • org.springframework.web.servlet.view.UrlBasedViewResolver#createView 在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现
@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// Check for special "redirect:" prefix.
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" prefix.
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// Else fall back to superclass implementation: calling loadView.
		return super.createView(viewName, locale);
	}
          • org.springframework.web.servlet.view.UrlBasedViewResolver#buildView 创建视图,解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
                //逻辑视图名解析为物理视图名 加前缀后后缀
		view.setUrl(getPrefix() + viewName + getSuffix());

		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}

		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}
    • org.springframework.web.servlet.view.AbstractView#render 封装View视图对象之后,调⽤了view对象的render⽅法,渲染数据
@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		//渲染数据
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}
      • org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel 渲染数据
@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因
		exposeModelAsRequestAttributes(model, request);

		// 将数据设置到请求域中
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(request, 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(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}

九⼤组件的初始化 1、org.springframework.web.servlet.DispatcherServlet#onRefresh DispatcherServlet中的onRefresh(),该⽅法中初始化了九⼤组件

@Override
	protected void onRefresh(ApplicationContext context) {
		//初始化策略
		initStrategies(context);
	}

2、org.springframework.web.servlet.DispatcherServlet#initStrategies 初始化策略

/**
	 * 初始化这个servlet使用的策略对象
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		//初始化多文件上传组件
		initMultipartResolver(context);
		//初始化本地语言环境
		initLocaleResolver(context);
		//初始化模板处理器
		initThemeResolver(context);
		//初始化HandlerMapping
		initHandlerMappings(context);
		//初始化参数适配器
		initHandlerAdapters(context);
		//初始化异常拦截器
		initHandlerExceptionResolvers(context);
		//初始化视图处理器
		initRequestToViewNameTranslator(context);
		//初始化视图转换器
		initViewResolvers(context);
		//初始化 FlashMap 管理器
		initFlashMapManager(context);
	}

自定义MVC框架

首先回顾一下:自定义IOC容器和AOP框架,我们在自定义IOC和AOP的框架上进行扩展开发

1、创建@Controller、@RequestMapping注解类

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "";
}

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value() default "";
}

2、创建Handler类,封装handler方法相关的信息

public class Handler {

    private Object controller; // method.invoke(obj,)

    private Method method;

    private Pattern pattern; // spring中url是支持正则的

    private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>

    private Set<String> securityUser; //允许访问的用户 为null代表不限制用户的访问,配置了用户才限制在此范围内的用户访问

    public Handler(Object controller, Method method, Pattern pattern,Set<String> securityUser) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
        this.securityUser = securityUser;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }

    public Set<String> getSecurityUser() {
        return securityUser;
    }

    public void setSecurityUser(Set<String> securityUser) {
        this.securityUser = securityUser;
    }
}

3、创建DispatcherServlet类

@WebServlet(name = "ozmvc", urlPatterns = "/*")
public class DispatcherServlet extends HttpServlet {

    // handlerMapping
    //存储url和Method之间的关联,就是Handler
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        initHandlerMapping(servletConfig);
    }

    /**
     * 构造一个HandlerMapping处理器映射器
     * 最关键的环节
     * 目的:将url和method建立关联
     */
    private void initHandlerMapping(ServletConfig servletConfig) {
        //存储url和Method之间的映射关系
        Map<String, Object> beanMap = BeanFactory.getBeans();
        if (beanMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
            // 获取ioc中当前遍历的对象的class类型
            Class<?> clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(Controller.class)) {
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
                baseUrl = annotation.value(); // 等同于/demo
            }

            //处理@Security
            Set<String> securityUserCl = new HashSet<>();
            if (clazz.isAnnotationPresent(Security.class)) {
                Security annotation = clazz.getAnnotation(Security.class);
                String value[] = annotation.value();
                securityUserCl.addAll(new HashSet<>(Arrays.asList(value)));
            }

            // 获取方法
            Method[] methods = clazz.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];

                //  方法没有标识LagouRequestMapping,就不处理
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }

                // 如果标识,就处理
                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String methodUrl = annotation.value();  // /query
                String url = baseUrl + methodUrl;    // 计算出来的url /demo/query

                //处理@Security
                Set<String> securityUser = new HashSet<>();
                if (method.isAnnotationPresent(Security.class)) {
                    Security securityAnnotation = method.getAnnotation(Security.class);
                    String[] value = securityAnnotation.value();
                    securityUser.addAll(new HashSet<>(Arrays.asList(value)));
                    securityUser.addAll(securityUserCl);
                }

                // 把method所有信息及url封装为一个Handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url), securityUser);

                // 计算方法的参数位置信息  // query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];

                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), j);  // <name,2>
                    }

                }

                // 建立url和method之间的映射关系(map缓存起来)
                handlerMapping.add(handler);
            }
        }
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))
        Handler handler = getHandler(req);

        if (handler != null) {
            //判断是否允许访问
            if (handler.getSecurityUser() != null && handler.getSecurityUser().size() > 0) {
                String username = req.getParameter("username");
                if (!handler.getSecurityUser().contains(username)) {
                    resp.getWriter().write("Permission denied;");
                    return;
                }
            }

            // 参数绑定
            // 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
            Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();


            // 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
            Object[] paraValues = new Object[parameterTypes.length];

            // 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致

            Map<String, String[]> parameterMap = req.getParameterMap();

            // 遍历request中所有参数  (填充除了request,response之外的参数)
            for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
                // name=1&name=2   name [1,2]
                String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2

                // 如果参数和方法中的参数匹配上了,填充数据
                if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                    continue;
                }

                // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
                Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置

                paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
            }

            int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
            paraValues[requestIndex] = req;

            int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
            paraValues[responseIndex] = resp;

            // 最终调用handler的method属性
            try {
                handler.getMethod().invoke(handler.getController(), paraValues);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;

    }
}

4、进行测试

测试相关源码请参见项目源码

项目源码:去看源码

SSM整合

SSM = Spring + SpringMVC + Mybatis = (Spring + Mybatis)+ SpringMVC

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/liliangsheng/ozspring-mvc-demo.git
git@gitee.com:liliangsheng/ozspring-mvc-demo.git
liliangsheng
ozspring-mvc-demo
OZSpringMvcDemo
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891