引言

Spring MVC是Spring框架的一部分,是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过分离Model、View和Controller,将web层进行职责解耦。Spring MVC配置是使用该框架进行Web开发的核心环节,合理的配置不仅能提高开发效率,还能优化应用性能。

本文将从基础到进阶,全面解析Spring MVC的配置文件,帮助开发者掌握Web开发的核心配置技巧,提升项目开发效率。我们将涵盖传统XML配置和基于注解的配置方式,并探讨各种进阶配置技巧和最佳实践。

Spring MVC基础配置

web.xml配置

在传统的Spring MVC应用中,web.xml是部署描述符文件,用于配置Servlet、Filter和Listener等。以下是一个基本的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"> <!-- 配置Spring MVC的DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</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> <!-- 在Web应用启动时立即加载Servlet --> <load-on-startup>1</load-on-startup> </servlet> <!-- 映射所有请求到DispatcherServlet --> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置字符编码过滤器,解决中文乱码问题 --> <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> <init-param> <param-name>forceEncoding</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> 

在这个配置中,我们做了以下几件事:

  1. 配置了DispatcherServlet,它是Spring MVC的核心,负责接收请求并分发到相应的处理器。
  2. 通过contextConfigLocation参数指定了Spring MVC配置文件的位置。
  3. 设置了load-on-startup为1,表示在Web应用启动时立即加载该Servlet。
  4. 将所有请求(/)映射到DispatcherServlet
  5. 配置了字符编码过滤器,解决中文乱码问题。

Spring MVC配置文件基础配置

Spring MVC的配置文件(通常是spring-mvc.xml)是配置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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 启用Spring MVC注解驱动 --> <mvc:annotation-driven/> <!-- 配置扫描包,使@Controller注解生效 --> <context:component-scan base-package="com.example.controller"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans> 

在这个配置中,我们做了以下几件事:

  1. 启用了Spring MVC的注解驱动,通过<mvc:annotation-driven/>元素,它会注册Spring MVC框架所需的各种Bean。
  2. 配置了包扫描,通过<context:component-scan>元素,指定了需要扫描的包,使@Controller@Service@Repository等注解生效。
  3. 配置了视图解析器,通过InternalResourceViewResolver类,指定了视图文件的前缀和后缀,这样控制器返回的逻辑视图名会被解析为具体的视图文件路径。

基于注解的配置

随着Spring 3.0的发布,Spring开始支持基于注解的配置,这种方式可以大大减少XML配置。以下是一个基于注解的配置示例:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "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; } } 

在这个配置中,我们做了以下几件事:

  1. 使用@Configuration注解标记这是一个配置类。
  2. 使用@EnableWebMvc注解启用Spring MVC的注解驱动,相当于XML配置中的<mvc:annotation-driven/>
  3. 使用@ComponentScan注解配置包扫描,相当于XML配置中的<context:component-scan>
  4. 通过@Bean注解定义一个视图解析器Bean,并设置其前缀和后缀。

要使用这个配置类,我们需要在web.xml中配置一个基于Java配置的DispatcherServlet

<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.example.config.WebConfig</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> 

或者,我们可以完全摆脱web.xml,使用Servlet 3.0+的API进行配置:

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 Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceEncoding(true); return new Filter[] { characterEncodingFilter }; } } 

在这个配置中,我们通过继承AbstractAnnotationConfigDispatcherServletInitializer类,实现了基于Java的Web应用初始化配置。

进阶配置技巧

视图解析器配置

视图解析器是Spring MVC中负责将逻辑视图名解析为具体视图对象的组件。除了前面介绍的InternalResourceViewResolver,Spring MVC还提供了多种视图解析器,我们可以根据需要选择合适的视图解析器,或者组合使用多个视图解析器。

多视图解析器配置

在复杂的应用中,我们可能需要支持多种视图技术,如JSP、FreeMarker、Thymeleaf等。这时,我们可以配置多个视图解析器,并设置它们的优先级:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="1"/> <property name="contentNegotiationManager"> <bean class="org.springframework.web.accept.ContentNegotiationManager"> <constructor-arg> <bean class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy"> <constructor-arg> <map> <entry key="json" value="application/json"/> <entry key="xml" value="application/xml"/> <entry key="html" value="text/html"/> </map> </constructor-arg> </bean> </constructor-arg> </bean> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> <bean class="org.springframework.web.servlet.view.xml.MarshallingView"> <property name="marshaller"> <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="packagesToScan" value="com.example.model"/> </bean> </property> </bean> </list> </property> </bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="order" value="2"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="order" value="3"/> <property name="prefix" value="/WEB-INF/freemarker/"/> <property name="suffix" value=".ftl"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> <property name="defaultEncoding" value="UTF-8"/> </bean> 

在这个配置中,我们配置了三个视图解析器:

  1. ContentNegotiatingViewResolver:这是一个特殊的视图解析器,它本身不解析视图,而是根据请求的媒体类型选择合适的视图解析器。我们将其优先级设置为最高(order值越小,优先级越高)。
  2. InternalResourceViewResolver:用于解析JSP视图,优先级次之。
  3. FreeMarkerViewResolver:用于解析FreeMarker模板,优先级最低。

此外,我们还配置了FreeMarkerConfigurer,用于设置FreeMarker模板的基本属性。

基于注解的多视图解析器配置

使用Java配置方式,多视图解析器的配置如下:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Bean public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager contentNegotiationManager) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager(contentNegotiationManager); // 默认视图 List<View> defaultViews = new ArrayList<>(); defaultViews.add(new MappingJackson2JsonView()); MarshallingView xmlView = new MarshallingView(); Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setPackagesToScan("com.example.model"); xmlView.setMarshaller(marshaller); defaultViews.add(xmlView); resolver.setDefaultViews(defaultViews); return resolver; } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setOrder(2); return resolver; } @Bean public ViewResolver freeMarkerViewResolver() { FreeMarkerViewResolver resolver = new FreeMarkerViewResolver(); resolver.setPrefix("/WEB-INF/freemarker/"); resolver.setSuffix(".ftl"); resolver.setContentType("text/html;charset=UTF-8"); resolver.setOrder(3); return resolver; } @Bean public FreeMarkerConfigurer freeMarkerConfigurer() { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setTemplateLoaderPath("/WEB-INF/freemarker/"); configurer.setDefaultEncoding("UTF-8"); return configurer; } @Bean public ContentNegotiationManager contentNegotiationManager() { ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean(); factory.setFavorPathExtension(true); factory.setIgnoreAcceptHeader(true); factory.setDefaultContentType(MediaType.TEXT_HTML); Map<String, MediaType> mediaTypes = new HashMap<>(); mediaTypes.put("json", MediaType.APPLICATION_JSON); mediaTypes.put("xml", MediaType.APPLICATION_XML); mediaTypes.put("html", MediaType.TEXT_HTML); factory.setMediaTypes(mediaTypes); try { return factory.getObject(); } catch (Exception e) { throw new RuntimeException(e); } } } 

拦截器配置

拦截器(Interceptor)是Spring MVC中的一种高级功能,它允许我们在请求处理的各个阶段执行预处理和后处理操作。拦截器可以用于日志记录、权限检查、性能监控等场景。

基于XML的拦截器配置

以下是一个基于XML的拦截器配置示例:

<mvc:interceptors> <!-- 日志拦截器 --> <bean class="com.example.interceptor.LoggingInterceptor"/> <!-- 权限检查拦截器 --> <mvc:interceptor> <mvc:mapping path="/admin/**"/> <mvc:exclude-mapping path="/admin/login"/> <mvc:exclude-mapping path="/admin/logout"/> <bean class="com.example.interceptor.AuthorizationInterceptor"/> </mvc:interceptor> <!-- 性能监控拦截器 --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.example.interceptor.PerformanceMonitorInterceptor"/> </mvc:interceptor> </mvc:interceptors> 

在这个配置中,我们定义了三个拦截器:

  1. LoggingInterceptor:一个全局的日志拦截器,拦截所有请求。
  2. AuthorizationInterceptor:一个权限检查拦截器,只拦截/admin/**路径下的请求,但排除了/admin/login/admin/logout路径。
  3. PerformanceMonitorInterceptor:一个性能监控拦截器,拦截所有请求。

下面是一个简单的拦截器实现示例:

public class LoggingInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("Request URL: {}", request.getRequestURL()); logger.info("Method: {}", request.getMethod()); logger.info("Client IP: {}", request.getRemoteAddr()); return true; // 返回true继续处理请求,返回false中断请求 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if (modelAndView != null) { logger.info("View name: {}", modelAndView.getViewName()); } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if (ex != null) { logger.error("Exception occurred: ", ex); } } } 

基于注解的拦截器配置

使用Java配置方式,拦截器的配置如下:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 日志拦截器 registry.addInterceptor(new LoggingInterceptor()); // 权限检查拦截器 registry.addInterceptor(new AuthorizationInterceptor()) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/login", "/admin/logout"); // 性能监控拦截器 registry.addInterceptor(new PerformanceMonitorInterceptor()) .addPathPatterns("/**"); } } 

参数绑定与类型转换

Spring MVC提供了强大的参数绑定功能,可以将HTTP请求参数自动绑定到控制器方法的参数上。同时,Spring MVC还支持自定义类型转换,以满足特殊的数据转换需求。

基本参数绑定

在Spring MVC中,我们可以通过@RequestParam注解将请求参数绑定到控制器方法的参数上:

@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/findById") public String findById(@RequestParam("id") Long userId, Model model) { User user = userService.findById(userId); model.addAttribute("user", user); return "user/detail"; } @RequestMapping("/findByPage") public String findByPage(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, Model model) { Page<User> page = userService.findByPage(pageNum, pageSize); model.addAttribute("page", page); return "user/list"; } } 

在这个示例中,我们使用@RequestParam注解将请求参数绑定到方法参数上。@RequestParam注解支持以下属性:

  • valuename:指定请求参数的名称。
  • required:指定参数是否必需,默认为true。
  • defaultValue:指定参数的默认值。

对象绑定

Spring MVC支持将请求参数直接绑定到Java对象上:

@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/save") public String save(User user) { userService.save(user); return "redirect:/user/list"; } @RequestMapping("/update") public String update(@ModelAttribute("user") User user) { userService.update(user); return "redirect:/user/list"; } } 

在这个示例中,Spring MVC会自动将请求参数绑定到User对象的属性上。@ModelAttribute注解可以指定模型属性的名称。

自定义类型转换

当需要将请求参数转换为自定义类型时,我们可以实现Converter接口:

public class StringToDateConverter implements Converter<String, Date> { private static final String DATE_PATTERN = "yyyy-MM-dd"; @Override public Date convert(String source) { if (StringUtils.isEmpty(source)) { return null; } SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN); dateFormat.setLenient(false); try { return dateFormat.parse(source); } catch (ParseException e) { throw new IllegalArgumentException("Invalid date format. Please use " + DATE_PATTERN); } } } 

然后,我们需要在Spring MVC配置中注册这个转换器:

<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.example.converter.StringToDateConverter"/> </set> </property> </bean> 

使用Java配置方式:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToDateConverter()); } } 

自定义数据绑定

在某些情况下,我们可能需要对数据绑定进行更精细的控制,例如,在绑定前对数据进行验证或转换。这时,我们可以使用@InitBinder注解:

@Controller @RequestMapping("/user") public class UserController { @InitBinder public void initBinder(WebDataBinder binder) { // 注册自定义编辑器 binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true)); // 设置允许的字段 binder.setAllowedFields("id", "name", "email", "birthDate"); // 设置不允许的字段 binder.setDisallowedFields("password"); // 设置必需的字段 binder.setRequiredFields("name", "email"); } @RequestMapping("/save") public String save(User user) { userService.save(user); return "redirect:/user/list"; } } 

在这个示例中,我们使用@InitBinder注解标记了一个方法,该方法会在控制器方法执行前执行,用于初始化数据绑定器。我们在这个方法中:

  1. 注册了一个自定义的日期编辑器,用于将字符串转换为日期对象。
  2. 设置了允许绑定的字段。
  3. 设置了不允许绑定的字段。
  4. 设置了必需的字段。

文件上传配置

Spring MVC提供了对文件上传的支持,通过配置MultipartResolver,我们可以轻松实现文件上传功能。

基于XML的文件上传配置

以下是一个基于XML的文件上传配置示例:

<!-- 配置MultipartResolver,用于支持文件上传 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置最大上传文件大小,单位为字节 --> <property name="maxUploadSize" value="10485760"/> <!-- 10MB --> <!-- 设置最大内存大小,超过此大小将写入临时文件 --> <property name="maxInMemorySize" value="4096"/> <!-- 4KB --> <!-- 设置默认编码 --> <property name="defaultEncoding" value="UTF-8"/> <!-- 设置上传文件的临时目录 --> <property name="uploadTempDir" value="/tmp"/> </bean> 

在这个配置中,我们使用CommonsMultipartResolver类作为MultipartResolver的实现,并设置了以下属性:

  • maxUploadSize:设置最大上传文件大小,单位为字节。
  • maxInMemorySize:设置最大内存大小,超过此大小将写入临时文件。
  • defaultEncoding:设置默认编码。
  • uploadTempDir:设置上传文件的临时目录。

基于注解的文件上传配置

使用Java配置方式,文件上传的配置如下:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Bean public MultipartResolver multipartResolver() { CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(10485760); // 10MB resolver.setMaxInMemorySize(4096); // 4KB resolver.setDefaultEncoding("UTF-8"); resolver.setUploadTempDir(new FileSystemResource("/tmp")); return resolver; } } 

文件上传控制器示例

配置好MultipartResolver后,我们可以编写控制器来处理文件上传:

@Controller @RequestMapping("/file") public class FileUploadController { @RequestMapping("/upload") public String upload(@RequestParam("file") MultipartFile file, Model model) { if (!file.isEmpty()) { try { // 获取文件名 String fileName = file.getOriginalFilename(); // 设置文件保存路径 String filePath = "https://www.oryoy.com/uploads/" + fileName; // 保存文件 File dest = new File(filePath); file.transferTo(dest); model.addAttribute("message", "File uploaded successfully: " + fileName); } catch (IOException e) { model.addAttribute("message", "Failed to upload file: " + e.getMessage()); } } else { model.addAttribute("message", "Failed to upload file because the file was empty."); } return "file/uploadResult"; } @RequestMapping("/uploadMultiple") public String uploadMultiple(@RequestParam("files") MultipartFile[] files, Model model) { List<String> fileNames = new ArrayList<>(); for (MultipartFile file : files) { if (!file.isEmpty()) { try { // 获取文件名 String fileName = file.getOriginalFilename(); // 设置文件保存路径 String filePath = "https://www.oryoy.com/uploads/" + fileName; // 保存文件 File dest = new File(filePath); file.transferTo(dest); fileNames.add(fileName); } catch (IOException e) { model.addAttribute("message", "Failed to upload file: " + e.getMessage()); return "file/uploadResult"; } } } model.addAttribute("message", "Files uploaded successfully: " + fileNames); return "file/uploadResult"; } } 

在这个示例中,我们定义了两个文件上传方法:

  1. upload:处理单个文件上传。
  2. uploadMultiple:处理多个文件上传。

这两个方法都使用@RequestParam注解将上传的文件绑定到MultipartFile参数上。MultipartFile接口提供了获取文件内容、文件名、文件大小等方法,以及保存文件到指定位置的方法。

异常处理

在Web应用中,异常处理是一个重要的环节。Spring MVC提供了多种异常处理机制,使我们能够优雅地处理各种异常情况。

基于XML的异常处理配置

以下是一个基于XML的异常处理配置示例:

<!-- 配置简单映射异常处理器 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 设置默认的异常视图 --> <property name="defaultErrorView" value="error/default"/> <!-- 设置异常属性名称,用于在视图中显示异常信息 --> <property name="exceptionAttribute" value="ex"/> <!-- 设置异常和视图的映射关系 --> <property name="exceptionMappings"> <props> <prop key="org.springframework.web.servlet.PageNotFound">error/404</prop> <prop key="org.springframework.dao.DataAccessException">error/dataAccessFailure</prop> <prop key="org.springframework.security.access.AccessDeniedException">error/accessDenied</prop> <prop key="java.lang.Exception">error/exception</prop> </props> </property> </bean> 

在这个配置中,我们使用SimpleMappingExceptionResolver类作为异常处理器,并设置了以下属性:

  • defaultErrorView:设置默认的异常视图。
  • exceptionAttribute:设置异常属性名称,用于在视图中显示异常信息。
  • exceptionMappings:设置异常和视图的映射关系。

基于注解的异常处理

使用注解方式,我们可以通过@ExceptionHandler注解在控制器中定义异常处理方法:

@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/findById") public String findById(@RequestParam("id") Long userId, Model model) { User user = userService.findById(userId); if (user == null) { throw new UserNotFoundException("User not found with id: " + userId); } model.addAttribute("user", user); return "user/detail"; } @ExceptionHandler(UserNotFoundException.class) public ModelAndView handleUserNotFoundException(UserNotFoundException ex) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", ex.getMessage()); modelAndView.setViewName("error/userNotFound"); return modelAndView; } @ExceptionHandler(Exception.class) public ModelAndView handleException(Exception ex) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", "An error occurred: " + ex.getMessage()); modelAndView.setViewName("error/exception"); return modelAndView; } } 

在这个示例中,我们在控制器中定义了两个异常处理方法:

  1. handleUserNotFoundException:处理UserNotFoundException异常。
  2. handleException:处理所有其他异常。

这种方式的好处是异常处理方法与控制器方法在同一个类中,便于维护。但是,如果我们希望在多个控制器中共享异常处理逻辑,可以使用@ControllerAdvice注解:

@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ModelAndView handleUserNotFoundException(UserNotFoundException ex) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", ex.getMessage()); modelAndView.setViewName("error/userNotFound"); return modelAndView; } @ExceptionHandler(DataAccessException.class) public ModelAndView handleDataAccessException(DataAccessException ex) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", "Data access error: " + ex.getMessage()); modelAndView.setViewName("error/dataAccessFailure"); return modelAndView; } @ExceptionHandler(Exception.class) public ModelAndView handleException(Exception ex) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errorMessage", "An error occurred: " + ex.getMessage()); modelAndView.setViewName("error/exception"); return modelAndView; } } 

在这个示例中,我们使用@ControllerAdvice注解标记了一个类,该类中的异常处理方法将应用于所有控制器。

使用@ResponseStatus注解处理异常

对于一些简单的异常,我们可以使用@ResponseStatus注解将其映射到HTTP状态码:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "User not found") public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String message) { super(message); } } 

在这个示例中,我们使用@ResponseStatus注解将UserNotFoundException异常映射到HTTP 404状态码。

性能优化配置

静态资源处理

在Spring MVC中,默认情况下,DispatcherServlet会拦截所有请求,包括静态资源请求。为了提高性能,我们需要对静态资源进行特殊处理。

基于XML的静态资源处理配置

以下是一个基于XML的静态资源处理配置示例:

<!-- 静态资源处理 --> <mvc:resources mapping="/static/**" location="/static/" cache-period="31556926"/> <mvc:resources mapping="/assets/**" location="/assets/" cache-period="31556926"/> <mvc:resources mapping="/favicon.ico" location="/favicon.ico" cache-period="31556926"/> <!-- 默认Servlet处理静态资源 --> <mvc:default-servlet-handler/> 

在这个配置中,我们使用了两种方式处理静态资源:

  1. <mvc:resources>:指定静态资源的映射规则和缓存时间。mapping属性指定URL模式,location属性指定资源位置,cache-period属性指定缓存时间(秒)。
  2. <mvc:default-servlet-handler>:启用默认Servlet处理静态资源。当Spring MVC找不到匹配的控制器方法时,会将请求交给容器默认的Servlet处理。

基于注解的静态资源处理配置

使用Java配置方式,静态资源的处理如下:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**") .addResourceLocations("/static/") .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)); registry.addResourceHandler("/assets/**") .addResourceLocations("/assets/") .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)); registry.addResourceHandler("/favicon.ico") .addResourceLocations("/favicon.ico") .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)); } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } } 

在这个配置中,我们通过实现WebMvcConfigurer接口,重写了addResourceHandlersconfigureDefaultServletHandling方法,实现了与XML配置相同的功能。

缓存配置

缓存是提高Web应用性能的重要手段。Spring MVC提供了多种缓存配置选项,包括HTTP缓存和Spring缓存抽象。

HTTP缓存配置

HTTP缓存是通过HTTP协议的缓存机制实现的,我们可以通过设置响应头来控制缓存行为:

@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/findById") public String findById(@RequestParam("id") Long userId, Model model, HttpServletResponse response) { // 设置缓存控制 response.setHeader("Cache-Control", "max-age=3600"); // 缓存1小时 response.setHeader("Pragma", "cache"); response.setHeader("Expires", new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz") .format(new Date(System.currentTimeMillis() + 3600 * 1000))); User user = userService.findById(userId); model.addAttribute("user", user); return "user/detail"; } } 

在这个示例中,我们通过设置Cache-ControlPragmaExpires响应头来控制缓存行为。

Spring缓存抽象配置

Spring提供了缓存抽象,可以与各种缓存实现(如EhCache、Redis、Caffeine等)集成。以下是一个基于XML的Spring缓存配置示例:

<!-- 启用缓存注解 --> <cache:annotation-driven cache-manager="cacheManager"/> <!-- 配置缓存管理器 --> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <!-- 配置EhCache工厂Bean --> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml"/> </bean> 

在这个配置中,我们做了以下几件事:

  1. 通过<cache:annotation-driven>启用缓存注解。
  2. 配置了EhCacheCacheManager作为缓存管理器。
  3. 配置了EhCacheManagerFactoryBean,指定了EhCache的配置文件位置。

使用Java配置方式:

@Configuration @EnableCaching @ComponentScan(basePackages = "com.example") public class CacheConfig { @Bean public CacheManager cacheManager() { return new EhCacheCacheManager(ehCacheManagerFactory().getObject()); } @Bean public EhCacheManagerFactoryBean ehCacheManagerFactory() { EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean(); cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml")); return cacheManagerFactoryBean; } } 

使用缓存注解

配置好缓存后,我们可以在服务层使用缓存注解:

@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Cacheable(value = "users", key = "#id") public User findById(Long id) { return userDao.findById(id); } @Override @CacheEvict(value = "users", key = "#user.id") public void update(User user) { userDao.update(user); } @Override @CacheEvict(value = "users", key = "#id") public void delete(Long id) { userDao.delete(id); } @Override @CacheEvict(value = "users", allEntries = true) public void deleteAll() { userDao.deleteAll(); } } 

在这个示例中,我们使用了三个缓存注解:

  1. @Cacheable:标记方法的返回值应该被缓存。当调用该方法时,Spring会先检查缓存中是否存在对应的数据,如果存在,则直接返回缓存中的数据,否则执行方法并将结果存入缓存。
  2. @CacheEvict:标记方法应该从缓存中移除数据。allEntries属性指定是否移除缓存中的所有数据。
  3. @CachePut:标记方法的返回值应该被更新到缓存中(示例中未使用)。

压缩配置

启用HTTP压缩可以减少网络传输的数据量,提高Web应用的性能。Spring MVC提供了对HTTP压缩的支持。

基于XML的压缩配置

以下是一个基于XML的压缩配置示例:

<!-- 配置压缩过滤器 --> <filter> <filter-name>compressionFilter</filter-name> <filter-class>org.springframework.web.filter.OncePerRequestFilter</filter-class> </filter> <filter-mapping> <filter-name>compressionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 

然后,我们需要实现压缩过滤器:

public class CompressionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String acceptEncoding = request.getHeader("Accept-Encoding"); if (acceptEncoding != null && acceptEncoding.contains("gzip")) { response.setHeader("Content-Encoding", "gzip"); CompressionResponseWrapper wrappedResponse = new CompressionResponseWrapper(response); wrappedResponse.setHeader("Content-Encoding", "gzip"); try { filterChain.doFilter(request, wrappedResponse); } finally { byte[] compressedBytes = wrappedResponse.getByteArray(); response.setContentLength(compressedBytes.length); response.getOutputStream().write(compressedBytes); } } else { filterChain.doFilter(request, response); } } private static class CompressionResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private PrintWriter writer; public CompressionResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public void write(int b) throws IOException { outputStream.write(b); } @Override public void write(byte[] b) throws IOException { outputStream.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { outputStream.write(b, off, len); } }; } @Override public PrintWriter getWriter() throws IOException { if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(outputStream, getCharacterEncoding())); } return writer; } public byte[] getByteArray() { if (writer != null) { writer.close(); } try { outputStream.flush(); } catch (IOException e) { // Ignore } return outputStream.toByteArray(); } } } 

基于注解的压缩配置

使用Java配置方式,压缩的配置如下:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.controller") public class WebConfig implements WebMvcConfigurer { @Bean public FilterRegistrationBean<CompressionFilter> compressionFilter() { FilterRegistrationBean<CompressionFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CompressionFilter()); registrationBean.addUrlPatterns("/*"); return registrationBean; } } 

最佳实践与常见问题解决

最佳实践

  1. 使用基于注解的配置:基于注解的配置比XML配置更简洁、更易于维护。建议在新项目中使用基于注解的配置方式。

  2. 合理组织包结构:按照功能或层次组织包结构,例如:

    com.example ├── controller ├── service ├── dao ├── model ├── config ├── interceptor └── util 
  3. 使用RESTful风格:尽量使用RESTful风格的URL设计,例如:

    @RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userService.findById(id); } @PostMapping public User createUser(@RequestBody User user) { return userService.save(user); } @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { user.setId(id); return userService.update(user); } @DeleteMapping("/{id}") public void deleteUser(@PathVariable Long id) { userService.delete(id); } } 
  4. 统一异常处理:使用@ControllerAdvice@ExceptionHandler注解统一处理异常,避免在每个控制器方法中重复处理异常。

  5. 使用DTO:使用DTO(Data Transfer Object)在控制器和客户端之间传输数据,避免直接暴露领域模型。

  6. 使用验证框架:使用Spring Validation框架验证输入数据,例如:

    @RestController @RequestMapping("/api/users") public class UserController { @PostMapping public ResponseEntity<?> createUser(@Valid @RequestBody UserDTO userDTO, BindingResult bindingResult) { if (bindingResult.hasErrors()) { // 处理验证错误 List<String> errors = bindingResult.getAllErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errors); } User user = convertToEntity(userDTO); User savedUser = userService.save(user); return ResponseEntity.ok(convertToDto(savedUser)); } // 其他方法... } 
  7. 使用缓存:对于频繁访问但不经常变化的数据,使用缓存提高性能。

  8. 使用异步处理:对于耗时的操作,使用异步处理提高系统的吞吐量,例如:

    @RestController @RequestMapping("/api/reports") public class ReportController { @GetMapping public CompletableFuture<Report> generateReport() { return CompletableFuture.supplyAsync(() -> { // 耗时的报表生成操作 return reportService.generateReport(); }); } } 

常见问题解决

  1. 中文乱码问题

    • 确保在web.xml中配置了字符编码过滤器:
       <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 
    • 确保数据库连接URL中指定了字符编码,例如:jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
  2. 静态资源无法访问

    • 确保在Spring MVC配置中正确配置了静态资源处理:
       <mvc:resources mapping="/static/**" location="/static/"/> <mvc:default-servlet-handler/> 
    • 或者使用Java配置: “`java @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(”/static/**“) .addResourceLocations(”/static/“); }

    @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

     configurer.enable(); 

    } “`

  3. 文件上传失败

    • 确保在Spring MVC配置中正确配置了MultipartResolver
       <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <!-- 10MB --> </bean> 
    • 确保表单的enctype属性设置为multipart/form-data
       <form method="POST" action="/file/upload" enctype="multipart/form-data"> <input type="file" name="file"/> <button type="submit">Upload</button> </form> 
  4. JSON数据无法正确绑定

    • 确保在Spring MVC配置中启用了JSON支持:
       <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> 
    • 确保在控制器方法中使用了@RequestBody注解:
       @PostMapping("/users") public User createUser(@RequestBody User user) { return userService.save(user); } 
  5. 跨域请求问题

    • 在控制器方法上添加@CrossOrigin注解:

       @CrossOrigin(origins = "http://example.com") @GetMapping("/users") public List<User> getUsers() { return userService.findAll(); } 

    • 或者全局配置CORS:

      @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://example.com") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } } 

总结

Spring MVC作为Java Web开发的主流框架,其配置是项目开发中的核心环节。本文从基础到进阶,全面解析了Spring MVC的配置文件和相关技巧,包括:

  1. 基础配置:介绍了web.xml配置、Spring MVC配置文件基础配置和基于注解的配置,为开发者提供了多种配置选择。

  2. 进阶配置技巧:详细讲解了视图解析器配置、拦截器配置、参数绑定与类型转换、文件上传配置和异常处理,帮助开发者掌握Spring MVC的高级功能。

  3. 性能优化配置:介绍了静态资源处理、缓存配置和压缩配置,帮助开发者提高Web应用的性能。

  4. 最佳实践与常见问题解决:提供了一系列最佳实践建议和常见问题的解决方案,帮助开发者避免常见的陷阱和错误。

通过合理配置Spring MVC,开发者可以大大提高项目开发效率,构建出高性能、易维护的Web应用。希望本文能够帮助开发者更好地理解和掌握Spring MVC的配置技巧,在实际项目中发挥其强大的功能。