4.SpringMVC

SpringMVC

SpringMVC的核心是DispatcherServlet,所有请求都要通过它转发,当一个用户发起一个请求,DispatcherServlet先找到处理器映射,根据映射找到类中对应的控制器,然后调用控制器中对应的方法,返回数据模型(即各种需要被前端用到的数据,比如实体类)以及视图名称,然后DispatcherServlet在根据控制器返回的视图名称找到视图解析器解析视图,最后输出到前端响应

  1. DispatcherServlet找到处理器映射
  2. 根据映射结果找到控制器中对应的方法
  3. 根据方法返回的模型和视图名找到视图解析器解析
  4. 然后根据视图解析器找到视图使用模型渲染结果

在这里模型指的是返回给用户并在浏览器显示的数据

1.通过java配置取代web.xml配置

servlet3.0规范定义了一个ServletContainerInitializer接口来初始化类,用于替代web.xml,在servlet容器启动时,会加载该接口的实现类用于加载相关配置,SpringMVC的AbstractAnnotationConfigDispatcherServletInitializer实现了这个接口,继承这个接口即可实现不通过web.xml配置文件,


//当web程序启动时,tomcat会加载实现了ServletContainerInitializer,而下面这个就实现了
//所以。这个就是一个配置类,用于代替web.xml,在该类被初始化时,会同时初始化Dispatcher核心类和ConTextLoaderListener
//后面ContextLoaderListener个类用于getServletConfigClasses方法
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

   //根配置,配置数据库等 其他要用到的组件信息
   @Override
   protected Class<?>[] getRootConfigClasses() {
       return new Class<?>[]{RootConfig.class};
   }

   //Dispatcher配置
   //使用conTextLoaderListener加载前端控制器的上下文
   @Override
   protected Class<?>[] getServletConfigClasses() {
       return new Class<?>[]{WebConfig.class};
   }


   //实现ServletContainerInitializer是用于替代web.xml,而Abstra....是SpringMVC 在替代web.xml的基础上加入MVC特有的组件
//而这个ServletContainer是servlet3.0以后的产物,servlet容器tomcat7或者更高的版本才能这么使用
   //拦截
   @Override
   protected String[] getServletMappings() {
       return new String[]{"/"};
   }
}

RootConfig

package spitter.config;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = {"spitter"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig {
}

WebConfig

package spitter.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();

        resourceViewResolver.setPrefix("/WEB-INF/view/");
        resourceViewResolver.setSuffix(".jsp");
        resourceViewResolver.setExposeContextBeansAsAttributes(true);

        return resourceViewResolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

其中getServletConfigClass用于获取配置Dispatcher相关属性,而rootConfig用于配置其他类,例如mybatis redies等

最后在创建一个控制器

package spitter.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
    static {
        System.out.println("初始化成功");
    }
    @RequestMapping(value = "/",method = RequestMethod.GET)
    public String home(){
        return "home";
    }
}

然后在对应设置的目录创建一个jsp,最后测试就能成功跳转到页面

传递模型数据到视图中

在控制器的方法中,可以指定模型参数,就可以把模型数据带到前端显示,实际这个参数就是一个map,具体如下,在这里传递了一个对象集合

    /**
     * 给定一个模型参数,用于传递 需要显示在前端的数据,实际上model是一个map,
     * 当不给其传递key时,他会自己推断,在这里名字为spittles,如果你不希望使用Model 作为参数
     * 你也可以使用Map model作为参数,效果已有
     */
    public String home(Model model){
        model.addAttribute(createSpittleList(20));
        return "home";
    }

接收请求的输入

SpringMVC自然也需要支持前端传参数到后台,SpringMVC中可以在方法参数前面添加一个@RequestParam表示要接受的参数的name,代码如下

   /**
     * 在这里@RequestParam代表必须要传的参数,也可以指定一个默认值,在没有传入参数的时候
     * 将赋值为默认值,默认值都必须是String类型的,不过在赋值给max的时候会做对应的转换
     * @param max
     * @param count
     * @return
     */
    public List<Spittle> spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
        System.out.println(max +" " +count);
        return findSpittle(max,count);
    }

如果在一个页面的form表单上不写action,那么这个表单提交时,就会默认提交到 跳转到这个页面上的路径,意思就是 如果你访问 /index,而Controller跳转到index.jsp,那么这个jsp就会提交到该控制器中去,即可指定一个POST同名方法,参数不一致即可(重载)

如下,其中redirect代表重定向,直接输入controller的url路径

    @RequestMapping(value = "view",method = RequestMethod.POST)
    public String spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
        System.out.println(max +" " +count);
        return "redirect:/view";
    }
    @RequestMapping(value = "view",method = RequestMethod.GET)
    public String spittles(){

        return "form";
    }

校验表单

普通的后台校验表单是获取到前端的值,然后做很多逻辑判断,这样既写了很多重复代码,而且如果字段比较多,写起来也比较麻烦,从Spring3.0开始,Spring提供了对java校验api,又称之为JSR-303,实际上是一个接口,而其实现类就是Hibernate Validator,下面是相关注解

  • @AssertFalse:所注解的属性必须是false
  • @AssertTrue:所注解的属性必须是true
  • @DecimalMax:所注解的属性必须是数字,且小于给定的值
  • @DecimalMin:所注解的属性必须是数字,且大于给定的值
  • @Digits:必须是数字,且必须有指定的数字
  • @Future:必须是将来的日期
  • @Max:必须是数字,且值要小于或者等于给定的值
  • @Min:同上,大于给定的值
  • @NotNULL:不能为空
  • @NUll:必须为null
  • @Past:必须是过去的日期
  • @Pattern:必须匹配正则表达式
  • @Size:值必须是String、集合、数组,且长度符合要求

用法如下

package spitter;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;


public class Spittle {
    private  Long id;
    @NotNull(message = "不能为空")
    @Size(min=5,max = 16,message = "不能超过5-16")
    private  String message;
    @NotNull
    @Size(min=5,max=25,message = "不能超过5-25")
    private  String time;
    @NotNull
    @Size(min=2,max=30,message = "不能超过2-30")
    private String latitude;
    @NotNull
    @Size(min=2,max = 30,message ="不能超过2-30")
    private String longitude;

    public Spittle(Long id, String message, String time, String latitude, String longitude) {
        this.id = id;
        this.message = message;
        this.time = time;
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public Spittle() {
    }

    public Spittle(String message, String time) {
        this.message = message;
        this.time = time;
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "Spittle{" +
                "id=" + id +
                ", message='" + message + '\'' +
                ", time='" + time + '\'' +
                ", latitude='" + latitude + '\'' +
                ", longitude='" + longitude + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getLatitude() {
        return latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }

    public String getLongitude() {
        return longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }
}

    @RequestMapping(value = "registry",method = RequestMethod.POST)
    public String processRegistration( @Valid Spittle spittle, Errors error){
        System.out.println(spittle);
        if(error.hasErrors()){
            System.out.println(error.getAllErrors());
            return "form";
        }

        return "home";
    }

建议不要使用高版本,我使用6.0.9报错,版本兼容报错,显示初始化失败,换成5.3版本以后正常执行

使用Apache Tiles视图定义布局

如果需要对每一个布局添加头部和尾部,常规做法是为每一个jsp模版设置头部和尾部,这样明显扩展性不好且增加日后维护成本,所以就需要布局引擎Apache Tiles

没什么卵用,多了些莫名其妙的配置,实际也就和一般的头部引用jsp,没什么差别

文件上传

Spring的文件上传比较简单,只要在Spring 中注册一个文件上传的解析器就行了,Spring中有两个解析器

  • ComonsMultipartResolver:通用
  • StandardServletMultipartResolver:适用于servlet3.0以后

基本用法

  1. 在Spring配置文件中实例化一个上传解析器
  2. 接收用MultipartFile

使用java配置的一般例子

在主配置类中重写方法


    /**
     * 设置上传文件的临时文件
     * @param registration
     */
    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        //设置零时文件夹,以及其他相关设置
        registration.setMultipartConfig(new MultipartConfigElement("/Users/f7689386/PycharmProjects",2097152,4194304,0));
    }

Controller写法

    public String processRegistration(@RequestPart("profilePicture")MultipartFile profilePicture){
        //获取名字
        System.out.println(profilePicture.getOriginalFilename());


        return "home";
    }
    

MultipartFile接口相关方法

public abstract interface MultipartFile {
  String getName();
  String getOriginalFilename();
  String getContentType();
  boolean isEmpty();
  long getSize();
  byte[] getBytes() throws java.io.IOException;
  InputStream getInputStream() throws java.io.IOException;
  void transferTo(File arg0) throws IOException,IllegalStateException;
}
 

定义异常通知切面

如果有多个控制器抛出同样的异常,要对每一个方法都要进行处理跳转到error页面,这样会有大量存在代码,可以抽取成一个切面,对每一个抛出该异常的方法 作为一个切点

在SpringMVC中,可以给类添加@ControllerAdvice代表这个类是一个通知类,@ExceptionHandler代表这个方法是一个异常通知方法

java

@ControllerAdvice
public class AppWideExceptionHandler{
   @ExceptionHandler(DuplicateSprittleException.class)
   public String duplicateSplittleHandler(){
       return "error";
   }
}

上面这个类代表如果有某个类抛出DulicateSprittleException异常,则执行上面那个方法,其中ControllerAdvice里面包含的@Component,所以在启动的时候它也会被扫描进来被Spring管理

重定向传递数据

当用户提交post请求以后,如果不使用重定向跳转而使用请求转发,那么在刷新页面或者后退可能会产生多次提交数据等危险操作,但是在SpringMVC中Model的生命周期都是一次请求,在重定向model存储的数据都会消失

有个方案是把需要访问的数据存储在会话Session中,Spring也认为这是一个不错的方式,但是Spring认为我们不需要管理这些数据,Spring提供了RedirectAttributes设置属性,在跳转到页面以后数据消失,类似一次请求存储的数据

    @RequestMapping(value = "redirect",method = RequestMethod.GET)
    public String redirect(RedirectAttributes model){
        model.addFlashAttribute("a","a");
        return "redirect:/spitter/(username)";
    }

如果传递的是一个变量名,还可以不设置key,他会默认以变量名作为key

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

推荐阅读更多精彩内容

  • 准备机器 操作系统:centos 7 机器:192.168.1.1 端口:27017 安装 下载MongoDB(...
    一个人一匹马阅读 155评论 0 1
  • 文/木字杰 Livorno 13/...
    木字杰阅读 454评论 13 9
  • 看到海康威视的招聘需求里面,有一项是 了解CMM标准,但无奈资料太少所以自己翻译了下维基百科。如果有什么翻译不到位...
    Draper阅读 3,678评论 0 2