Spring MVC的基本使用1
1、Spring MVC的基本介绍
spring mvc 是基于 spring 的一个框架,实际上就是 spring 的一个模块,是专门用来做 web 开发的。spring mvc 的底层实际上还是 servlet ,只是在 servlet 的基础上面加入了一些功能,让 web 开发更加方便,可以理解为是 servlet 的升级。
Spring MVC 框架是围绕一个 DispatcherServlet(中央调度器) 来设计的,这个Servlet会把请求分发给各个处理器,由各个处理器来处理请求(处理器就是应用中注解了 @Controller 和 @RequestMapping 的类和方法)。DispatcherServlet
其实就是个Servlet
(它继承自HttpServlet
基类),DispatcherServlet 也被称之为前端控制器。
DispatcherServlet
处理请求的工作流如下:
2、SpringMVC的基本使用
2.1、springmvc的使用
先在 idea 中通过 maven 创建一个 web 项目,创建完成后我们可以手动添加 src/main/java 和 src/main/resource 目录:
然后引入依赖,依赖配置文件 pom.xml 类似下面:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>maven_ee_test01</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency> </dependencies> </project>
在 maven 的 web 项目的 web.xml 配置文件中配置中央调度器。默认生成的 web.xml 配置文件跟下面不太一样,可以直接照着下面进行修改:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- SpringMVC的前端控制器 --> <servlet> <servlet-name>springmvcTest</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 可以自定义springmvc读取的配置文件的位置 --> <!-- <init-param>--> <!-- <param-name>contextConfigLocation</param-name>--> <!-- <param-value>/WEB-INF/xxx.xml</param-value> <!–指定配置文件的位置–>--> <!-- </init-param>--> <!-- 指定该servlet对象在tomcat启动时即创建。 load-on-startup:指定tomcat启动后创建对象的顺序,tomcat会根据各个servlet的该属性值按照顺序来创建各个servlet对象。它的值是大于等于0的整数,值越小创建时间越早。--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvcTest</servlet-name> <!-- 使用框架时,url-pattern一般来说有两种写法: 1)使用自定义扩展名:*.do、*.action、*.mvc等等,如<url-pattern>*.do</url-pattern> 2)直接使用斜杆:<url-pattern>/</url-pattern>,表示拦截所有请求 --> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
DispatcherServlet实际上
就是个Servlet
(它继承自HttpServlet
基类),在被创建时,会执行该 servlet 的 init() 方法。在 DispatcherServlet 的初始化过程中,该框架会尝试拿到项目中的 WebContent/WEB-INF 目录文件下的名为 [servlet-name]-servlet.xml 的配置文件,并创建其中所定义的bean。(比如是上面的配置,则该配置文件将会是 webcontent/WEB-INF/springmvcTest-servlet.xml,springmvc 会从该配置文件中加载应用程序上下文)。当然,我们也可以通过 init-param 标签自定义配置文件的位置及名称,不使用默认的。
中央调度器 DispatcherServlet 负责创建 springmvc 容器 对象,读取 spring 的 xml 配置文件,创建文件中的 Controller 对象。并且负责接收用户的请求,分派给各个 Controller 对象。
然后我们需要在 webcontent(webapp)/WEB-INF 目录下创建一个 spring 配置文件,并在该文件中开启组件扫描。比如下面的 springmvcTest-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录--> <context:component-scan base-package="controllerPackage"></context:component-scan> </beans>
然后需要创建控制器,通过控制器来处理请求。比如下面我们创建了一个控制器类,通过注解 @RequestMapping 来将URL映射到处理方法中,即 /test.do 请求将会由 doTest() 方法来处理:
package controllerPackage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class ControllerTest01 { /** *创建方法来处理请求,在springmvc中是通过方法来处理请求的。方法是自定义的,有多种返回值,多种参数 * @RequestMapping:请求映射,作用是把请求地址和方法绑定在一起。属性的value可以是一个string,也可以是一个数组,不同方法间的值不能重复。 * @RequestMapping 可以放在方法上,也可以直接放在类上 */ @RequestMapping(value = "/test.do") public ModelAndView doTest() { //Spring MVC 通过 ModelAndView 对象把模型和视图结合在一起 ModelAndView modelView = new ModelAndView(); //添加数据,框架最后会把数据放到request的作用域当中,类似于 request.setAttribute() modelView.addObject("name","张三"); modelView.addObject("age","22"); //指定视图,指定视图的完整路径。框架会对视图执行forward操作,类似于request.getRequestDispatcher("/show.jsp").forward(); modelView.setViewName("/show.jsp"); return modelView; } }
在项目 src/main/webapp 目录下创建 show.jsp,用来接收上面处理 /test.do 过后添加的数据:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>这里是show.jsp页面</h1> <h2>hello:${name} ---- ${age}</h2> </body> </html>
然后我们就可以通过访问 test.do 来进行测试了。启动 tomcat,添加项目,访问 test.do 请求。比如项目名为 springmvcProject,则访问路径为:http://localhost:8080/springmvcProject/test.do。最后可以看到结果如下:
2.2、配置视图解析器(统一指定视图的前缀和后缀)
上面的示例,我们把 show.jsp 建在了 webapp 目录下,这样的话用户可以直接通过访问 jsp 页面的路径来访问页面,而不是通过接口 .do 的形式来访问,而不通过接口就无法拿到数据,可能会出现下面这种情况:
如果我们不希望用户可以直接访问到 jsp 页面,我们可以把页面文件建在 WEB-INF 目录下,这样用户就无法通过输入页面路径来直接访问页面,因为 WEB-INF 下的资源是无法通过浏览器直接访问的。比如:
这样我们指定视图就可以写成:
modelView.setViewName("/WEB-INF/view/show.jsp");
在指定视图的时候,有可能有大量的重复路径,例如:
mv.setViewName("/WEB-INF/view/show1.jsp"); mv.setViewName("/WEB-INF/view/show2.jsp"); mv.setViewName("/WEB-INF/view/show3.jsp");
我们可以在 spring 的配置文件中配置视图解析器,可以指定视图的前缀(路径)和后缀(扩展名),让框架来找到对应的视图文件。例如,修改 springmvcTest-servlet.xml 文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录--> <context:component-scan base-package="controllerPackage"></context:component-scan> <!-- springmvc框架中的视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 前缀:视图文件的路径--> <property name="prefix" value="/WEB-INF/view/"/> <!-- 后缀:视图文件的扩展名--> <property name="suffix" value=".jsp"/> </bean> </beans>
在配置了视图解析器后就可以直接用逻辑名称(文件名)来指定视图,框架会使用 “视图解析器前缀+逻辑名称+视图解析器后缀” 来组成完整的路径。
示例:
mv.setViewName("show1"); //相当于 mv.setViewName("/WEB-INF/view/show1.jsp"); mv.setViewName("show2"); mv.setViewName("show3");
2.3、解决访问静态资源报404的问题
中央调度器的 url-pattern 可以使用自定义扩展名,如:*.do、*.action、*.mvc等等,如<url-pattern>*.do</url-pattern>;也可以直接使用斜杆:<url-pattern>/</url-pattern>,表示拦截所有请求。当我们直接使用斜杆来拦截所有请求时,你会发现,在使用浏览器访问静态资源(比如html、css、img等)时会报 404。(在某些时候如果我们不希望通过 .do 来访问后端接口,此时可以将 url-pattern 配置成 /,这样就不需要给接口名后面添加 .do 后缀,直接写即可,比如 @RequestMapping(value = "/test01"))
默认情况下,tomcat 会有一个 default 的 servlet(该 servlet 可以在 tomcat 安装目录下的 conf/web.xml 文件中找到),该 servlet 的 url-pattern 是 /,即拦截所有请求。当我们将中央调度器的 url-pattern 设置为 / 时,该中央调度器会替代 tomcat 的默认的 servlet,而因为中央调度器默认没有处理静态资源的能力,所以访问静态资源会报 404,但是访问动态资源比如 xxx.do 能正常访问。
所以当我们将中央调度器的 url-pattern 配置成 / 时,需要解决静态资源访问不到的问题。解决方法有以下两种:
1)使用<mvc:default-servlet-handler />
在 spring 的配置文件(比如springmvcTest-servlet.xml)中配置<mvc:default-servlet-handler />,如下:
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录--> <context:component-scan base-package="controllerPackage"></context:component-scan> <!--注解驱动--> <mvc:annotation-driven/> <mvc:default-servlet-handler /> </beans>
配置<mvc:default-servlet-handler />后,Spring MVC上下文中会定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
在配置了 <mvc:default-servlet-handler /> 后,访问 @RequestMapping 注解的方法可能报 404,这是因为 <mvc:default-servlet-handler /> 和 @RequestMapping 注解有冲突,此时我们在 spring 的配置文件上添加注解驱动即可。
2)使用<mvc:resources />
第一种方法即 <mvc:default-servlet-handler /> 是将静态资源的处理 由Spring MVC 框架交回给了 tomcat 处理。而使用 <mvc:resources /> 则是由Spring MVC框架自己处理静态资源,并可以添加一些有用的附加值功能。
在 spring 的配置文件下添加 <mvc:resources /> 标签,示例:
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录--> <context:component-scan base-package="controllerPackage"></context:component-scan> <!--<mvc:resources /> 标签 和 @RequestMapping 注解有冲突,所以需要添加注解驱动--> <mvc:annotation-driven/> <!-- mapping指的是浏览器访问资源的路径,比如http:localhost:8080/应用名/static/home.html locatioon指的是在服务器内,静态资源存放的位置,比如下面的即表示静态资源是在 webapp/static 目录下 --> <mvc:resources mapping="/static/**" location="/static/" /> <!-- 可以定义多个mvc:resource标签,为不同目录下的静态资源定义不同的mapping和location,但是建议将静态资源都放在同一个文件夹下,比如 webapp/static 下,这样就可以只保留上面一个标签--> <mvc:resources mapping="/css/**" location="/css/" /> </beans>
使用 <mvc:resources /> 允许静态资源放在任何地方,如 WEB-INF目录下、类路径下等。通过 location属性指定静态资源的位置,由于location属性是Resources类型,因此可以使用诸如"classpath:"等的资源前缀指定资源位置。传统Web容器的静态资源只能放在Web容器的根路径下,<mvc:resources />完全打破了这个限制。
其次,<mvc:resources />依据当前著名的Page Speed、YSlow等浏览器优化原则对静态资源提供优化。你可以通过cacheSeconds属性指定静态资源在浏览器端的缓存时间,一般可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的Expires 和 Cache-Control值。在接收到静态资源的获取请求时,会检查请求头的Last-Modified值,如果静态资源没有发生变化,则直接返回303相应状态码,提示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。
3、@RequestMapping注解
@RequestMapping 将请求地址和方法绑定在一起。属性的value可以是一个string,也可以是一个数组,不同方法间的值不能重复。
3.1、@RequestMapping作用在类上
@RequestMapping 既可以作用在方法上,也可以作用在类上。当@RequestMapping 标记在Controller 类上的时候,类里面使用@RequestMapping 标记的方法的请求地址都是相对于类上的@RequestMapping 而言的。简单来说,就是给类上的方法统一加了一个前缀。
示例:
package controllerPackage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/user") public class ControllerTest02 { @RequestMapping(value = "/test1.do") public ModelAndView doTest() { ... } @RequestMapping(value = "/test2.do") public ModelAndView doTest2() { ... } }
此时访问 test1.do 或者 test2.do 实际上应该访问的路径为:localhost:8080/项目名/user/test1.do。注意,需要在前面加上 /user。
3.2、method 属性
@RequestMapping 的 method 属性可以指定方法只能被指定的请求方式访问,如果不指定请求方式时,则表示不限制请求方式,即任何方式都行。该属性的值是 RequestMethod 类枚举值,比如:RequestMethod.GET、RequestMethod.POST 等等。
示例:
package controllerPackage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/user") public class ControllerTest02 { @RequestMapping(value = "/test1.do", method = RequestMethod.GET) public ModelAndView doTest() { ... } @RequestMapping(value = "/test2.do", method = {RequestMethod.GET, RequestMethod.DELETE}) public ModelAndView doTest2() { ... } }
指定了请求方式后,如果使用非指定的请求方式来访问接口的话,浏览器会报 405,提示该请求方式不被支持。
4、处理器方法接收前端请求的参数
4.1、通过HttpServletRequest对象获取参数
可以给 @RequestMapping 注解的方法添加 HttpServletRequest 对象的属性,通过该对象可以获取到前端请求的参数:
@Controller public class ControllerTest03 { @RequestMapping(value = "/test01.do") public ModelAndView doTest(HttpServletRequest request, HttpServletResponse response, HttpSession httpSessiont) { System.out.println(request.getParameter("name")); // 获取到前端发送的name属性值 //... } }
我们可以通过访问类似 http://localhost:8080/springmvcProject/test01.do?name=zhangsan 的地址给后台接口发送数据,后台接口即可接收到 name 属性的值。
4.2、通过同名形参直接获取参数
可以给 @RequestMapping 注解的方法添加跟前端发送的参数同名的形参,即可通过形参来直接获取前端发送的同名参数:
@Controller public class ControllerTest03 { @RequestMapping(value = "/test01.do") public ModelAndView doTest(String name, int age) { System.out.println(name + "----" + age); //直接获取到前端发送的name和age参数值 //... } }
我们可以通过访问类似 http://localhost:8080/springmvcProject/test01.do?name=zhangsan&age=22 的地址给后台接口发送数据,后台接口即可接收到对应的参数值。
这种通过同名形参直接获取前端同名参数的方法,实际上是 springmvc 框架通过 HttpServletRequest 对象的 getParameter() 方法来将前端参数赋值给对应的形参的。并且框架同时还提供类型转换的功能,例如上面,会先将 age 的值转换为 int 类型,然后再赋值给形参 age。
4.3、形参和实参不同名(@RequestParam())
通过与参数同名的形参能够直接获取前端参数,但如果不同名,我们也可以通过 @RequestParam() 注解来指定参数的别名来获取参数。
@Controller public class ControllerTest02 { @RequestMapping(value = "/test3.do") public ModelAndView doTest2(@RequestParam(name = "othername") String name) { System.out.println(name); //... } }
如上,前端请求的参数可以是 othername。通过 @RequestParam(name = "othername") 和 @RequestParam(value = "othername") 或者不加属性值即 @RequestParam("othername") 设置都一样。
@RequestParam() 还可以设置属性值 required 为 false,即 @RequestParam(required = false),此时前端可以不用传该参数 java 程序也不会报错。
4.4、通过对象来获取参数
如果参数过多,一个个写参数可能比较麻烦,此时我们可以定义一个 Java 实体类,该类的属性名跟前端参数名对应,通过类的对象作为形参来获取参数。
示例:
定义一个实体类:
public class Person { String name; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
将该类的对象作为形参,springmvc 框架会自动创建形参中类的对象,并且调用该对象的 setter 方法给属性赋值:
@Controller public class ControllerTest02 { @RequestMapping(value = "/test4.do") public ModelAndView doTest4(Person person) { System.out.println(person.getName()); //... } }
4.5、配置字符编码过滤器(解决中文乱码问题)
在获取前端中文参数时,可能会出现乱码问题。如果使用 HttpServletResponse 对象来返回数据,可以使用 resp.setContentType("text/html;charset=utf-8"); 来解决中文乱码问题,但需要在每个请求里都设置,比较麻烦。
我们可以在 WEB-INF/web.xml 的配置文件里设置字符编码过滤器来统一解决编码问题。
示例:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> ... <!-- characterEncodingFilter字符编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param> <!--要使用的字符集,一般使用UTF-8--> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param>
<init-param> <!--是否强制设置request的编码为encoding,默认为false--> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param>
<init-param> <!--是否强制设置response的编码为encoding--> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter>
<filter-mapping> <filter-name>characterEncodingFilter</filter-name> <!--这里不能留空或者直接写'/',否则不起作用--> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
上面设置编码过后,返回给前端的数据编码将会是 utf-8,解决中文乱码问题。
但是设置了上面字符编码过滤器后,如果直接访问服务器中的 html 页面,你会发现 html 页面里的中文变乱码了。此时将 forceRequestEncoding 和 forceResponseEncoding 去掉即可。
5、响应请求返回数据
5.1、返回ModelAndView类型(响应一个jsp页面)
若处理器方法处理完,需要跳转其它 jsp 资源,并且需要传递数据,则可以返回 ModelAndView 。此时JSP页面可以使用 JSTL 标签来匹配解析后端返回的数据。
示例:
package controllerPackage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class ControllerTest01 { @RequestMapping(value = "/test.do") public ModelAndView doTest() { ModelAndView modelView = new ModelAndView(); modelView.addObject("name","张三"); modelView.addObject("age","22"); modelView.setViewName("/show.jsp"); return modelView; } }
5.2、返回String(响应一个jsp页面)
处理器方法可以返回字符串,该字符串可以是逻辑名称(文件名称),也可以是完整的视图路径。最终该处理方法会跳转到该字符串所指定的资源。
示例:
package controllerPackage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class ControllerTest02 { @RequestMapping(value = "/returnStringTest.do") public String doTest() { //框架实际上是对视图执行forward操作 return "/WEB-INF/view/show1.jsp"; //或者如果我们配置了视图解析器,则应该直接写逻辑名称,比如:show1 } }
5.3、返回值为void(响应ajax请求)
在返回值为 void 时,我们可以用来响应 ajax 请求。
package controllerPackage; import com.alibaba.fastjson.JSON; ... @Controller public class ControllerTest02 { //直接给 ajax 请求返回字符串 @RequestMapping("/ajaxResponse.do") public void ajaxResponse(HttpServletRequest request, HttpServletResponse response) throws Exception{ PrintWriter out = response.getWriter(); out.write("hello"); } //给 ajax 请求返回一个json格式字符串 @RequestMapping("/ajaxJson.do") public void ajaxJson(HttpServletRequest request, HttpServletResponse response) throws Exception{ PrintWriter out = response.getWriter(); //通过 com.alibaba.fastjson.JSON 包来将对象转换成json格式字符串 Student student = new Student(1, "wen"); String stuStr = JSON.toJSONString(student); out.write(stuStr); } }
5.4、返回Object类型(包括Integer、String、Map、List、自定义对象等等,响应ajax请求)
处理器也可以返回 Object 对象,这个对象可以是 Integer、String、Map、List、自定义对象等等。但返回的对象不是作为逻辑视图出现的,而是直接给前端请求返回数据的。
首先需要先引入处理 json 的工具库,springmvc 中默认使用的是 jackson:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springmvc_maven_test01</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.4</version> </dependency> </dependencies> ... </project>
然后给 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" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录--> <context:component-scan base-package="controllerPackage"></context:component-scan> <!--注解驱动--> <mvc:annotation-driven/> </beans>
添加注解驱动时需要注意是添加 mvc 的,不要添加成其他同名的。
注解驱动的作用是将 java 对象转换为 json、xml、text、二进制等格式数据,框架会自动识别 java 对象能转换为什么数据格式,然后进行返回。<mvc:annotation-driven/> 在加入到配置文件后,会自动创建 HttpMessageConverter 接口的实现类对象,这个接口的实现类完成了 java 对象到其他数据格式的转换功能。
在使用时需要在方法前添加 @ResponseBody 注解,springmvc 通过该注解将会把处理器方法已经转换后生成的数据通过 HttpServletResponse 返回给前端的 ajax 请求。
代码示例:
package controllerPackage; import entity.Student; ... @Controller public class ControllerTest03 { @RequestMapping("/objJson.do") @ResponseBody //把处理器方法返回的对象转换为json后,通过HttpServletResponse返回给前端 public Student objJson(HttpServletRequest request, HttpServletResponse response){ Student student = new Student(1, "wen"); return student; } }
返回一个类对象,前端接收到的字符串转换为 json 格式后将会是一个对象 obj。
如果返回的是一个 Map,则前端接收到的字符串经转换为 json 后也是一个对象 obj;如果返回的是一个 list,则前端接收到的字符串经转换为 json 后将会是一个数组,并且顺序跟 list 添加元素的顺序一致;如果是一个字符串 String,前端接收到的也是字符串。
代码示例:
package controllerPackage; import entity.Student; ... @Controller public class ControllerTest03 { //返回一个list @RequestMapping("/objList.do") @ResponseBody //把处理器方法返回的对象转换为json后,通过HttpServletResponse返回给前端 public List<Student> objList(HttpServletRequest request, HttpServletResponse response){ List<Student> list = new ArrayList<>(); Student student1 = new Student(1, "wen"); Student student2 = new Student(2, "wen2"); list.add(student1); list.add(student2); return list; } //返回一个map @RequestMapping("/objMap.do") @ResponseBody //把处理器方法返回的对象转换为json后,通过HttpServletResponse返回给前端 public Map<String, String> objMap(HttpServletRequest request, HttpServletResponse response){ Map<String, String> testMap = new HashMap<>(); testMap.put("hello", "你好"); testMap.put("world", "世界"); return testMap; } //直接返回字符串 @RequestMapping("/objString.do") @ResponseBody //把处理器方法返回的对象转换为json后,通过HttpServletResponse返回给前端 public String objString(HttpServletRequest request, HttpServletResponse response){ return "你好啊hello~"; } }
5.4.1、解决返回 String 时中文乱码问题
使用上述方法来返回 String 时,如果字符串中有中文,则前端接收到的数据可能会有中文乱码问题(注意,此时返回字符串是直接返回数据,而不是返回视图 jsp 资源)。出现中文乱码问题这是默认返回的响应头 content-type 中指定的编码格式是 ISO-8859-1,如下:
解决中文乱码问题我们可以通过 @RequestMapping 注解的 produces 属性来指定响应头的 content-type,如下:
@Controller public class ControllerTest03 { @RequestMapping(value = "/objString.do", produces = "text/html;charset=utf-8") //produces相当于给response指定了Content-Type响应头 @ResponseBody public String objString(HttpServletRequest request, HttpServletResponse response){ return "你好啊hello~"; } }
6、Springmvc接收各种参数(字符串、json)
6.1、接收普通参数
前端发送请求方法:
$.ajax({
url: 'getStr.do',
type: 'post', //此时get请求和post请求发送参数的写法一样
data: {
name: 'wen',
age: 11
},
success: function (data) {
console.log('后端返回数据', data); //这里将接收到后端返回的字符串
}
})
springmvc 代码,请求参数名跟Controller的方法参数一致:
@Controller public class ControllerTest01 { @RequestMapping(value = "/getStr.do", produces = "text/html;charset=utf-8") @ResponseBody public String doTest(String name, int age) { System.out.println(name + "----" + age); //直接获取到前端发送的name和age参数值 return "你好啊hello~"; } }
此时可以看到前端发送的 url、响应头 content-type、发送头 content-type 和参数如下:
6.2、将对象作为参数
当请求参数过多时,后端可以以对象作为参数接收前端发送的数据,而此时前端发送数据跟发送普通参数一样。
前端代码如下,此时发送数据跟发送普通参数一致:
$.ajax({
url: 'getObj.do',
type: 'post',
data: {
name: 'wen',
age: 11
},
success: function (data) {
console.log('后端返回数据', data)
}
})
实体类如下,实体类可以写构造函数也可以不写。
package domain; public class Student { String name; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Student(String name, int age) { System.out.println(11112222); this.name = name; this.age = age; } }
controller 类:
@Controller public class ControllerTest01 { @RequestMapping(value = "/getObj.do") //此时不能写produces = "text/html;charset=utf-8" 因为此时返回的是对象,默认将是json格式,如果写成text/html将导致接口有问题 @ResponseBody public Student doTest02(Student student) { System.out.println(student.getName() + "----" + student.getAge()); return student; } }
此时可以看到前端发送的 url、响应头 content-type、发送头 content-type 和参数如下:
6.3、接收复杂对象
如果接收的是复杂对象,比如对象当中还嵌套着对象。此时前端发送请求时需指定 content-type 为 'application/json;charset=utf-8',并且后端需要用 @RequestBody 进行接收。
前端发送参数格式如下:
$.ajax({ url: 'getComplexObj.do', contentType: 'application/json;charset=utf-8', type: 'post', data: JSON.stringify({ complexName: '复杂对象名', //这里也可以是一个对象,这样的话此时后端的实体类中该字段也应该是一个实体类 student: { name: '学生名', age: 111 } }), success: function (data) { console.log('后端返回数据', data) } })
复杂对象类:
package domain; public class ComplexDomain { private Student student; //注意,此时student类中不能有自定义构造函数 private String complexName; public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public String getComplexName() { return complexName; } public void setComplexName(String complexName) { this.complexName = complexName; } }
controller类:
@Controller public class ControllerTest01 { @RequestMapping(value = "/getComplexObj.do") @ResponseBody public ComplexDomain doTest03(@RequestBody ComplexDomain complexDomain) { System.out.println(complexDomain.getComplexName() + "----" + complexDomain.getStudent().getName() + "----" + complexDomain.getStudent().getAge()); return complexDomain; } }
此时可以看到前端发送的 url、响应头 content-type、发送头 content-type 和参数如下: