Springspring学习

Spring(4)——Spring MVC

2017-03-14  本文已影响535人  拾壹北
Spring MVC请求流程

1、Spring MVC请求流程

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

3、笔记

1 2 3

4、注解

参考: Spring 注解总结

为什么会有注解?

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

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

Spring3支持的注解类型

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

这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即不自动装配):

@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注解,因为:

@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是所有受Spring管理组件的通用形式

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

这些注解与@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一个具有如下三个作用:

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个属性分成三类进行说明:

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);
    }

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数据到处理方法参数数据的绑定所用到的注解和什么情形下使用。

6、Controller如何获取Request参数

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>

要进行拦截的路径

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

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
(2)返回Map
(3)返回String
(4)返回void

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

小结

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

上一篇下一篇

猜你喜欢

热点阅读