MVC是一种软件架构设计模式,用于组织应用程序的结构,将业务逻辑、数据和界面显示分离:
MVC的优点:
在Web开发中,MVC的一般流程:
SpringMVC是Spring框架的一个模块,是一个基于MVC设计模式的优秀的Web框架。
特点:
核心组件:
SpringMVC的工作流程是围绕DispatcherServlet展开的:
基于XML配置文件搭建SpringMVC环境的步骤:
创建Maven Web项目
添加依赖:在pom.xml中添加SpringMVC相关依赖
org.springframework
spring-webmvc
5.3.23
javax.servlet
javax.servlet-api
4.0.1
provided
javax.servlet
jstl
1.2
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc-servlet.xml
1
springmvc
/
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello SpringMVC!");
return "hello"; // 返回视图名
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Hello SpringMVC
${message}
基于Java配置(无XML)搭建SpringMVC环境的步骤:
添加依赖:同上
创建Web应用初始化类(代替web.xml)
package com.example.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class>[] getRootConfigClasses() {
return new Class>[] { RootConfig.class };
}
@Override
protected Class>[] getServletConfigClasses() {
return new Class>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected javax.servlet.Filter[] getServletFilters() {
// 配置编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[] { characterEncodingFilter };
}
}
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
// 配置静态资源处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@Configuration
@ComponentScan(basePackages = {"com.example"},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
})
public class RootConfig {
// 配置数据源、事务管理等
}
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello SpringMVC (Annotation-based)!");
return "hello";
}
}
这种方式的优点是不需要XML配置文件,完全基于Java注解配置,更加简洁明了。
在SpringMVC中,视图解析器负责根据控制器返回的视图名找到对应的视图:
XML配置方式:
Java配置方式:
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(1);
return resolver;
}
SpringMVC支持两种页面跳转方式:
// 方式1:直接返回视图名
@RequestMapping("/test1")
public String test1() {
return "test"; // 转发到/WEB-INF/views/test.jsp
}
// 方式2:使用forward:前缀(不会被视图解析器解析)
@RequestMapping("/test2")
public String test2() {
return "forward:/WEB-INF/views/test.jsp"; // 直接转发到指定路径
}
// 方式3:转发到另一个控制器
@RequestMapping("/test3")
public String test3() {
return "forward:/test1"; // 转发到/test1对应的控制器
}
// 使用redirect:前缀
@RequestMapping("/test4")
public String test4() {
return "redirect:/test1"; // 重定向到/test1
}
// 重定向到外部URL
@RequestMapping("/test5")
public String test5() {
return "redirect:https://www.example.com"; // 重定向到外部网站
}
SpringMVC支持多种视图类型,可以为不同类型配置不同的视图解析器:
配置多个视图解析器:
SpringMVC提供了对Ajax请求的良好支持,通常使用@ResponseBody注解和Jackson库处理JSON数据:
com.fasterxml.jackson.core
jackson-databind
2.13.4
@Controller
@RequestMapping("/api")
public class AjaxController {
@GetMapping("/users")
@ResponseBody
public List getUsers() {
List users = userService.findAll();
return users; // 自动转换为JSON
}
@PostMapping("/users")
@ResponseBody
public ResponseEntity createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}
@GetMapping("/users/{id}")
@ResponseBody
public ResponseEntity getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
// 使用jQuery发送Ajax请求
$(document).ready(function() {
// GET请求获取用户列表
$.ajax({
url: '/api/users',
type: 'GET',
success: function(data) {
// 处理返回的数据
$.each(data, function(index, user) {
$('#userList').append('' + user.name + ' ');
});
},
error: function(xhr, status, error) {
console.error('Error:', error);
}
});
// POST请求创建用户
$('#createUserForm').submit(function(e) {
e.preventDefault();
var userData = {
name: $('#name').val(),
email: $('#email').val(),
age: $('#age').val()
};
$.ajax({
url: '/api/users',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(userData),
success: function(data) {
alert('用户创建成功:' + data.name);
},
error: function(xhr, status, error) {
alert('创建失败: ' + error);
}
});
});
});
如果控制器中的所有方法都返回数据而不是视图,可以使用@RestController注解简化:
@RestController
@RequestMapping("/api")
public class UserRestController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List getUsers() {
return userService.findAll();
}
@PostMapping("/users")
public ResponseEntity createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}
}
配置消息转换器:
Java配置方式:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
}
SpringMVC提供了丰富的参数注入方式,可以将HTTP请求参数绑定到控制器方法的参数上:
@GetMapping("/test")
public String test(String name, int age) {
// 自动获取请求参数name和age
return "test";
}
@GetMapping("/test")
public String test(
@RequestParam(value = "userName", required = false, defaultValue = "guest") String name,
@RequestParam(value = "userAge", defaultValue = "0") int age) {
// userName参数对应name变量,userAge参数对应age变量
return "test";
}
public class User {
private String name;
private int age;
private Address address; // 嵌套对象
// getter和setter方法
}
@PostMapping("/register")
public String register(User user) {
// 自动将请求参数映射到User对象的属性
// 例如:name参数映射到user.name,address.city映射到user.address.city
return "success";
}
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") Long userId) {
// 获取URL路径中的id变量
return "userDetail";
}
// 当变量名与路径变量名相同时,可以简化为
@GetMapping("/users/{id}/posts/{postId}")
public String getUserPost(@PathVariable Long id, @PathVariable Long postId) {
// 获取URL路径中的多个变量
return "postDetail";
}
@GetMapping("/test")
public String test(@RequestHeader("User-Agent") String userAgent) {
// 获取请求头中的User-Agent
return "test";
}
@GetMapping("/test")
public String test(@CookieValue(value = "sessionId", required = false) String sessionId) {
// 获取名为sessionId的Cookie值
return "test";
}
@PostMapping("/api/users")
@ResponseBody
public User createUser(@RequestBody User user) {
// 将请求体中的JSON数据绑定到User对象
return userService.save(user);
}
// 在同一个控制器的所有处理方法调用前被调用
@ModelAttribute
public void populateModel(@RequestParam(required = false) Long userId, Model model) {
if (userId != null) {
model.addAttribute("user", userService.findById(userId));
}
}
@GetMapping("/users/edit")
public String editUser(@ModelAttribute("user") User user) {
// 从Model中获取user属性
return "userForm";
}
// 数组参数
@GetMapping("/search")
public String search(@RequestParam("keyword") String[] keywords) {
// 获取多个同名参数,如?keyword=java&keyword=spring
return "result";
}
// List参数
@GetMapping("/search")
public String search(@RequestParam("id") List ids) {
// 获取多个同名参数,如?id=1&id=2&id=3
return "result";
}
// 使用@DateTimeFormat注解格式化日期
@GetMapping("/test")
public String test(@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthDate) {
// 将字符串参数转换为Date类型
return "test";
}
通过实现WebMvcConfigurer接口的addFormatters方法,注册自定义的Converter或Formatter:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 注册自定义转换器
registry.addConverter(new StringToEnumConverter());
}
}
// 自定义转换器
public class StringToEnumConverter implements Converter {
@Override
public UserStatus convert(String source) {
return UserStatus.valueOf(source.toUpperCase());
}
}
SpringMVC提供了多种方式在不同作用域间传递数据:
@GetMapping("/test")
public String test(Model model) {
// 添加属性到request作用域
model.addAttribute("message", "Hello World");
model.addAttribute("user", new User("Tom", 20));
// 或者一次添加多个属性
Map map = new HashMap<>();
map.put("count", 100);
map.put("pageSize", 10);
model.addAllAttributes(map);
return "test";
}
@GetMapping("/test")
public String test(ModelMap modelMap) {
// ModelMap是LinkedHashMap的子类,具有Map的特性
modelMap.addAttribute("message", "Hello World");
modelMap.put("user", new User("Tom", 20));
return "test";
}
@GetMapping("/test")
public ModelAndView test() {
ModelAndView mav = new ModelAndView();
// 设置视图名
mav.setViewName("test");
// 添加模型数据
mav.addObject("message", "Hello World");
mav.addObject("user", new User("Tom", 20));
return mav;
}
// 在Controller类的所有处理方法执行前执行
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("globalMessage", "This is a global message");
}
// 将方法返回值放入Model中
@ModelAttribute("currentTime")
public Date getCurrentTime() {
return new Date();
}
// 方法参数上的@ModelAttribute
@GetMapping("/test")
public String test(@ModelAttribute("user") User user) {
// 从Model中获取user对象
return "test";
}
@GetMapping("/test")
public String test(HttpServletRequest request) {
// 使用原生Servlet API设置属性
request.setAttribute("message", "Hello World");
// 获取Session对象并设置属性
HttpSession session = request.getSession();
session.setAttribute("sessionMessage", "Hello from Session");
return "test";
}
// 在Controller类上使用@SessionAttributes注解
@Controller
@SessionAttributes({"user", "settings"})
public class UserController {
@GetMapping("/login")
public String login(Model model) {
// 添加到Model的同时也会添加到Session中
model.addAttribute("user", new User("admin", "123456"));
return "welcome";
}
@GetMapping("/settings")
public String settings(Model model) {
model.addAttribute("settings", new Settings());
return "settings";
}
@GetMapping("/logout")
public String logout(SessionStatus status) {
// 清除Session中的指定属性
status.setComplete();
return "redirect:/login";
}
}
@GetMapping("/login")
public String login(HttpSession session) {
// 直接使用HttpSession对象
session.setAttribute("user", new User("admin", "123456"));
return "welcome";
}
@GetMapping("/init")
public String init(HttpServletRequest request) {
// 获取ServletContext对象
ServletContext application = request.getServletContext();
// 设置应用级属性
application.setAttribute("appName", "My SpringMVC App");
application.setAttribute("startTime", new Date());
return "index";
}
@PostMapping("/users")
public String createUser(User user, RedirectAttributes redirectAttributes) {
// 保存用户
userService.save(user);
// 添加Flash属性(会保存在Session中,重定向后自动添加到Model中)
redirectAttributes.addFlashAttribute("message", "用户创建成功");
// 添加URL参数
redirectAttributes.addAttribute("userId", user.getId());
// 重定向到用户详情页,URL会变成/users/123?success=true
return "redirect:/users/" + user.getId();
}
@GetMapping("/users/{id}")
public String showUser(@PathVariable Long id,
@RequestParam(required = false) Boolean success,
Model model) {
// 获取Flash属性,无需显式从Model中获取,自动添加到Model中
return "userDetail";
}
SpringMVC支持多种视图技术,通过不同的视图解析器来解析不同类型的视图:
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(1);
return resolver;
}
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setPrefix("");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html;charset=UTF-8");
resolver.setOrder(0);
return resolver;
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/ftl/");
configurer.setDefaultEncoding("UTF-8");
return configurer;
}
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(0);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML");
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);
return resolver;
}
内容协商视图解析器可以根据请求的媒体类型选择合适的视图解析器: