我们需要在 web.xml
中做如下配置:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet
:这是 Spring MVC 的核心类,叫前端控制器。contextConfigLocation
:这是适配器的初始化参数,我们需要配置 Spring 配置文件的全路径,跟他说配置文件在哪里,这样他就会去加载。默认情况下,你在方法的参数列表中写什么,前端表单中 input
标签的 name
属性值就要是什么,比如:
@RequestMapping(path = "/hello")
public String sayHello(String username) {
System.out.println("Hello Spring MVC!" + username);
return "success";
}
那么前端一定要这么写才能接收到参数:
<form action="hello" method="post">
<label>
姓名:
<input name="username" type="text">
</label> <br>
<input type="submit" value="提交">
</form>
或者直接拼接在请求路径上面。
@RequestMapping
:指定请求路径,匹配请求路径即可访问到该注解所在的方法,从而去处理业务逻辑。我们也可以直接写成一个对象:
@RequestMapping("/login")
public String login(Account account) {
System.out.println(account);
return "success";
}
这样在前端提供对应的表单即可:
<form action="login" method="post">
<label>
姓名:
<input name="username" type="text">
</label> <br>
<label>
密码:
<input name="password" type="text">
</label> <br>
<label>
年龄:
<input name="age" type="text">
</label> <br>
<label> 日期:
<input name="date" type="text">
</label>
<input type="submit" value="提交">
</form>
这一点比 Servlet
方便了很多很多。
不过也有一个问题,就是如果我们前端提供的数据和后端方法参数中的不匹配该怎么办呢?
只需要在参数上面接一个注解即可,这样前端传 name
即可:
public String sayHello(@RequestParam("name") String username)
再送你一个,如果不想一个一个的拿,想一下把表单中的数据全部拿出来该怎么做?
public String sayHello(@RequestBody String body)
这样就行了,这里的 body 就是表单中传入参数的键值对。
如果我们的路径中有 /login/{id}
这种请求 url
,那么我们就可以使用另一个注解:
@RequestMapping(path = "/hello/{id}")
public String sayHello(@PathVariable("id") String id)
再来一个获取请求头的注解:
public String sayHello(@RequestHeader("Accept") String accept)
@CookieValue 放在属性上:是获取 Cookie 的值;
@ModelAttribute 放在方法上:优先执行该方法。
放在属性值上:代表从上一个 `ModelAttribute` 中取出来具体的值。
我们可以使用 Model
类来将参数直接封装到请求作用域中:
@RequestMapping("model")
public String testModel(Model model) {
model.addAttribute("msg", "你好");
return "success";
}
同时我们可以在类上添加一个 @SessionAttributes
注解,这样就会把只存入 session
域中。
@SessionAttributes(value = {"msg"})
public class HelloController
如果我们想从 session
域中取值:
@RequestMapping("getAttr")
public String getAttribute(ModelMap modelMap) {
modelMap.get("msg");
return "success";
}
如果想从 session
中清除数据:
@RequestMapping("clear")
public String getAttribute(SessionStatus status) {
status.setComplete();
return "success";
}
上面的代码中我们返回了一个success
,这是个字符串?
确实是个字符串,但是如果我们配置了视图解析器之后就不一样了,他会去找这个名称的文件,比如我在 pages
文件夹下放了一个 success
的 jsp
文件,我想让页面直接找到它应该怎么做呢?
很简单,只需要在 Spring 的配置文件中配置如下信息:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
internalResourceViewResolver
:就是视图解析器,他是 ViewResolver
的子类。 prefix
:指定文件的前缀;suffix
:指定文件的后缀。在以前我们是自己写一个 Request 实现类去配合 Filter 实现全局编码控制,在 Spring MVC 中,这一步又被大大简化了。
我们只需要在 部署描述文件 中做如下配置即可:
<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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
CharacterEncodingFilter
:是 Spring MVC 为我们提供的过滤器,我们只需要设置其初始化参数即可。我们与数据库交换一般使用的日期类型为2020-06-06
这种格式的,但是网页上面提交的却是 2020/06/06
这个类型的,所以我们会收到一个 400 Bad Request
的错误提示,那么怎么解决这个问题呢?
我们可以写一个实现类去实现 Spring 为我们提供的一个接口 Converter
,然后泛型写我们想改变的类型,这里我们指定为 Date
类型:
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
Date date = null;
try {
date = new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
然后在 Spring 的配置文件中做如下配置:
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="top.wsuo.config.DateConverter"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
记得配置好 Bean
之后将转化类交给 Spring MVC 的 conversion-service
属性。
这样我们再去提交就没有问题了。
我们怎么将后台获取到的数据传给前端呢?
我们传统的思路是通过 request
域对象传值,那么 Spring MVC 可以吗? 那肯定可以。
首先是后端代码:
@RequestMapping("testString")
public String testString(Model model) {
System.out.println("testString 方法执行了!");
model.addAttribute("user", new User("妹妹", "1234", 18));
return "success";
}
然后是前端代码:
<body>
<h1>成功</h1>
${user.username} <br>
${user.password} <br>
${user.age}
</body>
执行结果:
这里使用 Model
就相当于将对象存入了域中,所以可以直接获取。
那么为什么我们返回 String 就可以直接跳转到页面了呢?
我们再来看一个方法:
@RequestMapping("testMAV")
public ModelAndView testModelAndView() {
System.out.println("testModelAndView 方法执行了!");
ModelAndView mv = new ModelAndView();
mv.addObject("user", new User("妹妹", "1234", 18));
mv.setViewName("success");
return mv;
}
这个方法的作用和上一个一模一样,这就说明,返回 String 类型的参数其实就是返回的 ModelAndView 类型的参数,只是多了一层封装,用的都是 视图解析器 。
如果我们的方法返回值不给他会怎么办呢?
@RequestMapping("testVoid")
public void testString() {
System.out.println("testVoid 方法执行了!");
}
结果就是他会找一个名字叫做 testVoid
的文件,如果我们没有提供就会 404。
我们也可以自己实现转发:
@RequestMapping("testString")
public String testString() {
System.out.println("testString 方法执行了!");
return "forward:/WEB-INF/pages/success.jsp";
}
有时候我们使用 js
或者 css
样式会失效,这是因为没有配置静态资源访问过滤,我们之前配置前端过滤器的时候是拦截所有的资源,这其中就包括静态资源,所以我们要在 Spring
的配置文件中配置一下:
<!-- 告诉前端控制器,那些静态资源不拦截 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
然后我们就可以使用 Ajax
异步发送请求了,代码如下:
<script>
// 页面加载,绑定单击事件
$(function () {
$("#btn").click(function () {
// alert("hello btn")
$.ajax({
// 编写 json 格式
url: "user/ajax",
contentType: "application/json;charset=utf-8",
data: '{"username":"王硕", "password":"123", "age":"18"}',
dataType: "json",
type: 'POST',
success: function (data) {
alert(data);
}
});
});
});
</script>
我们在最原始的 ajax
方法中配置了一些属性,然后用 data
接收,我们后台写一个接口:
@RequestMapping("ajax")
public void testAjax(@RequestBody String body) {
System.out.println("Ajax 执行了!");
System.out.println(body);
}
随后点击按钮,控制台输出如下:
Ajax 执行了!
{"username":"王硕", "password":"123", "age":"18"}
这说明后台已经拿到前端传的 JSON
格式的数据了,那么接下来,我们就将这些数据封装成一个对象再传回去,这就需要使用其他的 jar 包了。
这里我们使用 Jackson
:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
我们来测试一下,前端这么写:
后端这么写:
@RequestMapping("ajax")
@ResponseBody
public User testAjax(@RequestBody User user) {
System.out.println("Ajax 执行了!");
System.out.println(user);
user.setUsername("王硕二号");
user.setPassword("12345");
user.setAge(19);
return user;
}
执行结果:
拦截器是 SpringMVC 所特有的功能,它主要用于拦截控制器方法,也就是说你访问静态资源不关他的事,你访问接口才会被拦截。
注意:除了 过滤器 和 前端控制器 是在 web.xml
中配置以外,其余配置均在 spring 的配置文件中配置,如视图解析器、静态资源过滤器、拦截器等。
这里我们配置一下拦截器,首先需要创建一个类用于实现 Spring 为我们提供的接口:
public class HandlerInterceptorImpl implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle 方法执行了!");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle 方法执行了!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion 方法执行了!");
}
}
preHandle
:如果返回 true
表示放行,继续执行下一个拦截器。<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<bean class="top.wsuo.interceptor.HandlerInterceptorImpl" id="interceptor"/>
</mvc:interceptor>
</mvc:interceptors>
结果如下: