SSM-SpringMVC(一)-基本使用、请求参数获取、乱码、URL路径处理
简介
- SpringMVC的正式名称是Spring Web MVC,是属于Spring框架的一部分,是基于Server API(指Tomcat等服务器软件的API)的Web框架
- 即是对Tomcat的API(servlet/filter等)的进一步封装
- SpringMVC的核心功能:拦截和处理客户端的请求
- 官方参考文档
- 特点:mybatis、spring主要是用来操作dao、service层,不涉及到servlet,那么也就意味着Java项目、JavaEE项目都通用。那么SpringMVC是专门处理客户端发送过来以及响应给客户端的请求的,因此只能用于JavaEE项目
基本使用
- 创建一个javaEE项目(略)
-
添加依赖
<!-- 打包方式--> <packaging>war</packaging> <!-- JavaEE依赖--> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <!-- SpringMVC依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- 日志--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
-
web.xml配置DispatcherServlet
<!-- 1. 配置SpringMVC自带的DispatcherServlet,用来拦截请求 这里是web项目的入口 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--1.2 加载Spring的IoC容器 --> <!-- 创建servelt时,加载Spring的配置文件位置 --> <!-- 如果不写xml的具体位置classpath,则默认会去加载/WEB-INF/${servlet-name}-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param> <!--1.3 设置servelt的创建时刻 --> <!-- 项目一旦部署到服务器,就会创建Servlet 因为DispatcherServlet比较重,需要加载IoC容器,如果不设置默认是在客户端第一次发送请求的时候创建这个servlet 因此我们想要在部署到服务器的时候就创建这个servlet --> <load-on-startup>0</load-on-startup> </servlet> <!--2 设置拦截器 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 拦截除了JSP以外所有请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
- 程序运行会加载web.xml,根据内部配置文件会创建DispatcherServlet实例
- DispatcherServlet实例传参会传参applicationContext.xml创建IOC容器WebApplicationContext。之前在main函数中主动创建IOC容器,现在通过DispatcherServlet创建。
- 与之前的JavaEE项目相比,这里使用的是SpringMVC自带的servlet
- 设置初始化servlet时要加载的IoC容器路径
- 设置servelt的创建时刻
- 设置要拦截的客户端请求
-
创建IoC容器入口
applicationContext.xml
//设置要扫描注解的包 <context:component-scan base-package="com.zh"/>
-
新建Controller
//代表是控制器 @Controller public class UserController { //拦截哪个路径 @RequestMapping("/addUser") //返回值作为响应体直接返回给客户端 @ResponseBody public String add() { return "Add Success!"; } @RequestMapping("/getUser") @ResponseBody public String get() { return "Get Success!"; } @RequestMapping("/removeUser") @ResponseBody public String remove() { return "Remove Success!"; } }
- 验证:Idea本地运行,当客户端发送
context_path/addUser、getUser、removeUser
(无论是get还是post请求),都会到响应的方法,然后将数据响应给客户端
核心注解
-
@RequestMapping
可以用在类、方法上@Controller //用来类上,可以将/user/*下的所有请求都放在这个类中进行处理 @RequestMapping("/user") public class UserController { // /user/add @RequestMapping("/add") @ResponseBody public String add() { return "UserController - Add Success!"; } // /user/get @RequestMapping("/get") @ResponseBody public String get() { return "UserController - Get Success!"; } // /user/remove @RequestMapping("/remove") @ResponseBody public String remove() { return "UserController - Remove Success!"; } }
-
@PostMapping、@GetMapping
可以用在方法上,底层是基于@RequestMapping
@Controller @RequestMapping("/user") public class UserControllerMethod { // /user/add //@RequestMapping用在方法上,只拦截post请求 //@RequestMapping(value = "/add", method = RequestMethod.POST) //等价于上面 @PostMapping("/add") @ResponseBody public String add() { return "UserController - Add Success!"; } // /user/get //只拦截get请求 //@RequestMapping(value = "/get", method = RequestMethod.GET) //等价于上面 //可以拦截多个请求路径 @GetMapping({"/get", "/list"}) @ResponseBody public String get() { return "UserController - Get Success!"; } }
请求参数
获取请求参数
- 方法一:默认情况下,SpringMVC会主动传递一些参数给请求方法
- WebRequest、HttpServletRequest、HttpServletResponse、HttpSession等
- 参考https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#mvc-ann-arguments
-
可以通过这些参数来获取传参
@Controller @RequestMapping("/skill") public class SkillController { //通过参数获取 @RequestMapping("/add") @ResponseBody public String add(HttpServletRequest request, HttpServletResponse response, HttpSession session) { System.out.println(request.getParameter("name")); System.out.println(response); System.out.println(session); return "SkillController - Add Success!"; } }
- 方法二:默认情况下,请求参数会传递给同名的方法参数
- 通过
@RequestParam
指定方法参数对应的请求参数名 - required属性为false,请求参数是可选的,客户端可以不传;属性为true,请求参数是必须的,客户端必须传,否则出现400错误
- 如果用
@RequestParam
修饰,不写required,则默认为false;如果没有用@RequestParam
修饰,则默认必传
@RequestMapping("/add") @ResponseBody //使用Integer的好处,如果传递的是null则没有问题,但是如果是int将会报错 //如果不加@RequestParam,则默认是required = true,如果有@RequestParam,则默认是false //请求方传递过来的参数名称为my_level,接收为level public String add(@RequestParam(value = "my_level", required = false) Integer level, @RequestParam String name, @RequestParam String intro) { System.out.println(name); System.out.println(intro); System.out.println(level); return "SkillController - Add Success!"; }
- 通过
- 方法三:可以通过Model对象去接收请求参数
- Model对象的属性名需要和请求参数名保持一致
@RequestMapping("/add") @ResponseBody //可以传递模型对象,直接接收参数转化 public String add(Skill skill, String name, Integer level, Skill skill2){ //都能接收到值 System.out.println(skill); System.out.println(skill2); System.out.println(name); System.out.println(level); return "SkillController - Add Success!"; }
请求路径变量
-
如果请求路径中的内容是变化的,可以定义成(变量名),然后通过使用
@PathVariable
去获取值//这样写之后如果客户端请求为:context_path/get/id=10、context_path/get/10都可以接收到 @RequestMapping("/get/{id}") @ResponseBody public String get(@PathVariable("id") Integer id) { return "SkillController - Get Success! - " + id; }
利用反射获取参数名
- 正常情况下方法只编译参数类型,不会编译参数名称,但是也可以通过特殊方式将参数名称也编译到可执行文件中,这样其他模块调用该方法时就可以直接通过参数名称传递
- 从JDK8开始,可以通过
java.lang.reflect.Parameter
类获取参数名- 前提:在编译
*.java
的时候保留参数名信息到*.class
中, 比如javac -parameters *.java
- 可以通过
javap -v *.class
查看class文件的参数名信息 - 这样通过反射就可以获取方法的参数名称
- 前提:在编译
- SpringMVC内部通过Prior1itizedParameterNameDiscoverer类获取参数名
public class TestParam {
public void run(String name, int age) {}
public static void main(String[] args) throws Exception{
//获取这个类的run方法,第一个参数为String类型,第二个类型是int类型
Method method = TestParam.class.getMethod("run", String.class, int.class);
for (Parameter parameter : method.getParameters()) {
System.out.println(parameter.getName());
}
}
}
乱码处理
GET请求参数乱码
- 从Tomcat8开始已经没有这个问题:get请求参数如果为中文,则不会乱码
- Tomcat8以前的解决方案是
- 在TOMCAT_HOME/conf/server.xml中给Connector标签增加属性
URIEncoding="UTF-8"
- 在TOMCAT_HOME/conf/server.xml中给Connector标签增加属性
POST请求参数乱码
-
方法1:可以通过自定义Filter拦截所有请求,调用
request.setCharacterEncoding("UTF-8")
//方法1:使用这个注解,会被扫描到IoC容器 @WebFilter("/*") public class CharacterEncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } } //2. 不使用@WebFilter("/*"),在web.xml中引入 <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>com.zh.CharacterEncodingFilter</filter-class> </filter> <!--设置拦截哪些请求 --> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
方法二:SpringMVC已经内置了这样的Filter,直接在web.xml配置即可使用
<!-- 解决POST请求参数乱码问题 --> <filter> <filter-name>CharacterEncodingFilter</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> </filter> <!--设置拦截哪些请求 --> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
响应数据乱码
-
方法1: 设置
@RequestMapping
的produces属性//produces:响应数据为html格式,编码为UTF8 @RequestMapping(value = "/get", produces = "text/html; charset=UTF-8") @ResponseBody public String get() { return "<h1>This is xxx</h1>"; }
-
方法2:在applicationContext.xml中全局添加
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="defaultCharset" value="UTF-8"/> </bean> </mvc:message-converters> </mvc:annotation-driven>
-
注意: 以上问题只能针对响应数据是字符串类型的数据
URL请求路径的拦截处理
Servlet的url匹配:(*.do、/、/*
的区别)
*.do
- 拦截以.do结尾的请求url,比如:
http://localhost/text.do
- 不会拦截动态资源(比如
*.jsp
)、静态资源(比如*.html、*.js
)
- 拦截以.do结尾的请求url,比如:
/
- 会拦截静态资源(比如
*.html、*.js
),不会拦截动态资源(比如*.jsp
)
- 会拦截静态资源(比如
/*
- 会拦截动态资源(比如
*.jsp
)、静态资源(比如*.html、*.js
) - 一般用在Filter中
- 会拦截动态资源(比如
- 示例:
-
在web.xml中,设置servlet后,要配置servlet拦截哪些请求通过Controller进行处理
<!--2 设置拦截器 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 拦截除了JSP以外所有请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
-
问题: 如果客户端的请求地址为:http://localhost/test/login.html,那么就必须有对应的Controller来处理这个请求,但是很显然这个是不对的,html也应该放行,不拦截。
-
Tomcat的2个默认的Servelt(注意理解)
- 引入:
- 如果将web.xml中的servlet配置全部注释掉,然后运行项目,理论上此时应该无任何servlet拦截,客户端发送任何请求都无效
- 但是经过测试发现,如果客户端访问的是项目中的html文件,或者jsp文件,仍然能够正常响应,原因Tomcat有默认的Servlet,进行拦截处理请求
- 可以在TOMCAT_HOME/conf/web.xml中找到
org.apache.catalina.servlets.DefaultServlet
,url-pattern是/
,可以处理静态资源org.apache.jasper.servlet.JspServlet
, url-pattern是*.jsp
- 即Tomcat自己默认设置了2个Servlet,一个用来处理静态资源,一个用来处理.jsp动态资源;一旦在自己定义项目的web.xml中配置了Servlet,而且设置的拦截为
/
,那么Tomcat默认的DefaultServlet将被覆盖失效(同理,如果设置的拦截为.jsp那么Tomcat的JspServlet也将被覆盖失效) - 那么就面临一个问题,原来的DefaultServlet被覆盖,自定义的Servlet静态资源被拦截了,而且没有对应的Controller来处理,将如何解决?
- 即配置了DispatcherServlet,而且拦截配置为
/
,那么通过/
拦截的资源,要通过DispatcherServlet来处理。
- 即配置了DispatcherServlet,而且拦截配置为
静态资源被拦截的解决方案
- 从上面可知,如果要自己设置servelt处理客户端请求,需要解决静态资源、
*.jsp
都不要被拦截的问题
方案一:
-
如果SpringMVC的
DispatcherServlet
的url-pattern
设置为/
,会导致静态资源被拦截,解决方案:将静态资源交回给Tomcat的DefaultServlet去处理<mvc:default-servlet-handler/>
<mvc:default-servlet-handler/>
的原理- 会通过DefaultServletHttpRequestHandler对象将静态资源转发给Tomcat的DefaultServlet
- 使用了
<mvc:default-servlet-handler>
后,会导致controller无法处理请求- 加上
<mvc:annotation-driven>
即可保证controller正常使用
- 加上
-
综上,在applicationContext.xml文件中添加如下:
<!-- 保证@Controller能够正常使用 --> <mvc:annotation-driven/> <!-- DispatcherServlet不想处理的请求都交回给默认Servlet去处理 --> <mvc:default-servlet-handler/>
方案二:
- 由SpringMVC框架内部来处理静态资源(内部通过ResourceHttpRequestHandler对象)
- 同样需要加上
<mvc:annotation-driven/>
,确保controller正常使用 -
在applicationContext.xml文件中添加如下:
<!-- 保证@Controller能够正常使用 --> <mvc:annotation-driven/> <!-- 由SpringMVC框架内部来处理静态资源 --> <!-- **代表所有子路径 --> <!-- mapping是客户端的请求路径 --> <!-- location是服务器静态资源的位置 --> <!--项目中asset文件夹下通常存放静态资源:html、png、js、css等--> <mvc:resources mapping="/asset/**" location="/asset/"/>
总结
- 上面的两种方案,第一种是交给Tomcat的默认servlet去处理静态资源,第二种是交给Spring自带的类去处理
- 那么上面交给其他去处理的节点是:DispatcherServlet不处理的情况,才会去解决
- 即:当客户端访问一个静态资源,首先会通过DispatcherServlet的Controller去处理,当发现所有的Controller都没有对应的方法进行处理时,才会去进行上面2种方法进行处理。