复习宝典之SpringMVC

查看更多宝典,请点击《金三银四,你的专属面试宝典》

第七章:SpringMVC

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

最简单的、最经典就是Jsp(view) + Servlet(controller)  + JavaBean(model)。

mvc框架是为了解决传统MVC模式(Jsp +Servlet + JavaBean)的一些问题而出现的框架。

 

传统MVC模式问题:

1、所有的Servlet和Servlet映射都要配置在web.xml中,如果项目太大,web.xml就太庞大,并且不能实现模块化管理。

2、Servlet的主要功能就是接受参数、调用逻辑、跳转页面,但是像其他字符编码、文件上传等功能也要写在Servlet中。

3、接受参数比较麻烦(String name = request.getParameter(“name”),User user=new User,user.setName(name)),不能通过model接收,只能单个接收,接收完成后转换封装model。

4、跳转页面方式比较单一(forword,redirect),并且当我的页面名称发生改变时需要修改Servlet源代码。

 

1)前端控制器

前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。

spring MVC中的前端控制器就是DsipatcherServlet,在web.xml中配置好DispatcherServlet后,容器启动时会去WEB-INF文件夹下去找(默认[servlet-name]-servlet.xml)dispatcherServlet-servlet.xml,解析文件并初始化里面中的bean等,DispatcherServlet继承自抽象类:FrameworkServlet,间接继承了HttpServlet (FrameworkServlet继承自HttpServletBean,而HttpServletBean继承自HttpServlet )。

在使用springmvc时,前端控制器会默认加载一些组件,具体配置在springwebmvc下DispatcherServlet.properties中。

从配置文件可以看到DispatcherServlet在启动时会自动注册这些特殊的Bean,无需我们注册,如果我们注册了,默认的将不会注册。 因此BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不需要注册的,DispatcherServlet默认会注册这两个Bean。

 

Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理;

HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器;

HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且调用处理器的handleRequest方法进行功能处理;

ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图;

LocalResover:本地化解析,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化;

ThemeResovler:主题解析,通过它来实现一个页面多套风格,即常见的类似于如软件皮肤效果;

MultipartResolver:文件上传解析,用于支持文件上传;

HandlerExceptionResolver:处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息);

RequestToViewNameTranslator:当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名;

FlashMapManager:用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景。

 

2)springmvc的实现原理

概述一:

1、 用户发送请求至前端控制器DispatcherServlet。

2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、 Controller执行完成返回ModelAndView。

7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、 ViewReslover解析后返回具体View。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、 DispatcherServlet响应用户。

 

概述二:

1、 用户向服务器发送请求,请求被Spring前端控制器DispatcherServlet捕获(捕获)。

2、 DispatcherServlet 对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回(查找handler)。

3、 DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller),Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象(执行handler)。

4、DispatcherServlet 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver) (选择ViewResolver)。

5、通过ViewResolver 结合Model和View,来渲染视图,DispatcherServlet 将渲染结果返回给客户端(渲染返回)。

 

3)sturts2的实现原理

 

1、客户端浏览器发送请求;

2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

3、接着FilterDispatcher(StrutsPrepareAndExecuteFilter)被调用,FilterDispatcher(StrutsPrepareAndExecuteFilter)询问ActionMapper来决定这个请求是否需要调用某个Action;

4、如果ActionMapper决定需要调用某个Action,FilterDispatcher(StrutsPrepareAndExecuteFilter)把请求的处理交给ActionProxy;

5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

6、ActionProxy创建一个ActionInvocation的实例;

7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用;

8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。

 

面试:

1、浏览器发送请求,经过一系列的过滤器后,到达核心过滤器(StrutsPrepareAndExecuteFilter);

2、StrutsPrepareAndExecuteFilter通过ActionMapper判断当前的请求是否需要某个Action处理,如果不需要,则走原来的流程,如果需要则把请求交给ActionProxy来处理;

3、ActionProxy通过Configuration Manager询问框架的配置文件(Struts.xml),找到需要调用的Action类;

4、创建一个ActionInvocation实例,来调用Action的对应方法来获取结果集的name,在调用前后会执行相关拦截器;

5、通过结果集的Name知道对应的结果集来对浏览器进行响应。

 

4)struts2与springmvc的区别

目前企业中使用SpringMvc的比例已经远远超过Struts2,那么两者到底有什么区别,是很多初学者比较关注的问题,下面我们就来对SpringMvc和Struts2进行各方面的比较:

1、核心控制器(前端控制器、预处理控制器):对于使用过mvc框架的人来说这个词应该不会陌生,核心控制器的主要用途是处理所有的请求,然后对那些特殊的请求 (控制器)统一的进行处理(字符编码、文件上传、参数接受、异常处理等等),spring mvc核心控制器是Servlet,而Struts2是Filter。

2、控制器实例:Spring Mvc会比Struts快一些(理论上)。Spring Mvc是基于方法设计,而Sturts是基于对象,每次发一次请求都会实例一个action,每个action都会被注入属性,而Spring更像Servlet一样,只有一个实例,每次请求执行对应的方法即可(注意:由于是单例实例,所以应当避免全局变量的修改,这样会产生线程安全问题)。

3、管理方式:大部分的公司的核心架构中,都会使用到spring,而spring mvc又是spring中的一个模块,所以spring对于spring mvc的控制器管理更加简单方便,而且提供了全注解方式进行管理,各种功能的注解都比较全面,使用简单,而struts2需要采用XML很多的配置参数来管理(虽然也可以采用注解,但是几乎没有公司那样使用)。

4、参数传递:Struts2中自身提供多种参数接收,其实都是通过(ValueStack)进行传递和赋值,而SpringMvc是通过方法的参数进行接收。

5、学习难度:Struts很多技术点,比如拦截器、值栈及OGNL表达式,学习成本较高,springmvc 比较简单,较少的时间都能上手。

6、intercepter 的实现机制:struts有以自己的interceptor机制,spring mvc用的是独立的AOP方式。这样导致struts的配置文件量还是比spring mvc大,虽然struts的配置能继承,所以我觉得论使用上来讲,spring mvc使用更加简洁,开发效率Spring MVC确实比struts2高。

spring mvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上spring mvc就容易实现restful url。

struts2是类级别的拦截,一个类对应一个request上下文,实现restful url比较费劲;因为struts2 action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。

spring mvc的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架,方法之间不共享变量,而struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。

7、spring mvc处理ajax请求,直接返回数据,方法中使用注解@ResponseBody,spring mvc自动帮我们对象转换为JSON数据。而struts2是通过插件的方式进行处理。

在SpringMVC流行起来之前,Struts2在MVC框架中占核心地位,随着SpringMVC的出现,SpringMVC慢慢的取代struts2,但是很多企业都是原来搭建的框架,使用Struts2较多。

 

5)WebApplicationInitializer接口

概述一:

自从Servlet3.0+的出现,Spring-web模块提供了一种免配置文件,在如Tomcat(支持Servlet3.0+)这样的Servlet容器启动时,自动调用了实现的WebApplicationInitializer(全路径org.springframework.web.WebApplicationInitializer)接口的类的onStartup方法,并将容器的ServletContext往下传递。

从Spring-web模块的API中,找到子类AbstractAnnotationConfigDispatcherServletInitializer,它间接实现了WebApplicationInitializer接口,父类onStartup方法,首先创建RootWebApplicationContext并设置ContextLoaderListner监听器;其次,往servletContext注册DispatcherServlet实例。

由于AbstractAnnotationConfigDispatcherServletInitializer是抽象类,Spring容器不能注入。它重写了父类创建根ApplicationContext与ServletApplicationContext方法,并抽象出两个方法(用来创建根ApplicationContext和ServletApplicationContext容器时,需要注入的组件字类节数组)。

因此我们只需要编写SpringMVCInitializer来继承AbstractAnnotationConfigDispatcherServletInitializer类,让它来帮我们完成对DispatcherServlet实例的加载即可。

 

概述二:

现在JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer可以看做是Web.xml的替代,它是一个接口。通过实现WebApplicationInitializer,在其中可以添加servlet,listener等,在加载Web项目的时候会加载这个接口实现类,从而起到web.xml相同的作用。

为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。既然这样的话,那么SpringServletContainerInitializer作为ServletContainerInitializer的实现类,它的jar包下也应该有相应的文件。

首先,SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。(这个类上还有一个注解@HandlesTypes,它的作用是将感兴趣的一些类注入到ServletContainerInitializerde), 而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。

 

6)servlet3.0新特性

Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。其中有几项特性的引入让开发者感到非常兴奋,同时也获得了 Java 社区的一片赞誉之声:

1、异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。

2、新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。

3、可插性支持:熟悉 Struts2 的开发者一定会对其通过插件的方式与包括 Spring 在内的各种常用框架的整合特性记忆犹新。将相应的插件封装成 JAR 包并放在类路径下,Struts2 运行时便能自动加载这些插件。现在 Servlet 3.0 提供了类似的特性,开发者可以通过插件的方式很方便的扩充已有 Web 应用的功能,而不需要修改原有的应用。

 

7)RequestMapping参数类型与返回类型

@RequestMapping方法方法所支持的常见参数类型:

  • 请求或响应对象(Servlet API)。可以是任何具体的请求或响应类型的对象,比如,ServletRequest或HttpServletRequest对象。

  • HttpSession类型的会话对象(Servlet API)。使用该类型的参数将要求这样一个session的存在,因此这样的参数永不为null。

  • 当前请求的地区信息java.util.Locale,由已配置的最相关的地区解析器解析得到。在MVC的环境下,就是应用中配置的LocaleResolver或LocaleContextResolver

  • 与当前请求绑定的时区信息java.util.TimeZone(java 6以上的版本)/java.time.ZoneId(java 8),由LocaleContextResolver解析得到

  • org.springframework.http.HttpMethod。可以拿到HTTP请求方法

  • 包装了当前被认证用户信息的java.security.Principal

  • 带@PathVariable标注的方法参数,其存放了URI模板变量中的值。

  • 带@RequestParam标注的方法参数,其存放了Servlet请求中所指定的参数。参数的值会被转换成方法参数所声明的类型。

  • 带@RequestHeader标注的方法参数,其存放了Servlet请求中所指定的HTTP请求头的值。参数的值会被转换成方法参数所声明的类型。

  • 带@RequestBody标注的参数,提供了对HTTP请求体的存取。参数的值通过HttpMessageConverter被转换成方法参数所声明的类型。

  • 带@RequestPart标注的参数,提供了对一个"multipart/form-data请求块(request part)内容的存取。

  • HttpEntity<?>类型的参数,其提供了对HTTP请求头和请求内容的存取。请求流是通过HttpMessageConverter被转换成entity对象的。

  • java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap类型的参数,用以增强默认暴露给视图层的模型(model)的功能。

  • org.springframework.web.servlet.mvc.support.RedirectAttributes类型的参数,用以指定重定向下要使用到的属性集以及添加flash属性(暂存在服务端的属性,它们会在下次重定向请求的范围中有效)。

  • 命令或表单对象,它们用于将请求参数直接绑定到bean字段(可能是通过setter方法)。你可以通过@InitBinder标注和/或HanderAdapter的配置来定制这个过程的类型转换。RequestMappingHandlerAdapter类webBindingInitializer属性的文档。这样的命令对象,以及其上的验证结果,默认会被添加到模型model中,键名默认是该命令对象类的类名——比如,some.package.OrderAddress类型的命令对象就使用属性名orderAddress类获取。ModelAttribute标注可以应用在方法参数上,用以指定该模型所用的属性名。

  • org.springframework.validation.Errors / org.springframework.validation.BindingResult验证结果对象,用于存储前面的命令或表单对象的验证结果(紧接其前的第一个方法参数)。

  • org.springframework.web.bind.support.SessionStatus对象,用以标记当前的表单处理已结束。这将触发一些清理操作:@SessionAttributes在类级别标注的属性将被移除。

  • org.springframework.web.util.UriComponentsBuilder构造器对象,用于构造当前请求URL相关的信息,比如主机名、端口号、资源类型(scheme)、上下文路径、servlet映射中的相对部分(literal part)等。

 

BindingResult的特殊性:

在参数列表中,Errors或BindingResult参数必须紧跟在其所绑定的验证对象后面。这是因为,在参数列表中允许有多于一个的模型对象,Spring会为它们创建不同的BindingResult实例。因此,下面这样的代码是不能工作的:

BindingResult与@ModelAttribute错误的参数次序

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

上例中,因为在模型对象Pet和验证结果对象BindingResult中间还插了一个Model参数,这是不行的。要达到预期的效果,必须调整一下参数的次序:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

对于一些带有required属性的标注(比如@RequestParam、@RequestHeader等),JDK 1.8的java.util.Optional可以作为被它们标注的方法参数。在这种情况下,使用java.util.Optional与required=false的作用是相同的。

 

@RequestMapping方法方法支持的常见返回类型:

  • ModelAndView对象,其中model隐含填充了命令对象,以及标注了@ModelAttribute字段的存取器被调用所返回的值。

  • Model对象,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。

  • Map对象,用于暴露model,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。

  • View对象。其中model隐含填充了命令对象,以及标注了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。

  • String对象,其值会被解析成一个逻辑视图名。其中,model将默认填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。

  • void。如果处理器方法中已经对response响应数据进行了处理(比如在方法参数中定义一个ServletResponse或HttpServletResponse类型的参数并直接向其响应体中写东西),那么方法可以返回void。handler方法也可以增加一个Model类型的方法参数来增强model。

  • 如果处理器方法标注了ResponseBody,那么返回类型将被写到HTTP的响应体中,而返回值会被HttpMessageConverters转换成所方法声明的参数类型。

  • HttpEntity<?>或ResponseEntity<?>对象,用于提供对Servlet HTTP响应头和响应内容的存取。对象体会被HttpMessageConverters转换成响应流。

  • HttpHeaders对象,返回一个不含响应体的response。

  • 如果返回类型不是Spring MVC默认识别的类型,则会被处理成model的一个属性并返回给视图,该属性的名称为方法级的@ModelAttribute所标注的字段名(或者以返回类型的类名作为默认的属性名)。model隐含填充了命令对象以及标注了@ModelAttribute字段的存取器被调用所返回的值。

 

总结:

最常用的参数和返回类型应该是Spring MVC给你提供的抽象,比如Model、View和ModelView等概念,以及用来识别请求或者表示返回类型的标准,如@PathVariable和ResponseBody等,这些可以说时Spring MVC的核心。

另外有些参数类型可能会经常使用,比如需要访问HttpSession或者HttpServletRequest等原生的Servlet API,则直接声明该类型的一个参数即可。

以上类型即使在一个大型的Spring MVC项目中也不一定都需要用到。但是其中一些特性在特定的场景下非常有用。

比如,如果控制器希望获取当前URL的主机名、端口号、资源类型(scheme)、上下文路径等信息,则在@RequestMapping方法的参数中增加一个org.springframework.web.util.UriComponentsBuilder类型即可。Spring MVC会将相关信息自动填充好,你直接使用即可。

 

8)模型绑定(数据绑定)

使用@ModelAttribute、Model、Map、@SessionAttributes能便捷地将我们的业务数据封装到模型里并交由视图解析调用。

1、@RequestParam绑定单个请求参数值

@RequestParam用于将请求参数区数据映射到功能处理方法的参数上。

public String requestparam1(@RequestParam String username)

请求中包含username参数(如/requestparam1?username=zhang),则自动传入。

public String requestparam2(@RequestParam("username") String username)

通过@RequestParam("username")明确告诉Spring Web MVC使用username进行入参。

接下来我们看一下@RequestParam注解主要有哪些参数:

value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的名字为username的参数的值将传入;

required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报404错误码;

defaultValue:默认值,表示如果请求中没有同名参数时的默认值,默认值可以是SpEL表达式,如“#{systemProperties['java.vm.version']}”。

public String requestparam4(@RequestParam(value="username",required=false) String username) 

表示请求中可以没有名字为username的参数,如果没有默认为null,此处需要注意如下几点:

原子类型:必须有值,否则抛出异常,如果允许空值请使用包装类代替。

Boolean包装类型类型:默认Boolean.FALSE,其他引用类型默认为null。

public String requestparam5(
@RequestParam(value="username", required=true, defaultValue="zhang") String username)
       

表示如果请求中没有名字为username的参数,默认值为“zhang”。

如果请求中有多个同名的应该如何接收呢?如给用户授权时,可能授予多个权限,首先看下如下代码:

public String requestparam7(@RequestParam(value="role") String roleList)

如果请求参数类似于url?role=admin&rule=user,则实际roleList参数入参的数据为“admin,user”,即多个数据之间使用“,”分割;我们应该使用如下方式来接收多个请求参数:

public String requestparam7(@RequestParam(value="role") String[] roleList)   

public String requestparam8(@RequestParam(value="list") List<String> list)   

 

2、@PathVariable绑定URI模板变量值

@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。

@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(
      @PathVariable(value="userId") int userId,
      @PathVariable(value="topicId") int topicId)      

如请求的URL为“控制器URL/users/123/topics/456”,则自动将URL中模板变量{userId}和{topicId}绑定到通过@PathVariable注解的同名参数上,即入参后userId=123、topicId=456。

 

3、@CookieValue绑定Cookie数据值

@CookieValue用于将请求的Cookie数据映射到功能处理方法的参数上。

public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId) 

如上配置将自动将JSESSIONID值入参到sessionId参数上,defaultValue表示Cookie中没有JSESSIONID时默认为空。

public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)       

传入参数类型也可以是javax.servlet.http.Cookie类型。

@CookieValue也拥有和@RequestParam相同的三个参数,含义一样。

 

4、@RequestHeader绑定请求头数据

@RequestHeader用于将请求的头信息区数据映射到功能处理方法的参数上。

@RequestMapping(value="/header")
public String test(
      @RequestHeader("User-Agent") String userAgent,
      @RequestHeader(value="Accept") String[] accepts)
       

如上配置将自动将请求头“User-Agent”值入参到userAgent参数上,并将“Accept”请求头值入参到accepts参数上。测试代码在HeaderValueTypeController中。

@RequestHeader也拥有和@RequestParam相同的三个参数,含义一样。

 

5、@ModelAttribute绑定请求参数到命令对象

@ModelAttribute一个具有如下三个作用:

①绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用;

②暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用;

③暴露@RequestMapping方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。

 

一、绑定请求参数到命令对象

如用户登录,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。

public String test1(@ModelAttribute("user") UserModel user)

它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。

绑定请求参数到命令对象支持对象图导航式的绑定,如请求参数包含“?username=zhang&password=123&workInfo.city=bj”自动绑定到user中的workInfo属性的city属性中。

@RequestMapping(value="/model2/{username}")
public String test2(@ModelAttribute("model") DataBinderTestModel model) {

URI模板变量也能自动绑定到命令对象中,当你请求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked”会自动绑定到命令对象上。

当URI模板变量和请求参数同名时,URI模板变量具有高优先权。

 

二、暴露表单引用对象为模型数据

@ModelAttribute("cityList")
public List<String> cityList() {
  return Arrays.asList("北京", "山东");
}

如上代码会在执行功能处理方法之前执行,并将其自动添加到模型对象中,在功能处理方法中调用Model 入参的containsAttribute("cityList")将会返回true。

@ModelAttribute("user")  //①
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
//TODO 去数据库根据用户名查找用户对象
UserModel user = new UserModel();
user.setRealname("zhang");
    return user;
}

如你要修改用户资料时一般需要根据用户的编号/用户名查找用户来进行编辑,此时可以通过如上代码查找要编辑的用户。

也可以进行一些默认值的处理。

@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model)

此处我们看到①和②有同名的命令对象,那Spring Web MVC内部如何处理的呢:

  (1、首先执行@ModelAttribute注解的方法,准备视图展示时所需要的模型数据;@ModelAttribute注解方法形式参数规则和@RequestMapping规则一样,如可以有@RequestParam等;

(2、执行@RequestMapping注解方法,进行模型绑定时首先查找模型数据中是否含有同名对象,如果有直接使用,如果没有通过反射创建一个,因此②处的user将使用①处返回的命令对象。即②处的user等于①处的user。

 

三、暴露@RequestMapping方法返回值为模型数据

public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

大家可以看到返回值类型是命令对象类型,而且通过@ModelAttribute("user2")注解,此时会暴露返回值到模型数据(名字为user2)中供视图展示使用。那哪个视图应该展示呢?此时Spring Web MVC会根据RequestToViewNameTranslator进行逻辑视图名的翻译。

此时又有问题了,@RequestMapping注解方法的入参user暴露到模型数据中的名字也是user2,其实我们能猜到:

(3、@ModelAttribute注解的返回值会覆盖@RequestMapping注解方法中的@ModelAttribute注解的同名命令对象。

 

四、匿名绑定命令参数

public String test4(@ModelAttribute UserModel user, Model model)

public String test5(UserModel user, Model model)

此时我们没有为命令对象提供暴露到模型数据中的名字,此时的名字是什么呢?Spring Web MVC自动将简单类名(首字母小写)作为名字暴露,如“cn.javass.chapter6.model.UserModel”暴露的名字为“userModel”。

public @ModelAttribute List<String> test6()

public @ModelAttribute List<UserModel> test7()

对于集合类型(Collection接口的实现者们,包括数组),生成的模型对象属性名为“简单类名(首字母小写)”+“List”,如List<String>生成的模型对象属性名为“stringList”,List<UserModel>生成的模型对象属性名为“userModelList”。

其他情况一律都是使用简单类名(首字母小写)作为模型对象属性名,如Map<String, UserModel>类型的模型对象属性名为“map”。

 

6、@SessionAttributes绑定命令对象到session

有时候我们需要在多次请求之间保持数据,一般情况需要我们明确的调用HttpSession的API来存取会话数据,如多步骤提交的表单。Spring Web MVC提供了@SessionAttributes进行请求间透明的存取会话数据。

//1、在控制器类头上添加@SessionAttributes注解
@SessionAttributes(value = {"user"})   //①
public class SessionAttributeController

//2、@ModelAttribute注解的方法进行表单引用对象的创建
@ModelAttribute("user")   //②
public UserModel initUser()

//3、@RequestMapping注解方法的@ModelAttribute注解的参数进行命令对象的绑定
@RequestMapping("/session1")   //③
public String session1(@ModelAttribute("user") UserModel user)

//4、通过SessionStatus的setComplete()方法清除@SessionAttributes指定的会话数据
@RequestMapping("/session2")   //③
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
  if(true) { //④
      status.setComplete();
  }
  return "success";
}

@SessionAttributes(value = {"user"})含义:

@SessionAttributes(value = {"user"}) 标识将模型数据中的名字为“user” 的对象存储到会话中(默认HttpSession),此处value指定将模型数据中的哪些数据(名字进行匹配)存储到会话中,此外还有一个types属性表示模型数据中的哪些类型的对象存储到会话范围内,如果同时指定value和types属性则那些名字和类型都匹配的对象才能存储到会话范围内。

 

包含@SessionAttributes的执行流程如下所示:

① 首先根据@SessionAttributes注解信息查找会话内的对象放入到模型数据中;

② 执行@ModelAttribute注解的方法:如果模型数据中包含同名的数据,则不执行@ModelAttribute注解方法进行准备表单引用数据,而是使用①步骤中的会话数据;如果模型数据中不包含同名的数据,执行@ModelAttribute注解的方法并将返回值添加到模型数据中;

③ 执行@RequestMapping方法,绑定@ModelAttribute注解的参数:查找模型数据中是否有@ModelAttribute注解的同名对象,如果有直接使用,否则通过反射创建一个;并将请求参数绑定到该命令对象;

此处需要注意:如果使用@SessionAttributes注解控制器类之后,③步骤一定是从模型对象中取得同名的命令对象,如果模型数据中不存在将抛出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)

或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)异常。

④ 如果会话可以销毁了,如多步骤提交表单的最后一步,此时可以调用SessionStatus对象的setComplete()标识当前会话的@SessionAttributes指定的数据可以清理了,此时当@RequestMapping功能处理方法执行完毕会进行清理会话数据。

 

我们通过Spring Web MVC的源代码验证一下吧,此处我们分析的是Spring3.1的RequestMappingHandlerAdapter,读者可以自行验证Spring3.0的AnnotationMethodHandlerAdapter,流程一样:

(1、RequestMappingHandlerAdapter.invokeHandlerMethod

//1、RequestMappingHandlerAdapter首先调用ModelFactory的initModel方法准备模型数据:
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
//2、调用@RequestMapping注解的功能处理方法
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
//3、更新/合并模型数据
modelFactory.updateModel(webRequest, mavContainer);

 

(2、ModelFactory.initModel

Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
//1.1、将与@SessionAttributes注解相关的会话对象放入模型数据中
mavContainer.mergeAttributes(attributesInSession);
//1.2、调用@ModelAttribute方法添加表单引用对象
invokeModelAttributeMethods(request, mavContainer);
//1.3、验证模型数据中是否包含@SessionAttributes注解相关的会话对象,不包含抛出异常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
      //1.4、此处防止在@ModelAttribute注解方法又添加了会话对象
      //如在@ModelAttribute注解方法调用session.setAttribute("user", new UserModel());
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}

 

(3、ModelFactory.invokeModelAttributeMethods

for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
  String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
  //1.2.1、如果模型数据中包含同名数据则不再添加
if (mavContainer.containsAttribute(modelName)) {
  continue;
}
//1.2.2、调用@ModelAttribute注解方法并将返回值添加到模型数据中,此处省略实现代码
}

 

(4、requestMappingMethod.invokeAndHandle 调用功能处理方法,此处省略

 

(5、ModelFactory.updateMode 更新模型数据

//3.1、如果会话被标识为完成,此时从会话中清除@SessionAttributes注解相关的会话对象
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
//3.2、如果会话没有完成,将模型数据中的@SessionAttributes注解相关的对象添加到会话中
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
}
//省略部分代码

到此@SessionAtrribute介绍完毕。

多步骤提交表单需要考虑会话超时问题,这种方式可能对用户不太友好,我们可以采取隐藏表单(即当前步骤将其他步骤的表单隐藏)或表单数据存数据库(每步骤更新下数据库数据)等方案解决。

 

7、@Value绑定SpEL表示式

@Value用于将一个SpEL表达式结果映射到到功能处理方法的参数上。

public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)

到此数据绑定我们就介绍完了。

 

9)视图解析器

当我们对SpringMVC控制的资源发起请求时,这些请求都会被SpringMVC的DispatcherServlet处理,接着spring会分析看哪一个HandlerMapping定义的所有请求映射中存在对该请求的最合理的映射。然后通过该HandlerMapping取得其对应的Handler,接着再通过相应的HandlerAdapter处理该Handler。HandlerAdapter在对Handler进行处理之后会返回一个ModelAndView对象。在获得了ModelAndView对象之后,Spring就需要把该View渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是ViewResolver和View。当Handler返回的ModelAndView中不包含真正的视图,只返回一个逻辑视图名称的时候,ViewResolver就会把该逻辑视图名称解析为真正的视图View对象。View是真正进行视图渲染,把结果返回给浏览器的。

SpringMVC用于处理视图最重要的两个接口是ViewResolver和View。ViewResolver的主要作用是把一个逻辑上的视图名称解析为一个真正的视图,SpringMVC中用于把View对象呈现给客户端的是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。View接口的主要作用是用于处理视图,然后返回给客户端。

 

Spring为我们提供了非常多的视图解析器,下面将列举一些视图解析器:

AbstractCachingViewResolver:这是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。

 

UrlBasedViewResolver:它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。如prefix=/WEB-INF/jsps/,suffix=.jsp,返回的视图名称viewName=test/indx,则UrlBasedViewResolver解析出来的视图URL就是/WEB-INF/jsps/test/index.jsp。默认的prefix和suffix都是空串。URLBasedViewResolver支持返回的视图名称中包含redirect:前缀,这样就可以支持URL在客户端的跳转,如当返回的视图名称是”redirect:test.do”的时候,URLBasedViewResolver发现返回的视图名称包含”redirect:”前缀,于是把返回的视图名称前缀”redirect:”去掉,取后面的test.do组成一个RedirectView,RedirectView中将把请求返回的模型属性组合成查询参数的形式组合到redirect的URL后面,然后调用HttpServletResponse对象的sendRedirect方法进行重定向。同样URLBasedViewResolver还支持forword:前缀,对于视图名称中包含forword:前缀的视图名称将会被封装成一个InternalResourceView对象,然后在服务器端利用RequestDispatcher的forword方式跳转到指定的地址。使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。下面是一段UrlBasedViewResolver的定义,根据该定义,当返回的逻辑视图名称是test的时候,UrlBasedViewResolver将把逻辑视图名称加上定义好的前缀和后缀,即“/WEB-INF/test.jsp”,然后新建一个viewClass属性指定的视图类型予以返回,即返回一个url为“/WEB-INF/test.jsp”的InternalResourceView对象。

 

InternalResourceViewResolver:它是URLBasedViewResolver的子类,所以URLBasedViewResolver支持的特性它都支持。在实际应用中InternalResourceViewResolver也是使用的最广泛的一个视图解析器。那么InternalResourceViewResolver有什么自己独有的特性呢?单从字面意思来看,我们可以把InternalResourceViewResolver解释为内部资源视图解析器,这就是InternalResourceViewResolver的一个特性。InternalResourceViewResolver会把返回的视图名称都解析为InternalResourceView对象,InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务器端把请求forword重定向到目标URL。比如在InternalResourceViewResolver中定义了prefix=/WEB-INF/,suffix=.jsp,然后请求的Controller处理器方法返回的视图名称为test,那么这个时候InternalResourceViewResolver就会把test解析为一个InternalResourceView对象,先把返回的模型属性都存放到对应的HttpServletRequest属性中,然后利用RequestDispatcher在服务器端把请求forword到/WEB-INF/test.jsp。这就是InternalResourceViewResolver一个非常重要的特性,我们都知道存放在/WEB-INF/下面的内容是不能直接通过request请求的方式请求到的,为了安全性考虑,我们通常会把jsp文件放在WEB-INF目录下,而InternalResourceView在服务器端跳转的方式可以很好的解决这个问题下面是一个InternalResourceViewResolver的定义,根据该定义当返回的逻辑视图名称是test的时候,InternalResourceViewResolver会给它加上定义好的前缀和后缀,组成“/WEB-INF/test.jsp”的形式,然后把它当做一个InternalResourceView的url新建一个InternalResourceView对象返回。

 

XmlViewResolver:它继承自AbstractCachingViewResolver抽象类,所以它也是支持视图缓存的。XmlViewResolver需要给定一个xml配置文件,该文件将使用和Spring的bean工厂配置文件一样的DTD定义,所以其实该文件就是用来定义视图的bean对象的。在该文件中定义的每一个视图的bean对象都给定一个名字,然后XmlViewResolver将根据Controller处理器方法返回的逻辑视图名称到XmlViewResolver指定的配置文件中寻找对应名称的视图bean用于处理视图。该配置文件默认是/WEB-INF/views.xml文件,如果不使用默认值的时候可以在XmlViewResolver的location属性中指定它的位置。XmlViewResolver还实现了Ordered接口,因此我们可以通过其order属性来指定在ViewResolver链中它所处的位置,order的值越小优先级越高。

 

ResourceBundleViewResolver:它和XmlViewResolver一样,也是继承自AbstractCachingViewResolver,但是它缓存的不是视图,这个会在后面有说到。和XmlViewResolver一样它也需要有一个配置文件来定义逻辑视图名称和真正的View对象的对应关系,不同的是ResourceBundleViewResolver的配置文件是一个属性文件,而且必须是放在classpath路径下面的,默认情况下这个配置文件是在classpath根目录下的views.properties文件,如果不使用默认值的话,则可以通过属性baseName或baseNames来指定。baseName只是指定一个基名称,Spring会在指定的classpath根目录下寻找以指定的baseName开始的属性文件进行View解析,如指定的baseName是base,那么base.properties、baseabc.properties等等以base开始的属性文件都会被Spring当做ResourceBundleViewResolver解析视图的资源文件。

 

FreeMarkerViewResolver、VolocityViewResolver:这两个视图解析器都是UrlBasedViewResolver的子类。FreeMarkerViewResolver会把Controller处理方法返回的逻辑视图解析为FreeMarkerView,而VolocityViewResolver会把返回的逻辑视图解析为VolocityView。因为这两个视图解析器类似,所以这里我就只挑FreeMarkerViewResolver来做一个简单的讲解。FreeMarkerViewResolver和VilocityViewResolver都继承了UrlBasedViewResolver。对于FreeMarkerViewResolver而言,它会按照UrlBasedViewResolver拼接URL的方式进行视图路径的解析。但是使用FreeMarkerViewResolver的时候不需要我们指定其viewClass,因为FreeMarkerViewResolver中已经把viewClass定死为FreeMarkerView了。

 

视图解析链:

在SpringMVC中可以同时定义多个ViewResolver视图解析器,然后它们会组成一个ViewResolver链。当Controller处理器方法返回一个逻辑视图名称后,ViewResolver链将根据其中ViewResolver的优先级来进行处理。所有的ViewResolver都实现了Ordered接口,在Spring中实现了这个接口的类都是可以排序的。在ViewResolver中是通过order属性来指定顺序的,默认都是最大值。所以我们可以通过指定ViewResolver的order属性来实现ViewResolver的优先级,order属性是Integer类型,order越小,对应的ViewResolver将有越高的解析视图的权利,所以第一个进行解析的将是ViewResolver链中order值最小的那个。当一个ViewResolver在进行视图解析后返回的View对象是null的话就表示该ViewResolver不能解析该视图,这个时候如果还存在其他order值比它大的ViewResolver就会调用剩余的ViewResolver中的order值最小的那个来解析该视图,依此类推。当ViewResolver在进行视图解析后返回的是一个非空的View对象的时候,就表示该ViewResolver能够解析该视图,那么视图解析这一步就完成了,后续的ViewResolver将不会再用来解析该视图当定义的所有ViewResolver都不能解析该视图的时候,Spring就会抛出一个异常。

基于Spring支持的这种ViewResolver链模式,我们就可以在SpringMVC应用中同时定义多个ViewResolver,给定不同的order值,这样我们就可以对特定的视图特定处理,以此来支持同一应用中有多种视图类型。注意:像InternalResourceViewResolver这种能解析所有的视图,即永远能返回一个非空View对象的ViewResolver一定要把它放在ViewResolver链的最后面

 

10)redirectAttributes与flashAttributes

Spring MVC 3.1版本加了一个很有用的特性,Flash属性,它能解决一个长久以来缺少解决的问题,一个POST/Redirect/GET模式问题。

通常当我们生成一次http重定向请求的时候,被存储到请求数据会丢失,使得下一次GET请求不可能访问到这次请求中的一些有用的信息。

Flash attributes 的到来就是为了处理这一情况, Flash attributes 为一个请求存储意图为另外一个请求所使用的属性提供了一条途径, Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除。

为了这样做,,Flash 特性使用了两个集合, FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体。

对于每一次请求一个 “input” flash map 会被创建,来存储来自任何之前请求的,flash attribute 还有一个 “output” flash map 会被创建,来存储任何我们存储在这个请求中的之后的请求参数。

要想在你的 Spring MVC 应用中使用 Flash attribute,要用 3.1 版本或以上。并且要在 spring-servlet.xml 文件中加入 mvc:annotation-driven。

<mvc:annotation-driven />

这些都完成之后,Flash attribute 就会自动设为“开启”,以供使用了。只需在你的 Spring controller 方法中加入RedirectAttributes redirectAttributes。

import org.springframework.web.servlet.mvc.support.RedirectAttributes;
//...

@RequestMapping(value="addcustomer", method=RequestMethod.POST)
public String addCustomer(@ModelAttribute("customer") Customer customer,
final RedirectAttributes redirectAttributes) {
//...
redirectAttributes.addFlashAttribute("message", "Successfully added..");
//...

return "redirect:some_other_request_name";
}

addFlashAttribute 方法会自动向 output flash map 中添加给定的参数,并将它传递给后续的请求。

 

11)springmvc异常处理

Spring MVC处理异常有3种方式:

(1) 使用Spring MVC提供的简单异常处理器 SimpleMappingExceptionResolver;

(2) 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;

(3) 使用@ExceptionHandler注解实现异常处理;

 

自定义登录异常:

 

/**
 * @author: 肖德子裕
 * @date: 2018/11/05 11:26
 * @description: 自定义登录异常
 */
public class UserException extends RuntimeException{
    public UserException(String message) {
        super(message);
    }
​
    public UserException(String message, Throwable cause) {
        super(message, cause);
    }
​
    public UserException(Throwable cause) {
        super(cause);
    }
}

 


​全局异常处理:
/**
 * @author: 肖德子裕
 * @date: 2018/11/05 11:34
 * @description: 全局异常处理
 */
@ControllerAdvice("com.ex.controller")
public class ExceptionAdvice {
    /**
     * 全局异常处理方法
     * 用于处理用户登陆异常
     * @param e
     * @return
     */
    @ExceptionHandler(UserException.class)
    @ResponseBody
    public ResponseVO handlerLoginException(RuntimeException e){
        ResponseVO vo = new ResponseVO();
        vo.setCode(401);
        vo.setMessage(e.getMessage());
        return vo;
    }
​
    /**
     * 全局异常处理方法
     * 用于处理crud操作
     * @param e
     * @return
     */
    @ExceptionHandler(DataAccessException.class)
    @ResponseBody
    public ResponseVO handlerDataAccessException(RuntimeException e){
        ResponseVO vo = new ResponseVO();
        vo.setCode(500);
        vo.setMessage(e.getMessage());
        return vo;
    }
}

​用户操作Service
/**
 * @author: 肖德子裕
 * @date: 2018/11/05 11:21
 * @description: 用户操作Servie
 */
@Service
public class UserServiceImpl implements UserServie {
    @Autowired
    private UserDao userDao;
​
    @Override
    public User login(User user) {
        User newUser = userDao.getUserByName(user.getUserName());
        if(newUser == null){
            throw new UserException("用户不存在");
        }
        if(!user.getPassWord().equals(newUser.getPassWord())){
            throw new UserException("密码错误");
        }
        return newUser;
    }
​
    @Override
    public void addUser(User user) {
        userDao.saveUser(user);
    }
}

12)过滤器与拦截器

过滤器的实现:

/**
* @author: 肖德子裕
* @date: 2018/8/21 10:29
* @description: 权限过滤器
*/
public class UserLoginPrivilegeFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {
      HttpServletRequest req=(HttpServletRequest)request;
      HttpServletResponse resp=(HttpServletResponse)response;

      //效验用户是否登陆,即检查session是否存在用户对象
      HttpSession session=req.getSession();

      //判断用户是否登陆,没登陆不能提交订单
      User user=(User)session.getAttribute("user");
      if (user==null){
          //跳转到登陆页
          resp.sendRedirect(req.getContextPath()+"/login.jsp");
          return;
      }
      //放行
      chain.doFilter(req, response);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void destroy() {

  }
}

需要在xml文件中配置:

<!-- 用户是否登陆 -->
<filter>
  <filter-name>UserLoginPrivilegeFilter</filter-name>
  <filter-class>com.itheima.web.filter.UserLoginPrivilegeFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>UserLoginPrivilegeFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

拦截器的实现:

/**
* @author: 肖德子裕
* @date: 2018/9/12 15:25
* @description: 拦截器类1
*/
public class Interceptor1 implements HandlerInterceptor{

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
    // TODO Auto-generated method stub
    System.out.println("方法前 1");
    //判断用户是否登陆 如果没有登陆 重定向到登陆页面   不放行   如果登陆了 就放行了
    // URL http://localhost:8080/springmvc-mybatis/login.action
    //URI /login.action
    String requestURI = request.getRequestURI();
    if(!requestURI.contains("/login")){
        String username = (String) request.getSession().getAttribute("USER_SESSION");
        if(null == username){
          response.sendRedirect(request.getContextPath() + "/item/login.action");
          //不放行
          return false;
        }
    }
    //放行
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
        throws Exception {
    // TODO Auto-generated method stub
    System.out.println("方法后 1");
  }

  @Override
  public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
        throws Exception {
    // TODO Auto-generated method stub
    System.out.println("页面渲染后 1");
  }
}

需要在springmvc中配置:

<!-- Springmvc的拦截器 -->
<mvc:interceptors>
  <!-- 多个拦截器;运行时及其复杂 -->
  <mvc:interceptor>
      <mvc:mapping path="/**"/>
      <!-- 自定义的拦截器类 -->
      <bean class="com.interceptor.Interceptor1"/>
  </mvc:interceptor>
  <!--     <mvc:interceptor>
              <mvc:mapping path="/**"/>
              自定义的拦截器类
              <bean class="com.interceptor.Interceptor2"/>
          </mvc:interceptor> -->
</mvc:interceptors>

拦截规范:

  1. /* 拦截所有 jsp js png css 真的全拦截 建议不使用

  2. *.action *.do 拦截以do action 结尾的请求 肯定能使用

  3. / 拦截所有 (不包括jsp) (包含.js .png.css) 强烈建议使用

原文地址:https://www.cnblogs.com/xdzy/p/10485467.html