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

第七章: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) 强烈建议使用

复习宝典之SpringMVC的更多相关文章

  1. 复习宝典之Mysql数据库

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第一章:mysql数据库 1)mysql与mariaDb MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用 ...

  2. 复习宝典之Maven项目管理

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第二章:Maven项目管理 Maven是基于项目对象模型(POM project object model),可以通过一小段描述信息(配 ...

  3. 复习宝典之Redis

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第八章:Redis Redis是一个key-value的nosql数据库.先存到内存中,会根据一定的策略持久化到磁盘,即使断电也不会丢失 ...

  4. 复习宝典之Spring

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第六章:Spring Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管 ...

  5. 复习宝典之MyBatis

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第五章:MyBatis MyBatis是一个可以自定义SQL.存储过程和高级映射的持久层框架. 1)创建sqlsession的流程 my ...

  6. 复习宝典之Git分布式版本控制

    查看更多宝典,请点击<金三银四,你的专属面试宝典> 第三章:Git分布式版本控制 1)git文件状态 git中的文件有以下几种状态: 未跟踪(untrack):表示文件为新增加的. 已修改 ...

  7. Maven搭建SpringMVC + SpringJDBC项目详解

    前言 上一次复习搭建了SpringMVC+Mybatis,这次搭建一下SpringMVC,采用的是SpringJDBC,没有采用任何其他的ORM框架,SpringMVC提供了一整套的WEB框架,所以如 ...

  8. SpringMVC,采用的是SpringJDBC

    上一次复习搭建了SpringMVC+Mybatis,这次搭建一下SpringMVC,采用的是SpringJDBC,没有采用任何其他的ORM框 架,SpringMVC提供了一整套的WEB框架,所以如果想 ...

  9. 复习一下SpringMVC的工作原理

    上面的是springMVC的工作原理图: 1.客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中 ...

随机推荐

  1. CSS3,transform3D立体可拖拽正方体实现原理

    ---恢复内容开始--- 今天咱们来说一下,CSS中的3D效果 .如果你把transform真的掌握的和纯熟的话,就可以直接通过CSS做出很多很炫酷的效果,甚至.轮播图和选项卡都可以通过CSS来做,咱 ...

  2. 通过JTS源码分析Rtree(未完待续)

    前言 R树在数据库等领域做出的功绩是非常显著的.它很好的解决了在高维空间搜索等问题.它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想,并在添加.删除操作时采用合并.分解结点的方法,保证树 ...

  3. hdu 3613 Best Reward (manachar算法)

    Best Reward Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Prob ...

  4. velecity报错:Caused by: org.apache.velocity.exception.ParseErrorException: Lexical error, Encountered: <EOF> after : "\'/order/pay?activity=\" + activityId);\r\n }*/\r\n</script>\r\n#end\r\n" at /a

    Caused by: org.apache.velocity.exception.ParseErrorException: Lexical error, Encountered: <EOF> ...

  5. css tips: 清除float影响,containing的div跟随floated sub等

    /** * For modern browsers * 1. The space content is one way to avoid an Opera bug when the * content ...

  6. 【Leetcode】【Medium】Combinations

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exampl ...

  7. 使用MongoDB血泪般的经验教训

    故事背景,天书世界,现在项目已经属于成熟维护期,是时候总结一下当时的想法 第一个问题,为什么使用mongodb? 数据库对于游戏项目本身的要求与传统业务系统差异较大,所以nosql的弱结构性对于我那是 ...

  8. 1874 football game(三分法and method to compute the area of trianngle)

    FInd the max area. 1. 三分法 2. NAN (not comparable with number) http://acm.timus.ru/problem.aspx?space ...

  9. python UI自动化实战记录八:添加配置

    添加配置文件写入测试地址等,当环境切换时只需修改配置文件即可. 1 在项目目录下添加文件 config.ini 写入: [Domain] domain = http://test.domain.cn ...

  10. 两种MVC框架比较

    基于Web的MVC framework在J2EE的世界内已是空前繁荣. TTS网站上几乎每隔一两个星期就会有新的MVC框架发布.目前比较好的MVC,老牌的有Struts.Webwork.新兴的MVC框 ...