SpringMVC常用配置3
我们的案例都会有一个MvcConfig的类用来做一个简单的配置,主要是通过ViewResolver来解决映射路径和实际页面的位置,这个类我们还可以继续扩展,让其解决更多的问题,我列举几个:
- 静态资源映射
- 拦截器使用
- 全局配置问题
等等。这些问题我们可以重新定义一个新的类来解决,也可以扩展MvcConfig来解决。我们来一个个看看。
静态资源映射
我们都知道在SpringMVC中静态资源文件都是直接访问的,而不需要映射,这些静态资源主要包括js文件、css文件、图片文件等,那么这个需要我们单独处理,否则系统会找不到路径。OK,这个问题的解决也很容易,假设我有一张图片放在src/main/resources/assets/img目录下,然后想在jsp页面中将其展示出来,我们先来看看jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Hello Sang!</title> </head> <body> <p>Welcome To SpringMVC World!</p> <p> <img src="../assets/img/1.png"> </p> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
然后我们创建MVCConfig类,作用还是和上文一样,不同的是这次我们继承自WebMvcConfigurerAdapter,然后重写WebMvcConfigurerAdapter类中的addResourceHandlers方法来解决这个问题。如下:
@Configuration @EnableWebMvc @ComponentScan("org.sang") public class MVCConfig extends WebMvcConfigurerAdapter{ @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/classes/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; } /** * /**的意思是所有文件,包括文件夹中的子文件 * /*是所有文件,不包含子文件 * /是web项目的根目录 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //两个*表示以/assets开始的任意层级的路径都可以访问得到图片,如<img src="../assets/img/1.png"> //一个*表示只可以访问assets目录下的图片文件 registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
OK,这里继承WebMvcConfigureAdapter之后,我们可以重写它里边的很多方法,重写这些方法我们可以对SpringMVC进行配置,addResourceHandler指的是访问路径,addResourceLocations指的是文件放置目录。
拦截器
拦截器在JavaEE开发中还是非常重要的,乱码解决、权限控制等等都会用到,使用Servlet的时候有一个Filter类用来进行过滤,那么SpringMVC也在这方面给我们提供了相应的解决方案。
- 定义拦截器
拦截器的定义我们可以通过继承HandlerInterceptorAdapter或者实现HandlerInterceptor接口,我这里以实现接口为例:
public class MyInterceptors implements HandlerInterceptor { public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("preHandle"); return true; } public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("afterCompletion"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
preHandle方法发生在请求发生前执行,postHandle发生在请求发生后执行,afterCompletion在请求完成时执行,实际上执行时机紧挨着postHandle这个方法。然后在MVCConfig类中添加addInterceptors方法注册拦截器,如下:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptors()); } @Bean public MyInterceptors myInterceptors() { return new MyInterceptors(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这样注册成功之后,我们在浏览器中访问时,打印日志如下:
全局配置
全局资源的配置问题,我们可以通过@ControllerAdvice来把控制器的全局配置放在同一个位置,这样我们可以统一处理下面几个问题:
1 .全局异常处理
2 .预设键值对绑定到Model中
3 .预处理前台请求参数
OK,下面来一个一个看一下。
全局异常处理
全局异常主要是通过@ExceptionHandler这个注解来解决。如下:
@ControllerAdvice public class ExceptionHandlerAdvice { //@ExceptionHandler用来设置拦截条件,这里表示拦截所有的Exception @ExceptionHandler(value = Exception.class) public ModelAndView exception(Exception e, WebRequest request) { ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("errorMsg", e.getMessage()); return modelAndView; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
首先通过@ControllerAdvice声明一个控制器建言,由于这个注解组合了@Component注解,这个这个类会自动注册为Spring容器中的Bean。@ExceptionHandler可以定义全局处理,其中Value属性用来表示过滤拦截条件,Exception.class表示拦截所有的Exception。构造ModelAndView时传入的error表示出错的页面。OK,我们来看一下控制器,我在控制器中添加如下方法:
@RequestMapping("/user") public String user(@ModelAttribute("msg") String msg, UserBean userBean) { System.out.println("username is :" + userBean.getUsername() + ";and id is :" + userBean.getId()); throw new IllegalArgumentException("抱歉,参数异常/ 来自@ModelAttribute:" + msg); }
- 1
- 2
- 3
- 4
- 5
当我访问/user这个地址的时候,直接抛一个异常,这个异常会被使用了@ExceptionHandler注解并且满足过滤条件的方法接收并处理,我们这里当然是来到了exception这个方法中,在这个方法中我们又定位到了error.jsp页面。同时这里的参数还使用了@ModelAttribute注解,这个注解我在下一小节再来说。我们再来看看这个error.jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>出错啦</title> </head> <body> <p> <h1>${errorMsg}</h1> </p> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这个页面很简单,就显示一下错误信息就行了。我们来看看访问结果:
控制台输出的错误信息:
预设键值对绑定到Model中
有的时候我们需要预设键值对到Model中,就像上面那个案例那样,这个时候我们可以在ExceptionHandlerAdvice类中再添加一个方法:
@ModelAttribute public void addAttributes(Model model) { model.addAttribute("msg", "额外信息"); }
- 1
- 2
- 3
- 4
在这个方法中我们向Model中绑定键值对,绑定完成之后,在任何Controller中我们都可以通过给方法的参数设定@ModelAttribute注解来访问这里存入的值,相当于这里的值是一个全局变量。OK ,这里的访问案例和上文一致,我就不再赘述了。
预处理前台请求参数
OK,还有一种需求,有的时候我们需要预处理前台传来的参数,比如说禁止掉某一个参数,这个也可以统一处理,OK,继续在ExceptionHandlerAdvice方法中添加方法
@InitBinder public void initBinder(WebDataBinder webDataBinder) { webDataBinder.setDisallowedFields("id"); }
- 1
- 2
- 3
- 4
这个表示将客户端传来的id参数忽略掉,但是注意接收的方式,这里通过对象来接收参数的时候才有效(通过对象接收这个参数的时候才会屏蔽掉id),如果直接提取还是可以提取到的,我们来看一下控制器方法,还是刚才抛异常那个方法,但是在抛异常之前我先打印一下日志:
@RequestMapping("/user") public String user(@ModelAttribute("msg") String msg, UserBean userBean) { System.out.println("username is :" + userBean.getUsername() + ";and id is :" + userBean.getId()); throw new IllegalArgumentException("抱歉,参数异常/ 来自@ModelAttribute:" + msg); }
- 1
- 2
- 3
- 4
- 5
我们看看控制台的输出:
OK, id已经被屏蔽掉了。