当前位置: 首页 > news >正文

Java经典框架之Spring MVC

Spring MVC

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. Spring MVC 入门案例
2. 基于注解的使用方式
3. 处理及响应请求
4. 文件上传下载操作
5. 静态资源处理
6. 服务端数据校验
7. 数据回写操作
8. 异常处理
9. JSON数据操作
10. Restful风格编程
11. 拦截器
12. Spring和Spring MVC整合操作
  

一、Spring MVC 入门案例

1. 什么是MVC?
模型-视图-控制器(MVC 是一个众所周知的以设计界面应用程序为基础的设计模式。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(manager或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。
  
2. Spring MVC
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架。
使用了MVC架构模式的思想,将Web层进行职责解耦。
基于请求驱动指的就是使用请求-响应模型。
框架的目的就是帮助我们简化开发。
Spring Web MVC也是要简化我们日常Web开发的。
  
SpringMVC的优点
性能比Struts2好。
简单、便捷,易学。
和Spring无缝衔接【IoC,AOP】。
使用约定优于配置。
支持Restful。
异常处理,国际化,数据验证,类型转换等。
使用的人多,使用的公司多。
  
3.第一个Spring MVC 案例
3.1 创建一个Web项目
创建一个基于Maven的Web项目,添加相关的项目结构。

   
3.2 添加对应的依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.2.RELEASE</version>
</dependency>
<dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope>
</dependency>
  
Maven的依赖管理具有传递性。

  
3.3 添加一个Spring MVC的配置文件
添加一个Spring MVC的配置文件,该配置文件的内容和我们前面介绍的Spring的配置文件的内容是一致的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
    
然后在配置文件中向Spring IoC容器中注入两个实例。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 注入一个处理器映射器 --><bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /><!-- 注入一个处理器适配器 --><bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /></beans>
     
3.4 配置前端控制器
Spring MVC是一个控制层框架,那么需要处理所有的请求,那么我们需要在web.xml文件中配置对应的拦截。
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置一个Spring MVC 的前端控制器目的是所有的客户端的请求都会被 DispatcherServlet 处理--><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 关联自定义的Spring MVC的配置文件 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
     
3.5 创建自定义的控制器
package com.bobo.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 UserController implements Controller {/*** 具体处理请求的方法* @param httpServletRequest* @param httpServletResponse* @return* @throws Exception*/@Overridepublic ModelAndView handleRequest(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws Exception {System.out.println("请求进来了...");ModelAndView mm = new ModelAndView();mm.setViewName("/index.jsp");return mm;}
}
     
3.6 自定义控制器的注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 注入一个处理器映射器 --><bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /><!-- 注入一个处理器适配器 --><bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /><!-- 将自定义的控制器注入到IoC容器中 name="/user" 用户访问的请求地址 --><bean class="com.bobo.controller.UserController" name="/user" /></beans>
   
3.7 Tomcat插件
通过Tomcat插件来运行项目
<plugins><!-- tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><!-- 端口号 --><port>8082</port><!-- /表示访问路径 省略项目名 --><path>/</path><!-- 设置编码方式 --><uriEncoding>utf-8</uriEncoding></configuration></plugin>
</plugins>
  
3.8 测试
启动成功

    
http://localhost:8082/user

  

   
访问成功!!!
    
4. 基于注解的实现
通过上一个普通实现的方式大家会发现其实现步骤比较繁琐,而且自定义的Controller也只有一个默认的方法被调用,不是很方便。我们在开发中常用的是基于注解的方式实现的,接下来就实现。
   
4.1 修改Spring MVC 的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven />
</beans>
     
4.2 创建控制器
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;import javax.jws.WebParam;@Controller // 将当前类的对象交给容器管理
@RequestMapping("/user") // 配置的而是请求的路径
public class UserController {/*** 查询方法* 请求地址* http://localhost:8082/user/query* @return*/@RequestMapping("/query")public ModelAndView query(){System.out.println("query");ModelAndView mm = new ModelAndView();mm.setViewName("/index.jsp");return mm;}/***  添加方法*  请求地址*  http://localhost:8082/user/addUser* @return*/@RequestMapping("/addUser")public ModelAndView addUser(){System.out.println("add User ...");ModelAndView mm = new ModelAndView();mm.setViewName("/index.jsp");return mm;}
}
  
4.3测试

      
5.Spring MVC 的工作原理
5.1 Spring MVC 原理图

    
Spring MVC 默认的配置信息
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServletcontext.
# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
  
5.2 工作原理文字说明
用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等。
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等。
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中。
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
ViewResolver 结合Model和View,来渲染视图。
将渲染结果返回给客户端。
    
5.3 相关核心组件的说明

  

二、Spring MVC的核心操作

1.响应请求
1.1 ModelAndView
前面案例中使用的就是ModelAndView方式。
/*** 查询方法* 请求地址* http://localhost:8082/user/query* @return*/@RequestMapping("/query")public ModelAndView query(){System.out.println("query");ModelAndView mm = new ModelAndView();mm.setViewName("/index.jsp");return mm;}
  
1.2 返回void
接收请求的方法如果没有要响应的资源,这时我们可以将方法的返回结果设置为 void。
@RequestMapping("/update")
public void updateUser(){System.out.println("update .... ");
}
  

  

客户端发送了一个请求,服务器获取到请求后没有给客户端响应,那么课后会有错误提示,如果确实是服务器没有要响应的内容,那么我们需要添加一个 @ResponseBody 注解,表示请求到此为止。
/*** 返回结果信息为空* void + @ResponseBody*/@RequestMapping("/update")@ResponseBodypublic void updateUser(){System.out.println("update .... ");}
  
1.3 返回一个字符串
为了简化响应操作,我们可以直接返回一个要调整的页面对应的字符串即可。
*** 要跳转到哪个页面我们直接返回该页面对应的字符串即可** @return*/@RequestMapping("/deleteUser")public String deleteUser(){System.out.println("delete .....");// "/index.jsp"中的 "/" 表示的是绝对路径return "redirect:/index.jsp";}
    

      
视图解析器在解析我们要返回的页面的时候我们可以给视图解析器配置对应的前后缀。来简化我们的响应。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven /><!-- 配置视图解析器 --><bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver" ><!-- 配置视图解析器的前后缀--><property name="prefix" value="/" /><property name="suffix" value=".jsp" /></bean>
</beans>
  
响应页面的时候就可以简写了。
/*** 要跳转到哪个页面我们直接返回该页面对应的字符串即可** @return*/@RequestMapping("/deleteUser1")public String deleteUser1(){System.out.println("delete1 .....");// "/index.jsp"中的 "/" 表示的是绝对路径return "index";}
  
我们发现返回字符串的方式默认的跳转方式是请求转发,如果我们要实现重定向只需要在响应的字符串前面添加 redirect: 即可。
@RequestMapping("/deleteUser")
public String deleteUser(){System.out.println("delete .....");// "/index.jsp"中的 "/" 表示的是绝对路径return "redirect:/index.jsp";
}
     
1.4 通过Servlet处理响应
Spring MVC中的 DispatcherServlet本质上就是一个Servlet,所以我们当然也可以在控制器中使用Servlet来处理响应请求,只是这种方式比较繁琐,一般不用。
@RequestMapping("/queryAll")
public void queryAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("query All ... ");request.getRequestDispatcher("/index.jsp").forward(request,response);//response.sendRedirect("/index.jsp");
}
   

   
2.接收请求数据
介绍用户发送请求中携带的各种参数。
   
2.1 简单数据类型
如果客户端传递的简单数据类型,我们可以直接在形参中声明要接收的信息。但是要注意声明的类型我们建议搭建都使用对应的包装类,因为传递过来的参数有可能为null。
 /*** 接收的参数为简单数据类型* @param userName* @param age* @return*/@RequestMapping("/query1")public String query1(String userName, Integer  age){System.out.println("query1 ....."+ userName  + "   " + age);return "/index.jsp";}
    

  
前面的方式要求形参和传递的参数名称要一致,如果不一致我们需要通过 @RequestParam 注解来设置映射。
 /*** 接收的参数为简单数据类型* @param userName* @param age* @return*/@RequestMapping("/query1")public String query1(@RequestParam(value = "name",defaultValue = "lisi") String userName,@RequestParam(value = "userAge",defaultValue = "18") Integer  age){System.out.println("query1 ....."+ userName  + "   " + age);return "/index.jsp";}
   
客户端访问的地址:http://localhost:8082/stu/query1?name=zhangsan&userAge=22。
  

  
如果没有传递参数:http://localhost:8082/stu/query1
会使用设置的默认值覆盖。
 

    
2.2 简单对象
如果客户端传递的参数比较多,我们可以将传递的数据封装到自定义的对象中。
package com.bobo.bean;import org.springframework.web.bind.annotation.RequestParam;import java.util.List;public class StudentBean {private Integer id;private String userName;private Integer age;private String address;private List<String> favrites;public List<String> getFavrites() {return favrites;}public void setFavrites(List<String> favrites) {this.favrites = favrites;}// 包装类private Book book;public Book getBook() {return book;}public void setBook(Book book) {this.book = book;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "StudentBean{" +"id=" + id +", userName='" + userName + '\'' +", age=" + age +", address='" + address + '\'' +", book=" + book +'}';}
}
     
接收请求
/*** 通过自定义对象来接收参数* @param stu* @return*/@RequestMapping("/addStudent")public String addStudent(StudentBean stu){System.out.println(stu);return "/index.jsp";}
  
http://localhost:8082/stu/addStudent?id=666&userName=aaa&age=18&address=changsha。

    
Book
package com.bobo.bean;public class Book {private Integer bookId;private String bookName;private String author;public Integer getBookId() {return bookId;}public void setBookId(Integer bookId) {this.bookId = bookId;}public String getBookName() {return bookName;}public void setBookName(String bookName) {this.bookName = bookName;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}@Overridepublic String toString() {return "Book{" +"bookId=" + bookId +", bookName='" + bookName + '\'' +", author='" + author + '\'' +'}';}
}
  
http://localhost:8082/stu/addBook?bookId=123&bookName=helloWorld&author=bobo。  
  

       
2.3 包装对象
我们自定义的对象中的属性是另一个对象(自定义对象),那这种情况我们应该怎么传递及接收相关的数据。

  
请求:http://localhost:8082/stu/addStudent?id=666&userName=aaa&age=18&address=changsha&book.bookId=999&book.bookName=Javabianchengrumen&book.author=bobo
  

  
2.4 数组和集合类型
当我们在客户端通过表单提交复选框的数据的时候,我们可以通过数组的形式来接收。
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 16:36To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body><h1>学生管理</h1><form action="/stu/addStudentNew" method="post" ><label>学生姓名:</label><input type="text" name="userName"><br><label>兴趣爱好:</label><input type="checkbox" name="favrites" value="footerball">足球<input type="checkbox" name="favrites" value="basketball">篮球<br><input type="submit" value="提交"></form>
</body>
</html>
@RequestMapping("/addStudentNew")
public String addStudentNew(String userName,String[] favrites){System.out.println(userName + ":" + Arrays.toString(favrites) );return "/index.jsp";
}
  
页面效果

   
那如果我们不用数组而是使用List集合呢
@RequestMapping("/addStudentList")
public String addStudentList(String userName, List<String> favrites){System.out.println(userName + ":" + favrites );return "/index.jsp";
}
  
报错

    
也就是告诉大家我们不能在形参中直接通过集合来接收参数!
我们也可以通过自定义对来接收,在对象中声明数组或者集合类型的变量来接收。
  

  
属性声明为List类型也可以。

  
2.5 Date类型
如果我们需要接收一个时间类型数据,直接在形参中声明 Date 类型,会抛出类型转换的异常。

     
自己实现转换器
package com.bobo.convert;import org.springframework.core.convert.converter.Converter;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 自定义一个转换器* 实现 String到Date类型的转换*/
public class DateConvert implements Converter<String, Date> {@Overridepublic Date convert(String s) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date = null;try {date = sdf.parse(s);} catch (ParseException e) {e.printStackTrace();}return date;}
}
     
配置自定义的转换器

    
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven  conversion-service="conversionServiceFactoryBean"/><!-- 配置视图解析器 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" ><!--  配置视图解析器的前后缀<property name="prefix" value="/" /><property name="suffix" value=".jsp" />--></bean><!-- 注册自定义的转换器 --><bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean"id="conversionServiceFactoryBean"><property name="converters"><set><bean class="com.bobo.convert.DateConvert"/></set></property></bean>
</beans>
   
测试
http://localhost:8082/stu/addUser1?birth=2021-01-29

    
接收请求碰到的参数相关的类型小结。

        
3.响应用户数据
Maven项目创建的web.xml的schema不完整,我们修改如下。
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app  version="2.5"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>Archetype Created Web Application</display-name><!--配置一个Spring MVC 的前端控制器目的是所有的客户端的请求都会被  DispatcherServlet 处理--><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 关联自定义的Spring MVC的配置文件 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- 配置设置编码的过滤器 --><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>
  
3.1 通过ModelAndView方式
@RequestMapping("/fun1")public ModelAndView fun1(){System.out.println("fun1...");ModelAndView mm = new ModelAndView();mm.setViewName("/person1.jsp");// 绑定一个回传信息mm.addObject("msg","Hello World");return mm;}
   
在页面中通过EL表达获取。
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 17:20To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>Person管理</h1><label>msg:</label> ${ msg } <br>
</body>
</html>
  
效果

  
3.2 Map集合
我们可以在形参中声明一个Map类型的变量,然后将要传递的数据保存在该变量中,注意我们自己不用去实例化该Map容器。
    /*** 响应数据我们可以在形参中声明一个Map集合*     来保存要响应的数据* @return*/@RequestMapping("/fun2")public String fun2(Map<String,Object> map){map.put("msg","map类型数据");map.put("username","张三");System.out.println("fun2....");return "/person2.jsp";}
  
页面获取
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 17:20To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>Person管理</h1><label>msg:</label> ${ requestScope.msg } <br><label>msg:</label> ${ sessionScope.msg } <br><label>msg:</label> ${ applicationScope.msg } <br><label>userName:</label> ${ username } <br>
</body>
</html>
    

   
3.3 Model对象
    @RequestMapping("/fun3")public String fun3(Model model){model.addAttribute("msg","msg-->model");model.addAttribute("username","bobo");System.out.println("fun3....");return "/person2.jsp";}
  

    
3.4 ModelMap对象
ModelMap可以看成是Model和Map的组合,因为ModelMap提供了Model和Map的API。
    @RequestMapping("/fun4")public String fun4(ModelMap model){model.addAttribute("msg","msg-->modelMap你好啊");model.addAttribute("username","bobo");System.out.println("fun4....");return "/person2.jsp";}
   

  
注意以上几种方式传递的数据都是保存在request作用域中的。
验证
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 17:20To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>Person管理</h1><label>msg:</label> ${ requestScope.msg } <br><label>msg:</label> ${ sessionScope.msg } <br><label>msg:</label> ${ applicationScope.msg } <br><label>userName:</label> ${ username } <br>
</body>
</html>
     

     
如果我们需要将某个参数保存到session作用域中我们只需要在类的头部添加 @SessionAttributes 注解即可。
   

  
效果

     
4.中文乱码问题
接收请求中的数据--> POST方式提交【在Request中设置对应的编码方式】 GET方式提交【在Web容器中设置 Tomcat】
响应请求中的数据--> Response设置对应的编码方式
   
GET方式提交设置编码:

  
POST方式提交及响应Response的设置。
在CharacterEncodingFilter这个过滤器中提供的有设置Request和response对应编码的方法。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String encoding = this.getEncoding();if (encoding != null) {if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {request.setCharacterEncoding(encoding);}if (this.isForceResponseEncoding()) {response.setCharacterEncoding(encoding);}}filterChain.doFilter(request, response);
}
     
在web.xml文件中配置编码过滤器的设置。
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app  version="2.5"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>Archetype Created Web Application</display-name><!--配置一个Spring MVC 的前端控制器目的是所有的客户端的请求都会被  DispatcherServlet 处理--><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 关联自定义的Spring MVC的配置文件 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- 配置设置编码的过滤器 --><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>
     

   
服务端获取的中文信息就没有乱码了。

  

三、常见应用

1.文件上传操作
1.1 添加fileUpload的依赖
<!-- 添加fileUpload的依赖 -->
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version>
</dependency>
   
因为依赖的传递性会自动添加commons-io这个jar

   
1.2 创建表单
要实现文件上传操作,那么表单的提交方式必须是 POST 方式,同时enctype必须是multipart/form-data 二进制方式提交。
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 20:03To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body><h1>文件上传:</h1><form action="/user/fileUpload" method="post" enctype="multipart/form-data"><label>用户名:</label><input type="text" name="username"><br><label>头像:</label><input type="file" name="headFile"><br><input type="submit" value="上传"></form>
</body>
</html>
   
1.3 修改配置文件
我们可以在Spring MVC的配置文件中设置上传的相关参数。设置上传文件的大小,限制上传文件的类型等。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven  /><!-- 配置文件上传的解析器 --><bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"id="multipartResolver"><!--通过设值注入的方式设置上传的相关参数 --><property name="maxUploadSize" value="5232880"/></bean><!-- 防止资源文件被Spring MVC拦截。我们在Spring MVC设置资源的映射关系 --><!--  防止资源文件被spring MVC拦截<mvc:resources mapping="/img/**" location="/img/" cache-period="31556926"/><mvc:resources mapping="/js/**" location="/js/" cache-period="31556926"/><mvc:resources mapping="/css/**" location="/css/" cache-period="31556926"/>--></beans>
  
注意:
CommonsMultipartResolver这个Bean的id必须为multipartResolver,
原因:CommonsMultipartResolver Bean是在DispatcherServlet中加载的,而DispatcherServlet是通过名字来查找这个Bean的。而其他的,则是按照类型查找。
     
1.4 控制器中处理文件
在控制器中我们要接收上传的文件,我们只需要在形参中声明 MultipartFile类型即可。
package com.bobo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;@Controller
@RequestMapping("/user")
public class UserController {/*** 文件上传操作* @return*/@RequestMapping("/fileUpload")public String fileUpload(String username, MultipartFile headFile) throws IOException {System.out.println(username+"  : " + headFile.getOriginalFilename() );headFile.transferTo(new File("d:/tools/","123.jpg"));return "/index.jsp";}
}
     
2.文件下载操作
文件下载的操作关键是在服务端的处理。
 /*** 文件下载*/@RequestMapping("/download")public void fileDownload(HttpServletRequest request, HttpServletResponse response) throws Exception {File file = new File("d:/tools/","123.jpg");// 设置响应头信息response.setCharacterEncoding("utf-8");response.setContentType("multipart/form-data");response.setHeader("Content-Disposition", "attachment;fileName=" + file.getName());// 打开需要下载的文件InputStream in = new FileInputStream(file);// 激活下载的操作ServletOutputStream outputStream = response.getOutputStream();// 实现下载操作--> 本质就是一个文件的复制操作byte[] b = new byte[1024*1024];int length= 0;while((length = in.read(b)) > 0){outputStream.write(b,0,length);}// 关闭连接outputStream.close();in.close();}
      
SpringMVC中提供的下载方式
/*** 使用SpringMVC中提供的下载方式* @param request* @return* @throws Exception*/@RequestMapping("/download2")public ResponseEntity<byte[]> download2(HttpServletRequest request) throws Exception{File file = new File("d:/tools/","123.jpg");byte[] body = null;InputStream in = new FileInputStream(file);body = new byte[in.available()];in.read(body);HttpHeaders headers = new HttpHeaders();headers.add("Content-Disposition","attachment;fileName=" + file.getName());HttpStatus status = HttpStatus.OK;ResponseEntity<byte[]> entity = new ResponseEntity<>(body,headers,status);return entity;}
  
3.静态资源文件处理
其实我们在前面的课程里面都没有在Web项目中使用静态资源文件(html,css,js,图片等资源)。当我们添加了html页面后访问的时候发现404了。

  
原因是什么呢?
我们自己配置的web.xml中的信息覆盖掉了Tomcat默认的配置。
   

  

  
找到原因了,怎么解决了?我们在自己的web.xml专门制定不用覆盖的请求类型。
<!-- 防止资源文件被Spring MVC的前端控制器拦截 --><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.html</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.css</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.js</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.jpg</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.png</url-pattern></servlet-mapping>
   
重启在访问就可以了。

  
当然我们还可以在SpringMVC的配置文件中设置 资源的映射关系。
<!-- 防止资源文件被Spring MVC拦截。我们在Spring MVC设置资源的映射关系 -->
<!-- 防止资源文件被spring MVC拦截 -->
<mvc:resources mapping="/img/**" location="/img/" cache-period="31556926"/>
<mvc:resources mapping="/js/**" location="/js/" cache-period="31556926"/>
<mvc:resources mapping="/css/**" location="/css/" cache-period="31556926"/>
  
4.服务端数据校验
4.1 为什么需要服务端校验?
最早的校验,就是服务端校验。早期的网站,用户输入一个邮箱地址,校验邮箱地址需要将地址发送到服务端,服务端进行校验,校验成功后,给前端一个响应。有了JavaScript,校验工作可以放在前端去执行。那么为什么还需要服务端校验呢? 因为前端传来的数据不可信。前端很容易获取都后端的数据接口,如果有人绕过页面,就会出现非法数据,所以服务端也要数据校验,总的来说:
1. 前端校验要做,目的是为了提高用户体验。
2. 后端校验也要做,目的是为了数据安全。
   
4.2 普通校验
Springmvc本身没有校验功能,它使用Hibernate的校验框架,Hibernate的校验框架和orm没有关系。
   
引入hibernate相关的依赖
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.3.0.Alpha1</version>
</dependency>
     
将验证框架引入SpringMVC中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven  validator="validatorFactoryBean"/><!-- 配置Hibernate-validator验证框架 --><bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" id="validatorFactoryBean"><property name="providerClass" value="org.hibernate.validator.HibernateValidator"/><property name="validationMessageSource" ref="bundleMessageSource"/></bean><!-- 添加Hibernate验证框架的相关属性信息 --><bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="bundleMessageSource"><property name="fileEncodings" value="utf-8"/><property name="cacheSeconds" value="120" /></bean></beans>
    

   
bean对象中设置验证规则

   

      
Controller设置校验
package com.bobo.controller;import com.bobo.bean.UserBean;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;@Controller
@RequestMapping("/user")
public class UserController {/**** @param user @Validated 修饰表示user会被校验* @param br   校验的结果* @return*/@RequestMapping("/addUser")public String addUser(@Validated UserBean user, BindingResult br, Model m) {// 对客户端提交的用户数据检验/*if(user != null){if(user.getUserName().length() > 3 ){}}*/// 获取校验结果List<ObjectError> allErrors = br.getAllErrors();for (ObjectError allError : allErrors) {System.out.println(allError.getDefaultMessage());}m.addAttribute("errors",allErrors);return "/index.jsp";}
}
   
测试,提交的表单数据都为空的情况。

   
4.3 分组校验
为什么要使用分组校验
因为一个对象有多个属性,而不同的controller校验的需求是不一样的,必须c1只需要校验对象的账号是否为空就可以了,而c2不光要校验账号为空还需要校验手机号必须不能为空,这时分组校验就能解决这个问题了。实现步骤如下:
   
定义分组

     
校验规则中使用分组

      
Controller中使用
package com.bobo.controller;import com.bobo.bean.UserBean;
import com.bobo.group.GroupInterface1;
import com.bobo.group.GroupInterface2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;@Controller
@RequestMapping("/user")
public class UserController {/**** @param user @Validated 修饰表示user会被校验* @param br   校验的结果* @return*/@RequestMapping("/addUser")public String addUser(@Validated(value = GroupInterface2.class) UserBean user, BindingResult br, Model m) {// 对客户端提交的用户数据检验/*if(user != null){if(user.getUserName().length() > 3 ){}}*/// 获取校验结果List<ObjectError> allErrors = br.getAllErrors();for (ObjectError allError : allErrors) {System.out.println(allError.getDefaultMessage());}m.addAttribute("errors",allErrors);return "/index.jsp";}@RequestMapping("/udpateUser")public String udpate(@Validated(value = GroupInterface1.class) UserBean user, BindingResult br, Model m){return "/index.jsp";}}
   
5.数据回写
5.1 普通实现方式
@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/doLogin")public String doLogin(String userName, String password, Model model){model.addAttribute("userName",userName);model.addAttribute("password",password);return "forward:/login.jsp";}
}
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/30Time: 14:14To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body><h1>登录页面</h1>获取全局配置信息:${as} <br><form action="/user/doLogin" method="post"><label>用户名:</label><input type="text" name="userName" value="${userName}"><br><label>密码:</label><input type="text" name="password" value="${password}"><br><input type="submit" value="提交"></form>
</body>
</html>
  

    
5.2 通过Model方式实现
如果使用对象去接收客户端传递的参数,那么对象默认会被自动放到Model中(Request作用域中),在前端页面可以直接使用对象的数据。这样开发效率会更高。
/*** 如果我们用对象去接收请求传递的参数。*    那么该对象默认会被保存到Model对象中*    model.addAttribute("userBean",user)* @param user* @return*/@RequestMapping("/addUser")public String addUser(UserBean user) {System.out.println(user);return "/user.jsp";}
   
前端页面中的信息
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/29Time: 20:03To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
获取全局配置信息:${as} <br><form action="/user/addUser" method="post" ><label>编号:</label><input type="text" name="id" value="${user.id}"><br><label>用户名:</label><input type="text" name="userName" value="${user.userName}"><br><input type="submit" value="添加"></form>
</body>
</html>
     
效果

     
5.3 @ModelAttribute注解的实现
修改参数回写变量名
在需要回传的对象前添加@ModelAttribute注解可以设置对象在Model中的key值。实现更加灵活的操作。

    
配置全局变量名
    /*** 配置全局的信息*    该类中的其他方法在处理请求后都会绑定本方法的返回信息* @return*/@ModelAttribute("as")public List<String> getAllAddress(){return Arrays.asList("深圳","长沙","北京");}
   

   
6.异常处理
如果我们对系统没有做统一的异常管理,那么当系统抛出异常信息的时候会给客户很不好的体验。

    
全局异常处理器处理
package com.bobo.resolver;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 全局异常处理器*/
@Component
public class MyExceptionResolver implements HandlerExceptionResolver {/*** 处理异常* @param httpServletRequest* @param httpServletResponse* @param o* @param e* @return*/@Overridepublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {System.out.println(e.getMessage());ModelAndView mm = new ModelAndView();mm.setViewName("/500.jsp");return mm;}
}
  
不要忘了添加扫描路径

    
这时再抛出异常看到的就是我们自定义的错误页面了。

     
7.JSON数据操作

  
7.1 响应JSON数据
我们在本课程中使用的是Jackson.
添加依赖
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.5.2</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.5.2</version>
</dependency>
  
控制层中响应数据
@Controller
@RequestMapping("/person")
public class PersonController {@RequestMapping("/getUser")@ResponseBodypublic UserBean getUser(){UserBean user = new UserBean();user.setId(666);user.setUserName("波波");user.setGender("男");user.setAddress("湖南长沙");return user;}
}
   

    
    @RequestMapping("/getAllUser")@ResponseBodypublic List<UserBean> getAllUser(){UserBean user = new UserBean();user.setId(666);user.setUserName("波波");user.setGender("男");user.setAddress("湖南长沙");UserBean user2 = new UserBean();user2.setId(123);user2.setUserName("波波1");user2.setGender("男1");user2.setAddress("湖南长沙1");List<UserBean> list = new ArrayList<>();list.add(user);list.add(user2);return list;}

   

通过以上案例可知,我们返回的任意的Java对象数据Jackson都会将其转换为JSON数据。

注意:
对于Gson和Jackson这两个json处理依赖,直接添加即可。 除此之外,其他的JSON解析器如Fastjson都需要手动配置HttpMessageConverter.
实际上,在SpringMVC中,是由一个名叫HttpMessageConverter的类来提供对象到JSON字符串的转换的。而SpringMVC默认就提供了Gson和Jackson的HttpMessageConverter,分别是org.springframework.http.converter.json.GsonHttpMessageConverter和MappingJackson2HttpMessageConverter。对于其他的JSON解析器,只需要开发者手动配置一
下HttpMessageConverter即可。
   
本案例使用 Jackson处理
FastJSON的配置案例:
<mvc:annotation-driven validator="validatorFactoryBean"><mvc:message-converters><beanclass="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean></mvc:message-converters></mvc:annotation-driven>
   
7.2 接收JSON数据
注意:JSON只能是在请求体中,因此JSON只能放在POST获取PUT请求中。
    /*** 客户端发送的是JSON格式的字符串* @param user*/@RequestMapping("/addUser2")@ResponseBodypublic void addUser2(@RequestBody UserBean user){System.out.println(user);}
     
通过JQuery的ajax发送json数据
<%--Created by IntelliJ IDEA.User: dpbDate: 2021/1/30Time: 16:23To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title><script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<input type="button" value="提交JSON数据" onclick="fun1();">
<script type="text/javascript">function fun1(){$.ajax({type: 'POST',url: "person/addUser2",contentType: "application/json",//如果想以json格式把数据提交到后台的话,这个必须有,否则只会当做表单提交data: JSON.stringify({"userName":"bobo","password":"12345"}),//JSON.stringify()必须有,否则只会当做表单的格式提交dataType: "json",//期待返回的数据类型success: function(data){alert("success:"+data);},error:function(data){alert("error"+data);}});}
</script>
</body>
</html>
   
测试,当我们点击提交按钮,会把JSON数据发送给服务器,Jackson会将我们的JSON字符串转换为UserBean对象。
  

  
8、Restful风格
RESTful是一种软件设计规范,是客户端和服务端进行数据交互的一个规范。 早期使用JSP页面开发网页时,数据交互基本都是通过表单提交,然后通过内置对象传递。当HTML5兴起,移动互联网兴起,网站后端服务,不仅要考虑PC端的网页,也要考虑移动端数据的展示、小程序、HTML5页面等。如果需要多个终端(Android、iOS、小程序、Pad、HTML5页面)共用一个后端,一般来说主流方案就是使用JSON进行传递。RESTful则规范了请求的URL,注意RESTful只是一个规范,不是一个技术。
   
在RESTful中:
1. 一个URL操作一个资源。
2. 请求的URL中不能有动词。
3. 使用HTTP的请求方式来描述请求行为,例如:

  
在RESTful接口中,所有的方法都是返回JSON,没有返回页面的(ModelAndView),因此,所有的方法上都需要添加@ResponseBody注解。一个替代的简化方案,是使用 @RestController 代替@Controller。@RestController实际上是一个组合注解,是@Controller和@ResponseBody的组合:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {String value() default "";
}
     
Restful风格是使用

     
控制器中的编码
package com.bobo.controller;import com.bobo.bean.UserBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import java.util.ArrayList;
import java.util.List;/*** Restful风格编程介绍*/
/*@ResponseBody
@Controller*/
@RestController
public class StudentController {/*** 查询用户的方法* 基于Restf风格的规范,该请求只能接受GET方式提交的请求* @return*///@RequestMapping("/getAll")@GetMapping("/stus/{id}")//@RequestMapping(value = "/stus",method = RequestMethod.POST)public List<UserBean> getAllUser(@PathVariable Integer id){System.out.println("查询数据--->"+id);List<UserBean> list = new ArrayList<>();list.add(new UserBean(1,"root","123456"));list.add(new UserBean(2,"admin","123456"));return list;}/*** 添加用户数据*    接受POST方式提交* @param user* @return*/@PostMapping("/stus")public String  addUser(@RequestBody UserBean user){System.out.println("添加数据"+user);return "数据添加成功...";}@DeleteMapping("/stus/{id}")public String deleteUser(@PathVariable Integer id){System.out.println("删除的编号:" + id);return "删除数据成功...";}@PutMapping("/stus")public String updateUser(@RequestBody UserBean user){System.out.println("更新数据:" + user);return "更新数据成功...";}
}
   
POSTMan中的提交设置

     
9、拦截器
9.1 简介
SpringMVC中的拦截器对应了Web基础中的过滤器。
   
拦截器和过滤器的区别:

   
9.2 使用
创建拦截器
package com.bobo.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 MyInterceptor implements HandlerInterceptor {/*** 自定义处理器处理请求之前执行的方法* @param httpServletRequest* @param httpServletResponse* @param o* @return*    true 表示放过请求*    false 表示拦截请求* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {System.out.println("preHandle .... ");return true;}/*** 在目标方法执行完成后执行的方法* postHandle 在afterCompletion之前执行*    返回ModelAndView之前执行*    我们可以修改ModelAndView中的信息* @param httpServletRequest* @param httpServletResponse* @param o* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,ModelAndView modelAndView) throws Exception {System.out.println("ModelAndView执行之前操作...");modelAndView.setViewName("/index.jsp");System.out.println("postHandle ....");}/*** 在目标方法执行完成后执行的方法*    返回ModelAndView之后执行*    改变不了ModelAndView中的信息*    只能做一些资源回收相关的工作* @param httpServletRequest* @param httpServletResponse* @param o* @param e* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {System.out.println("afterCompletion ... ");}
}
   
需要将自定义的拦截器添加到IoC容器中,修改配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 添加对应的扫描路径 --><context:component-scan base-package="com.bobo.controller,com.bobo.resolver"/><!-- 开启Spring MVC 注解的使用方式 --><mvc:annotation-driven  /><!-- 配置自定义的拦截器 --><mvc:interceptors><mvc:interceptor><!-- ;拦截的映射地址  /** 表示拦截根目录及其子目录下的所有的请求 --><mvc:mapping path="/user/**"/><bean class="com.bobo.interceptor.MyInterceptor" /></mvc:interceptor></mvc:interceptors></beans>
   
拦截器工作原理

   

四、Spring MVC 整合Spring框架

1.整合实现步骤
1.1 创建Web项目

    
补全相关的目录结构

    
1.2 添加相关的依赖
我们需要在pom文件中添加对应的依赖。
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.4.RELEASE</version>
</dependency>
<dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope>
</dependency>
   
1.3 添加Spring的配置文件
在Spring的配置文件中添加扫描路径。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
"><!-- 配置扫描路径 --><context:component-scan base-package="com.bobo.service.impl,com.bobo.dao.impl"use-default-filters="true"><!-- 排除掉Controller注解的使用 --><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan></beans>
   
1.4 添加Spring MVC的配置文件
在Spring MVC 的配置文件中添加扫描和开启注解的使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd
"><!-- 配置扫描路径 --><context:component-scan base-package="com.bobo.controller" use-default-filters="false" ><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan><!-- 开启注解 --><mvc:annotation-driven ></mvc:annotation-driven>
</beans>
    

    
1.5 添加Tomcat的插件
  <plugins><!-- tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><!-- 端口号 --><port>8082</port><!-- /表示访问路径 省略项目名 --><path>/</path><!-- 设置编码方式 --><uriEncoding>utf-8</uriEncoding></configuration></plugin></plugins>
    
1.6 web.xml文件的配置
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app  version="2.5"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>Archetype Created Web Application</display-name><!-- 配置Spring --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 配置Servlet的前端控制器 --><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 关联自定义的Spring MVC的配置文件 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!-- 支持Restful风格编程 --><url-pattern>/</url-pattern></servlet-mapping><!-- 配置字符编码的过滤器 --><!-- 配置设置编码的过滤器 --><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- default 防止静态资源拦截 --><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.html</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.css</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.js</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.jpg</url-pattern></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.png</url-pattern></servlet-mapping>
</web-app>
   
1.7创建对应的逻辑代码
创建JavaBean对象
package com.bobo.bean;public class UserBean {private Integer id;private String userName;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}@Overridepublic String toString() {return "UserBean{" +"id=" + id +", userName='" + userName + '\'' +'}';}public UserBean(Integer id, String userName) {this.id = id;this.userName = userName;}public UserBean() {}
}
  
创建Dao层
package com.bobo.dao;import com.bobo.bean.UserBean;public interface IUserDao {UserBean getUserBean();
}
   
创建实现
package com.bobo.dao.impl;import com.bobo.bean.UserBean;
import com.bobo.dao.IUserDao;
import org.springframework.stereotype.Repository;@Repository
public class UserDaoImpl implements IUserDao {@Overridepublic UserBean getUserBean() {return new UserBean(666,"bobo");}
}
    
创建Service接口
package com.bobo.service;import com.bobo.bean.UserBean;public interface IUserService {UserBean getUserBean();
}
   
创建对应的实现
package com.bobo.service.impl;import com.bobo.bean.UserBean;
import com.bobo.dao.IUserDao;
import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate IUserDao dao;@Overridepublic UserBean getUserBean() {return dao.getUserBean();}
}
   
创建Controller
package com.bobo.controller;import com.bobo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@Autowiredprivate IUserService service;@GetMapping("/user/query")public String query(){return service.getUserBean().toString();}
}
    
1.8 测试效果

    
能够获取到Dao中返回的信息,那就说明访问到来Controller中的方法,说明Spring MVC没有问题,同时Controller可以获取Service对象,Service可以获取Dao中的对象,那说明Spring的IoC容器也是OK的!
   
2.Spring IoC 源码浅析

     
分析的入口代码
package com.bobo.test;import com.bobo.bean.UserBean;
import com.bobo.service.IUserService;
import com.bobo.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test01 {@Testpublic void fun1(){// 进入源码 Ctrl+鼠标点击 进入ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");System.out.println(ac.getBean(IUserService.class));}
}
   
进入ClassPathXmlApplicationContext构造方法
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[]{configLocation}, true, (ApplicationContext)null);
}
  
再进入
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,ApplicationContext parent) throws BeansException {// 初始化父类super(parent);// 设置本地配置信息this.setConfigLocations(configLocations);// 完成Spring容器的初始化操作if (refresh) {this.refresh();}
}
   
看源码的时候如果要回退那么就 Ctrl+Alt+ 方向键
public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {// 准备工作this.prepareRefresh();// 获取BeanFactory工厂对象,并且完成 配置文件的而加重解析操作ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during contextinitialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}}
}
    
重点查看obtainFreshBeanFactory方法。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {// 刷新BeanFactory对象this.refreshBeanFactory();// 获取BeanFactory对象 说明已经完成了 BeanFactory的创建和 配置文件的加载解析操作ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();if (this.logger.isDebugEnabled()) {this.logger.debug("Bean factory for " + this.getDisplayName() + ": " +beanFactory);}return beanFactory;
}
    

   
进入
protected final void refreshBeanFactory() throws BeansException {// 判断BeanFactory是否已经存在if (this.hasBeanFactory()) {// 存在 就销毁和关闭this.destroyBeans();this.closeBeanFactory();}try {// 创建BeanFactory对象DefaultListableBeanFactory beanFactory = this.createBeanFactory();beanFactory.setSerializationId(this.getId());this.customizeBeanFactory(beanFactory);// 加载解析配置文件this.loadBeanDefinitions(beanFactory);synchronized(this.beanFactoryMonitor) {this.beanFactory = beanFactory;}} catch (IOException var5) {throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);}
}
    
创建BeanFactory对象的具体方法。
protected DefaultListableBeanFactory createBeanFactory() {return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}
    

     
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException {XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);beanDefinitionReader.setEnvironment(this.getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));this.initBeanDefinitionReader(beanDefinitionReader);// 核心代码 直接进入this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {Resource[] configResources = this.getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}String[] configLocations = this.getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}
}
   
进入loadBeanDefinitions
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {Assert.notNull(resources, "Resource array must not be null");int counter = 0;Resource[] var3 = resources;int var4 = resources.length;for(int var5 = 0; var5 < var4; ++var5) {Resource resource = var3[var5];counter += this.loadBeanDefinitions((Resource)resource);}// 统计解析的配置文件的个数return counter;
}
     
进入loadBeanDefinitions方法中。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return this.loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (this.logger.isInfoEnabled()) {this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());}Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!((Set)currentResources).add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");} else {int var5;try {// 获取配置文件对应的字节输入流InputStream inputStream = encodedResource.getResource().getInputStream();try {InputSource inputSource = new InputSource(inputStream);// 设置对应的编码方式if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 核心代码 执行解析操作var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());} finally {inputStream.close();}} catch (IOException var15) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);} finally {((Set)currentResources).remove(encodedResource);if (((Set)currentResources).isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}return var5;}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 将xml中的内容加载到Document对象中Document doc = this.doLoadDocument(inputSource, resource);// 完成配置文件的注册操作 将配置文件中的信息载入到BeanDefinition对象中return this.registerBeanDefinitions(doc, resource);} catch (BeanDefinitionStoreException var4) {throw var4;} catch (SAXParseException var5) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"Line " + var5.getLineNumber() + " in XML document from " + resource + " isinvalid", var5);} catch (SAXException var6) {throw new XmlBeanDefinitionStoreException(resource.getDescription(),"XML document from " + resource + " is invalid", var6);} catch (ParserConfigurationException var7) {throw new BeanDefinitionStoreException(resource.getDescription(),"Parser configuration exception parsing XML from " + resource, var7);} catch (IOException var8) {throw new BeanDefinitionStoreException(resource.getDescription(),"IOException parsing XML document from " + resource, var8);} catch (Throwable var9) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, var9);}
}
   
关键方法registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();int countBefore = this.getRegistry().getBeanDefinitionCount();// 具体注册的方法documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;this.logger.debug("Loading bean definitions");// 获取Document对象的root标签Element root = doc.getDocumentElement();// 具体操作的方法this.doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;this.delegate = this.createDelegate(this.getReaderContext(), root, parent);// 对profile标签处理if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute("profile");if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (this.logger.isInfoEnabled()) {this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());}return;}}}// 解析配置文件之前的操作this.preProcessXml(root);// 解析配置文件this.parseBeanDefinitions(root, this.delegate);// 解析配置文件之后的操作this.postProcessXml(root);this.delegate = parent;
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {// 获取根节点下的所有的直接子标签NodeList nl = root.getChildNodes();// 循环获取每一个子标签for(int i = 0; i < nl.getLength(); ++i) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element)node;if (delegate.isDefaultNamespace(ele)) {// 解析默认的标签this.parseDefaultElement(ele, delegate);} else {// 解析自定义的标签delegate.parseCustomElement(ele);}}}} else {delegate.parseCustomElement(root);}
}
    
默认标签

     
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 配置文件的解析BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 注册我们获取的 BeanDefinitionHolder 对象BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,this.getReaderContext().getRegistry());} catch (BeanDefinitionStoreException var5) {this.getReaderContext().error("Failed to register bean definitionwith name '" + bdHolder.getBeanName() + "'", ele, var5);}this.getReaderContext().fireComponentRegistered(newBeanComponentDefinition(bdHolder));}
}
     
具体的解析操作
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele,BeanDefinition containingBean) {// 获取bean标签的id属性String id = ele.getAttribute("id");// 获取bean标签的name属性String nameAttr = ele.getAttribute("name");List<String> aliases = new ArrayList();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",;");aliases.addAll(Arrays.asList(nameArr));}String beanName = id;if (!StringUtils.hasText(id) && !aliases.isEmpty()) {beanName = (String)aliases.remove(0);if (this.logger.isDebugEnabled()) {this.logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");}}if (containingBean == null) {// 检查name是否唯一this.checkNameUniqueness(beanName, aliases, ele);}AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition,this.readerContext.getRegistry(), true);} else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (this.logger.isDebugEnabled()) {this.logger.debug("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");}} catch (Exception var9) {this.error(var9.getMessage(), ele);return null;}}String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName,aliasesArray);} else {return null;}
}
    
方法registerBeanDefinition:
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {String beanName = definitionHolder.getBeanName();// 关键代码registry.registerBeanDefinition(beanName,definitionHolder.getBeanDefinition());String[] aliases = definitionHolder.getAliases();if (aliases != null) {String[] var4 = aliases;int var5 = aliases.length;for(int var6 = 0; var6 < var5; ++var6) {String alias = var4[var6];registry.registerAlias(beanName, alias);}}
}
     
我们需要解析的信息要保存到我们前面实例化的BeanFactory的工厂对象中,DefaultListableBeanFactory对象。

   
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition)beanDefinition).validate();} catch (BeanDefinitionValidationException var9) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var9);}}BeanDefinition oldBeanDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);if (oldBeanDefinition != null) {if (!this.isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound.");}if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {if (this.logger.isWarnEnabled()) {this.logger.warn("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");}} else if (!beanDefinition.equals(oldBeanDefinition)) {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");}} else if (this.logger.isDebugEnabled()) {this.logger.debug("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");}this.beanDefinitionMap.put(beanName, beanDefinition);} else {if (this.hasBeanCreationStarted()) {synchronized(this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;if (this.manualSingletonNames.contains(beanName)) {Set<String> updatedSingletons = new LinkedHashSet(this.manualSingletonNames);updatedSingletons.remove(beanName);this.manualSingletonNames = updatedSingletons;}}} else {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.manualSingletonNames.remove(beanName);}this.frozenBeanDefinitionNames = null;}if (oldBeanDefinition != null || this.containsSingleton(beanName)) {this.resetBeanDefinition(beanName);}}
}
    

   
关键结论:SpringIoC容器启动的时候创建的BeanFactory对象的具体实例是(DefultListableBeanFactory),然后我们加载解析的配置文件中的相关标签会保存在BeanDefinition对象中,而多个BeanDefinition对象最终都会保存在DefaultListableBeanFactory对象的beanDefinitionMap属性中。
    
@Override
public void refresh() throws BeansException, IllegalStateException {//startupShutdownMonitor对象在spring环境刷新和销毁的时候都会用到,确保刷新和销毁不会同时执行synchronized (this.startupShutdownMonitor) {// 准备工作,例如记录事件,设置标志,检查环境变量等,并有留给子类扩展的位置,用来将属性加入到applicationContext中prepareRefresh();// 创建beanFactory,这个对象作为applicationContext的成员变量,可以被applicationContext拿来用,// 并且解析资源(例如xml文件),取得bean的定义,放在beanFactory中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 对beanFactory做一些设置,例如类加载器、SPEL解析器、指定bean的某些类型的成员变量对应某些对象.prepareBeanFactory(beanFactory);try {// 子类扩展用,可以设置bean的后置处理器(bean在实例化之后这些后置处理器会执行)postProcessBeanFactory(beanFactory);// 执行beanFactory后置处理器(有别于bean后置处理器处理bean实例,beanFactory 后置处理器处理bean定义)invokeBeanFactoryPostProcessors(beanFactory);// 将所有的bean的后置处理器排好序,但不会马上用,bean实例化之后会用到registerBeanPostProcessors(beanFactory);// 初始化国际化服务initMessageSource();// 创建事件广播器initApplicationEventMulticaster();// 空方法,留给子类自己实现的,在实例化bean之前做一些ApplicationContext相关的操作onRefresh();// 注册一部分特殊的事件监听器,剩下的只是准备好名字,留待bean实例化完成后再注册registerListeners();// 单例模式的bean的实例化、成员变量注入、初始化等工作都在此完成finishBeanFactoryInitialization(beanFactory);// applicationContext刷新完成后的处理,例如生命周期监听器的回调,广播通知等finishRefresh();}catch (BeansException ex) {logger.warn("Exception encountered during context initialization -cancelling refresh attempt", ex);// 刷新失败后的处理,主要是将一些保存环境信息的集合做清理destroyBeans();// applicationContext是否已经激活的标志,设置为falsecancelRefresh(ex);// Propagate exception to caller.throw ex;}}
}
    
3. Spring MVC 源码浅析
Spring MVC 源码分析的入口是 DispatcherServlet。因为他就是一个Servlet所以我们前提是要清楚Servlet的生命周期。

   
DispatcherServlet的类图结构

      
3.1 分析init方法
在父类的HttpServletBean中找到了对应的init方法。
public final void init() throws ServletException {if (this.logger.isDebugEnabled()) {this.logger.debug("Initializing servlet '" + this.getServletName() +"'");}try {PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(),this.requiredProperties);BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader,this.getEnvironment()));this.initBeanWrapper(bw);bw.setPropertyValues(pvs, true);} catch (BeansException var4) {this.logger.error("Failed to set bean properties on servlet '" +this.getServletName() + "'", var4);throw var4;}// 关键代码this.initServletBean();if (this.logger.isDebugEnabled()) {this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");}
}
    
进入initServletBean方法。
protected final void initServletBean() throws ServletException {this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");}long startTime = System.currentTimeMillis();try {// 初始化 Web容器this.webApplicationContext = this.initWebApplicationContext();// 预留给我们扩展的方法this.initFrameworkServlet();} catch (ServletException var5) {this.logger.error("Context initialization failed", var5);throw var5;} catch (RuntimeException var6) {this.logger.error("Context initialization failed", var6);throw var6;}if (this.logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms");}
}
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());WebApplicationContext wac = null;// 如果找到了 rootContextif (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac =(ConfigurableWebApplicationContext)wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}this.configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// 查找容器wac = this.findWebApplicationContext();}if (wac == null) {// 创建容器wac = this.createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// 刷新容器this.onRefresh(wac);}if (this.publishContext) {String attrName = this.getServletContextAttributeName();this.getServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet '"+ this.getServletName() + "' as ServletContext attribute with name [" + attrName+ "]");}}
return wac;
}
    
createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {Class<?> contextClass = this.getContextClass();if (this.logger.isDebugEnabled()) {this.logger.debug("Servlet with name '" + this.getServletName() + "'will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]");}if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");} else {ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);wac.setEnvironment(this.getEnvironment());wac.setParent(parent);wac.setConfigLocation(this.getContextConfigLocation());// 核心代码this.configureAndRefreshWebApplicationContext(wac);return wac;}
}
   
configureAndRefreshWebApplicationContext方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac){if (ObjectUtils.identityToString(wac).equals(wac.getId())) {if (this.contextId != null) {wac.setId(this.contextId);} else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());}}wac.setServletContext(this.getServletContext());wac.setServletConfig(this.getServletConfig());wac.setNamespace(this.getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, newFrameworkServlet.ContextRefreshListener()));ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());}this.postProcessWebApplicationContext(wac);this.applyInitializers(wac);// 关键代码 完成SpringMVC配置文件的加载解析操作 Spring容器初始化操作wac.refresh();
}
   

   
DispatcherServlet的初始化操作其实就是IoC容器的初始化操作过程,完成了Spring MVC配置文件的加载解析操作。
    
3.2 分析service方法
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (HttpMethod.PATCH != httpMethod && httpMethod != null) {super.service(request, response);} else {// 具体处理请求的方法this.processRequest(request, response);}
}
protected final void processRequest(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();LocaleContext localeContext = this.buildLocaleContext(request);RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());this.initContextHolders(request, localeContext, requestAttributes);try {this.doService(request, response);} catch (ServletException var17) {failureCause = var17;throw var17;} catch (IOException var18) {failureCause = var18;throw var18;} catch (Throwable var19) {failureCause = var19;throw new NestedServletException("Request processing failed",var19);} finally {this.resetContextHolders(request, previousLocaleContext,previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}if (this.logger.isDebugEnabled()) {if (failureCause != null) {this.logger.debug("Could not complete request",(Throwable)failureCause);} else if (asyncManager.isConcurrentHandlingStarted()) {this.logger.debug("Leaving response open for concurrent processing");} else {this.logger.debug("Successfully completed request");}}this.publishRequestHandledEvent(request, response, startTime,(Throwable)failureCause);}
}
   
具体处理请求 this.doService(request, response);
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {if (this.logger.isDebugEnabled()) {String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");}Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap();Enumeration attrNames = request.getAttributeNames();label108:while(true) {String attrName;do {if (!attrNames.hasMoreElements()) {break label108;}attrName = (String)attrNames.nextElement();} while(!this.cleanupAfterInclude &&!attrName.startsWith("org.springframework.web.servlet"));attributesSnapshot.put(attrName,request.getAttribute(attrName));}}// 将Web容器保存到了 Request请求中request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());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 {// 处理请求分发this.doDispatch(request, response);} finally {if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() &&
attributesSnapshot != null) {this.restoreAttributesAfterInclude(request, attributesSnapshot);}}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Object dispatchException = null;try {// 检查是否有文件上传processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request,mappedHandler.getHandler());if (this.logger.isDebugEnabled()) {this.logger.debug("Last-Modified value for [" +getRequestUri(request) + "] is: " + lastModified);}if ((new ServletWebRequest(request,response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest,response)) {return;}mv = ha.handle(processedRequest, response,mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response,mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response,mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response,mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response,mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}
}
   
文件上传操作
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");} else {if (!(request.getAttribute("javax.servlet.error.exception")instanceof MultipartException)) {// 文件上传操作的具体执行return this.multipartResolver.resolveMultipart(request);}this.logger.debug("Multipart resolution failed for current request before - skipping re-resolution for undisturbed error rendering");}}return request;
}
    

   
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {Assert.notNull(request, "Request must not be null");// 是否需要延迟 处理if (this.resolveLazily) {return new DefaultMultipartHttpServletRequest(request) {protected void initializeMultipart() {MultipartParsingResult parsingResult = CommonsMultipartResolver.this.parseRequest(request);this.setMultipartFiles(parsingResult.getMultipartFiles());this.setMultipartParameters(parsingResult.getMultipartParameters());this.setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());}};} else {// 解析请求MultipartParsingResult parsingResult = this.parseRequest(request);return new DefaultMultipartHttpServletRequest(request,parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(),parsingResult.getMultipartParameterContentTypes());}
}
protected MultipartParsingResult parseRequest(HttpServletRequest request)throws MultipartException {String encoding = this.determineEncoding(request);FileUpload fileUpload = this.prepareFileUpload(encoding);try {// 获取提交的表单中的所有的表单域List<FileItem> fileItems = ((ServletFileUpload)fileUpload).parseRequest(request);// 解析每一个表单域return this.parseFileItems(fileItems, encoding);} catch (SizeLimitExceededException var5) {throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(),var5);} catch (FileSizeLimitExceededException var6) {throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), var6);} catch (FileUploadException var7) {throw new MultipartException("Failed to parse multipart servlet request", var7);}
}
     
具体处理表单中提交的各个部分
protected CommonsFileUploadSupport.MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap();Map<String, String[]> multipartParameters = new HashMap();Map<String, String> multipartParameterContentTypes = new HashMap();Iterator var6 = fileItems.iterator();while(true) {while(var6.hasNext()) {FileItem fileItem = (FileItem)var6.next();// 普通表单域处理if (fileItem.isFormField()) {String partEncoding = this.determineEncoding(fileItem.getContentType(), encoding);String value;if (partEncoding != null) {try {// 获取对应的编码方式value = fileItem.getString(partEncoding);} catch (UnsupportedEncodingException var12) {if (this.logger.isWarnEnabled()) {this.logger.warn("Could not decode multipartitem '" + fileItem.getFieldName() + "' with encoding '" + partEncoding + "': using platform default");}value = fileItem.getString();}} else {// 得到提交的值value = fileItem.getString();}String[] curParam = (String[])multipartParameters.get(fileItem.getFieldName());if (curParam == null) {// 将提交的数据保存起来multipartParameters.put(fileItem.getFieldName(), new String[]{value});} else {String[] newParam = StringUtils.addStringToArray(curParam, value);multipartParameters.put(fileItem.getFieldName(),newParam);}multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());} else {// 表单提交的文件信息CommonsMultipartFile file = new CommonsMultipartFile(fileItem);// 将表单提交的文件信息 封装到了 CommonsMultipartFile 对象中multipartFiles.add(file.getName(), file);if (this.logger.isDebugEnabled()) {this.logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() + " bytes with original filename [" + file.getOriginalFilename() + "], stored " + file.getStorageDescription());}}}return new CommonsFileUploadSupport.MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);}
}
   
文件上传处理完成
继续回到doDispatch方法中
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Object dispatchException = null;try {// 检查是否有文件上传processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 处理器映射器mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {this.noHandlerFound(processedRequest, response);return;}// 获取处理器适配器HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request,mappedHandler.getHandler());if (this.logger.isDebugEnabled()) {this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest,response)) {return;}// 处理适配器处理请求mv = ha.handle(processedRequest, response,mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response,mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handlerdispatch failed", var21);}this.processDispatchResult(processedRequest, response,mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}
}
   

  
基于普通的使用方式
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return ((Controller)handler).handleRequest(request, response);
}
   
通过源码的简单分析论证了我们前面给大家介绍的Spring MVC的工作原理。

   
4. Spring MVC和Spring IoC的关联关系
Spring容器是一个父容器,SpringMVC容器是一个子容器,它继承自Spring容器。因此,在SpringMVC容器中,可以访问到Spring容器中定义的Bean,而在Spring容器中,无法访问SpringMVC容器中定义的Bean。在Web开发中,Controller全部在SpringMVC中扫描,除了Controller之外的Bean,全部在Spring容器中扫描(Service、Dao),按这种方式扫描,扫描完完成后,Controller可以访问到Service。
   
1. 为什么不全部都在Spring中扫描
因为处理器映射器只会去SpringMVC中查找到Controller,如果没有,就找不到,不会去 Spring中找,这就决定了,Controller必须在SpringMVC中扫描。
  
2. 为什么不全部在SpringMVC中扫描
在SSM整合或者Spring+SpringMVC+JdbcTemplate中,可以全部在SpringMVC中扫描,但是,在SSH整合中,这种方式不允许。
  

   
分析Spring的初始化,应该从web.xml中入手。
<!-- 配置Spring -->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
   
ContextLoaderListener这个监听器是系统启动的时候会触发。
public void contextInitialized(ServletContextEvent event) {// 初始化web容器this.initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");} else {Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {if (this.context == null) {// 创建容器对象this.context = this.createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;if (!cwac.isActive()) {if (cwac.getParent() == null) {ApplicationContext parent = this.loadParentContext(servletContext);cwac.setParent(parent);}// 完全Spring IoC容器的初始化操作this.configureAndRefreshWebApplicationContext(cwac, servletContext);}}// 将Spring的IoC容器对象保存在了Servlet容器中 key是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;} else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;} catch (RuntimeException var8) {logger.error("Context initialization failed", var8);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);throw var8;} catch (Error var9) {logger.error("Context initialization failed", var9);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);throw var9;}
}
   
在SpringMVC整合Spring中,在ContextLoaderListener完成的是SpringIoC的初始化操作,同时将IoC容器保存在了Servlet上下文中。
   
再看SpringMVC中的处理
protected WebApplicationContext initWebApplicationContext() {// 获取Spring IoC容器对象WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}this.configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {wac = this.findWebApplicationContext();}if (wac == null) {wac = this.createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {this.onRefresh(wac);}if (this.publishContext) {String attrName = this.getServletContextAttributeName();this.getServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");}}return wac;
}
   
如何获取Spring IoC容器对象的。
public static WebApplicationContext getWebApplicationContext(ServletContext sc){return getWebApplicationContext(sc,WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
   
然后进入createWebApplicationContext方法。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {Class<?> contextClass = this.getContextClass();if (this.logger.isDebugEnabled()) {this.logger.debug("Servlet with name '" + this.getServletName() + "'will try to create custom WebApplicationContext context of class '" +contextClass.getName() + "', using parent context [" + parent + "]");}if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");} else {ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);wac.setEnvironment(this.getEnvironment());// Spring MVC的IoC容器对象设置Spring 的IoC容器对象为父容器wac.setParent(parent);wac.setConfigLocation(this.getContextConfigLocation());this.configureAndRefreshWebApplicationContext(wac);return wac;}
}
  
得出的重点结论:Spring的IoC容器对象是SpringMVC容器的父容器,所以SpringMVC中的对象可以将SpringIOC容器中的对象注入,但是反过来就不行了!!!
  

五、Spring和SpringMVC总结

相关专题常见面试题:
1. 介绍下Spring的IoC
2. 介绍下Spring的AOP
3. 事务的传播属性
4. 事务的隔离级别
5. 介绍下Spring中用到的设计模式有哪些?
6. 介绍下Spring MVC的工作原理
7. Spring和SpringMVC的关系

相关文章:

Java经典框架之Spring MVC

Spring MVC Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. Spring MVC 入门案例 2. 基…...

Golang make vs new

文章目录 1.简介2.区别3.new 可以初始化 slice&#xff0c;map 和 channel 吗&#xff1f;4.make 可以初始化其他类型吗&#xff1f;5.小结参考文献 1.简介 在 Go 语言中&#xff0c;make 和 new 是两个用于创建对象的内建函数&#xff0c;但它们有着不同的用途和适用范围。 …...

Arthas

概述 Arthas&#xff08;阿尔萨斯&#xff09; 能为你做什么&#xff1f; Arthas 是Alibaba开源的Java诊断工具&#xff0c;深受开发者喜爱。 当你遇到以下类似问题而束手无策时&#xff0c;Arthas可以帮助你解决&#xff1a; 这个类从哪个 jar 包加载的&#xff1f;为什么会…...

IP代理科普| 共享IP还是独享IP?两者的区别与优势

通俗地讲&#xff0c;共享IP就像乘坐公共汽车一样&#xff0c;您可以到达目的地&#xff0c;但将与其他乘客共享旅程&#xff0c;座位很可能是没有的。独享IP就像坐出租车一样&#xff0c;您可以更快到达目的地&#xff0c;由于车上只有您一个人&#xff0c;座位是您一个人专用…...

龙芯loongarch64服务器编译安装tensorflow-io-gcs-filesystem

前言 安装TensorFlow的时候,会出现有些包找不到的情况,直接使用pip命令也无法安装,比如tensorflow-io-gcs-filesystem,安装的时候就会报错: 这个包需要自行编译,官方介绍有限,这里我讲解下 编译 准备 拉取源码:https://github.com/tensorflow/io.git 文章中…...

开源持续测试平台Linux MeterSphere本地部署与远程访问

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…...

Kubernetes(K8S)快速入门

概述 在本门课程中&#xff0c;我们将会学习K8S一些非常重要和核心概念&#xff0c;已经操作这些核心概念对应组件的相关命令和方式。比如Deploy部署&#xff0c;Pod容器&#xff0c;调度器&#xff0c;Service服务&#xff0c;Node集群节点&#xff0c;Helm包管理器等等。 在…...

将遗留系统分解为微服务:第 2 部分

在当今不断发展的技术环境中&#xff0c;从整体架构向微服务的转变对于许多企业来说都是一项战略举措。这在报销计算系统领域尤其重要。正如我在上一篇文章第 1 部分应用 Strangler 模式将遗留系统分解为微服务-CSDN博客中提到的&#xff0c;让我们探讨如何有效管理这种转变。 …...

RK3588平台开发系列讲解(AI 篇)RKNN-Toolkit2 模型的加载转换

文章目录 一、Caffe 模型加载接口二、TensorFlow 模型加载接口三、TensorFlowLite 模型加载接口四、ONNX 模型加载五、DarkNet 模型加载接口六、PyTorch 模型加载接口沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 RKNN-Toolkit2 目前支持 Caffe、TensorFlow、Tensor…...

CNVD原创漏洞审核和处理流程

一、CNVD原创漏洞审核归档和发布主流程 &#xff08;一&#xff09;审核和归档流程 审核流程分为一级、二级、三级审核&#xff0c;其中一级审核主要对提交的漏洞信息完整性进行审核&#xff0c;漏洞符合可验证&#xff08;通用型漏洞有验证代码信息或多个互联网实例、事件型…...

【java爬虫】基于springboot+jdbcTemplate+sqlite+OkHttp获取个股的详细数据

注&#xff1a;本文所用技术栈为&#xff1a;springbootjdbcTemplatesqliteOkHttp 前面的文章我们获取过沪深300指数的成分股所属行业以及权重数据&#xff0c;本文我们来获取个股的详细数据。 我们的数据源是某狐财经&#xff0c;接口的详细信息在下面的文章中&#xff0c;本…...

智能优化算法应用:基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工兔算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工兔算法4.实验参数设定5.算法结果6.参考文…...

【ubuntu 22.04】安装vscode并配置正常访问应用商店

注意&#xff1a;要去vscode官网下载deb安装包&#xff0c;在软件商店下载的版本不支持输入中文 在ubuntu下用火狐浏览器无法访问vscode官网&#xff0c;此时可以手动进行DNS解析&#xff0c;打开DNS在线查询工具&#xff0c;解析以下主机地址&#xff08;复制最后一个IP地址&a…...

K8s出现问题时,如何排查解决!

K8s问题的排查 1. POD启动异常、部分节点无法启动pod2. 审视集群状态3. 追踪事件日志4. 聚焦Pod状态5. 检查网络连通性6. 审视存储配置7. 研究容器日志8. K8S集群网络通信9. 问题&#xff1a;Service 是否通过 DNS 工作&#xff1f;10. 总结1、POD启动异常、部分节点无法启动p…...

2015年第四届数学建模国际赛小美赛B题南极洲的平均温度解题全过程文档及程序

2015年第四届数学建模国际赛小美赛 B题 南极洲的平均温度 原题再现&#xff1a; 地表平均温度是反映气候变化和全球变暖的重要指标。然而&#xff0c;在以前的估计中&#xff0c;在如何界定土地平均数方面存在一些方法上的差异。为简单起见&#xff0c;我们只考虑南极洲。请建…...

npm常见错误

三个方面 1. npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! phantomjs-prebuilt2.1.15 install: node install.js npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the phantomjs-prebuilt2.1.15 install script. np…...

JVM入门到入土-Java虚拟机寄存器指令集与栈指令集

JVM入门到入土-Java虚拟机寄存器指令集与栈指令集 HotSpot虚拟机中的任何操作都需要入栈和出栈的步骤。 由于跨平台性的设计&#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同&#xff0c;所以不能设计为基于寄存器的。优点是跨平台&#xff0c;指令集小&#x…...

MS2244模拟开关可Pin to Pin兼容NJM2244

MS2244 是一款集成的视频开关&#xff0c;实现三输入视频或音频信号的三选一。可Pin to Pin兼容NJM2244。 芯片集成了 75Ω驱动电路&#xff0c;可以直接驱动电视监控器。芯片工作电压 5V&#xff5e;12V&#xff0c;带宽 10MHz&#xff0c;抗串扰 70dB (4.43MHz)。另外芯片还集…...

PostgreSQL 可观测性最佳实践

简介 软件简述 PostgreSQL 是一种开源的关系型数据库管理系统 (RDBMS)&#xff0c;它提供了许多可观测性选项&#xff0c;以确保数据库的稳定性和可靠性。 可观测性 可观测性&#xff08;Observability&#xff09;是指对数据库状态和操作进行监控和记录&#xff0c;以便在…...

51单片机相关寄存器

前言 单片机复习的时候对应寄存器的记忆感觉很混乱&#xff0c;这里进行一下整理,后面的单词是我用来辅助记忆的&#xff0c;可能并不是表示原本的含义。 P3口的第二功能 0RXD 串行数据输入口 1TXD串行数据输出口2INT0外部中断0输入3INT1外部中断1输入4T0定时器0外部计数输入…...

二叉树进阶题目(超详解)

文章目录 前言根据二叉树创建字符串题目分析写代码 二叉树的层序遍历题目分析 写代码二叉树的层序遍历II题目分析写代码 二叉树的最近公共祖先题目分析写代码时间复杂度 优化思路优化的代码 二叉搜索树与双向链表题目分析写代码 从前序与中序遍历序列构造二叉树题目分析写代码从…...

W6100-EVB-Pico评估版介绍

文章目录 1 简介2 硬件资源2.1 硬件规格2.2 引脚定义2.3 工作条件 3 参考资料3.1 Datasheet3.2 原理图3.3 尺寸图&#xff08;尺寸&#xff1a;mm&#xff09;3.4 参考例程 4 硬件协议栈优势 1 简介 W6100-EVB-Pico是一款基于树莓派RP2040和全硬件TCP/IP协议栈以太网芯片W6100的…...

嵌入式面试准备

题目都摘于网上 嵌入式系统中经常要用到无限循环&#xff0c;如何用C编写死循环 while(1){}或者for(;&#x1f609; 内存分区 代码区&#xff0c;全局区&#xff08;全局变量&#xff0c;静态变量&#xff0c;以及常量&#xff09;&#xff0c;栈区&#xff0c;堆区 const关键…...

在Linux Docker中部署RStudio Server,实现高效远程访问

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 安装RStudio Server二. 本地访问三. Linux 安装cpolar四. 配置RStudio serv…...

EternalBlue【永恒之蓝】漏洞详解(复现、演示、远程、后门、入侵、防御)内容丰富-深入剖析漏洞原理-漏洞成因-以及报错解决方法-值得收藏!

漏洞背景&#xff1a; 1.何为永恒之蓝&#xff1f; 永恒之蓝&#xff08;Eternal Blue&#xff09;爆发于2017年4月14日晚&#xff0c;是一种利用Windows系统的SMB协议漏洞来获取系统的最高权限&#xff0c;以此来控制被入侵的计算机。甚至于2017年5月12日&#xff0c; 不法分子…...

长链接与在线文件

什么是在线文件 常见的聊天工具&#xff0c;比如。。。微信&#xff0c;你可以发送一个文件给对端&#xff0c;即使对端不在线&#xff0c;这个文件也可以暂存在服务器上面&#xff0c;直到接收端上线消费或者超时&#xff0c;这个叫离线文件。与之对应的&#xff0c;在线文件要…...

Python内置数据类型等入门语(句)法

内置数据类型 数字&#xff08;Number&#xff09;关键字: int 、float、complex字符串&#xff08;String&#xff09;关键字&#xff1a;单引号&#xff0c;双引号 三引号都可以表示&#xff0c;8 种内置类型都可转为字符串类型列表&#xff08;List&#xff09; 关键符号 […...

ElasticSearch之RestClient笔记

1. ElasticSearch 1.1 倒排索引 1.2 ElasticSearch和Mysql对比 1.3 RestClient操作 导入依赖 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.15.…...

饥荒Mod 开发(二二):显示物品信息

饥荒Mod 开发(二一)&#xff1a;超大便携背包&#xff0c;超大物品栏&#xff0c;永久保鲜 饥荒Mod 开发(二三)&#xff1a;显示物品栏详细信息 饥荒中的物品没有详细信息&#xff0c;基本上只有一个名字&#xff0c;所以很多物品的功能都不知道&#xff0c;比如浆果吃了也不知…...

Microsoft Edge使用方法和心得

Microsoft Edge使用方法和心得 大家好&#xff0c;我是豪哥&#xff0c;一名来自杭州的Java程序员&#xff0c;今天我想分享一下我对Microsoft Edge的使用方法和心得。作为一名热爱编程的程序员&#xff0c;我发现一个高效的浏览器对于我们的工作和学习至关重要。而Microsoft …...

Kafka操作指令笔记

查堆积用命令查&#xff1a; ./kafka-consumer-groups.sh --bootstrap-server {kafka集群地址} --describe --group {消费组名称}bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --all-groups #查看所有组别的积压情况可以通过grep、awk或其他文…...

WAVE SUMMIT+ 2023倒计时2天,传文心一言将曝最新进展!

传文心一言将曝最新进展&#xff01; 亮点一&#xff1a;趋势引领&#xff0c;“扛把子”文心一言将曝新进展亮点二&#xff1a;干货十足&#xff0c;硬核低门槛开发秘籍大放送亮点三&#xff1a;蓄势待发&#xff0c;大模型赋能产业正当时亮点四&#xff1a;群星闪耀&#xff…...

Crow:Middlewares 庖丁解牛5 context

Crow:Middlewares 庖丁解牛4 partial_context-CSDN博客 基于partial_context再来解释context namespace detail {template<typename... Middlewares>struct partial_context : public pop_back<Middlewares...>::template rebind<partial_context>, public…...

CentOS 7 设置网络

CentOS 7 设置网络 正常情况 ①登陆进去之后使用下面的命令修改文件 echo ONBOOTyes >> /etc/sysconfig/network-scripts/ifcfg-ens33②如果是虚拟机重启后使用如下命令进行查看IP地址 ip addr注&#xff1a;到这里如果显示有两部分&#xff0c;则代表网络设置成功&a…...

装饰器模式(Decorator)

装饰器模式(Decorator Pattern)是一种结构型设计模式,用于动态地给一个对象添加额外的职责。装饰器提供了一个灵活的替代扩展功能的方案,相比继承更加灵活。 在Java中,装饰器模式通常涉及以下几个部分: 组件(Component):定义一个对象接口,可以给这些对象动态添加职责…...

关于“Python”的核心知识点整理大全34

目录 第&#xff11;3 章 外星人 13.1 回顾项目 game_functions.py 13.2 创建第一个外星人 13.2.1 创建 Alien 类 alien.py 13.2.2 创建 Alien 实例 alien_invasion.py 13.2.3 让外星人出现在屏幕上 game_functions.py 13.3 创建一群外星人 13.3.1 确定一行可容纳…...

设计模式--抽象工厂模式

实验4&#xff1a;抽象工厂模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解抽象工厂模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用抽象工厂模式解决实际问题。 [实验任务]&#xff1a;人与肤色 使用抽象…...

浅析海博深造

文章目录 深造作用 留学种类 选专业 择校 申请流程 申请方式 深造作用 1、个人能力提升&#xff08;学术专业、语言、新文化或新生活方式&#xff09; 2、更好的职业发展&#xff08;起点更高、结交新朋友或扩大社交圈&#xff09; 3、北京上海落户优惠 4、海外居留福…...

【Hive_05】企业调优1(资源配置、explain、join优化)

1、 计算资源配置1.1 Yarn资源配置1.2 MapReduce资源配置 2、 Explain查看执行计划&#xff08;重点&#xff09;2.1 Explain执行计划概述2.2 基本语法2.3 案例实操 3、分组聚合优化3.1 优化说明&#xff08;1&#xff09;map-side 聚合相关的参数 3.2 优化案例 4、join优化4.1…...

synchronized

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;JavaEE &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; synchronized 1. 特性1.1 互斥1.2 可重入 …...

Vue在页面上添加水印

第一步&#xff1a;在自己的项目里创建一个js文件&#xff1b;如图所示我在在watermark文件中创建了一个名为waterMark.js文件。 waterMark.js /** 水印添加方法 */ let setWatermark (str1, str2) > {let id 1.23452384164.123412415if (document.getElementById(id) …...

SQL server 数据库练习题及答案(练习2)

使用你的名字创建一个数据库 创建表&#xff1a; 数据库中有三张表&#xff0c;分别为student,course,SC&#xff08;即学生表&#xff0c;课程表&#xff0c;选课表&#xff09; 问题&#xff1a; --1.分别查询学生表和学生修课表中的全部数据。--2.查询成绩在70到80分之间…...

minicube搭建golang容器服务

引言 最近在自己电脑上搭建一个小型k8s环境&#xff0c;以学习云原生相关内容。这里我主要分为三部分记录&#xff1a; 容器及容器编排理论环境安装相关rpcx服务实战 还在调试中&#xff0c;先总结整理下&#xff0c;这里后续补充上我的github工程链接。 一、容器及容器编排理…...

图片批量处理:图片批量缩放,高效调整尺寸的技巧

在数字媒体时代&#xff0c;图片处理已是日常生活和工作中不可或缺的一部分。有时候要批量处理图片&#xff0c;如缩放图片尺寸&#xff0c;以满足不同的应用需求。现在一起来看看办公提效式具如何高效的将图片批量处理方法&#xff0c;快速、准确地批量调整图片尺寸操作。 下…...

直接插入排序【从0-1学数据结构】

文章目录 &#x1f497; 直接插入排序Java代码C代码JavaScript代码稳定性时间复杂度空间复杂度 我们先来学习 直接插入排序, 直接排序算是所有排序中最简单的了,代码也非常好实现,尽管直接插入排序很简单,但是我们依旧不可以上来就直接写代码,一定要分析之后才开始写,这样可以提…...

C++/CLI——1简介

C/CLI——1简介 如果你是.net程序员&#xff0c;不免会用到C/C写的库。对于简单的调用&#xff0c;可以直接使用DllImport来完成就可以&#xff0c;详情可参考C#调用C/C从零深入讲解。但是对于复杂的C类和对象&#xff0c;尤其是类似于OCC的大型C项目&#xff0c;DllImport可能…...

C#实现串口通讯

1、官网下载Launch Virtual Serial Port Driver Virtual Serial Port Driver - create and emulate virtual COM port&#xff0c;开个虚拟串口&#xff1a; Pair模式&#xff08;一对&#xff0c;成双成对的意思&#xff0c;就是COM1向COM2传或者COM2向COM1,好比两台机器的CO…...

NLP论文阅读记录 - 以大语言模型为参考学习总结

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作2.1文本生成模型的训练方法2.2 基于LLM的自动评估2.3 LLM 蒸馏和基于 LLM 的数据增强 三.本文方法3.1 Summarize as Large Language Models3.1.1 前提3.1.2 大型语言模型作为参考具有…...

前端---资源路径

当我们使用img标签显示图片的时候&#xff0c;需要指定图片的资源路径&#xff0c;比如: <img src"images/logo.png">这里的src属性就是设置图片的资源路径的&#xff0c;资源路径可以分为相对路径和绝对路径。 1. 相对路径 从当前操作 html 的文档所在目录算…...

【2024考研】心情记录

今天是12.26日。距离24考研已经过去了2天&#xff0c;自认为缓过来了&#xff0c;故写下这篇文章。 25日早上简单过了一下答案&#xff0c;但实在是记不住答案了&#xff0c;不知道是我的脑子抵触还是怎的&#xff0c;像一块灰色的布遮住了我的记忆&#xff0c;羞于打开&#x…...