学习中心
登录
  • 全部
  • MCA高级架构师
  • C++就业方向课程
  • AI大模型全链路实战
  • Java后端工程师
  • 网络安全大师课
  • GoLang后端工程师
  • 云原生架构师
  • Python全系列大师课
  • 鸿蒙原生开发工程师
  • AIoT智能物联网
  • AI人工智能算法班
  • C++逆向开发工程师
  • 大数据开发工程师
  • 嵌入式物联网工程师
  • C++ 后端开发工程师
  • Python大数据
  • HTML5前端+全栈
  • Elastic认证特训营
  • 数据结构与算法大师课
  • IT全系列高阶大师班
  • Python全栈工程师
  • Web前端架构师
  • 软件测试开发工程师
  • golang云原生开发工程师
  • AI实战工具课
  • AI大模型工程师
  • AI人工智能零基础入门班
  • CKA认证
  • CKS认证
  • 数据分析全岗位实战班
  • Python数据分析挖掘
  • 游戏后端架构师
  • Python办公自动化职场班
  • C++游戏开发工程师
优秀笔记
  • 2022-01-08:数组中只有0和1,每过1代,0旁边只有
    2022-01-08:数组中只有0和1,每过1代,0旁边只有1个1,当前0会变成1。每过1代,0旁边有2个1,当前0还是0。 比如10001,经过1代,会变成11011,再过1代,还是11011 。 求一个数组经过M代以后的数组。函数定义是void f(int[] arr,int m) 。 答案2022-01-08: x里有有限个0。 1x1,中间0,x中有2m个0变成1,最中间的0不会变成1。 1x,右0,x中有m个0变成1。 x1,左0,x中有m个0变成1。 时间复杂度:O(N)。 空间复杂度:O(1)。 代码用golang编写。代码如下: ```go package main import "fmt" func main() { arr := []byte{0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0} f(arr, 2) fmt.Println(arr) } func f(arr []byte, m int) { //找中间0 oneIndexList := make([]int, 0) for i, a := range arr { if len(oneIndexList) == 2 { oneIndexList = oneIndexList[1:] } if a == 1 { oneIndexList = append(oneIndexList, i) } if len(oneIndexList) == 2 { for j := oneIndexList[0] + 1; j 0 { left := oneLeftIndex - m right := oneLeftIndex - 1 if left = 0; i-- { a := arr[i] if a == 1 { oneRightIndex = i break } } if oneRightIndex len(arr)-1 { right = len(arr) - 1 } for i := left; i <= right; i++ { arr[i] = 1 } } } ``` 执行结果如下: ![图片](https://img-blog.csdnimg.cn/96a288603ecf4054b7f03e12f4f30492.png) --- 题目来自左神,代码是自己写的。
    福大大架构师每日一题 2022-01-08 18:21
    3524 2
  • 2021-12-24:划分字母区间。 字符串 S 由小写字母
    2021-12-24:划分字母区间。 字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。 力扣763。某大厂面试题。 答案2021-12-24: 时间紧,具体见代码。 代码用golang编写。代码如下: ```go package main import "fmt" func main() { S := "ababcbacadefegdehijhklij" ret := partitionLabels(S) fmt.Println(ret) } func partitionLabels(S string) []int { str := []byte(S) far := make([]int, 26) for i := 0; i right { ans = append(ans, right-left+1) left = i } if right < far[str[i]-'a'] { right = far[str[i]-'a'] } } ans = append(ans, right-left+1) return ans } ``` 执行结果如下: ![图片](https://img-blog.csdnimg.cn/68735afa1ab94ea2a3b23879bcb6ebb4.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56aP5aSn5aSn5p625p6E5biI5q-P5pel5LiA6aKY,size_17,color_FFFFFF,t_70,g_se,x_16) --- [左神java代码](https://github.com/algorithmzuo/coding-for-great-offer/blob/main/src/class38/Problem_0763_PartitionLabels.java)
    福大大架构师每日一题 2021-12-24 23:54
    2631 0
  • 变量的作用域
    ``` '''变量的作用域: 程序代码能访问该变量的区域 根据变量的有效范围可分为 ·局部变量 在函数内定义并使用变量,只在函数内有效, 局部变量使用global声明,这个变量就会变成全局变量 ·全局变量 函数体外定义的变量,可作用于函数内外的''' def fun(a,b): c=a+b #c,就称为局部变量,因为c是在函数体内进行定义变量,a,b为函数的形参,作用范围也是函数内部,也相当于局部变量 print(c) #print(c) 因为a,c超出了起作用的范围(也称为超出了作用域) #print(a) name='杨' #name的作用范围为函数内部和外部都可以使用 --> print(name) def fun1(): print(name) #调用函数 fun1() def fun2(): global age #函数内部定义的变量,局部变量,局部变量使用global声明,这个变量实际上就变成了全局变量 age=18 print(age) fun2() print(age) ```
    LZ 2021-12-24 21:40
    2397 9
  • 并发架构学习笔记一
    ``` 服务并行&并发 并行和并发 并行:同一时刻多个任务同时进行 并发:多个任务交替进行 宏观上并行和并发统称为并发 集群 同质的(同样的配置,运行同样的程序,对外提供同样的服务)。 缺点:修改服务本地缓存中的信息会有问题。 无状态服务好一些。 有状态的服务需要建立用户和服务器的对应关系。 单一服务节点集群(即同一用户只会连一个服务器) 优点:可以使用本地存储 缺点:数据会丢 容错性差 信息共享节点集群(同一个用户随机轮询后面多个服务器) 优点:提升计算能力。cpu和内存 缺点:存储会成为瓶颈 信息一致节点集群(每个节点挂一个存储,存储之间有数据同步) 例如阿里盘古。 在一定时间之后能读取到变更,根据变更时间的长度,将一致性分为几类: 强一致性,最终一致性 分布式系统和微服务系统 分布式系统的意义 解决问题:单一功能过于复杂后可能用户还没请求,自己就崩了。修改后单一系统测试成本很高。 存在问题:各系统之间的数据一致性,幂等问题。应用有从属关系 微服务系统 应用和应用之间,都是独立的。 保证接口不变,其他随意。 ```
    东尼大木 2021-12-24 18:07
    2398 2
  • 三高项目指标和优化方案学习
    ``` 三高项目指标 并发数(系统同时服务的调用房的数量): 1.并发的用户数,有一部分用户使用业务,另一部分挂机,没有具体操作 2.并发连接数。用户和服务器之间的连接。一部分连接在传输数据,一部分连接仅仅连接而已。 3.并发请求数。有可能请求静态数据,有可能是写操作 4.并发线程数,系统内,并发的线程数。 吞吐量:单位时间内,能接受和返回的数据请求量 1.看业务逻辑。 2.看请求的数据。 常用指标 TPS:每秒进行的事务数目。(整体定义事务的请求、操作、返回) QPS:每秒进行的查询量 一个TPS包括N个QPS 平均响应时间:(请求发出,接受到响应,之间的时间) 所有响应时间的平均值。 可靠性指标:平均无故障时间。系统上线,到第一次发生故障,运行时间的平均值。 阿尔达姆定律:在理论上,执行所有任务的速度会随着系统资源的提升而加快,然而,无论提升的幅度有多大,理论速度总是受限于无法从改进中受益的那部分任务的执行速度 [2]。这带来了一个根本性的难题 —— 一旦所有可并行化部分的吞吐量都达到最大时,串行化部分的吞吐量将成为系统吞吐量的天花板 指标之间的关系 并发数对吞吐量的关系,到一定阈值之内,增加并发量吞吐量上升,但是超过了一定阈值后,吞吐量会下降 并发数对响应时间:前期缓慢上升,后期指数上升。互相影响 用户角度:不受限制 系统角度:恶性循环 如何优化系统: 第一个优化:并发数。 优化方案 分流:将请求到达系统前的各个阶段,对请求进行分流。 好处:减少每个系统的并发数。让用户访问离他最近的系统,降低网络延时,减少平均响应时间。 第一阶段DNS:将域名解析成IP地址。DNS结构:A(Address)记录:ip;CName(Canonical Name):别名/域名到域名 DNS解析流程 localDNS(本地域名服务器)->根域名服务器->顶级域名服务器->域名服务器 DNS负载均衡:域名添加多个A记录(ip地址) CDN:用于减少网络的拓扑距离,将请求发送到距离用户网络拓扑最近的服务器(拓扑的近 是动态变化的,网络负载) ```
    东尼大木 2021-12-23 12:39
    2206 0
  • 05 处理器适配器的详细处理过程
    # 05 处理器适配器的详细处理过程 ​ 当把需要的处理器和适配器找到之后,下面就开始执行具体的处理过程了,通过下述代码进行调用: ``` mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ``` 找到RequestMappingHandlerAdapter的类中 ```java @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 先声明ModelAndView结果 ModelAndView mav; // 检查请求是否支持,1、通过supportedMethods方法判断是否包含请求方法,2、检查请求中是否包含session checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. // 处理时是否对session加锁,默认为false if (this.synchronizeOnSession) { // 获取session对象 HttpSession session = request.getSession(false); // 对session是否为空做判断,如果不等于空 if (session != null) { // 获取session中锁对象 Object mutex = WebUtils.getSessionMutex(session); // 加锁后执行调用处理器方法逻辑 synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary // 没有session,则忽略加所,直接执行调用处理器方法逻辑 mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } // 如果响应结果不包含缓存控制头 if (!response.containsHeader(HEADER_CACHE_CONTROL)) { // 如果该处理器方法包含sessionAttribute if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { // 应哟个sessionAttributes的缓存策略 applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { //不包含SessionAttribute,准备请求。内部逻辑应用配置的缓存策略,本适配器默认没有缓存策略,故所有请求都不返回缓存响应头 prepareResponse(response); } } // 返回结果 return mav; } ``` ### 1、mav = invokeHandlerMethod(request, response, handlerMethod); ```java @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 把请求和响应封装为一个ServletWebRequest对象 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 创建WebDataBinderFactory工厂,该工厂用于获取处理器方法对应的WebDataBinder组件 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 获取当前处理器方法对应的Model工厂,该工厂用于获取处理器方法对应的model ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 创建一个Servlet下可调用处理器的方法,内部创建了一个ServletInvocableHandlerMethod对象 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); //设置参数解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } // 设置返回值处理器 if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } // 设置DataBinder工厂 invocableMethod.setDataBinderFactory(binderFactory); // 设置参数名获取器,用于获取方法上的参数名 invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 创建用于处理过程中使用的ModelAndView容器 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // 向MV容器中添加FlashMap的属性 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 初始化Model,包含调用Model相关的初始化方法,如ModelAttribute注解标记的方法 modelFactory.initModel(webRequest, mavContainer, invocableMethod); //在重定向时忽略默认的Model属性值,只考虑重定向Model的属性值,默认为true mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 准备异步相关的处理 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 调用处理器方法并处理返回值 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // 获取MV结果 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { // 标记请求完成 webRequest.requestCompleted(); } } ``` ##### 1、WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ```java private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { // 获取处理器方法所在的Bean类型 Class handlerType = handlerMethod.getBeanType(); // 从InitBinder缓存中获取当前处理器类型标记了@InitBinder注释的方法 Set methods = this.initBinderCache.get(handlerType); // 如果为空,说明缓存中没有,进入获取逻辑 if (methods == null) { // 找到所有标记了@InitBinder的方法 methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); // 添加到缓存中 this.initBinderCache.put(handlerType, methods); } // 保存全部标记了@InitBinder方法的结果列表 List initBinderMethods = new ArrayList<>(); // Global methods first // 遍历全局InitBinder增强器缓存 this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> { // 判断是否能够应用到当前处理器中,判断依据是@ControllerAdvice if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); // 添加处理器方法所在Bean的@InitBinder方法 for (Method method : methods) { Object bean = handlerMethod.getBean(); // 把Bean和方法封装为InvocableHandlerMethod initBinderMethods.add(createInitBinderMethod(bean, method)); } // 把InitBinder方法参数列表作为参数,创建WebDataBinder工厂 return createDataBinderFactory(initBinderMethods); } ``` ##### 2、ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ```java private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { // 获取SessionAttributes的处理器,用于处理处理器Bean类型上标记的@SessionAttributes注解 SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); // 获取处理器Bean类型 Class handlerType = handlerMethod.getBeanType(); // 从ModelAttribute缓存中获取Bean类型对应的@ModelAttribute标记的方法 Set methods = this.modelAttributeCache.get(handlerType); if (methods == null) { // 若缓存中没有,则进入获取逻辑,获取处理器类型中标记了@ModelAttribute注解且未标记@RequestMapping注解的方法,作为ModelAttribute方法 methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); // 加入缓存 this.modelAttributeCache.put(handlerType, methods); } // 创建一个列表,用于保存@ModelAttribute注解标注方法的InvocableHandlerMethod,可以调用封装 List attrMethods = new ArrayList<>(); // Global methods first this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> { // 通过@ControllerAdvice的属性进行过滤,判断是否可被应哟个到当前处理器Bean中 if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { // 解析处理器增强Bean为实例 Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { //遍历对应的方法集合,创建ModelAttribute可调用方法,添加到结果列表中 attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } } }); //全局@ModelAttribute方法添加完成之后,添加当前处理器Bean中的@ModelAttribute方法 for (Method method : methods) { Object bean = handlerMethod.getBean(); attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } // 返回Model工厂实例,封装全部@ModelAttribute的可调用方法 return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); } ``` ##### 3、ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); ```java protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) { //创建了一个新的对象 return new ServletInvocableHandlerMethod(handlerMethod); } ``` ##### 4、modelFactory.initModel(webRequest, mavContainer, invocableMethod); ```java public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { // 使用SEssionAttribute处理器,先从Session中获取@SessionAttributes注解声明的session属性 Map sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); // 把@SessionAttributes声明的session属性全部合并到ModelAndView容器中 container.mergeAttributes(sessionAttributes); // 调用@ModelArrtibute注解标记的方法,把方法返回值放入ModelAndView容器的Model中 invokeModelAttributeMethods(request, container); // 遍历返回的name列表 for (String name : findSessionAttributeArguments(handlerMethod)) { // 如果MV容器中不包含name属性,则从session中获取该属性,添加到容器中 if (!container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); } } } ----------------------------------------------------------------------------------------- private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception { // 遍历全部模型属性方法,直到为空 while (!this.modelMethods.isEmpty()) { // 获取当前遍历的可调用模型属性方法 InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod(); // 检查该方法上是否存在@ModelAttribute注解 ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class); Assert.state(ann != null, "No ModelAttribute annotation"); // 如果当前容器中已经包含了模型属性方法声明的模型名,则不覆盖现有属性 if (container.containsAttribute(ann.name())) { // 如果注解标记了binding=false,则把这个属性名添加到容器的不绑定列表中,用于在参数绑定时忽略此模型属性的绑定 if (!ann.binding()) { container.setBindingDisabled(ann.name()); } continue; } // 根据请求执行模型属性方法,获取模型属性方法的返回值 Object returnValue = modelMethod.invokeForRequest(request, container); // 如果方法声明的返回值类型不是void,则继续处理这个返回值 if (!modelMethod.isVoid()){ // 根据返回值与返回类型获取返回的属性名,一般是从方法注解@ModelAttribute的name属性中获取该属性名 String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType()); // 判断是否声明了binding=false if (!ann.binding()) { container.setBindingDisabled(returnValueName); } // 再次判断容器的Model中是否包含此模型属性 if (!container.containsAttribute(returnValueName)) { // 不包含,则把模型属性名与对应的返回值添加到容器中 container.addAttribute(returnValueName, returnValue); } } } } ``` ##### 5、invocableMethod.invokeAndHandle(webRequest, mavContainer); ```java public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 执行调用方法获取处理器方法的返回值,内部包含参数解析 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 设置响应状态码 setResponseStatus(webRequest); // 如果返回值为空 if (returnValue == null) { // 如果判断请求内容未修改或响应状态码不是空或容器中标记了请求已处理 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); // 表示请求已经处理完成,直接返回 mavContainer.setRequestHandled(true); return; } } // 如果响应状态原因存在内容,则直接返回 else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } // 如果请求没有被处理,则标记请求未被处理 mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { //执行返回值处理器的处理返回逻辑 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } // 异常记录日志,并且抛出 catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } ----------------------------------------------------------------------------------------- @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 解析处理器方法的所有参数,返回一个参数数组,用于反射调用 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } // 通过反射执行真实的处理器方法,获取处理器方法执行后的返回值,此处就是调用Controller中的方法 return doInvoke(args); } ----------------------------------------------------------------------------------------- protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取方法上的参数数组 MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; // 遍历参数 for (int i = 0; i flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); // 通过传入的请求封装获取原始的请求 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); // 如果请求不为空 if (request != null) { // 把重定向的删除属性添加到当前请求的输出FlashMap中 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; } ```
    连鹏举 2021-08-10 04:31
    3135 1
  • 04 SpringMVC源码解析
    # 04SpringMVC源码解析1 ​ 在讲解springmvc之前,其实是需要大家了解一点tomcat的源码知识的,但是大部分的初学者还只停留在应用的层面,所以,下面展示tomcat容器初始化的流程图和加载servlet的流程图,大家只需要先记住他们的执行顺序即可,等后续开始tomcat源码之后我们再做下一步深入了解。 从上述流程开始看起,我们发现最终会调用Servlet的init方法,SpringMVC中最核心的类就是DispatcherServlet,因此需要找到init方法。 ### 1、DispatcherServlet的初始化 DispatcherServlet的类图: ​ 可以看到,DispatcherServlet继承自HttpServlet,它的本质就是一个Servlet,但是此类中并没有init方法,因此要去父类中进行查找,最终在HttpServletBean类中重写了父类GenericServlet的init方法。因此当tomcat容器启动的时候会调用init方法开始执行,中间会经历N多个环节,此处不需要了解,唯一需要注意的一个点,就在于SpringMVC的组件会调用DispatcherServlet的组件进行初始化工作,这些初始化工作会完成对于九大组件的初始化,这个初始化会从DispatcherServlet.properties文件中进行相应的属性值加载。 HttpServletBean---------init() ```java public final void init() throws ServletException { // Set bean properties from init parameters. // 将web.xml文件中初始化参数设置到bean中,requiredProperties为必须参数 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { //将DispatcherServlet类添加到BeanWrapper的包装类中 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); //对DispatcherServlet进行初始化工作 initBeanWrapper(bw); //将配置的初始化值设置到DispatcherServlet中 bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. // 模板方法,子类初始化的入口方法 initServletBean(); } ``` 调用子类方法实现初始化BeanServlet FrameworlServlet------initServletBean ```java protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'"); } // 设置开始时间 long startTime = System.currentTimeMillis(); try { // webApplicationContext是FrameworkServlet的上下文,后续的方法是进行上下万的初始化 this.webApplicationContext = initWebApplicationContext(); // 初始化FrameworkServlet,默认实现为null,由子类进行实现 initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } if (logger.isDebugEnabled()) { String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data"; logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); } } ``` 此后的流程会进入到Spring的onRefresh方法中,最终会调用DispatcherServlet中的onRefresh方法。 ```java @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * Initialize the strategy objects that this servlet uses. * May be overridden in subclasses in order to initialize further strategy objects.进行springmvc组件的初始化, */ protected void initStrategies(ApplicationContext context) { // 文件上传解析器 initMultipartResolver(context); // 区域信息解析器,国际化相关 initLocaleResolver(context); // 主题解析器 initThemeResolver(context); // 处理映射器 initHandlerMappings(context); // 处理适配器 initHandlerAdapters(context); // 异常解析器 initHandlerExceptionResolvers(context); // RequestToViewName解析器 initRequestToViewNameTranslator(context); // 视图解析器 initViewResolvers(context); // FlashMap解析器 initFlashMapManager(context); } ``` ​ 这几个组件的初始化过程都差不多,因此我们选择一个来重点描述,其他的需要大家下去之后自己来研究了。 ```java private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 是否查找所有HandlerMapping标识 if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. // 从上下文中查找HandlerMapping类型的Bean Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { // 根据指定名称获取HandlerMapping对象 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. // 确保至少有一个HandlerMapping,如果没有找到,使用默认策略,注册一个默认的 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } } ``` 通过默认策略来加载默认的配置项 ```java protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) { String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return new LinkedList<>(); } } ``` 默认策略就完成了从DispatcherServlet.properties文件中加载的属性值 ```java private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } } ``` ​ 以上的操作就是DispatcherServlet的组件的初始化过程,下去之后一定要把每个过程详细看一下,如果能够结合spring进行查看就更好了。 ### 2、DispatcherServlet的请求处理 ​ 我们都知道当发送请求的时候每次都是找到Servlet的doget或者dopost方法中,但是你在DispatcherServlet中并没有看到这两个方法,还是按照老规矩,子类没有去父类找实现。 FrameworkServlet.java ```java @Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } ``` ​ 大家看到无论发送什么请求,最终都会进入到processRequest方法中,此方法用来处理我们从浏览器发送的请求, ```java protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 记录启动时间 long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 获取之前设置的LocaleContext上下文 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 以当前的request作用域来创建一个上下文对象 LocaleContext localeContext = buildLocaleContext(request); // 获取之前的request属性值 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // previousAttributes若为null,那么就new ServletRequestAttributes(request, response);如果不等于空,就直接返回 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 获取异步管理器 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //将之前设置request和locale上下文绑定到requestContext中 initContextHolders(request, localeContext, requestAttributes); try { //在子类中进行逻辑实现 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); } } ``` ​ 大家看到了,在上述方法中,实际调用的是子类的doService方法,也就是DispatcherServlet中的方法实现,现在终于回归到我们要重点关注的类了,接着向下看; DispatcherServlet doService ```java @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. // 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本 // 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去 Map attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. // 把一些常用对象放进请求域 方便Handler里面可以随意获取 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 从flashMapManager中获取重定向的相关参数 if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { //DispatcherServlet中最重要的方法,由此方法来分发请求,进行处理 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } } ``` ​ 到此为止,就该执行DispatcherServlet的核心方法doDispatcher,此方法完成了我们需要的全部功能,接着向下看。 3、DispatcherServlet的核心处理方法doDispatcher ```java protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 定义一个已处理请求,指向参数的request HttpServletRequest processedRequest = request; // 定义处理器执行连,内部封装拦截器列表和处理器 HandlerExecutionChain mappedHandler = null; // 是否有文件上传的请求标志 boolean multipartRequestParsed = false; // 获取异步管理器,执行异步操作 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { // 保存处理器执行的返回结果 ModelAndView mv = null; // 保存处理过程中的异常 Exception dispatchException = null; try { // 判断当前请求是否有上传需求,并返回保存到processedRequest中 processedRequest = checkMultipart(request); // 判断当前请求是否是文件上传的请求,如果是则说明是上传请求已经处理 multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 获取可处理当前请求的请求处理器,通过HandlerMapping进行查找 mappedHandler = getHandler(processedRequest); // 如果没有,就执行没有处理器的逻辑 if (mappedHandler == null) { // 在内部处理中抛出异常或者返回404 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 根据当前请求的处理器获取支持该处理器的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // 处理last-modified请求头,用于判断请求内容是否发生修改 String method = request.getMethod(); boolean isGet = "GET".equals(method); // 只有get请求和head请求执行此判断 if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 通过mappedHandler这个HandlerExecutionChain执行链的封装,链式执行所有连接器的前置拦截方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 任意一个拦截器的前置拦截方法返回false,提前结束请求的处理 return; } // Actually invoke the handler. // 执行处理适配器的处理方法,传入请求,对请求进行处理,此方法的返回值是ModelAndView对象,封装了模型和视图 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 如果是异步处理,则直接返回,后续处理通过异步执行 if (asyncManager.isConcurrentHandlingStarted()) { return; } // 返回的mv对象中如果没有视图名称,则根据请求设置默认视图名 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. // 4.3版本之后提供了error类型异常的处理 dispatchException = new NestedServletException("Handler dispatch failed", err); } // 对下执行结果进行处理,包括视图的处理和异常的处理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 链式执行拦截器链的afterCompletion方法 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { // 拦截error类型异常,拦截后链式执行拦截器链的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); } } } } ``` ​ 如果把刚刚的大致流程都搞清楚的话,那么我们下面开始分析每一个环节的具体流程,下面的代码会比较多,大家尽力去理解。 ### 3、上传组件的请求处理 processedRequest = checkMultipart(request); ```java protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 判断当前请求是否包含文件上传的需求,如果是则执行后续逻辑 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // 判断当前请求是否是MultipartHttpServletRequest类型,如果是的话,就判断当前请求的类型是否是Request,如果是打印日志即可 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } //判断是否有异常 else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 将当前请求包装返回一个新的包装对象StandardMultipartHttpServletRequest return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; } ``` ### 4、获取请求处理器 mappedHandler = getHandler(processedRequest); ```java protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 判断当前处理器映射列表不为空 if (this.handlerMappings != null) { // 遍历全部处理器映射 for (HandlerMapping mapping : this.handlerMappings) { // 执行当前处理器映射的获取处理器方法,获取与本次请求适配的处理器执行链 HandlerExecutionChain handler = mapping.getHandler(request); // 不为空直接返回,即便有多个处理器执行链匹配,也只返回第一个,处理器映射排在前面的优先返回 if (handler != null) { return handler; } } } return null; } ``` 在springmvc中默认会加载三个请求处理类:RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping。这几个类都是在初始化的时候设置成功的,同样的,他们也具备相同的父类AbstractHandlerMapping,无论哪一个处理类最终都会嗲用getHandler方法,此方法在父类中,没有在子类中实现,下面来看这个方法的逻辑: AbstractHandlerMapping getHandler() ```java public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //此方法留给子类实现,用于查找handler处理器,每个子类都有不同的实现,因此需要单独去查看 Object handler = getHandlerInternal(request); // 如果handler为空,那么就使用默认的 if (handler == null) { handler = getDefaultHandler(); } // 如果还是为空的话,那么就直接返回 if (handler == null) { return null; } // Bean name or resolved handler? // 如果返回的handler为string,则使用Spring容器实例化 if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // 查询匹配的拦截器,组装handler生成HandlerExecutionChain HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } // 判断是否是cors请求,cors是跨域请求 if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); config = (config != null ? config.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } // 返回处理器链 return executionChain; } ``` ### 5、获取请求处理类的适配器类 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ```java protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { // 遍历处理适配器列表,根据support方法来进行判断 for (HandlerAdapter adapter : this.handlerAdapters) { // 当找到支持的适配器则返回 if (adapter.supports(handler)) { return adapter; } } } // 未找到适配器则直接抛出异常 throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } ``` 此适配器集合共有三个具体实现子类,分别是:HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter,然后根据support方法来判断使用哪种适配器,并将对应的适配器对象返回。 ### 6、执行前置拦截器链 if (!mappedHandler.applyPreHandle(processedRequest, response)) ```java boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取当前处理器执行链中的所有拦截器 HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { // 正序遍历全部拦截器 for (int i = 0; i = 0; i--) { HandlerInterceptor interceptor = interceptors[i]; // 直接执行,无返回值 interceptor.postHandle(request, response, this.handler, mv); } } } ``` ### 9、处理Controller返回的结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); ```java private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // 判断是否是error视图 boolean errorView = false; // 如果有异常,就 进入异常处理逻辑,返回到异常页面 if (exception != null) { // 如果异常类型为ModelAndViewDefiningException if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); // 直接使用异常中封装的ModelAndView作为最终的mv结果 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { // 其他异常类型,先获取处理器 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); // 执行process处理其异常方法,获取处理了异常结果后得到的mv结果 mv = processHandlerException(request, response, handler, exception); // 如果mv不为空,则说明返回了包含异常的视图, errorView = (mv != null); } } // Did the handler return a view to render? // 如果mv不为空且mv没有标记为被清理, if (mv != null && !mv.wasCleared()) { // 执行视图渲染的操作 render(mv, request, response); // 如果是异常视图,渲染后需要清空请求属性中的异常信息 if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { // 如果视图为空,则打印一个日志 if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } // 如果异步处理已经开始,则直接返回结束执行 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } // 执行拦截器的AfterCompletion方法 if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } } ``` ​ 在上述的处理过程中,有两个比较重要的方法,第一个是发生异常时,把异常处理为mv返回值的逻辑processHandlerException,第二个是对返回的mv结果进行渲染的逻辑render。 ### 10、处理器异常处理方法 mv = processHandlerException(request, response, handler, exception); ```java @Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; // 如果处理器异常解析器列表不为空 if (this.handlerExceptionResolvers != null) { // 遍历该列表 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { // 执行处理器异常解析器的解析异常方法,拿到解析的ModelAndView的结果 exMv = resolver.resolveException(request, response, handler, ex); // 如果不为空,则将此结果作为对异常处理后的mv结果使用,中断后续的遍历动作 if (exMv != null) { break; } } } // 如果返回的异常mv不为null if (exMv != null) { // 如果mv内部为空 if (exMv.isEmpty()) { // 设置异常属性到请求属性中 request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... // 如果异常mv不包含视图 if (!exMv.hasView()) { // 采用与doDispatch方法中相同的处理逻辑来给很具请求获取默认视图名 String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { exMv.setViewName(defaultViewName); } } if (logger.isTraceEnabled()) { logger.trace("Using resolved error view: " + exMv, ex); } else if (logger.isDebugEnabled()) { logger.debug("Using resolved error view: " + exMv); } // 暴露溢写异常信息到请求属性中 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); // 返回新的mv异常视图模型 return exMv; } // 如果没有处理器异常解析器,则原封不动抛出原始异常,交给web框架处理 throw ex; } ``` 11、视图渲染方法: render(mv, request, response); ```java protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. // 先通过Locale解析器获取请求对应的Locale Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); // 设置获取的Locale为响应的Locale response.setLocale(locale); // 最终获取的视图 View view; // 如果mv中的视图为视图名,则获取这个视图名 String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. // 把视图名解析为视图 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 { // 如果mv中的status为空,则把其设置为响应的状态码, if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 执行视图的渲染方法,每种模板引擎都有其对应的视图实现,视图渲染对应于模板引擎的渲染模板 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } } ``` view = resolveViewName(viewName, mv.getModelInternal(), locale, request); ```java @Nullable protected View resolveViewName(String viewName, @Nullable Map model, Locale locale, HttpServletRequest request) throws Exception { // 如果视图解析器列表不为空 if (this.viewResolvers != null) { // 遍历视图解析器列表 for (ViewResolver viewResolver : this.viewResolvers) { // 调用视图解析器的resolveViewName方法,把视图名解析为视图 View view = viewResolver.resolveViewName(viewName, locale); // 第一个不为空的视图返回 if (view != null) { return view; } } } return null; } ```
    连鹏举 2021-08-10 04:30
    2855 0
  • 03 SpringMVC的使用2
    # 03SpringMVC的使用 ### 1、SpringMVC的返回JSON数据 ​ 到目前为止我们编写的所有Controller的方法的返回值都是String类型,但是大家应该都知道,我们有时候数据传递特别是在ajax中,我们返回的数据经常需要使用json,那么如何来保证返回的数据的是json格式呢?使用@ResponseBody注解 pom.xml ```xml 4.0.0 com.mashibing springmv_ajax 1.0-SNAPSHOT org.springframework spring-context 5.2.3.RELEASE org.springframework spring-web 5.2.3.RELEASE org.springframework spring-webmvc 5.2.3.RELEASE javax.servlet servlet-api 2.5 provided javax.servlet jsp-api 2.0 provided com.fasterxml.jackson.core jackson-core 2.10.3 com.fasterxml.jackson.core jackson-databind 2.10.3 com.fasterxml.jackson.core jackson-annotations 2.10.3 ``` springmvc.xml ```xml ``` JsonController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.ArrayList; import java.util.Date; import java.util.List; @Controller public class JsonController { @ResponseBody @RequestMapping("/json") public List json(){ List list = new ArrayList(); list.add(new User(1,"zhangsan",12,"男",new Date(),"1234@qq.com")); list.add(new User(2,"zhangsan2",12,"男",new Date(),"1234@qq.com")); list.add(new User(3,"zhangsan3",12,"男",new Date(),"1234@qq.com")); return list; } } ``` User.java ```java package com.mashibing.bean; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Date; public class User { private Integer id; private String name; private Integer age; private String gender; @JsonFormat( pattern = "yyyy-MM-dd") private Date birth; @JsonIgnore private String email; public User() { } public User(Integer id, String name, Integer age, String gender, Date birth, String email) { this.id = id; this.name = name; this.age = age; this.gender = gender; this.birth = birth; this.email = email; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", birth=" + birth + ", email='" + email + '\'' + '}'; } } ``` 同时@ResponseBody可以直接将返回的字符串数据作为响应内容 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class OtherController { @ResponseBody @RequestMapping("/testResponseBody") public String testResponseBody(){ return "success"; } } ``` ### 2、发送ajax请求获取json数据 ajax.jsp ```jsp Title 获取用户信息 $("a:first").click(function () { $.ajax({ url:"${ctp}/json", type:"GET", success:function (data) { console.log(data) $.each(data,function() { var user = this.id+"--"+this.name+"--"+this.age+"--"+this.gender+"--"+this.birth+"--"+this.email; $("div").append(user+''); }) } }); return false; }); ``` ### 3、使用@RequestBody获取请求体信息 testOther.jsp ```jsp Title ``` OtherController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class OtherController { @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody String body){ System.out.println("请求体:"+body); return "success"; } } ``` 同时@RequestBody能够接受json格式的请求数据: testOther.jsp ```jsp Title 发送json数据 $("a:first").click(function () { var user = {id:"1",name:"zhangsan",age:"12",gender:"男",birth:"2020-3-13",email:"123@qq.com"}; var userJson = JSON.stringify(user); $.ajax({ url:"${ctp}/testRequestJson", type:"POST", data:userJson, contentType:"application/json", success:function (data) { alert(data); } }); return false; }); ``` OtherController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class OtherController { @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody String body){ System.out.println("请求体:"+body); return "success"; } @RequestMapping("/testRequestJson") public String testRequestBody(@RequestBody User user){ System.out.println("对象:"+user); return "success"; } } ``` 在接受请求的时候还可以使用HttpEntity对象,用来接受参数,可以获取请求头信息。 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class OtherController { @RequestMapping("/testHttpEntity") public String testRequestBody(HttpEntity httpEntity){ System.out.println(httpEntity); return "success"; } } ``` ### 4、使用RespsonseEntity可以用来定制响应内容 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class OtherController { @RequestMapping("/testResponseEntity") public ResponseEntity testResponseEntity(){ String body = "hello"; MultiValueMap header = new HttpHeaders(); header.add("Set-Cookie","name=zhangsan"); return new ResponseEntity(body,header, HttpStatus.OK); } } ``` ### 5、文件下载 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import java.io.FileInputStream; import java.io.FileNotFoundException; @Controller public class OtherController { @RequestMapping("/download") public ResponseEntity download(HttpServletRequest request) throws Exception { //获取要下载文件的路径及输入流对象 ServletContext servletContext = request.getServletContext(); String realPath = servletContext.getRealPath("/script/jquery-1.9.1.min.js"); FileInputStream fileInputStream = new FileInputStream(realPath); byte[] bytes = new byte[fileInputStream.available()]; fileInputStream.read(bytes); fileInputStream.close(); //将要下载文件内容返回 HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("Content-Disposition","attachment;filename=jquery-1.9.1.min.js"); return new ResponseEntity(bytes,httpHeaders,HttpStatus.OK); } } ``` ### 6、文件上传 ​ Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 **MultipartResolver** 实现的。Spring 用 **Jakarta Commons FileUpload** 技术实现了一个 MultipartResolver 实现类:**CommonsMultipartResovler** ​ Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver。 pom.xml ```xml 4.0.0 com.mashibing springmvc_upload 1.0-SNAPSHOT org.springframework spring-context 5.2.3.RELEASE org.springframework spring-web 5.2.3.RELEASE org.springframework spring-webmvc 5.2.3.RELEASE javax.servlet javax.servlet-api 4.0.1 provided javax.servlet jsp-api 2.0 provided com.fasterxml.jackson.core jackson-core 2.10.3 com.fasterxml.jackson.core jackson-databind 2.10.3 com.fasterxml.jackson.core jackson-annotations 2.10.3 commons-io commons-io 2.6 commons-fileupload commons-fileupload 1.4 ``` web.xml ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc.xml springmvc / encoding org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true encoding /* ``` springmvc.xml ```xml ``` index.jsp ```jsp $Title$ 文件: 描述: ``` UploadHandler.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; @Controller public class UploadHandler { @RequestMapping(value = "/testUpload", method = RequestMethod.POST) public String testUpload(@RequestParam(value = "desc", required = false) String desc, @RequestParam("file") MultipartFile multipartFile) throws IOException { System.out.println("desc : " + desc); System.out.println("OriginalFilename : " + multipartFile.getOriginalFilename()); multipartFile.transferTo(new File("D:\\file\\"+multipartFile.getOriginalFilename())); return "success"; //增加成功页面: /views/success.jsp } } ``` success.jsp ```jsp Title success ``` 如果是多文件上传,那么又改如何处理呢? index.jsp ```jsp $Title$ 文件: 文件: 文件: 描述: ``` UploadHandler.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; @Controller public class UploadHandler { @RequestMapping(value = "/testUpload", method = RequestMethod.POST) public String testUpload(@RequestParam(value = "desc", required = false) String desc, @RequestParam("file") MultipartFile[] multipartFile) throws IOException { System.out.println("desc : " + desc); for (MultipartFile file : multipartFile) { if (!file.isEmpty()) { System.out.println("OriginalFilename : " + file.getOriginalFilename()); file.transferTo(new File("D:\\file\\" + file.getOriginalFilename())); } } return "success"; //增加成功页面: /views/success.jsp } } ``` ### 7、Springmvc拦截器 ​ SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现**HandlerInterceptor**接口。 ​ **preHandle**():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false ​ **postHandle**():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。 ​ **afterCompletion**():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。 ##### 1、自定义第一个拦截器 MyFirstInterceptor.java ```java package com.mashibing.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyFirstInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(this.getClass().getName()+"------->preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(this.getClass().getName()+"------->postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(this.getClass().getName()+"------->afterCompletion"); } } ``` TestInterceptorController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class TestInterceptorController { @RequestMapping("test01") public String test01(){ System.out.println("test01"); return "success"; } } ``` springmvc.xml ```xml ``` success.jsp ```jsp Title success ``` ​ 通过运行结果能够发现拦截器的执行顺序如下: ​ 可以看到先执行拦截器的preHandle方法----》执行目标方法----》执行拦截器的postHandle方法----》执行页面跳转----》执行拦截器的afterCompletion方法 ​ 在配置拦截器的时候有两个需要注意的点: ​ 1、如果prehandle方法返回值 为false,那么意味着不放行,那么就会造成后续的所有操作都中断 ​ 2、如果执行到方法中出现异常,那么后续流程不会处理但是afterCompletion方法会执行 ##### 2、定义多个拦截器 ​ 再添加另外一个拦截器 MySecondInterceptor.java ```java package com.mashibing.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MySecondInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(this.getClass().getName()+"------->preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(this.getClass().getName()+"------->postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(this.getClass().getName()+"------->afterCompletion"); } } ``` 看到如下执行顺序: 调整两个拦截器的配置顺序: 大家可以看到对应的效果,谁先执行取决于配置的顺序。 ​ 拦截器的preHandle是按照顺序执行的 ​ 拦截器的postHandle是按照逆序执行的 ​ 拦截器的afterCompletion是按照逆序执行的 ​ 如果执行的时候核心的业务代码出问题了,那么已经通过的拦截器的afterCompletion会接着执行。 ### 8、拦截器跟过滤器的区别 ​ 1、过滤器是基于函数回调的,而拦截器是基于java反射的 ​ 2、过滤器依赖于servlet容器,而拦截器不依赖与Servlet容器 ​ 3、过滤器几乎对所有的请求都起作用,而拦截器只能对action请求起作用 ​ 4、拦截器可以访问action的上下文,而过滤器不可以 ​ 5、在controller的生命周期中,拦截器可以多次调用,而过滤器只能在web容器初始化的时候初始化一次,后续匹配的所有请求都会经过过滤器来进行过滤 ### 9、SpringMVC的国际化操作 ​ 在日常工作中,如果你的网站需要给不同语言地区的人进行查看,此时就需要使用国际化的基本操作,springmvc的国际化操作比较容易。 index.jsp ```jsp $Title$ 国际化页面登录 ``` login.jsp ```jsp Title : : "/> ``` I18nController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class I18nController { @RequestMapping("i18n") public String i18n(){ return "login"; } } ``` login_en_US.properties ```properties welcomeinfo=welcome to mashibing.com username=USERNAME password=PASSWORD loginBtn=LOGIN ``` login_zh_CN.properties ```pro welcomeinfo=欢迎进入马士兵教育 username=用户名 password=密码 loginBtn=登录 ``` springmvc.xml ```xml ``` ​ 其实SpringMVC中国际化的处理非常简单,就是按照浏览器所带来的语言信息决定的。 ```java Locale locale = request.getLocale();//获取浏览器的区域信息 ``` 在DispatcherServlet中会包含一个组件,用来专门获取区域信息 通过图片能够发现,默认调用的是org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver类 在程序中可以获取Locale的相关信息: ```java package com.mashibing.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Locale; @Controller public class I18nController { @Autowired private MessageSource messageSource; @RequestMapping("i18n") public String i18n(Locale locale){ System.out.println(locale); String username = messageSource.getMessage("username", null, locale); System.out.println(username); return "login"; } } ``` ### 10、通过超链接来切换国际化 login.jsp ```jsp Title : : "/> 中文英文 ``` MyLocaleResolver.java ```java package com.mashibing; import org.springframework.web.servlet.LocaleResolver; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Locale; public class MyLocaleResolver implements LocaleResolver { /** * 解析并返回locale * @param request * @return */ @Override public Locale resolveLocale(HttpServletRequest request) { Locale locale = null; String localeStr = request.getParameter("locale"); if(localeStr!=null && ! "".equals(localeStr)){ locale = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]); }else{ locale = request.getLocale(); } return locale; } /** * 不支持设置locale的信息 * @param request * @param response * @param locale */ @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { throw new UnsupportedOperationException( "Cannot change HTTP accept header - use a different locale resolution strategy"); } } ``` springmvc.xml ```xml ``` 除了可以自定义区域信息解析器之外,我们还可以使用SpringMVC中自带的SessionLocaleResolver: I18nController.java ```java package com.mashibing.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.i18n.SessionLocaleResolver; import javax.servlet.http.HttpSession; import java.util.Locale; @Controller public class I18nController { @Autowired private MessageSource messageSource; @RequestMapping("i18n") public String i18n(@RequestParam(value = "locale",defaultValue = "zh_CN") String localeStr,Locale locale, HttpSession session){ Locale l = null; if(localeStr!=null && ! "".equals(localeStr)){ l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]); }else{ l = locale; } session.setAttribute(SessionLocaleResolver.class.getName() + ".LOCALE",l); return "login"; } } ``` springmvc.xml ```xml --> ``` 使用LocaleChangeInterceptor来实现国际化: springmvc.xml ```xml --> --> --> --> --> --> --> --> --> ``` I18nController.java ```java package com.mashibing.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.i18n.SessionLocaleResolver; import javax.servlet.http.HttpSession; import java.util.Locale; @Controller public class I18nController { @Autowired private MessageSource messageSource; @RequestMapping("i18n") public String i18n(@RequestParam(value = "locale",defaultValue = "zh_CN") String localeStr,Locale locale, HttpSession session){ // Locale l = null; // if(localeStr!=null && ! "".equals(localeStr)){ // l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]); // }else{ // l = locale; // } // session.setAttribute(SessionLocaleResolver.class.getName() + ".LOCALE",l); return "login"; } } ``` ### 11、SpringMVC异常处理机制 ​ 在SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常。 在容器启动好,进入DispatcherServlet之后,会对HandlerExceptionResolver进行初始化操作: 会默认的从DispatcherServlet.properties中找到对应的异常处理类: ```properties #默认的处理类 org.springframework.web.servlet.HandlerExceptionResolver= #处理@ExceptionHandler org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ #处理@ResponseStatus,给自定义异常使用 org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ #判断是否是SpringMVC自带异常 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver ``` 自己定义异常处理方式: index.jsp ```jsp $Title$ 自己处理异常 ``` ExceptionController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.jws.WebParam; @Controller public class ExceptionController { @RequestMapping("exception1") public String exception(){ System.out.println("exception......."); System.out.println(10/0); return "success"; } @ExceptionHandler(value = {ArithmeticException.class}) public ModelAndView handlerException(Exception exception){ ModelAndView mv = new ModelAndView(); mv.setViewName("error"); mv.addObject("ex",exception); return mv; } } ``` error.jsp ```jsp Title 我的出错页面: 错误信息:${ex} ``` ​ 在一个类中可能会包含多个异常的处理方法,在不同的方法上可以使用不同范围的异常,在查找的时候会优先调用范围小的异常处理; ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.jws.WebParam; @Controller public class ExceptionController { @RequestMapping("exception1") public String exception(){ System.out.println("exception......."); System.out.println(10/0); return "success"; } @ExceptionHandler(value = {ArithmeticException.class}) public ModelAndView handlerException1(Exception exception){ System.out.println("handlerException1........"); ModelAndView mv = new ModelAndView(); mv.setViewName("error"); mv.addObject("ex",exception); return mv; } @ExceptionHandler(value = {Exception.class}) public ModelAndView handlerException2(Exception exception){ System.out.println("handlerException2........"); ModelAndView mv = new ModelAndView(); mv.setViewName("error"); mv.addObject("ex",exception); return mv; } } ``` ​ 在不同的类中可能会包含不同的异常处理,因此我们需要定义一个全局的异常控制器,使用@ControllerAdvice注解标注,如果本类跟全局都有相关异常的处理,那么会优先使用本类的。 ```java package com.mashibing.controller; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class MyGlobalExceptionHandler { @ExceptionHandler(value = {ArithmeticException.class}) public ModelAndView handlerException1(Exception exception){ System.out.println("handlerException1........"); ModelAndView mv = new ModelAndView(); mv.setViewName("error"); mv.addObject("ex",exception); return mv; } } ``` @ResponseStatus的使用: ​ @ResponseStatus可以标注到方法上,但是标注在方法之后可能导致该方法无法被访问,因此更多的是在自定义类上 ```java package com.mashibing.controller; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.jws.WebParam; @Controller public class ExceptionController { @ResponseStatus(reason = "不知道什么原因,反正错误",value = HttpStatus.NOT_ACCEPTABLE) @RequestMapping("exception1") public String exception(){ System.out.println("exception......."); return "success"; } } ``` ​ @ResponseStatus作用在类上 UserNameException.java ```java package com.mashibing.controller; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(reason = "名字不是admin",value = HttpStatus.NOT_ACCEPTABLE) public class UserNameException extends RuntimeException { } ``` ExceptionController.java ```java package com.mashibing.controller; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.jws.WebParam; @Controller public class ExceptionController { @RequestMapping("exception1") public String exception(){ System.out.println("exception......."); return "success"; } @RequestMapping("exception2") public String exception2(String username){ System.out.println("exception2222......."); if ("admin".equals(username)){ return "success"; }else{ throw new UserNameException(); } } } ``` springmvc自定义的异常: index.jsp ```jsp $Title$ 自己处理异常 自定义异常处理 Springmvc自己异常处理 ``` ExceptionController.java ```java package com.mashibing.controller; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.jws.WebParam; @Controller public class ExceptionController { @RequestMapping("exception1") public String exception(){ System.out.println("exception......."); return "success"; } @RequestMapping("exception2") public String exception2(String username){ System.out.println("exception2222......."); if ("admin".equals(username)){ return "success"; }else{ throw new UserNameException(); } } @RequestMapping(value = "exception3",method = RequestMethod.POST) public String exception3(String username){ System.out.println("exception3......."); return "success"; } } ```
    连鹏举 2021-08-10 04:30
    1825 0
  • 02 SpringMVC的使用1
    # 02SpringMVC的使用 ### (1)SpringMVC的请求处理 ##### 1、SpringMVC对请求参数的处理 ​ 在之前的servlet中我们可以通过request.getParameter()来获取请求中的参数,但是在我们编写的SpringMVC的应用程序中,在具体请求的方法中并不包含request参数,那么我们应该如何获取请求中的参数呢? ​ 需要使用以下几个注解: ​ @RequestParam:获取请求的参数 ​ @RequestHeader:获取请求头信息 ​ @CookieValue:获取cookie中的值 @RequestParam的基本使用 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class RequestController { /** * 如何获取SpringMVC中请求中的信息 * 默认情况下,可以直接在方法的参数中填写跟请求一样的名称,此时会默认接受参数 * 如果有值,直接赋值,如果没有,那么直接给空值 * * @RequestParam:获取请求中的参数值,使用此注解之后,参数的名称不需要跟请求的名称一致,但是必须要写 * public String request(@RequestParam("user") String username){ * * 此注解还包含三个参数: * value:表示要获取的参数值 * required:表示此参数是否必须,默认是true,如果不写参数那么会报错,如果值为false,那么不写参数不会有任何错误 * defaultValue:如果在使用的时候没有传递参数,那么定义默认值即可 * * * @param username * @return */ @RequestMapping("/request") public String request(@RequestParam(value = "user",required = false,defaultValue = "hehe") String username){ System.out.println(username); return "success"; } } ``` @RequestHeader的基本使用: ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import sun.management.resources.agent; @Controller public class RequestController { /** * 如果需要获取请求头信息该如何处理呢? * 可以使用@RequestHeader注解, * public String header(@RequestHeader("User-Agent") String agent){ * 相当于 request.getHeader("User-Agent") * * 如果要获取请求头中没有的信息,那么此时会报错,同样,此注解中也包含三个参数,跟@RequestParam一样 * value * required * defalutValue * @param agent * @return */ @RequestMapping("/header") public String header(@RequestHeader("User-Agent") String agent){ System.out.println(agent); return "success"; } } ``` @CookieValue的基本使用 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import sun.management.resources.agent; @Controller public class RequestController { /** * 如果需要获取cookie信息该如何处理呢? * 可以使用@CookieValue注解, * public String cookie(@CookieValue("JSESSIONID") String id){ * 相当于 * Cookie[] cookies = request.getCookies(); * for(Cookie cookie : cookies){ * cookie.getValue(); * } * 如果要获取cookie中没有的信息,那么此时会报错,同样,此注解中也包含三个参数,跟@RequestParam一样 * value * required * defalutValue * @param id * @return */ @RequestMapping("/cookie") public String cookie(@CookieValue("JSESSIONID") String id){ System.out.println(id); return "success"; } } ``` ​ 如果请求中传递的是某一个对象的各个属性值,此时如何在控制器的方法中获取对象的各个属性值呢? ​ 在SpringMVC的控制中,能直接完成对象的属性赋值操作,不需要人为干预。 User.java ```java package com.mashibing.bean; import java.util.Date; public class User { private Integer id; private String name; private Integer age; private Date date; private Address address; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", date=" + date + ", address=" + address + '}'; } } ``` Address.java ```java package com.mashibing.bean; public class Address { private String province; private String city; private String town; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getTown() { return town; } public void setTown(String town) { this.town = town; } @Override public String toString() { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + ", town='" + town + '\'' + '}'; } } ``` login.jsp ```jsp Title 编号: 姓名: 年龄: 日期: 省份: 城市: 区域: ``` UserController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController { @RequestMapping("/addUser") public String addUser(User user){ System.out.println(user); return "success"; } } ``` ##### 2、乱码问题的解决 ​ 我们在表单或者发送请求的时候,经常会遇到中文乱码的问题,那么如何解决乱码问题呢? ​ GET请求:在server.xml文件中,添加URIEncoding=“UTF-8” ​ POST请求:编写过滤器进行实现 ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc-servlet.xml springmvc / characterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* ``` **注意:如果配置了多个过滤器,那么字符编码过滤器一定要在最前面,否则失效。** ##### 3、SpringMVC对原生API的支持 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.BufferedReader; import java.io.PrintWriter; @Controller public class UserController { @RequestMapping("/addUser") public String addUser(User user){ System.out.println(user); return "success"; } /** * SpringMVC也可以在参数上使用原生的Servlet API * * HttpSession * HttpServletRequest * HttpServletResponse * * java.security.Principal 安全协议相关 * Locale:国际化相关的区域信息对象 * InputStream: * ServletInputStream inputStream = request.getInputStream(); * OutputStream: * ServletOutputStream outputStream = response.getOutputStream(); * Reader: * BufferedReader reader = request.getReader(); * Writer: * PrintWriter writer = response.getWriter(); * @param session * @param request * @param response * @return */ @RequestMapping("api") public String api(HttpSession session, HttpServletRequest request, HttpServletResponse response){ request.setAttribute("requestParam","request"); session.setAttribute("sessionParam","session"); return "success"; } } ``` ##### 4、使用Model,Map,ModelMap传输数据到页面 ​ 在刚开始的helloworld项目中,我们传递了参数回到我们页面,但是后续的操作都只是接受用户的请求,那么在SpringMVC中除了可以使用原生servlet的对象传递数据之外,还有什么其他的方式呢? ​ 可以在方法的参数上传入Model,ModelMap,Map类型,此时都能够将数据传送回页面 OutputController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Map; @Controller public class OutputController { @RequestMapping("output1") public String output1(Model model){ model.addAttribute("msg","hello,Springmvc"); return "output"; } @RequestMapping("output2") public String output2(ModelMap model){ model.addAttribute("msg","hello,Springmvc"); return "output"; } @RequestMapping("output3") public String output1(Map map){ map.put("msg","hello,Springmvc"); return "output"; } } ``` 当使用此方式进行设置之后,会发现所有的参数值都设置到了request作用域中,那么这三个对象是什么关系呢? ##### 5、使用ModelAndView对象传输数据到页面 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class OutputController { @RequestMapping("mv") public ModelAndView mv(){ ModelAndView mv = new ModelAndView(); mv.setViewName("output"); mv.addObject("msg","hello.modelAndView"); return mv; } } ``` ​ 发现当使用modelAndView对象的时候,返回值的类型也是此对象,可以将要跳转的页面设置成view的名称,来完成跳转的功能,同时数据也是放到request作用中。 ##### 6、使用session传输数据到页面 @SessionAttribute:此注解可以表示,当向request作用域设置数据的时候同时也要向session中保存一份,此注解有两个参数,一个value(表示将哪些值设置到session中),另外一个type(表示按照类型来设置数据,一般不用,因为有可能会将很多数据都设置到session中,导致session异常)。 ```java @Controller @SessionAttributes(value = "msg") public class OutputController { @RequestMapping("output1") public String output1(Model model){ model.addAttribute("msg","hello,Springmvc"); System.out.println(model.getClass()); return "output"; } } ``` ##### 7、使用@ModelAttribute来获取请求中的数据 ​ @ModelAttribute注解用于将方法的参数或者方法的返回值绑定到指定的模型属性上,并返回给web视图。首先来介绍一个业务场景,来帮助大家做理解,在实际工作中,有些时候我们在修改数据的时候可能只需要修改其中几个字段,而不是全部的属性字段都获取,那么当提交属性的时候,从form表单中获取的数据就有可能只包含了部分属性,此时再向数据库更新的时候,肯定会丢失属性,因为对象的封装是springmvc自动帮我们new的,所以此时需要先将从数据库获取的对象保存下来,当提交的时候不是new新的对象,而是在原来的对象上进行属性覆盖,此时就需要使用@ModelAttribute注解。 User.java ```java package com.mashibing.bean; public class User { private Integer id; private String name; private String password; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } } ``` UserController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController { Object o1 = null; Object o2 = null; Object o3 = null; @RequestMapping("update") public String update(@ModelAttribute("user") User user,Model model){ System.out.println(user); o2 = model; //可以看到所有的model都是同一个对象 System.out.println(o1==o2); //可以看到存储的user对象也是同一个 System.out.println(user == o3); return "output"; } @ModelAttribute public void MyModelAttribute(Model model){ o1 = model; User user = new User(); user.setId(1); user.setName("张三"); user.setAge(12); user.setPassword("123"); model.addAttribute("user",user); System.out.println("modelAttribute:"+user); o3 = user; } } ``` index.jsp ```jsp $Title$ 姓名:张三 密码: 年龄: ``` 其实在使用的时候可以简化写法,也就是说,在方法的参数上不加@ModelAttribute也不会有问题 ```java @RequestMapping("update") public String update(User user,Model model){ System.out.println(user); o2 = model; //可以看到所有的model都是同一个对象 System.out.println(o1==o2); //可以看到存储的user对象也是同一个 System.out.println(user == o3); return "output"; } ``` 如果添加的@ModelAttribute(“”)属性的值不对,那么也是获取不到值的。同时可以添加@SessionAttributes属性,但是注意,如果没有设置值的话,会报错 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; @Controller @SessionAttributes("u") public class UserController { Object o1 = null; Object o2 = null; Object o3 = null; @RequestMapping("update") public String update(@ModelAttribute("u") User user,Model model){ System.out.println(user); o2 = model; //可以看到所有的model都是同一个对象 System.out.println(o1==o2); //可以看到存储的user对象也是同一个 System.out.println(user == o3); return "output"; } @ModelAttribute public void MyModelAttribute(Model model){ o1 = model; User user = new User(); user.setId(1); user.setName("张三"); user.setAge(12); user.setPassword("123"); model.addAttribute("user",user); System.out.println("modelAttribute:"+user); o3 = user; } } ``` 注意:ModelAttribute除了可以使用设置值到model中之外,还可以利用返回值。 ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; @Controller public class UserController { Object o1 = null; Object o2 = null; Object o3 = null; @RequestMapping("update") public String update(@ModelAttribute("u") User user,Model model){ System.out.println(user); o2 = model; //可以看到所有的model都是同一个对象 System.out.println(o1==o2); //可以看到存储的user对象也是同一个 System.out.println(user == o3); return "output"; } @ModelAttribute("u") public User MyModelAttribute(Model model){ o1 = model; User user = new User(); user.setId(1); user.setName("张三"); user.setAge(12); user.setPassword("123"); // model.addAttribute("user",user); System.out.println("modelAttribute:"+user); o3 = user; return user; } } ``` 总结:通过刚刚的给参数赋值,大家应该能够发现,当给方法中的参数设置值的时候,如果添加了@ModelAttribute注解,那么在查找值的时候,是遵循以下方式: 1、方法的参数使用参数的类型首字母小写,或者使用@ModelAttribute("")的值 2、先看之前是否在model中设置过该属性值,如果设置过就直接获取 3、看@SessionAttributes注解标注类中的方法是否给session中赋值,如果有的话,也是直接获取,没有报异常 ##### 8、使用forward实现页面转发 ​ 在发送请求的时候,可以通过forward:来实现转发的功能: ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ForWardController { /** * 当使用转发的时候可以添加前缀forward:index.jsp,此时是不会经过视图解析器的,所以要添加完整的名称 * * forward:也可以由一个请求跳转到另外一个请求 * * @return */ @RequestMapping("/forward01") public String forward(){ System.out.println("1"); return "forward:/index.jsp"; } @RequestMapping("/forward02") public String forward2(){ System.out.println("2"); return "forward:/forward01"; } } ``` ##### 9、使用redirect来实现重定向 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class RedirectController { /** * redirect :重定向的路径 * 相当于 response.sendRedirect("index.jsp") * 跟视图解析器无关 * @return */ @RequestMapping("redirect") public String redirect(){ System.out.println("redirect"); return "redirect:/index.jsp"; } @RequestMapping("/redirect2") public String redirect2(){ System.out.println("redirect2"); return "redirect:/redirect"; } } ``` 在javaweb的时候大家应该都接触过重定向和转发的区别,下面再详细说一下: 转发: ​ 由服务器的页面进行跳转,不需要客户端重新发送请求: ​ 特点如下: ​ 1、地址栏的请求不会发生变化,显示的还是第一次请求的地址 ​ 2、请求的次数,有且仅有一次请求 ​ 3、请求域中的数据不会丢失 ​ 4、根目录:localhost:8080/项目地址/,包含了项目的访问地址 重定向: ​ 在浏览器端进行页面的跳转,需要发送两次请求(第一次是人为的,第二次是自动的) ​ 特点如下: ​ 1、地址栏的地址发生变化,显示最新发送请求的地址 ​ 2、请求次数:2次 ​ 3、请求域中的数据会丢失,因为是不同的请求 ​ 4、根目录:localhost:8080/ 不包含项目的名称 ​ 对比: | **区别** | **转发forward()** | **重定向sendRedirect()** | | ---------------- | ------------------ | ------------------------ | | **根目录** | 包含项目访问地址 | 没有项目访问地址 | | **地址栏** | 不会发生变化 | 会发生变化 | | **哪里跳转** | 服务器端进行的跳转 | 浏览器端进行的跳转 | | **请求域中数据** | 不会丢失 | 会丢失 | ##### 10、静态资源的访问 ​ 当页面中包含静态资源的时候我们能够正确的获取到吗? hello.jsp ```jsp Title hello springmvc ``` ​ 此时大家发现我们请求的图片根本访问不到,根据查看发现路径是没有问题的,那么为什么会找不到静态资源呢? ​ 大家发现此时是找不到对应的mapping映射的,此时是因为DispatcherServlet会拦截所有的请求,而此时我们没有对应图片的请求处理方法,所以此时报错了,想要解决的话非常简单,只需要添加一个配置即可 ``` ``` ​ 但是加上此配置之后,大家又发现此时除了静态资源无法访问之外,我们正常的请求也无法获取了,因此还需要再添加另外的配置: ``` ``` ### (2)自定义视图解析器 ​ 我们在之前的操作中已经用了SpringMVC中提供的视图解析器,那么如果我们需要实现自己的视图解析器该如何操作呢? MyViewController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class MyViewController { @RequestMapping("/myview") public String myView(Model model){ model.addAttribute("msb","马士兵"); return "msb:/index"; } } ``` MyViewResolver.java ```java package com.mashibing.view; import org.springframework.core.Ordered; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import java.util.Locale; public class MyViewResolver implements ViewResolver, Ordered { private int order = 0; public View resolveViewName(String viewName, Locale locale) throws Exception { //如果前缀是msb:开头的就进行解析 if (viewName.startsWith("msb:")){ System.out.println("msb:"); return new MyView(); }else{ //如果不是,则直接返回null return null; } } public int getOrder() { return this.order; } public void setOrder(Integer order) { this.order = order; } } ``` MyView.java ```java package com.mashibing.view; import org.springframework.core.Ordered; import org.springframework.web.servlet.View; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; public class MyView implements View { public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("保存的对象是:"+model); response.setContentType("text/html"); response.getWriter().write("欢迎加入马士兵教育"); } /** * 返回数据内容的类型 * @return */ public String getContentType() { return "text/html"; } } ``` springmvc.xml ```xml ``` ### (3)自定义类型转换器 ​ 在日常的企业开发需求中,我们输入文本框的内容全部都是字符串类型,但是在后端处理的时候我们可以用其他基本类型来接受数据,也可以使用实体类来接受参数,这个是怎么完成的呢?就是通过SpringMVC提供的类型转换器,SpringMVC内部提供了非常丰富的类型转换器的支持,但是有些情况下有可能难以满足我们的需求,因此需要我们自己实现,如下: User.java ```java package com.mashibing.bean; public class User { private Integer id; private String name; private Integer age; private String gender; public User() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + '}'; } } ``` MyConverter.java ```java package com.mashibing.converter; import com.mashibing.bean.User; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @Component public class MyConverter implements Converter { public User convert(String source) { User user = null; String[] split = source.split("-"); if (source!=null && split.length==4){ user = new User(); user.setId(Integer.parseInt(split[0])); user.setName(split[1]); user.setAge(Integer.parseInt(split[2])); user.setGender(split[3]); } return user; } } ``` UserController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController { @RequestMapping("/user") public String add(User user, Model model){ System.out.println(user); model.addAttribute("user","user"); return "success"; } } ``` success.jsp ```jsp Title ${requestScope.user} ``` springmvc.xml ```xml ``` ### (4)自定义日期格式化转换器 ​ 有时候我们经常需要在页面添加日期等相关信息,此时需要制定日期格式化转换器,此操作非常简单:只需要在单独的属性上添加@DateTimeFormat注解即可,制定对应的格式 User.java ```java package com.mashibing.bean; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; public class User { private Integer id; private String name; private Integer age; private String gender; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; public User() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", birth=" + birth + '}'; } } ``` index.jsp ```jsp $Title$ 编号: 姓名: 年龄: 性别: 日期: ``` DateConvertionController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class DateConvertionController { @RequestMapping("dateConvertion") public String dateConvertion(User user){ System.out.println(user); return "hello"; } } ``` springmvc.xml ```xml ``` ​ 此时运行发现是没有问题的,但是需要注意的是,如果同时配置了自定义类型转换器之后,那么日期格式转化是有问题的。 springmvc.xml ```xml ``` ​ 原因就在于ConversionServiceFactoryBean对象中有且仅有一个属性converters,此时可以使用另外一个类来做替换FormattingConversionServiceFactoryBean springmvc.xml ```xml --> ``` ### (5)数据校验 ​ 一般情况下我们会在前端页面实现数据的校验,但是大家需要注意的是前端校验会存在数据的不安全问题,因此一般情况下我们都会使用前端校验+后端校验的方式,这样的话既能够满足用户的体验度,同时也能保证数据的安全,下面来说一下在springmvc中如何进行后端数据校验。 ​ JSR303是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。JSR 303 (Java Specification Requests意思是Java 规范提案)通过**在** **Bean** **属性上标注**类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。 JSR303: Hibernate Validator 扩展注解: ​ spring中拥有自己的数据校验框架,同时支持JSR303标准的校验框架,可以在通过添加注解的方式进行数据校验。在spring中本身没有提供JSR303的实现,需要导入依赖的包。 pom.xml ```xml 4.0.0 com.mashibing springmvc_viewResolver 1.0-SNAPSHOT org.springframework spring-context 5.2.3.RELEASE org.springframework spring-web 5.2.3.RELEASE org.springframework spring-webmvc 5.2.3.RELEASE javax.servlet servlet-api 2.5 provided javax.servlet jsp-api 2.0 provided org.hibernate hibernate-validator 5.1.0.Final ``` index.jsp ```jsp $Title$ 编号: 姓名: 年龄: 性别: 日期: 邮箱: ``` DataValidateController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid; @Controller public class DataValidateController { @RequestMapping("/dataValidate") public String validate(@Valid User user, BindingResult bindingResult) { System.out.println(user); if (bindingResult.hasErrors()) { System.out.println("验证失败"); return "redirect:/index.jsp"; } else { System.out.println("验证成功"); return "hello"; } } } ``` User.java ```java package com.mashibing.bean; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.springframework.format.annotation.DateTimeFormat; import javax.validation.constraints.NotNull; import javax.validation.constraints.Past; import java.util.Date; public class User { private Integer id; @NotNull @Length(min = 5,max = 10) private String name; private Integer age; private String gender; @Past @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; @Email private String email; public User() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", birth=" + birth + ", email='" + email + '\'' + '}'; } } ``` 此时大家发现在报错的地方无法出现错误提示,可以换另外一种方式: index.jsp ```jsp $Title$ 添加用户 ``` add.jsp ```jsp $Title$ id: name: age: gender: birth: email: ``` DataValidateController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid; @Controller public class DataValidateController { @RequestMapping("/dataValidate") public String validate(@Valid User user, BindingResult bindingResult, Model model) { System.out.println(user); if (bindingResult.hasErrors()) { System.out.println("验证失败"); return "add"; } else { System.out.println("验证成功"); return "hello"; } } @RequestMapping("add") public String add(Model model){ model.addAttribute("user",new User(1,"zhangsan",12,"女",null,"1234@qq.com")); return "add"; } } ``` web.xml ```xml org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:springmvc.xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc.xml springmvc / encoding org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true encoding /* ``` 原生的表单如何获取错误信息: DataValidateController.java ```java package com.mashibing.controller; import com.mashibing.bean.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller public class DataValidateController { @RequestMapping("/dataValidate") public String validate(@Valid User user, BindingResult bindingResult, Model model) { System.out.println(user); Map errorsMap = new HashMap(); if (bindingResult.hasErrors()) { System.out.println("验证失败"); List fieldErrors = bindingResult.getFieldErrors(); for (FieldError fieldError : fieldErrors) { System.out.println(fieldError.getDefaultMessage()); System.out.println(fieldError.getField()); errorsMap.put(fieldError.getField(),fieldError.getDefaultMessage()); } model.addAttribute("errorInfo",errorsMap); return "add"; } else { System.out.println("验证成功"); return "hello"; } } @RequestMapping("add") public String add(Model model){ model.addAttribute("user",new User(1,"zhangsan",12,"女",null,"1234@qq.com")); return "add"; } } ``` add.jsp ```jsp $Title$ 编号:--->${errorInfo.id} 姓名:--->${errorInfo.name} 年龄:--->${errorInfo.age} 性别:--->${errorInfo.gender} 生日:--->${errorInfobirth} 邮箱:--->${errorInfo.email} ``` ​
    连鹏举 2021-08-10 04:28
    1990 0
  • 01 SpringMVC简单介绍及使用
    # 01SpringMVC简单介绍及使用 ### 1、什么是MVC? ​ MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是**降低了视图与业务逻辑间的双向偶合**。MVC不是一种设计模式,**MVC是一种架构模式**。当然不同的MVC存在差异。 ​ **Model(模型):**数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。 ​ **View(视图):**负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。 ​ **Controller(控制器):**接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。 ​ 其实在最早期的时候还有model1和model2的设计模型 **最典型的MVC就是JSP + servlet + javabean的模式。** 代码展示: HelloServlet.java ```java package com.mashibing.controller; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class HelloServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method = request.getParameter("method"); if (method.equals("add")){ request.getSession().setAttribute("msg","add"); }else if(method.equals("sub")){ request.getSession().setAttribute("msg","sub"); } request.getRequestDispatcher("index.jsp").forward(request,response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } ``` web.xml ```xml HelloServlet com.mashibing.controller.HelloServlet HelloServlet /user ``` index.jsp ```jsp $Title$ ${msg} ``` 输入网址:http://localhost:8080/servlet_demo_war_exploded/user?method=add ### 2、SpringMVC ##### 1、SpringMVC的介绍 ```txt Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more commonly known as “Spring MVC”. Spring Web MVC是构建在Servlet API上的原始Web框架,从一开始就包含在Spring Framework中。 正式名称 “Spring Web MVC,” 来自其源模块(spring-webmvc)的名称,但它通常被称为“Spring MVC”。 ``` ​ 简而言之,springMVC是Spring框架的一部分,是基于java实现的一个轻量级web框架。 ​ 学习SpringMVC框架最核心的就是DispatcherServlet的设计,掌握好DispatcherServlet是掌握SpringMVC的核心关键。 ##### 2、SpringMVC的优点 ​ 1.清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、试图解析器(view resoler)等等。每一个角色都可以由一个专门的对象来实现。 ​ 2.强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器validator)的引用。 ​ 3.可适配、非侵入:可以根据不同的应用场景,选择何事的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。 ​ 4.可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。 ​ 5.可定制的绑定(binding)和验证(validation):比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。 ​ 6.可定制的handler mapping和view resolution:Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。 ​ 7.灵活的model转换:在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。 ​ 8.可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。 ​ 9.简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。 ​ 10.JSP表单标签库:在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。 ​ 11.Spring Bean的生命周期:可以被限制在当前的HTTp Request或者HTTp Session。准确的说,这并非Spring MVC框架本身特性,而应归属于Spring MVC使用的WebApplicationContext容器。 ##### 3、SpringMVC的实现原理 ​ springmvc的mvc模式: SpringMVC的具体执行流程: ​ 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。 ``` 1、DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。 2、HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。 3、返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet 4、HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。 5、执行handler找到具体的处理器 6、Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。 7、HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。 8、DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。 9、视图解析器将解析的逻辑视图名传给DispatcherServlet。 10、DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染 11、将响应数据返回给客户端 ``` ### 3、基于XML的Hello_SpringMVC 1、添加pom依赖 ```xml org.springframework spring-context 5.2.3.RELEASE org.springframework spring-web 5.2.3.RELEASE org.springframework spring-webmvc 5.2.3.RELEASE ``` 2、编写web.xml文件 ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml springmvc / ``` 3、编写springmvc需要的spring配置文件,applicationContext.xml ```xml ``` 4、HelloController.java ```java package com.mashibing.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //创建模型和视图对象 ModelAndView mv = new ModelAndView(); //将需要的值传递到model中 mv.addObject("msg","helloSpringMVC"); //设置要跳转的视图, mv.setViewName("hello"); return mv; } } ``` 5、创建hello.jsp页面 ```jsp Title ${msg} ``` 6、配置tomcat,发送请求 http://localhost:8080/hello ### 4、基于注解的Hello_SpringMVC 1、添加pom依赖 ```xml org.springframework spring-context 5.2.3.RELEASE org.springframework spring-web 5.2.3.RELEASE org.springframework spring-webmvc 5.2.3.RELEASE ``` 2、编写web.xml文件 ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml springmvc / ``` 3、编写applicationContext.xml文件 ```xml ``` 4、编写HelloController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController{ /* * @RequestMapping就是用来标识此方法用来处理什么请求,其中的/可以取消 * 取消后默认也是从当前项目的根目录开始查找,一般在编写的时候看个人习惯 * 同时,@RequestMapping也可以用来加在类上, * */ @RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; } } ``` 5、编写hello.jsp ```jsp Title ${msg} ``` 6、输入请求http://localhost:8080/hello ### 5、注意细节 ##### 1、springmvc_helloworld运行流程: ​ 通过上述的代码,我们能够总结出具体的运行流程: ​ 1、客户端发送请求http://localhost:8080/hello ​ 2、由tomcat接受到对应的请求 ​ 3、SpringMVC的前端控制器DispatcherServlet接收到所有的请求 ​ 4、查看请求地址和@RequestMapping注解的哪个匹配,来找到具体的类的处理方法 ​ 5、前端控制器找到目标处理类和方法之后,执行目标方法 ​ 6、方法执行完成之后会有一个返回值,SpringMVC会将这个返回值用视图解析器进行解析拼接成完整的页面地址 ​ 7、DispatcherServlet拿到页面地址之后,转发到具体的页面 ##### 2、springmvc的配置文件 web.xml ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml springmvc / ``` ##### 3、DispatcherServlet的url-pattern web.xml ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml springmvc / ``` ##### 4、@RequestMapping ​ @RequestMapping用来匹配客户端发送的请求,可以在方法上使用,也可以在类上使用。 ​ 方法:表示用来匹配要处理的请求 ​ 类上:表示为当前类的所有方法的请求地址添加一个前置路径,访问的时候必须要添加此路径 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/mashibing") public class HelloController{ /* * @RequestMapping就是用来标识此方法用来处理什么请求,其中的/可以取消 * 取消后默认也是从当前项目的根目录开始查找,一般在编写的时候看个人习惯 * 同时,@RequestMapping也可以用来加在类上, * */ @RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; } } ``` ​ **注意:在整个项目的不同方法上不能包含相同的@RequestMapping值** ​ 除此以外,@RequestMapping注解还可以添加很多额外的属性值,用来精确匹配请求 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/mashibing") public class HelloController{ /* * @RequestMapping就是用来标识此方法用来处理什么请求,其中的/可以取消 * 取消后默认也是从当前项目的根目录开始查找,一般在编写的时候看个人习惯 * 同时,@RequestMapping也可以用来加在类上, * */ @RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; } /** * Request的其他属性值 * value:要匹配的请求 * method:限制发送请求的方式: POST GET * params:表示请求要接受的参数,如果定义了这个属性,那么发送的时候必须要添加参数 * params有几种匹配规则 * 1、直接写参数的名称,param1,param2 * params = {"username"} * 2、表示请求不能包含的参数,!param1 * params = {"!username"} * 3、表示请求中需要要包含的参数但是可以限制值 param1=values param1!=value * params = {"username=123","age"} * params = {"username!=123","age"} * headers:填写请求头信息 * chrome:User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 * firefox:User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0 * * consumers:只接受内容类型是哪种的请求,相当于指定Content-Type * produces:返回的内容类型 Content-Type:text/html;charset=utf-8 * * @return */ @RequestMapping(value = "/hello2",method = RequestMethod.POST) public String hello2(){ return "hello"; } @RequestMapping(value = "/hello3",params = {"username!=123","age"}) public String hello3(String username){ System.out.println(username); return "hello"; } @RequestMapping(value = "/hello4",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"}) public String hello4(){ return "hello"; } } ``` ​ @RequestMapping还包含了很多复杂的匹配功能,提供了通配符的支持: ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/mashibing") public class HelloController{ /* * @RequestMapping就是用来标识此方法用来处理什么请求,其中的/可以取消 * 取消后默认也是从当前项目的根目录开始查找,一般在编写的时候看个人习惯 * 同时,@RequestMapping也可以用来加在类上, * */ @RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; } /** * Request的其他属性值 * value:要匹配的请求 * method:限制发送请求的方式: POST GET * params:表示请求要接受的参数,如果定义了这个属性,那么发送的时候必须要添加参数 * params有几种匹配规则 * 1、直接写参数的名称,param1,param2 * params = {"username"} * 2、表示请求不能包含的参数,!param1 * params = {"!username"} * 3、表示请求中需要要包含的参数但是可以限制值 param1=values param1!=value * params = {"username=123","age"} * params = {"username!=123","age"} * headers:填写请求头信息 * chrome:User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36 * firefox:User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0 * * consumers:只接受内容类型是哪种的请求,相当于指定Content-Type * produces:返回的内容类型 Content-Type:text/html;charset=utf-8 * * @return */ @RequestMapping(value = "/hello2",method = RequestMethod.POST) public String hello2(){ return "hello"; } @RequestMapping(value = "/hello3",params = {"username!=123","age"}) public String hello3(String username){ System.out.println(username); return "hello"; } @RequestMapping(value = "/hello4",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"}) public String hello4(){ return "hello"; } /** * @Request包含三种模糊匹配的方式,分别是: * ?:能替代任意一个字符 * *: 能替代任意多个字符和一层路径 * **:能代替多层路径 * @return */ @RequestMapping(value = "/**/h*llo?") public String hello5(){ System.out.println("hello5"); return "hello"; } } ``` ### 6、@PathVariable ​ 如果需要在请求路径中的参数像作为参数应该怎么使用呢?可以使用@PathVariable注解,此注解就是提供了对占位符URL的支持,就是将URL中占位符参数绑定到控制器处理方法的参数中。 ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/mashibing") public class HelloController{ @RequestMapping(value = "/pathVariable/{name}") public String pathVariable(@PathVariable("name") String name){ System.out.println(name); return "hello"; } } ``` ### 7、REST ​ REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种[软件架构](https://baike.baidu.com/item/软件架构)风格。它是一种针对[网络应用](https://baike.baidu.com/item/网络应用/2196523)的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。 ​ 在三种主流的[Web服务](https://baike.baidu.com/item/Web服务)实现方案中,因为REST模式的Web服务与复杂的[SOAP](https://baike.baidu.com/item/SOAP/4684413)和[XML-RPC](https://baike.baidu.com/item/XML-RPC)对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务进行图书查找;[雅虎](https://baike.baidu.com/item/雅虎/108276)提供的Web服务也是REST风格的。 ​ REST,翻译过来叫做表现层状态转化,是目前最流行的一个互联网软件架构,它架构清晰,符合标准,易于理解,扩展方便。 ​ **表现层(Representation)**:把资源具体呈现出来的形式,因此叫做表现层。 ​ **资源(Resource)**:网络上的一个具体信息,文本,图片,音频,视频都可以称之为资源,如果想要访问到互联网上的某一个资源,那么就必须要使用一个URL来唯一性的获取改资源,也可以这么说,URL是每一个资源的唯一标识符。 ​ **状态转化(State Transfer)**:当客户端发出一个请求的时候,就代表客户端跟服务端的一次交互过程,HTTP是一种无状态协议,即所有的状态都保存在服务器端,因此,客户端如果想要操作服务器,必须通过某些手段,让服务器的状态发生转化,而这种转化是建立在表现层的,这就是名字的由来(非人话) ​ 人话:我们在获取资源的时候就是进行增删改查的操作,如果是原来的架构风格,需要发送四个请求,分别是: ​ 查询:localhost:8080/query?id=1 ​ 增加:localhost:8080/insert ​ 删除:localhost:8080/delete?id=1 ​ 更新:localhost:8080/update?id=1 ​ 按照此方式发送请求的时候比较麻烦,需要定义多种请求,而在HTTP协议中,有不同的发送请求的方式,分别是GET、POST、PUT、DELETE等,我们如果能让不同的请求方式表示不同的请求类型就可以简化我们的查询 ​ GET:获取资源 /book/1 ​ POST:新建资源 /book ​ PUT:更新资源 /book/1 ​ DELETE:删除资源 /book/1 ​ 一切看起来都非常美好,但是大家需要注意了,我们在发送请求的时候只能发送post或者get,没有办法发送put和delete请求,那么应该如何处理呢?下面开始进入代码环节: RestController.java ```java package com.mashibing.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Controller public class RestController { @RequestMapping(value = "/user",method = RequestMethod.POST) public String add(){ System.out.println("添加"); return "success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE) public String delete(@PathVariable("id") Integer id){ System.out.println("删除:"+id); return "success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.PUT) public String update(@PathVariable("id") Integer id){ System.out.println("更新:"+id); return "success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public String query(@PathVariable("id") Integer id){ System.out.println("查询:"+id); return "success"; } } ``` web.xml ```xml springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc-servlet.xml springmvc / hiddenFilter org.springframework.web.filter.HiddenHttpMethodFilter hiddenFilter /* ``` rest.jsp ```jsp Title 查询 ``` success.jsp ```jsp Title 666 ```
    连鹏举 2021-08-10 04:27
    1969 0
我要提问
写笔记