1 Star 0 Fork 4

nainiu192141 / OZSpringMvcDemo

forked from Orange_Zh / OZSpringMvcDemo 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

介绍

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

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

手写SpringMvc框架,使用SpringDataJpa,SSS整合(Spring+SpringMVC+SpringDataJPA),登录验证使用SpringMVC拦截器实现 展开 收起
Java
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/felixxu/ozspring-mvc-demo.git
git@gitee.com:felixxu/ozspring-mvc-demo.git
felixxu
ozspring-mvc-demo
OZSpringMvcDemo
master

搜索帮助

14c37bed 8189591 565d56ea 8189591