SpringMVC

408 阅读8分钟

SpringMVC 是由Spring提供的,建立在 Servlet API 之上的 Web 框架

三层架构

系统标准的三层架构包括:表现层、业务层、持久层。 表现层:

web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web 层,web 需要接收 http 请求,完成 http 响应。

业务层:

service 层。它负责业务逻辑处理。web 层依赖业务层,但是业务层不依赖 web 层。业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)

持久层:

dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。

MVC

MVC是是模型(model)-视图(view)-控制器(controller)的缩写,MVC要实现的目标是将软件用户界面和业务逻辑分离以使代码可扩展性、可复用性、可维护性、灵活性加强。

  • model(模型):Model层是业务模型,业务模型进行业务处理,返回数据
  • View(视图):View层是界面。通常指的就是 jsp 或者 html。作用一般就是展示数据
  • Controller(控制器):Controller层用来调度View层和Model层,控制器只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。本身不输出任何东西和做任何处理。所以Controller中的内容能少则少,这样才能提供最大的灵活性。 三层架构的意义在于各层解耦,各司其职

MVC设计模式在于解耦 View 和 Model

SpringMVC配置文件

<!--注解扫描-->
<context:component-scan base-package="com.eponine.mvc"></context:component-scan>

<!--配置mvc注解支持: 例如json解析注解-->
<mvc:annotation-driven></mvc:annotation-driven>

 <!--视图解析器-->
    <bean id="viewResolver"
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    </bean>

web.xml

<!--mvc核心:前端控制器-->
<servlet>
   <servlet-name>dispatcherServlet</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <!--配置servlet初始化参数 读取springmvc配置文件-->
   <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
   </init-param>
   <!--配置servlet启动优先级,数值越低优先级越高-->
   <load-on-startup>5</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>dispatcherServlet</servlet-name>
   <!--拦截以/开头的请求-->
   <url-pattern>/</url-pattern>
</servlet-mapping>

controller

@Controller
public class HelloController {

    //url http://localhost:8080/hello
    @RequestMapping("/hello")
    public String hello(){
        System.out.println("Hello");
        return "/WEB-INF/pages/success.jsp";
    }

}

SpringMVC执行流程

web.xml配置了前端控制器DispatcherServlet

  1. DispatcherServlet被加载时,调用servlet生命周期的init()方法,配置初始化(IOC容器、HandlerMapping、HandlerAdapter、ViewResolver 等等)

配置初始化完毕,HandlerMapping中已经保存了mapping(请求URL)对应的handlerMethod(处理方法)

  1. 每次请求到来,会调用DispathcerServlet.doDispatch()方法处理

  2. dispatchServlet 根据请求request,调用处理器映射器(HandlerMapping),获取到对应的 handler

  3. dispatchServlet 根据handler, 获取对应的处理器适配器(HandlerAdapter)

  4. 由 HandlerAdapter 执行 handler(目标方法),返回ModelAndView

  5. dispatchServlet处理结果: this.processDispatchResult(...)

  • 通过ModelAndView获取到逻辑视图viewName("/WEB-INF/pages/success.jsp")

  • 调用视图解析器 (ViewResolver) 根据视图名viewName解析,得到View对象

  • 由view对象进行结果渲染:

  1. 将数据保存在request域中,
  2. 转发到对应页面

源码级别的流程分析:

  1. 当我们配置了<load-on-startup>5</load-on-startup> 会在容器启动的时候创建DispatchServlet

  2. 完成DispatchServlet的创建会调用initStrategies()方法,完成springMVC核心9大组件的初始化

    1. 在这里会初始化 处理器映射器、处理器适配器、视图解析器、等等

      1.  this.initHandlerMappings(context);
         this.initHandlerAdapters(context);
         this.initViewResolvers(context);
        
      2. 怎么初始化的?

        1. 先根据类型去IOC容器中找handlerMapping ,如果有handlerMapping 的定义信息,就创建对象放入IOC容器
        2. 如果第一步没有根据类型找到,再根据名称+类型找定义信息,如果有创建对象
        3. 如果前两步都没有完成对象的创建,就读取默认配置DispatcherServlet.properties创建对象放入到IOC容器
  3. 完成初始化之后,就是等待请求来,每个被拦截到的请求都会被doDispatch()方法处理

  4. 先根据mapping(/hello)找到HandlerMapping,就是由哪个handlerMethod处理请求

  5. 在根据刚才的handler找到由哪个处理器适配器可以处理这个方法

  6. 由处理器适配器去执行handlerMothod,返回ModelAndView对象

  7. 由 DispatcherServlet调用processDispatchResult()处理结果

    1. 怎么处理?

    2. 先根据ModelAndView获取viewName

    3. 调用视图解析器解析viewName,返回view对象

    4. 通过view对象完成结果处理view.render(mv.getModelInternal(), request, response);

      1. 第一步:把数据存入到request域
      2. 第二步:转发到页面,页面就可以从request域获取数据

SpringMVC的核心组件

  • HandlerMapping (处理器映射器) 负责根据请求找到相对应的处理器(我们自己写的Controller)
  • HandlerAdapter(处理器适配器) 负责controller方法调用
  • ViewReslover(视图解析器) 负责解析处理ModelAndView,得到物理视图View对象

@RequestMapping

@RequestMapping 注解用来进行URL和方法的绑定。可以通过它的属性对请求做出各种限制,常用选项有:

  • value:用来指定请求URL(和path作用一样)
  • method:用来限定请求方式类型
  • params:用来限定请求必须传递哪些参数 上面的选项可以单独使用,也可以组合使用,组合使用时,必须同时满足才行。

@RequestMapping 可以写在方法上,表示对该方法限制,也可以写在类上,表示对该类中所有方法进行限制

value属性
@Controller
public class HelloController {

    //url http://localhost:8080/hello?username=hawk&age=18
    @RequestMapping(value = "/hello",
            method = RequestMethod.GET,
            params = {"username","age"})
    public String hello() {
        System.out.println("Hello");
        return "success";
    }
}

SpringMVC参数接收

在SpringMVC中可以使用多种类型来接收前端传入的参数

  • 简单类型:基本类型的包装类型、String
  • 对象类型
  • 数组类型
  • 集合类型

简单类型参数

保证前台传递的参数名和方法中的形参名称一致 如果没有传递参数,会将null赋值给参数,如果参数是基本类型,会出现异常

//url http://localhost:8080/hello1?name=hawk&age=18
@RequestMapping("/hello1")
public String hello1(String name,Integer age){
    System.out.println(name+":"+age);
    return "success";
}

对象类型参数

保证传递的参数和对象属性名称一致(SpringMVC调用对象属性对应的set方法进行参数封装)

//url http://localhost:8080/hello2?name=hawk&age=18
@RequestMapping("/hello2")
public String hello2(User user){
    System.out.println(user);
    return "success";
}

数组类型参数

保证参数名和方法形参名称一致,可以使用数组类型接收,也可以使用字符串接收

//url http://localhost:8080/hello3?name=hawk&name=dover
@RequestMapping("/hello3")
public String hello3(String[] name){
    for (String thisName : name) {
        System.out.println(thisName);
    }
    return "success";
}
//url http://localhost:8080/hello3?name=hawk&name=dover
    @RequestMapping("/hello3")
    public String hello3(String name){
        System.out.println(name);
        return "success";
    }

集合类型参数

获取集合参数时,需要借助注解 @RequestParam

//url http://localhost:8080/hello4?name=hawk&name=dover
@RequestMapping("/hello4")
public String hello4(@RequestParam ArrayList<String> name){
    for (String s : name) {
        System.out.println(s);
    }
    return "success";
}

中文乱码处理

我们可以使用过滤器对请求进行拦截,设置编码,解决中文乱码问题,SpringMVC已经为我们封装好了乱码处理的过滤器,只需要在web.xml中配置,指定需要的编码即可.

<!--中文乱码处理过滤器-->
<filter>
   <filter-name>characterEncodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
   <!--指定编码格式-->
   <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>characterEncodingFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

自定义类型转换器

接收日期类型参数

//url http://localhost:8080/hello5?date=1998/01/02
@RequestMapping("/hello5")
public String hello5(Date date){
    System.out.println(date);
    return "success";
}

如果把传递的日期格式改为1998-1-2后台出现转换异常:

ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '1998-1-2';

自定义类型转换器步骤如下:

  1. 实现Converter接口,定义类型转换器类
  2. 配置,将自定义类型转换器加入到类型转换器集合中
public class DateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String s) {
        //滤空
        if(s==null){
            return null;
        }
        //首先以yyyy/MM/dd格式处理
        try{
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
            Date parse = simpleDateFormat.parse(s);
            return parse;
        }catch (Exception e){
            //出现异常以yyyy-MM-dd格式处理
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date parse = null;
            try {
                parse = simpleDateFormat.parse(s);
                return parse;
            } catch (ParseException ex) {
                //格式不正确
                ex.printStackTrace();
                return null;
            }
            
        }
        
    }
}
<!--配置mvc注解支持: 例如json解析注解-->
<mvc:annotation-driven conversion-service="dateConverter"></mvc:annotation-driven>
 <!--配置自定义类型转换器:第一步-->
    <bean id="dateConverter"
          class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.eponine.mvc.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>

文件上传

单文件上传

  1. 配置文件上传的解析器
<!--文件上传-->
<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--单次上传文件大小限制 单位字节,10M-->
    <property name="maxUploadSize" value="10485760"></property>
</bean>
  1. 完成上传表单
<form action="/hello6" method="post" enctype="multipart/form-data">
    <input type="file" name="file" multiple> <br/>
    <input type="submit" value="上传">
</form>
  1. 后台接收并保存上传文件
@RequestMapping("/hello6")
public String hello6(MultipartFile file){
    String filename = file.getOriginalFilename();
    File realFile = new File("E:/file/" + filename);
    try {
        file.transferTo(realFile);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "success";
}

多文件上传

@RequestMapping("/hello7")
public String hello7(MultipartFile[] file){
    for (MultipartFile multipartFile : file) {
        String filename = multipartFile.getOriginalFilename();
        File realFile = new File("E:/file/" + filename);
        try {
            multipartFile.transferTo(realFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    return "success";
}

常用注解

@RequestParam

@RequestParam标注在方法参数之前,用于对传入的参数做一些限制,支持三个属性:

  • value:默认属性,指定接收哪个参数的值,默认接收和方法参数同名的请求参数
  • required:请求中是否必须有该参数,如果设置了默认值,请求中可以没有该参数
  • defaultValue:默认值,如果传递参数将覆盖默认值

多个属性可以组合使用,该注解还可用于接收集合类型参数

//url http://localhost:8080/hello1?name=hawk&age=18
@RequestMapping("/hello1")
public String hello1(@RequestParam(value = "name") String userName, Integer age,
                     @RequestParam(required = false,defaultValue = "123456")String password){
    System.out.println(userName+":"+age+":"+password);
    return "success";
}

@RequestHeader

用于接收HTTP请求头信息

@RequestHeader(key) 指定接收单个的header信息

@CookieValue

用于接收指定cookie的value值

释放静态资源

前端控制器(DispatchServlet) 配置的 ==/== ,会导致静态资源(js/html/jpeg...)都会被拦截,进行路径匹配寻找对应的handler进行处理

解决方案1

在spring-mvc.xml中配置默认的servlet处理

     <!--当映射器找不到对应Url的mapping时,先不要报错,而是交给tomcat默认的servlet处理-->
     <mvc:default-servlet-handler/>

解决方案2

在web.xml中配置固定的目录匹配规则,静态资料的路径不要与该路径开头即可,该方式适合新建项目使用,否则中途修改,需要改大量mapping

 <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--方式一:增加固定前缀,这样静态资源就不会被拦截-->
    <url-pattern>/eponine/*</url-pattern>
 </servlet-mapping>