Spring MVC 详解

微信扫一扫,分享到朋友圈

Spring MVC 详解

介绍

Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。

与之相反的是基于组件的、事件驱动的Web框架,如Tapestry、JSF等,在此就不介绍了。

Spring Web MVC也是服务到工作者模式的实现,但进行可优化。前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。

体系结构

请求流程

第一步:用户发送请求到前端控制器(DispatcherServlet)。

第二步:前端控制器请求 HandlerMapping 查找 Handler,可以根据 xml 配置、注解进行查找。

第三步: 处理器映射器 HandlerMapping 向前端控制器返回 Handler

第四步:前端控制器调用处理器适配器去执行 Handler

第五步:处理器适配器执行 Handler

第六步:Handler 执行完成后给适配器返回 ModelAndView

第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的一个底层对象,包括 Model 和 View

第八步:前端控制器请求试图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。

第九步:试图解析器向前端控制器返回 view

第十步:前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域

第十一步:前端控制器向用户响应结果

1、前端控制器DispatcherServlet(不需要程序员开发)。
  作用:接收请求,响应结果,相当于转发器,中央处理器。有了DispatcherServlet减少了其它组件之间的耦合度。
2、处理器映射器HandlerMapping(不需要程序员开发)。
  作用:根据请求的url查找Handler。
3、处理器适配器HandlerAdapter(不需要程序员开发)。
  作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler。
4、处理器Handler(需要程序员开发)。
  注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
5、视图解析器ViewResolver(不需要程序员开发)。
  作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
6、视图View(需要程序员开发jsp)。
  注意:View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
ps:不需要程序员开发的,需要程序员自己做一下配置即可。

组件说明

DispatcherServlet:前端控制器 
用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器 
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler:处理器 
Handler是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
HandlAdapter:处理器适配器 
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
ViewResolver:视图解析器 
ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
View:视图 
SpringMVC框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

处理器和适配器

前端控制器(dispatchServlet)

在web.xml中配置前端控制器,在服务器启动时就被创建,用来对请求和响应进行接收 和 分发处理,其在配置时可以设置一个初始化参数,用来定位SpringMVC.xml的地址。



  display null
  
  
      dispatcherServlet
      org.springframework.web.servlet.DispatcherServlet
      
      
          contextConfigLocation
          classpath:spring-mvc.xml
      
  
  
      dispatcherServlet
      *.action
  
  
      index.jsp
  

处理器映射器(handlerMapping)

处理器映射器主要有如下几种:

1、BeanNameUrlHandlerMapping 根据对象的名字来进行处理器映射,此时该对象要继承AbstractController 实现handlerRequestInternal方法;对请求的处理主要在该方法中完成。


注意:使用 对象名映射 时,对象配置时的name属性必须为 “/xxx.action” 的形式,即 对象名 即为访问的 URL名

2、SimpleUrlHandlerMapping

SimpleUrlHandlerMapping 的配置

            
                
                    indexController
                    indexController
                    indexController
                
            
        

Controller对象的配置


此时当请求的url 为xxx/a.action 或 xxx/b.action 或 xxx/c.action 时,映射器都会映射为id是 indexController 的controller对象这。

ControllerClassNameHandlerMapping 控制器类名映射

控制器类名处理器映射对象配置

注意:在用 控制器类名处理器映射 时,类名必须为xxxController ,访问地址为输入 /xxxController 或 /xxxcontroller 或 /xxx;

不过工业上一班用注解方式实现  控制器映射器对 控制器的 映射  如:@controller ; @requestMapping(value="/xxx.action",method=RequestMethod.POST) 等;

  在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。
  在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器。
  在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。
  在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。
    对于适配器和解析器的配置,我们可以使用代替

@Controller 
public class FirstHandler{
    @RequestMapping("/handler.action")
    public ModelAndView handler(){
        ModelAndView mav = new ModelAndView();
        mav.addObject("name", "zhangsan");
        mav.setViewName("index");
        return mav;
    }
}

前端控制器和视图解析器

配置前端控制器

在 web.xml 文件中进行如下配置:


  SpringMVC_01
  
  
    springmvc
    org.springframework.web.servlet.DispatcherServlet
    
    
        contextConfigLocation
        classpath:springmvc.xml
    
  
 
  
    springmvc
    
    *.do
  

配置处理器适配器

在 springmvc.xml 文件中配置。用来约束我们所需要编码的 Handler类。

第一种配置:编写 Handler 时必须要实现 Controller


第二种配置:编写 Handler 时必须要实现 HttpRequestHandler


编写 Handler

在 springmvc.xml 文件中配置。通俗来讲,就是请求的 URL 到我们这里所编写的 Handler 类的某个方法进行一些业务逻辑处理。我们在上面讲解了两个处理器适配器来约束 Handler,那么我们就通过上面两种配置分别编写两个 Handler。

第一种:实现Controller 接口

public class HelloController implements Controller{
 
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        ModelAndView modelView = new ModelAndView();
        //类似于 request.setAttribute()
        modelView.addObject("name","张三");
        modelView.setViewName("/WEB-INF/view/index.jsp");
        return modelView;
    }
}

第二种:实现 HttpRequestHandler 接口

public class HelloController2 implements HttpRequestHandler{
 
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute("name", "张三");
        request.getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request, response);
    }
}

请求映射和参数绑定

SpringMVC中,如何处理请求是很重要的任务。请求映射都会使用@RequestMapping标注。其中,类上的标注相当于一个前缀,表示该处理器是处理同一类请求;方法上的标注则更加细化。如,类的标注可能是“user”,表示全部都是与用户相关的操作;具体到方法可能有“create”“update”“delete”等,分别表示对用户进行哪一类操作。

@Controller  
@RequestMapping(value="/user")                 //①处理器的通用映射前缀  
public class HelloWorldController2 {  
    @RequestMapping(value = "/hello2")        //②相对于①处的映射进行窄化  
    public ModelAndView helloWorld() {  
         //省略实现  
    }  
}

URL路径映射

这种映射涉及的属性只有value。

@RequestMapping(value={"/test1", "/user/create"}) //或,表示多个路径都可以映射到同一个处理方法
@RequestMapping(value="/users/{userId}/topics/{topicId}") //也可以使用大括号,表示变量占位符
@RequestMapping(value="/product?") //可匹配“/product1”或“/producta”,但不匹配“/product”或“/productaa”
@RequestMapping(value="/products/**/{productId}") //可匹配“/products/abc/abc/123”或“/products/123”
@RequestMapping(value="/products/{categoryCode:\d+}-{pageNumber:\d+}") //支持正则表达式

请求方法映射限定

不仅要提供value属性,还要提供method属性。

@RequestMapping(value="/create", method = RequestMethod.GET) //表示可处理匹配“/create”且请求方法为“GET”的请求
@RequestMapping(value="/create", method = RequestMethod.POST) //表示可处理匹配“/create”且请求方法为“POST”的请求

请求参数映射限定

需要提供params属性和method属性。

以下面的控制器为例,

@Controller  
@RequestMapping("/parameter1")       //①处理器的通用映射前缀  
public class RequestParameterController1 {
    // Something...
}
@RequestMapping(params="create", method=RequestMethod.GET) //表示请求中有“create”的参数名且请求方法为“GET”即可匹配,
                                                             如可匹配的请求URL“http://×××/parameter1?create”

@RequestMapping(params="create", method=RequestMethod.POST) //表示请求中有“create”的参数名且请求方法为“POST”即可匹配

@RequestMapping(params="!create", method=RequestMethod.GET) //表示请求中没有“create”参数名且请求方法为“GET”即可匹配,
                                                              如可匹配的请求URL“http://×××/parameter1?abc”

@RequestMapping(params="submitFlag=create", method=RequestMethod.GET) //表示请求中有“submitFlag=create”请求参数且请求方法为“GET”即可匹配,
                                                                        如请求URL为http://×××/parameter2?submitFlag=create

@RequestMapping(params="submitFlag=create", method=RequestMethod.POST) //表示请求中有“submitFlag=create”请求参数且请求方法为“POST”即可匹配

@RequestMapping(params="submitFlag=create", method=RequestMethod.GET) //表示请求中有“submitFlag=create”请求参数且请求方法为“GET”即可匹配,
                                                                        如请求URL为http://×××/parameter2?submitFlag=create

与value中的参数组合表示“或”不同,params参数组合表示“且”,即:

@RequestMapping(params={"test1", "test2=create"}) //表示请求中的有“test1”参数名 且 有“test2=create”参数即可匹配,如可匹配的请求URL“http://×××/parameter3?test1&test2=create

Validation 校验

1.比较

@Valid是使用hibernate validation的时候使用
@Validated 是只用spring  Validator 校验机制使用

2.实现

其中,@valid,java的jsr303声明了这类接口,hibernate-validator对其进行了实现

3.依赖

在使用maven框架整合时,需要引入的依赖部分如下:

        
       
            javax.validation  
            validation-api  
            1.1.0.Final  
          
  
          
            org.hibernate  
            hibernate-validator  
            5.2.1.Final  
        

4.JSR303定义的校验类型

空检查

@Null       验证对象是否为null

@NotNull    验证对象是否不为null, 无法查检长度为0的字符串

@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.

@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

 

Booelan检查

@AssertTrue     验证 Boolean 对象是否为 true  

@AssertFalse    验证 Boolean 对象是否为 false  

 

长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  

@Length(min=, max=) Validates that the annotated string is between min and max included.

 

日期检查

@Past           验证 Date 和 Calendar 对象是否在当前时间之前  

@Future     验证 Date 和 Calendar 对象是否在当前时间之后  

@Pattern    验证 String 对象是否符合正则表达式的规则

 

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null

@Min            验证 Number 和 String 对象是否大等于指定的值  

@Max            验证 Number 和 String 对象是否小等于指定的值  

@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度

@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度

@Digits     验证 Number 和 String 的构成是否合法  

@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

 

@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

 

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)

@CreditCardNumber信用卡验证

@Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。

@ScriptAssert(lang= ,script=, alias=)

@URL(protocol=,host=, port=,regexp=, flags=)

5.使用

新建一个类

/**
* 
* @Title: AppConfigList.java
* @Package com.bonree.browser.model
* @Description: 白名单Url匹配规则类
* @author phycho
* @date 2017/6/26 下午4:45:30
* @version V1.0
*/

public class AppConfigList extends Page {

private Integer id;//Id

@NotNull(message = "appId不能为空", groups = {AccountGroup.Update.class})
private Integer appId;// 应用id

@NotEmpty(message = "规则不能为空", groups = {AppConfigGroup.Add.class,AppConfigGroup.Update.class})
@Size(max = 200,groups= {AppConfigGroup.Add.class,AppConfigGroup.Update.class})
private String url;// url规则

@NotNull(message = "typec不能为空", groups = {AccountGroup.Update.class})
private Byte typec = 1;// 类型

private Date createTime;// 创建时间

private Date modifTime;// 更新时间

@NotNull(message = "状态参数不能为空", groups = {AppConfigGroup.Update.class})
private Byte status = 1;// 状态

private Double orders;// 字段顺序

}


校验逻辑

    /**
     * @param result 绑定结果
     * @param model 数据模型
     * @return 是否发生错误( true:发生错误,false:未发生错误)
     * @Description: 后台校验
     */
    public static boolean validate(BindingResult result, Model model) {
        boolean validateResult = false;
        if (result.hasErrors()) {
            validateResult = true;
            if (model != null) {
                FieldError error = result.getFieldErrors().get(0);// 为了避免大量的校验在前端堆积,影响用户体验,只返回一个错误提示
                log.debug("validate error: " + error.getDefaultMessage());
                model.addAttribute("msg", error.getDefaultMessage());
            }
        }
        return validateResult;
    }
 

Controller中的使用

// 添加白名单规则
    @RequestMapping(value = "/page/add", method = RequestMethod.POST)
    @ResponseBody
    public ResultData addUrlRule(@Validated(AppConfigGroup.Add.class) AppConfigList appConfigList, BindingResult result, Model model) {
        // 后台校验
        if (ValidateHandler.validate(result, model)) {
            return null;
        }
        appConfigList.setStatus((byte) 1);
        appConfigList.setCreateTime(new Date());
        appConfigList.setModifTime(new Date());
        Double orders = appConfigService.selectOrdersByAppId(appConfigList.getAppId());
        if (orders == null) {
            appConfigList.setOrders((double) 1000);
        } else {
            appConfigList.setOrders(orders + 1000);
        }
        return getResult(appConfigService.insert(appConfigList));
    }
 

6.补充

Apr 8, 2014 2:13:57 PM org.apache.catalina.core.StandardWrapperValve invoke  
SEVERE: Servlet.service() for servlet springmvc threw exception  
javax.validation.UnexpectedTypeException: No validator could be found for type: java.lang.Integer  
    at org.hibernate.validator.engine.ConstraintTree.verifyResolveWasUnique(ConstraintTree.java:383)  
    at org.hibernate.validator.engine.ConstraintTree.findMatchingValidatorClass(ConstraintTree.java:364)  
    at org.hibernate.validator.engine.ConstraintTree.getInitializedValidator(ConstraintTree.java:313)  
    at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:144)  
    at org.hibernate.validator.engine.ConstraintTree.validateComposingConstraints(ConstraintTree.java:233)  
    at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:128)  
    at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:117)  
    at org.hibernate.validator.metadata.MetaConstraint.validateConstraint(MetaConstraint.java:84)  
    at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:452)

异常处理与拦截器

Spring MVC提供的3类处理方式,实现在控制层controller的外围处理异常,大概示意图如下:

基于异常类(自定义),即针对某类异常;

基于控制器(controller),即针对某个控制器;

全局异常处理;

Spring MVC异常处理

LoginController控制器中处理登录的方法login,代码如下:

/**
 * 登录
 * @param username
 * @param password
 * @param session
 * @return
 */
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public LuoblogResult login(String username, String password, HttpSession session) {
    // 登录的业务逻辑
    // 当用户名或密码错误将抛出异常
    Author author = authorService.login(username, password);
    return new LuoblogResult(true, author);
}

1.基于自定异常类处理

是时候让我们尝尝Spring异常处理这个香饽饽,只需要使用@ResponseState注解对自定义异常类进行标注,Spring在处理异常的时候会利用反射对该注解标记的异常特殊处理(个人推测):

/**
 * 业务异常
 * Created by luokaiqiongmou on 2016/12/6.
 */
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "业务异常,伪装为错误请求")
public class BusinessException extends RuntimeException{
    // 一些内容
}

2.基于控制器,处理异常

现在我们可以针对某个控制器处理异常,只需要在控制器中增加一个异常处理方法,并使用@ExceptionHandler标注:

@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "业务异常,针对控制器处理")
    @ExceptionHandler(BusinessException.class)
    public void conflict() {
        // 不做任何事或者可以做任何事
    }

3. 全局的异常处理

针对某个控制器处理异常的方式会造成代码入侵

方式一:使用注解
现在创建一个专门处理异常的类,并添加@ControllerAdvice注解,如下:

/**
 * 异常处理
 * Created by luokaiqiongmou on 2016/12/17.
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public LuoblogResult handle(BusinessException e) {
        logger.warn("GloabalExceptionHandler handing a Exception: " + e.getMessage());
        // 业务失败返回
        return new LuoblogResult(false, e.getDescription());
    }
}
方式二:实现HandlerExceptionResolver,并注入到spring容器中
创建一个类实现HandlerExceptionResolver,并在配置文件中注入bean,参考:自定义异常处理器,以下方式未做测试

Spring已经为我们预定义了一个处理异常的解析类SimpleMappingExceptionResolve,添加如下配置文件即可:


    
      
        
         
         
      
    

    
    
    
        
    
    
  

……

CSS - Place the image next to the centered text

上一篇

新中式茶饮风口已至,谁将成为下一个星巴克?

下一篇

你也可能喜欢

Spring MVC 详解

长按储存图像,分享给朋友