Spring(4)——Spring MVC

Spring MVC请求流程

1、Spring MVC请求流程

  • (1)初始化:(对DispatcherServlet和ContextLoderListener进行初始化),读取web.xml,根据web.xml中指定的xml配置文件(spring/mvc.xml | spring/app.xml),引入MVC相关配置(路由映射规则、视图模板配置等);引入应用上下文Context相关配置(数据源、DAO、Service等非web层组件)
  • (2)客户端http请求
  • (3)请求交由前端控制器DispatcherServlet处理,DispatcherServlet根据Request的url在HandlerMapping中找到对应的HandlerExecutionChain
  • (4)在执行链HandlerExecutionChain中找到HandlerAdapter;这里为什么不是 DispatcherServlet 直接把请求传递给Handler(即某某Controller),而是给HandlerAdapter代理呢?因为controller有多种实现方式,HandlerAdapter从中间代理可以简化很多操作,比如数据绑定、数据校验等;
  • (5)HandlerAdapter进行数据适配,将请求传递给具体的Controller;
  • (6)由我们写的Controller进行数据操作,得到model后进行返回;这里还要经过ReturnValueHandler和HttpMessageConvert处理,如果controller返回的是json数据而不需要进行视图渲染,则在ReturnValueHandler中进行设置
  • (7)根据视图模型名称传递给DispatcherServlet
  • (8)将ModelAndView传给ViewResolver进行视图解析
  • (9)视图解析器返回解析好的视图模板文件
  • (10)将视图模板文件传递给View进行处理
  • (11)视图渲染
  • (12)响应请求

2、配置文件结构(建议)

3、笔记

1
2
3

4、注解

参考: Spring 注解总结

为什么会有注解?

注解可以简化借助xml对bean的配置工作:通过在类、方法上加上注解和相应的注解属性,再配置spring要扫描的包路径,spring将会把合适的java类全部注册成spring Bean。

注解实现Bean配置主要用来进行如依赖注入、生命周期回调方法定义等,不能消除XML文件中的Bean元数据定义,且基于XML配置中的依赖注入的数据将覆盖基于注解配置中的依赖注入的数据。

Spring3支持的注解类型

Spring3的基于注解实现Bean依赖注入支持如下4种注解:

  • Spring自带依赖注入注解: Spring自带的一套依赖注入注解;
    @Required:依赖检查;
    @Autowired:自动装配,用于替代基于XML配置的自动装配;基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入
    @Value:注入SpEL表达式;用于注入SpEL表达式,可以放置在字段方法或参数上

    @Value(value = "SpEL表达式")  
    @Value(value = "#{message}")  
    

    @Qualifier限定描述符除了能根据名字进行注入,还能进行更细粒度的控制如何选择候选者

    @Qualifier(value = "限定标识符") 
    
  • JSR-250注解:Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持;
    @Resource:自动装配,默认根据类型装配,如果指定name属性将根据名字装配,可以使用如下方式来指定字段或setter方法:.

    @Resource(name = "标识符")  
    

    @PostConstruct和PreDestroy:通过注解指定初始化和销毁方法定义

  • JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,可能在加入到未来JDK版本,从Spring3开始支持;
    @Inject:等价于默认的@Autowired,只是没有required属性
    @Named:指定Bean名字,对应于Spring自带@Qualifier中的缺省的根据Bean名字注入情况
    @Qualifier:只对应于Spring自带@Qualifier中的扩展@Qualifier限定描述符注解,即只能扩展使用,没有value属性

  • JPA注解: JPA全称Java Persistence API,JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。用于注入持久化上下文和实体管理器。参考:JPA常用注解
    @Entity 标识这个pojo是一个jpa实体
    @Entity @Table(name = "users") 指定表名为users
    @Id 注解在属性上,设为主键
    @Column 设置字段类型
    @OrderBy 字段排序
    @GeneratedValue 主键生成策略
    @PersistenceContext 用于注入EntityManagerFactory和EntityManager
    @PersistenceUnit
    ... ...

这4种类型的注解在Spring3中都支持,类似于注解事务支持,想要使用这些注解需要在Spring容器中**开启注解驱动,使用<context:annotation-config />简化配置 **:

Spring2.1添加了一个新的context的Schema命名空间,该命名空间对注释驱动、属性文件引入、加载期织入等功能提供了便捷的配置。我们知道**注释本身是不会做任何事情的,它仅提供元数据信息。要使元数据信息真正起作用,必须让负责处理这些元数据的处理器工作起来。 **
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor就是处理这些注释元数据的处理器。但是直接在Spring配置文件中定义这些Bean显得比较笨拙。Spring为我们提供了一种方便的注册这些BeanPostProcessor的方式,这就是<context:annotation-config />:

<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"    
xsi:schemaLocation="http://www.springframework.org/schema/beans    
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd    
http://www.springframework.org/schema/context    
http://www.springframework.org/schema/context/spring-context-2.5.xsd">    
    <context:annotation-config />    
</beans> 
@Autowired,@Qualifier,@Resource

上面三个注解的作用都是对bean进行自动装配;所谓自动装配,就是容器自动配置bean,而不需手动显示配置。
自动装配有4种类型(除了no即不自动装配):

  • byName:根据与bean的属性具有相同name(或者id)的其他bean进行注入
  • byType: 根据与bean的属性具有相同类型的其他bean进行注入
  • constructor:根据与bean的构造函数参数有相同类型的bean进行注入
  • autodetect : 首先尝试使用constructor进行注入,失败则尝试使用byType。

@Autowired:

  • 可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作
  • byType进行自动装配,如果出现多个相同的类型,就会抛出BeanCreationException异常,我们可以使用@Qualifier配合@Autowired来解决这些问题。

@Qualifier:进行更细粒度的“候选bean”控制

// 可能存在多个UserDao实例 :这样,Spring会找到id为userDao的bean进行装配。 
@Autowired    
public void setUserDao(@Qualifier("userDao") UserDao userDao) {    
    this.userDao = userDao;    
} 

// 可能不存在UserDao实例 
@Autowired(required = false)    
public void setUserDao(UserDao userDao) {    
      this.userDao = userDao;    
}    

@Resource:推荐使用它来代替Spring专有的@Autowired注解,因为:

  • 它默认按byName自动注入;
  • @Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
  • 装配顺序
    如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
    如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常;
    如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
    如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配
@PostConstruct

在方法上加上注解@PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行(注:Bean初始化包括,实例化Bean,并装配Bean的属性(依赖注入))。

它的一个典型的应用场景是,当你需要往Bean里注入一个其父类中定义的属性,而你又无法复写父类的属性或属性的setter方法时,如:

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {    
    private SessionFactory mySessionFacotry;    
    @Resource    
    public void setMySessionFacotry(SessionFactory sessionFacotry) {    
        this.mySessionFacotry = sessionFacotry;    
    }    
    @PostConstruct    
    public void injectSessionFactory() {    
        super.setSessionFactory(mySessionFacotry);    
    }    
    ...    
}   
这里通过@PostConstruct,为UserDaoImpl的父类里定义的一个sessionFactory私有属性,
注入了我们自己定义的sessionFactory(父类的setSessionFactory方法为final,不可复写),
之后我们就可以通过调用super.getSessionFactory()来访问该属性了。
@PreDestroy

在方法上加上注解@PreDestroy,这个方法就会在Bean初始化之后被Spring容器执行。由于我们当前还没有需要用到它的场景,这里不不去演示。其用法同@PostConstruct。


以上我们介绍了通过@Autowired或@Resource来实现在Bean中自动注入的功能,下面我们将介绍如何注解Bean,从而从XML配置文件中完全移除Bean定义的配置

@Component(不推荐使用)、@Repository、@Service、@Controller

只需要在对应的类上加上一个@Component注解,就将该类定义为一个Bean了:

@Component    
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {    
      ...    
}   

使用@Component注解定义的Bean,默认的名称(id)是小写开头的非限定类名。如这里定义的Bean名称就是userDaoImpl。你也可以指定Bean的名称:

  • @Component("userDao")

@Component是所有受Spring管理组件的通用形式

Spring还提供了更加细化的注解形式:

  • @Repository 对应存储层Bean
  • @Service 对应业务层Bean
  • @Controller 对应展示层Bean(页面控制器)

这些注解与@Component的语义是一样的,完全通用,在Spring以后的版本中可能会给它们追加更多的语义。所以,我们推荐使用@Repository、@Service、@Controller来替代@Component。

使用<context:component-scan />让Bean定义注解工作起来
<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"    
xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd    
    http://www.springframework.org/schema/context    
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">    
      <context:component-scan base-package="com.kedacom.ksoa" />    
</beans>   

从上面的几个注解可以看到:原先需要在xml配置文件中定义的bean,现在不需要定义了,而是直接写在了注解里;所有通过<bean>元素定义Bean的配置内容已经被移除,如果使其生效,仅需要添加一行<context:component-scan />配置就解决所有问题了——Spring XML配置文件得到了极致的简化(当然配置元数据还是需要的,只不过以注释形式存在罢了)。<context:component-scan />的base-package属性指定了需要扫描的类包,类包及其递归子包中所有的类都会被处理。

<context:component-scan />还允许定义过滤器将基包下的某些类纳入或排除。Spring支持以下4种类型的过滤方式:
(过滤器类型)(表达式范例)(说明)
注解---org.example.SomeAnnotation---将所有使用SomeAnnotation注解的类过滤出来
类名指定---org.example.SomeClass---过滤指定的类
正则表达式---com.kedacom.spring.annotation.web..---通过正则表达式过滤一些类
AspectJ表达式---org.example..
Service+---通过AspectJ表达式过滤一些类

值得注意的是<context:component-scan />配置项不但启用了对类包进行扫描以实施注释驱动Bean定义的功能,同时还启用了注释驱动自动注入的功能(即还隐式地在内部注册了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor),因此当使用<context:component-scan />后,就可以将<context:annotation-config />移除了

@ModelAttribute

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

  • ① 绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用
    public String test1(@ModelAttribute("user") UserModel user)
    只是此处多了一个注解@ModelAttribute("user"),
    它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。
    我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。

  • ②暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping 注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用;
    /**
    * 设置这个注解之后可以直接在前端页面使用hb这个对象(List)集合
    * @return
    */
    @ModelAttribute("hb")
    public List<String> hobbiesList(){
    List<String> hobbise = new LinkedList<String>();
    hobbise.add("basketball");
    hobbise.add("football");
    hobbise.add("tennis");
    return hobbise;
    }

    JSP页面展示出来:
    <br>  
      初始化的数据 :    ${hb }  
    <br>  
    
    <c:forEach items="${hb}" var="hobby" varStatus="vs">  
    <c:choose>  
    <c:when test="${hobby == 'basketball'}">  
        篮球<input type="checkbox" name="hobbies" value="basketball">  
    </c:when>  
    <c:when test="${hobby == 'football'}">  
        足球<input type="checkbox" name="hobbies" value="football">  
    </c:when>  
    <c:when test="${hobby == 'tennis'}">  
        网球<input type="checkbox" name="hobbies" value="tennis">  
    </c:when>  
      </c:choose>  
    </c:forEach>  
    
  • ③暴露@RequestMapping 方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。
    public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

5、Controller的请求映射与RESTful模式

RESTful

REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。

原则条件:

REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联。

分层系统:

另一个重要的 REST 原则是分层系统,这表示组件无法了解它与之交互的中间层以外的组件。通过将系统知识限制在单个层,可以限制整个系统的复杂性,促进了底层的独立性。
当 REST 架构的约束条件作为一个整体应用时,将生成一个可以扩展到大量客户端的应用程序。它还降低了客户端和服务器之间的交互延迟。统一界面简化了整个系统架构,改进了子系统之间交互的可见性。REST 简化了客户端和服务器的实现。

我的理解:既然HTTP请求无状态 -> 那么客户端请求如何映射到服务器资源? -> 服务端通过路径+请求方法+参数+提交的内容类型+返回的内容类型+Header+...来进行地址映射,确定一个唯一的响应接口。

在Controller中使用@RequestMapping进行地址映射

参考:
@RequestMapping 用法详解之地址映射
@RequestParam @RequestBody @PathVariable 等参数绑定注解详解

@RequestMapping是一个用来处理请求地址映射的注解,可用于类(Controller Bean)或该类的方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径;用于方法上,表示方法响应的子路径以及要响应的请求需要具备哪些条件(-->RESTful)。

在具体的方法参数里,可以使用@RequestParam、 @RequestBody、 @RequestHeader 、 @PathVariable进行参数绑定,将请求参数作为方法参数使用(实际上是对Servlet做了封装,很酷)

// 例子
@Controller
@RequestMapping("/demo")
public class DemoModelController {

/**
 * 列表查询
 * 
 * @param map the map
 * @param rowBounds the row bounds
 * @return the list
 */
@RequestMapping(value = "/list",
        method = {RequestMethod.GET},
        consumes = {MediaType.ALL_VALUE},
        produces = {MediaType.TEXT_HTML_VALUE})
@ModelAttribute("list")
public List<DemoModel> listView(@RequestParam Map<String,String> map,
                                RowBounds rowBounds) {
    if(rowBounds!=null){
        LOG.info("rowbunds.offset = {}",rowBounds.getOffset());
        LOG.info("rowbunds.limit = {}",rowBounds.getLimit());
    }
    if(map!=null){
        LOG.info("map = {}",map);
    }
    return demoModelService.findAll(rowBounds);
}
@RequestMapping注解有六个属性

这6个属性可以用来将请求“过滤”(或者说映射)到具体的方法,下面我们把这6个属性分成三类进行说明:

  • value和method
    value: 指定请求的实际地址,指定的地址可以是URI Template 模式:

      value的uri值为以下三类:
      A) 可以指定为普通的具体值,如 /demo;
      B) 可以指定为含有某变量的一类值(URI Template Patterns with Path Variables),如 /{id},其中id为log类型;
      C) 可以指定为含正则表达式的一类值( URI Template Patterns with Regular Expressions);
    

method: 指定请求的method类型, GET、POST、PUT、DELETE等

    @RequestMapping(value="/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
      return appointmentBook.getAppointmentsForDay(day);
    }
  • consumes和produces
    consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
    produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;

      // cousumes的样例:方法仅处理request Content-Type为“application/json”类型的请求。
      @Controller
      @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
      public void addPet(@RequestBody Pet pet, Model model) {    
          // implementation omitted
      }
    
      produces的样例:方法仅处理request请求中Accept头中包含了"application/json"的请求,同时暗示了返回的内容类型为application/json
      @Controller
      @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
      @ResponseBody
      public Pet getPet(@PathVariable String petId, Model model) {    
          // implementation omitted
      }
    
  • params和headers
    params: 指定request中必须包含某些参数值是,才让该方法处理。

  • params 为请求参数的数组 支持一些简单的表达式
    params={"name", "!id", "name!=James"} 表示必须携带name参数 / 不能带名称为id的参数 , 而且name的值不能为James 等等表达式
    params = {"name=kobe", "number=23"}) 否则 404错误

headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。

    // params的样例:
    @Controller
    @RequestMapping("/owners/{ownerId}")
    public class RelativePathUriTemplateController {
      @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
      public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {    
        // implementation omitted
      }
    }

    // headers的样例:
    @Controller
    @RequestMapping("/owners/{ownerId}")
    public class RelativePathUriTemplateController {
    @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
      public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {    
        // implementation omitted
      }
    }
参数绑定

下面主要讲解request数据到处理方法参数数据的绑定所用到的注解和什么情形下使用。

  • @PathVariable
    处理requet uri 部分(这里指uri template中variable,不含queryString部分)
    当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。
    @Controller
    @RequestMapping("/owners/{ownerId}")
    public class RelativePathUriTemplateController {
    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    // implementation omitted
    }
    }

  • @RequestHeader, @CookieValue
    处理request header部分,可以把Request请求header部分的值绑定到方法的参数上;可以使用Map来接收整个Header、Cookie,也可以绑定具体的值:

    @RequestMapping(value = "/list")
    @ModelAttribute("list")
    public List<DemoModel> listView(@RequestHeader Map<String,String> map) {
      for(Map.Entry entry : map.entrySet()){
          System.out.println(entry.getKey() + " <---> " + entry.getValue());
      }
    }
    
    @RequestMapping("/displayHeaderInfo.do") 
    public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive)  { 
      //...  
    } 
    
  • @RequestParam, @RequestBody;
    处理request body部分

    @RequestMapping(method = RequestMethod.GET) 
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) { 
        Pet pet = this.clinic.loadPet(petId); 
        model.addAttribute("pet", pet); 
        return "petForm"; 
    } 
    
  • @SessionAttributes, @ModelAttribute
    处理attribute类型

     @RequestMapping("/editPet.do") 
     @SessionAttributes("pet") 
     public class EditPetForm { 
        // ... 
     }
    

6、Controller如何获取Request参数

  • 1、利用原有的Servlet方法,使用HttpServletRequest

  • 2、利用@RequestParam注解
    @RequestParam("username")String name
    当username在request中不存在,会抛出异常,可以使用@RequestParam(value="username" required=false default=" 默认值")这样请求有值就取,没有值就不取。

  • 3、使用实体类封装
    将实体类(需要具有setter,getter方法)作为Controller方法的参数,请求参数与实体类的属性保持一致,则会完成自动绑定,将请求参数自动绑定到这个实体类上,方法直接使用即可。

7、请求拦截

Spring MVC中通过配置<mvc:interceptors>来设置拦截方式,其子标签<mvc:interceptor>下有3种子标签来配置拦截方式:

<mvc:interceptors>
  <!-- 日志拦截器 -->
  <mvc:interceptor>
  <mvc:mapping path="/**"/>
  <mvc:exclude-mapping path="/static/**" />
  <bean class="HandlerInterceptor拦截器" />
  </mvc:interceptor>
</mvc:interceptors>
  • mvc:mapping 拦截器路径配置

要进行拦截的路径

  • mvc:exclude-mapping 拦截器不需要拦截的路径

例如资源文件等不需要进行拦截的,可以在这里进行排除

  • <bean class="HandlerInterceptor拦截器" />

SpringMVC中使用Interceptor拦截器

SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那样子判断当前时间是否是购票时间。

(1)定义Interceptor实现类

Spring MVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的。在Spring MVC 中定义一个Interceptor 非常简单,主要有两种方式,第一种方式是要定义的Interceptor类要实现了Spring 的HandlerInterceptor 接口,或者是这个类继承实现了HandlerInterceptor 接口的类,比如Spring 已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter ;第二种方式是实现Spring的WebRequestInterceptor接口,或者是继承实现了WebRequestInterceptor的类。

(2)实现HandlerInterceptor接口

HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法

顾名思义,该方法将在请求处理之前进行调用。Spring MVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。

(2)postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法

由preHandle 方法的解释我们知道这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行,这和Struts2 里面的Interceptor 的执行过程有点类型。Struts2 里面的Interceptor 的执行过程也是链式的,只是在Struts2 里面需要手动调用ActionInvocation 的invoke 方法来触发对下一个Interceptor 或者是Action 的调用,然后每一个Interceptor 中在invoke 方法调用之前的内容都是按照声明顺序执行的,而invoke 方法之后的内容就是反向的。

(3)afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle,
Exception ex) 方法

该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

    /**
     * 例子
     * version    date      author
     * ──────────────────────────────────
     * 1.0       17-3-17   wanlong.ma
     * Description: 拦截器 【拦截所有的请求,并通过logger的方式打印到控制台上,每次请求的ip地址,格式为request ip:{ip}】
     * Others:
     * Function List:
     * History:
     */
    public class RequestIpHandlerInterceptor extends HandlerInterceptorAdapter {
        private static Logger logger = LoggerFactory.getLogger(RequestIpHandlerInterceptor.class);

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String ip = RequestUtils.getRemoteHost(request);
            logger.info("request ip:{{}}",ip);
            return true;
        }
    }
  • 拦截器不拦截静态资源的三种处理方式

SpringMVC 拦截器不拦截静态资源的三种处理方式

增加拦截器之后,如果不进行相关设置,那么一些不需要拦截的对静态资源文件的请求也会被拦截。

方案一、拦截器中增加针对静态资源不进行过滤(涉及spring-mvc.xml)

<!--静态资源文件路径配置-->
<mvc:resources location="/" mapping="/**/*.js"/>
<mvc:resources location="/" mapping="/**/*.css"/>
<mvc:resources location="/assets/" mapping="/assets/**/*"/>
<mvc:resources location="/images/" mapping="/images/*" cache-period="360000"/>

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**/*"/>
        <!--不拦截这些静态资源请求-->
        <mvc:exclude-mapping path="/**/fonts/*"/>
        <mvc:exclude-mapping path="/**/*.css"/>
        <mvc:exclude-mapping path="/**/*.js"/>
        <mvc:exclude-mapping path="/**/*.png"/>
        <mvc:exclude-mapping path="/**/*.gif"/>
        <mvc:exclude-mapping path="/**/*.jpg"/>
        <mvc:exclude-mapping path="/**/*.jpeg"/>
        <mvc:exclude-mapping path="/**/*login*"/>
        <mvc:exclude-mapping path="/**/*Login*"/>
        <bean class="com.luwei.console.mg.interceptor.VisitInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

8、Controller的几种类型的返回

Spring MVC 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void。

(1)返回ModelAndView
  • 对于ModelAndView构造函数可以指定返回页面的名称,也可以通过setViewName方法来设置所需要跳转的页面:
    @RequestMapping(value="/index1",method=RequestMethod.GET)
    public ModelAndView index(){
    ModelAndView modelAndView = new ModelAndView("/user/index");
    modelAndView.addObject("name", "xxx");
    return modelAndView;
    }

    // 返回的是一个包含模型和视图的ModelAndView对象
    @RequestMapping(value="/index2",method=RequestMethod.GET)  
    public ModelAndView index2(){  
      ModelAndView modelAndView = new ModelAndView();  
      modelAndView.addObject("name", "xxx");  
      modelAndView.setViewName("/user/index");  
      return modelAndView;  
    }
    
(2)返回Map
  • 主要包含Spring封装好的Model和ModelMap,以及java.util.Map;当没有视图返回的时候视图名称将由requestToViewNameTranslator决定;响应的view应该也是该请求的view,等同于void返回:
    @RequestMapping(value="/index3",method=RequestMethod.GET)
    public Map<String, String> index3(){
    Map<String, String> map = new HashMap<String, String>();
    map.put("key1", "1");
    //map.put相当于request.setAttribute方法
    return map;
    }
    在jsp页面中可直通过${key1}获得到值, map.put()相当于request.setAttribute方法。
(3)返回String
  • 指定返回的视图页面名称,结合设置的返回地址路径加上页面名称后缀即可访问到;例如下面的例子将返回到视图 hello.vm(.jsp...):
    @RequestMapping(value="/showdog")
    public String hello1(){
    return "hello";
    }

  • 如果方法声明了注解@ResponseBody ,则会直接将返回值输出到页面:
    @RequestMapping(value="/print")
    @ResponseBody
    public String print(){
    String message = "Hello World, Spring MVC!";
    return message;
    }

(4)返回void

如果返回值为空,则响应的视图页面对应为访问地址:
@RequestMapping("/index")
public void index() {
return;
}

小结
  • 1、使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很大的灵活性,而模型数据又可以通过 ModelMap 控制。
  • 2、使用void,map,Model 时,返回对应的逻辑视图名称真实url为:prefix前缀+视图名称 +suffix后缀组成。
  • 3、使用String,ModelAndView返回视图名称可以不受请求的url绑定,ModelAndView可以设置返回的视图名称。

9、数据格式转换

  • 日期格式转换
  • 数字格式转换
  • 类型转换
(1)Converter接口

推酷:SpringMVC之类型转换Converter
SpringMVC之类型转换Converter 1
SpringMVC之类型转换Converter 2

在Spring3中引入了一个Converter接口,使用Converter接口可以进行自定义的数据转换,它支持从一个Object转为另一种类型的Object。除了Converter接口之外,实现ConverterFactory接口和GenericConverter接口也可以实现我们自己的类型转换逻辑。

例子:

// Converter
public class StringToDateConverter implements Converter<String, Date> {
    private static Logger logger = LoggerFactory.getLogger(StringToDateConverter.class);

    @Override
    public Date convert(String source) {
        System.out.println("->> i'm here ! ");
        if(!canConverte(source)){
            logger.warn("参数有误,无法解析为Date类型:{}",source);
            return new Date(0); // 返回一个
        }

        DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
        DateTime dateTime = DateTime.parse(source, dateTimeFormatter);
        return dateTime.toDate();
    }

    /**
     * 是否可以转换
     * @param source
     * @return
     */
    private boolean canConverte(String source){
        if(Strings.isNullOrEmpty(source))
            return false;

        List<String> stringList = Splitter.on("-").trimResults().splitToList(source);
        if(stringList.size() != 3)
            return false;
        return true;
    }
}

<!--注册时间格式转换注解驱动-->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.quanr.fresh.support.StringToDateConverter"/>
        </set>
    </property>
</bean>

@Controller
@RequestMapping("/support")
public class SupportController {
    @RequestMapping(value = "/dateformat", method = RequestMethod.GET)
    @ResponseBody
    public ResultModel dateFormat(@RequestParam("date") Date date){
        System.out.println("-->>>" + date);
        ResultModel resultModel = new ResultModel();
        resultModel.setMessage(date.toString());
        return resultModel;
    }
}
2、@DateTimeFormat

x、单元测试

TODO

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,441评论 1 133
  • Spring MVC一、什么是 Spring MVCSpring MVC 属于 SpringFrameWork 的...
    任任任任师艳阅读 3,372评论 0 32
  • 题目1: DOM0 事件和DOM2级在事件监听使用方式上有什么区别? DOM0级方法指定的事件处理程序被认为是元素...
    cross_王阅读 260评论 0 0