# OZSpringMvcDemo **Repository Path**: felixxu/ozspring-mvc-demo ## Basic Information - **Project Name**: OZSpringMvcDemo - **Description**: 手写SpringMvc框架,使用SpringDataJpa,SSS整合(Spring+SpringMVC+SpringDataJPA),登录验证使用SpringMVC拦截器实现 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2021-03-29 - **Last Updated**: 2021-03-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### 介绍 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 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请求处理流程](https://images.gitee.com/uploads/images/2020/1126/110856_33be4031_797716.jpeg "Spring MVC请求处理流程.jpg") **流程说明:** - 第⼀步:⽤户发送请求⾄前端控制器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这样的结构,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](https://images.gitee.com/uploads/images/2020/1126/151457_f2cff13e_797716.png "SpringMvc Demo目录结构.png") **DemoController** ```Java @Controller @RequestMapping("/demo") public class DemoController { @RequestMapping("/handle01") public String handle01(String name,Map model) { System.out.println("++++++++handler业务逻辑处理中...."); Date date = new Date(); model.put("date",date); return "success"; } } ``` **springmvc.xml** ```XML ``` **web.xml** ```XML springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath*:springmvc.xml springmvc / ``` 2、断点查看请求到Handler的调用栈 ![SpringMvc请求流程调用栈](https://images.gitee.com/uploads/images/2020/1126/153110_868e3fcc_797716.png "SpringMvc请求流程调用栈.png") 由上图我们可以看到整个过程调用的方法栈 3、org.springframework.web.servlet.DispatcherServlet#doDispatch **关建方法,SpringMVC处理请求的流程就在这个方法** ```Java 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 ```Java @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 完成视图的渲染 ```Java 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实现 ```Java @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视图对象的过程中,要将逻辑视图名解析为物理视图名 ```Java 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⽅法,渲染数据 ```Java @Override public void render(@Nullable Map 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 mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); //渲染数据 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } ``` - - - org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel 渲染数据 ```Java @Override protected void renderMergedOutputModel( Map 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使用的策略对象 *

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框架),我们在自定义IOC和AOP的框架上进行扩展开发 1、创建@Controller、@RequestMapping注解类 ```Java @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方法相关的信息 ```Java public class Handler { private Object controller; // method.invoke(obj,) private Method method; private Pattern pattern; // spring中url是支持正则的 private Map paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 private Set securityUser; //允许访问的用户 为null代表不限制用户的访问,配置了用户才限制在此范围内的用户访问 public Handler(Object controller, Method method, Pattern pattern,Set 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 getParamIndexMapping() { return paramIndexMapping; } public void setParamIndexMapping(Map paramIndexMapping) { this.paramIndexMapping = paramIndexMapping; } public Set getSecurityUser() { return securityUser; } public void setSecurityUser(Set securityUser) { this.securityUser = securityUser; } } ``` 3、创建DispatcherServlet类 ```Java @WebServlet(name = "ozmvc", urlPatterns = "/*") public class DispatcherServlet extends HttpServlet { // handlerMapping //存储url和Method之间的关联,就是Handler private List handlerMapping = new ArrayList<>(); @Override public void init(ServletConfig servletConfig) throws ServletException { initHandlerMapping(servletConfig); } /** * 构造一个HandlerMapping处理器映射器 * 最关键的环节 * 目的:将url和method建立关联 */ private void initHandlerMapping(ServletConfig servletConfig) { //存储url和Method之间的映射关系 Map beanMap = BeanFactory.getBeans(); if (beanMap.isEmpty()) { return; } for (Map.Entry 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 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 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); // } } // 建立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 parameterMap = req.getParameterMap(); // 遍历request中所有参数 (填充除了request,response之外的参数) for (Map.Entry 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、进行测试 测试相关源码请参见项目源码 项目源码:[去看源码](https://gitee.com/orangezh/ozspring-mvc-demo/tree/master/OZSpringMvcDemo) #### SSM整合 SSM = Spring + SpringMVC + Mybatis = (Spring + Mybatis)+ SpringMVC