SSM框架之SpringMVC

一、SpringMVC概述

1. 基本组件

  • SpringMVC组件
image-20200607151127712.png

2. 工作流程

  • SpringMVC工作流程
  • SpringMVC摆脱了每个servlet程序都要在web.xml中配置对应的servlet和servletMapping的麻烦。通过配置==DispatcherServlet==来拦截所有的请求,至于具体的每个请求,则单独在==controller包里配置不同的Controller类==,每个类都自定义一个==String返回值(当然也可以无返回值)的方法==,@RequestMapping注解在这些方法上,就匹配了对应的请求路径,return的值通过==视图解析器==就找到了对应的文件路径。从而完成了页面跳转。在web.xml中配置去读取SpringMVC的配置文件的init-param
image-20200607151258286.png

3. 工作环境搭建

  1. 创建Maven-webapp工程,如果下载速度过慢,将修改Maven仓库镜像源
  2. 添加一个键值对archeTypeCatalog,internal,解决工程创建过慢问题
  3. 导入坐标
  4. 配置Tomcat服务器
  5. 添加SpringMVC.xml配置文件
  6. 配置web.xml文件

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

web.xml

<!--配置servlet-->
<servlet>
    <!--配置Servlet的名称和对应的类DispatcherServlet(Spring固定的类(servlet))-->
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--配置DispatcherServlet的初始化参数,让它去读取bean.xml配置文件(应该说是SpringMVC的配置文件,而不是Spring的配置文件),从而可以扫描包下的注解-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:bean.xml</param-value>
    </init-param>
    <!--配置load-on-startup,以后启动服务器的时候就创建DispatcherServlet,而不是在第一次请求的时候创建-->
    <load-on-startup>1</load-on-startup>
</servlet>

<!--配置servlet匹配-->
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--配置拦截,/表示任何请求都被拦截-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

二、实例配置

1. springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<-- 开启SpringMVC注解配置支持,开启它,就可以使得requestMapping生效,一定要配置-->
//区别于Spring的annotaiton-driven,后者是开启Spring的事务注解配置支持<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

<!--开启注解扫描-->
<mvc:annotation-driven/>
<!--扫描指定包下的注解-->
<context:component-scan base-package="com.zt"/>

<!--视图解析器对象InternalResourceViewResolver类,将来通过return的字符内容找目录内容里对应名称的jsp文件,跳转到对应的jsp页面-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--配置解析的目录(前缀)-->
    <property name="prefix" value="/WEB-INF/pages/"/>
    <!--配置解析的文件后缀名-->
    <property name="suffix" value=".jsp"/>
</bean>
</beans>

2. Controller配置

  • 每一个controller对应一个应用
//交给Spring管理该控制类
@Controller
public class HelloController{
    // 给该方法添加一个请求路径,以后访问该web工程下的这个路径,就执行这个方法
    @RequestMapping(path = "/hello") //请求的路径
    public String sayHello(){
        System.out.println("Hello SpringMVC");
        // return的内容一般默认为是要跳转的页面的名称(success.jsp,在WEB-INF下创建)
        // 当返回success时,通过bean.xml中配置的视图解析器,找到对应目录下的对应名称的jsp文件(视图解析器配置了前缀和后缀路径)
        return "success";
    }
}

3. web.xml配置中文拦截器

  • 配置字符集过滤器,拦截中文字符,转为utf-8格式
<!--配置解决中文乱码的过滤器-->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <!--CharacterEncodingFilter中的参数encoding-->
        <param-name>encoding</param-name>
        <!--设置encoding字符集为UTF-8-->
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!--拦截所有-->
    <url-pattern>/*</url-pattern>
</filter-mapping>

三、部分注解

1. @RequestMapping

  • 作用:用于建立请求url和处理请求的方法之间的关系。放在某个类上,就相当于:
<a href="user/hello">SpringMVC入门</a>
@Controller
@RequestMapping(path = "/user")
public class HelloController{
    @RequestMapping(path = "/hello")
        public String sayHello(){
        System.out.println("Hello SpringMVC");
        return "success";
    }
}
  • value=属性,path=属性
  • method=post\get\delete...等等(参数为数组类型)
@RequestMapping(method = {RequestMethod.GET})
  • params,要求必须传入一个该参数
@RequestMapping(path = "/hello",params = {"username"})
@RequestMapping(path = "/hello",params = {"username=abc"})  //要求页面上的username值必须是abc才能访问

2. @RequestParams

  • 作用:1、把请求中指定名称的参数赋值给方法中的形参 2、要求必须提供一个该名称的参数,与@RequstMapping的param区别在于,这里的参数名为一个键,"name=zt"是作为一个整体,而不是要求必须值为zt。
@RequestMapping(value = "/testRequestParam")
// @RequestParam修饰Parameters,name="name",表示访问时客户端url必须传入一个name属性的值,
// 即使是和形参username相同名字的也不行
// 然后将拿到的值放进username
public String testAnno(@RequestParam(name = "name",required = true) String username){
    System.out.println(username);
    return "success";
}

3. @RequestBody

  • 区别于@ResponseBody,@ResponseBody可作用于类或方法上,把return的值直接返回给客户端页面如果有了@ResponseBody,那么同样的字符串"success"就会覆盖视图解析器的功能。
  • 作用:用于获取请求体(整个form表单)内容,直接使用得到的是key=value&key=value...结构的数据GET请求方式不适用
  • 属性:required:表示是否有请求体,默认值是true,当取值为true时,GET请求方式会报错。如果取值为false,GET请求得到的是null。
<form action="anno/testRequestBody" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
        <input type="submit" value="提交">
</form>
public String testBody(@RequestBody String body){
    System.out.println(body);
    return "success";
}

4. @PathVariable

  • 用占位符获取客户端传递的参数
// {uid}作占位符,和@PathVariable中的value名相同
@RequestMapping(value = "/testPathVariable/{uid}")
public String testPath(@PathVariable(value = "uid") String id){
    System.out.println(id);
    return "success";
}

5. @CookieValue

  • 获取Cookie的值,value为固定的"JSESSIONID"
@RequestMapping(value = "/testCookie")
public String testCookie(@CookieValue(value = "JSESSIONID") String cookieValue){
    System.out.println(cookieValue);
    return "success";
}

6. @ModelAttribute

  • 范围:方法或者参数
  • 作用:出现在方法上,方法会优先于servlet程序的方法执行。即用户请求所访问的那个url对应的方法会在注解标记的方法之后执行。应用场景:用户没有提交完整的javabean中的所有属性的值,即空了一些属性没有写,这时候可以用@ModelAttribute注解修饰的方法来填充这些没有填的属性的值(可以用数据库中的历史值)

设置表单中只提交两项,第三项由@ModelAttribute注解的方法添加

<form action="anno/testModelAttribute" method="post">
    姓名<input type="text" name="name"><br/>
    年龄<input type="text" name="age"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/testModelAttribute")
public String testAttribute(User u){
    System.out.println(u);
    return "success";
}
@ModelAttribute
public User setUser(User user){  //参数user,把表单中的项添加到user中
    user.setDate(new Date());
    return user;
}

7. @SessionAttribute

  • 作用:用于多次执行控制器方法间的参数共享,只能作用在类上
  • 属性: value\name:用于指定存入的属性名称 types:用于指定存入的数据类型
@Controller
@RequestMapping(value = "/anno")
//把name=value键值对存到session域中
@SessionAttributes(value = {"name"})
public class Annotation {

@RequestMapping(value = "/testSessionAttributes")
public String testSession(Model model){
    // SpringMVC底层会存储到request域对象中
    model.addAttribute("name","周涛");
    return "success";
}

@RequestMapping(value = "/getSessionAttributes")
public String getSession(ModelMap modelMap){
    String value =(String)modelMap.get("name"); //modelMap取到request域中的值
    System.out.println(value);
    return "success";
}

@RequestMapping(value = "/delSessionAttributes")
    public String delSession(SessionStatus status){
    status.setComplete();
    return "success";
}

四、请求参数绑定

4.1 封装基本类型

  • 用户参数username=abcd,password=1234MVC框架,sayhello(String username,String password)当MVC框架形参名和用户参数的name属性名相同时,会自动传递过来
<a href="param/testParam?username=我">请求参数绑定</a>
@RequestMapping(value = "testParam")
public String testParam(String username){
System.out.println(username);
return "success";
}

4.2 封装Javabean

1.Javabean中只有基本类型

  • 表单项内容要和Javabean的属性名称一致
<form action="param/saveAccount" method="post">
    <input type="text" name="username"><br/>  //名称要对应
    <input type="password" name="password"><br/>
    <input type="text" name="money"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
    System.out.println(account);
    return "success";
}

2.Javabean中有别的引用类型

  • 表单项要和引用类型的属性名相同
<form action="param/saveAccount" method="post">
    <input type="text" name="username"><br/>
    <input type="password" name="password"><br/>
    <input type="text" name="money"><br/>
    <input type="text" name="user.name"><br/>  //这两行改为Account对象中的user对象加属性,user.name
    <input type="text" name="user.age"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
    System.out.println(account);
    return "success";
}

3.封装集合对象

<form action="param/saveAccount" method="post">
    姓名<input type="text" name="username"><br/>
    密码<input type="password" name="password"><br/>
    工资<input type="text" name="money"><br/>
    Listname<input type="text" name="users[0].name">   //要封装的是User,实际需要对应的是User的name和age
    Listage<input type="text" name="users[0].age">
    mapusername<input type="text" name="map['1'].name">  //手动给String=1,map[1]就表示user(根据键得值)
    mapuserage<input type="text" name="map['1'].age">
    <input type="submit" value="提交"/>
</form>
private List<User> users;
private Map<String,User> map;

4.自定义类型转换器

  • 对于浏览器提交的数据类型,有些需要进行转换才能封装到Javabean中

转换日期

  1. 前端页面
<form action="param/saveUser" method="post">
    姓名<input type="text" name="name"><br/>
    年龄<input type="text" name="age"><br/>
    生日<input type="text" name="date"><br/>
    <input type="submit" value="提交"/>
</form>
  1. 自定义日期类型转换器
public class StringToDateConvertor implements Converter<String,Date> {  //实现Converter<S,T>接口

    @Override
    public Date convert(String source) {
        Date date = null ;
        if (source==null) {
        throw new RuntimeException("请输入日期");
        }else {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            try {
                date = df.parse(source);
                return date;
            } catch (ParseException e) {
            e.printStackTrace();
            }
        }
        return null;
    }
}
  1. 将类型转换器配置到SpringMVC.xml中
<!--开启SpringMVC注解配置支持,支持conversionService-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!--配置converters属性,converters是set集合类型-->
    <property name="converters">
    <!--配set集合用set标签-->
    <set>
        <!--注入自定义转换器到IoC容器中,可以配置多个转换器-->
        <bean class="com.zt.utils.StringToDateConvertor"/>
    </set>
    </property>
</bean>
  1. controller中接收
@RequestMapping(value = "/saveUser")
public String saveUser(User user){
    System.out.println(user);
    return "success";
}

五、响应数据和结果视图

1. Controller控制器返回

1.方法返回值为String字符串

@RequestMapping(value = "/testString")
public String testString(Model model){
    System.out.println("testString方法执行了");
    // 模拟从数据库中查询出一个用户的对象,把对象存起来转发到页面上
    User user = new User();
    user.setUsername("美美");
    user.setPassword("123");
    user.setAge(20);
    // 把user存入到request域中
    model.addAttribute("users",user);
    return "success";
}
${users.username}<br/>
${users.password}<br/>
${users.age}

2.方法返回值为void类型

  • 默认会转发到请求路径上,也就是访问的资源路径。
/*
* * @param
*@return 如果不写请求转发或重定向,默认转发到value名的jsp页面:/testVoid.jsp页面
* 如果项目中没有testVoid.jsp页面,可以请求转发或重定向到已有页面
*/
@RequestMapping(value = "/testVoid")
public void voidTest(HttpServletRequest req, HttpServletResponse resp){
    System.out.println("voidTest被执行了");
}
/*
请求转发:
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
请求重定向:
resp.sendRedirect(req.getContextPath()+"/index.jsp"); //req.getContextPath()获得项目路径
直接响应:
resp.getWriter().write("你好");
*/

3.返回ModelAndView类型

  • ModelAndView底层返回视图时调用的是视图解析器,return字符串的底层其实就是调用的ModelAndView
@RequestMapping(value = "/testModelAndView")
public ModelAndView testModel(){
    ModelAndView mv = new ModelAndView();
    User user = new User();
    user.setUsername("美美");
    user.setPassword("123");
    user.setAge(20);
    // 往ModelAndView中添加对象
    mv.addObject("users",user);
    // 调用视图解析器返回success.jsp
    mv.setViewName("success");
    return mv;
}

2. 转发和重定向

  • 转发和重定向用不了视图解析器,所以路径得自己去补全。==重定向不能访问WEB-INF这个安全目录==,因为重定向相当于两次客户端请求,而客户端是不能直接访问的。
@RequestMapping(value = "/testForward")
public String Forward(){
    // 使用forward: 关键字表明这是一个请求转发
    // 以前直接return字符串需要先配置视图解析器,现在可以不用配置而直接转发
    '/'在服务器被解析为tomcat服务器配置时的路径,https://ip:port/+工程名/,也就是webapp下
    return "forward:/WEB-INF/pages/success.jsp";请求转发和重定向也可以重定向到某个方法

    return "redirect:/index.jsp"; //使用关键字的重定向不用加项目名,即req.getContextPath()获得项目路径,可以直接写该项目下的路径
    }
}

3. @ResponseBody响应数据

  • @ResponseBody可以直接响应给客户端字符串数据,以JSON数据格式返回

六、SpringMVC文件上传实例

1. 依赖环境

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

表单项

<%--必须是多表单和post请求--%>
<form action="file/upload" method="post" enctype="multipart/form-data"> //必须是多表单和post请求,设置多表单的目的是把这个表单中的文件内容和普通表单项分开
比如分开username,password和file。
    <input type="text" name="username"><br/>
    <input type="password" name="password"><br/>
    <input type="file" name="upload"><br/>  //必须是file类型
    <input type="submit" value="上传"><br/>
</form>

2. 传统文件上传

@Controller
@RequestMapping(value = "/file")
public class FileController {
@RequestMapping(value = "/upload")
public String upload(HttpServletRequest request) throws Exception {
    ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
    List<FileItem> items = upload.parseRequest(request);
    for (FileItem item : items){
        if (!item.isFormField()){//如果不是普通表单项,则是文件项
            String path = request.getSession().getServletContext().getRealPath("/upload/"); //取得upload目录在磁盘中的绝对路径
            UUID uuid = UUID.randomUUID();
            String name = uuid + item.getName(); //UUID是随机生成的,在分布式系统中是唯一值,以确保下载的文件不重名,即使是相同文件
            item.write(new File(path+item.getName())); //加上文件名,写入
        }
    }
    return "success";
}

3. SpringMVC上传

1.配置文件解析器

<!--配置文件解析器对象,名称必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--maxUploadSize属性:文件最大size,单位比特-->
    <property name="maxUploadSize" value="10485760"/>
</bean>

2.配置前端页面

<form action="user/upload" enctype="multipart/form-data" method="post">
姓名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="file" name="upload"><br/>  //file的name属性要和controller的形参名一样,这样才能参数绑定,完成解析
<input type="submit" value="提交"><br/>
</form>

3.配置controller

@RequestMapping(value = "/upload")
public String upload(HttpServletRequest req, MultipartFile upload) throws Exception {  //需要upload,来完成上传,需要req来取得路径
// 解析的目的是区分普通表单项和文件项
// 文件upload已经通过绑定参数拿到,不需要再解析
// 拿到web工程下upload目录的绝对路径,可以先判断该路径是否存在,如果不存在就创建这个目录。路径取决于该web工程输出到哪,而不是Java工程的路径
    String path = req.getSession().getServletContext().getRealPath("/upload/");//如果是部署在maven工程下,则输出到的是target下
    File file = new File(path);
    if (!file.exists()){
    file.mkdirs(); //如果不存在就创建这个目录
    }
    // 拿到文件名,传统文件名,上传时的文件名
    String filename = upload.getOriginalFilename();
    // 通过设置UUID把文件的名称设置为唯一值
    // 把UUID中的"-"用""替代
    String uuid = UUID.randomUUID().toString().replace("-","");
    // 修改当前上传到服务器的文件名
    filename = uuid + "_" + filename;
    // 完成文件上传,File(String parent,String Child)
    upload.transferTo(new File(path,filename));
    return "success";
}

4.原理分析

image-20200607163555585.png

4. 跨服务器文件上传

  • 在很多时候,应用服务器和文件服务器分别负责不同的功能,文件多数由应用服务器转发保存到文件服务器。
  • 每个服务器都是一个进程,要配置不同的端口号

导入依赖

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.19.4</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.19.4</version>
</dependency>

编写服务

@RequestMapping(value = "/upload2")

public String upload2(HttpServletRequest req, MultipartFile upload) throws Exception {
    // 拿到跨服务器的图片服务器的path,上传至此
    String path = "http://localhost:9090/SpringMVC_02_image/images/";

    // 生成唯一的文件名
    String filename = upload.getOriginalFilename();
    String uuid = UUID.randomUUID().toString().replace("-","");
    filename = uuid + "_" + filename;

    // 应用服务器作为客户端
    Client client = Client.create();
    // 和图片服务器进行连接
    WebResource webResource = client.resource(path + filename);
    // 上传文件,通过字节流的方式
    webResource.put(upload.getBytes());
    return "success";
}

七、拦截器Interceptors

  • 拦截器是SpringMVC框架特有的,类似Servlet中的过滤器。也有拦截器链(类似过滤器链),先执行前置代码,然后放行(类似doFilter())最后执行后置代码。
  • 区别:拦截器的/只会拦截Controller类中的方法,而不会拦截.jsp.html等等。拦截器能做的过滤器都能做,过滤器能做的拦截器不一定能做。过滤器在DispatcherServlet之前执行,拦截器在DispatcherServlet之后执行*。
  • 预处理:preHandle:类似AOP的前置通知,在controller的方法被执行之前执行
  • 后处理:postHandle:类似AOP的后置通知,在controller的方法执行之后(但是在方法中的return语句之前,如果方法没有return值,那就在方法全部执行之后再执行)执行,要求预处理必须return true
  • 最终处理:afterCompletion :类似AOP的最终通知,在controller的return执行完后执行
  1. 定义拦截器类
//实现拦截器必须实现HandlerInterceptor方法
//如果在拦截器预处理或后处理中指定了跳转页面,就不会再执行controller中的跳转页面了
public class MyInterceptor implements HandlerInterceptor {

    /*拦截器预处理,在执行controller中的方法之前执行
    * 返回true表示放行,false表示不放行,即无法执行controller中的方法,也不会执行后处理
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截成功");
        request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }

    /*
    * 要求预处理必须返回true的时候才会执行后处理
    * 后处理方法,在controller方法执行后,方法中的return之前
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("后置方法执行了");
    }

    /*
    * 最终处理,一定会执行
    * */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("最终处理执行了");
    }
}
  1. xml中配置拦截器
<mvc:interceptors>
    <!--配置某个拦截器-->
    <mvc:interceptor>
        <!--mapping配置需要拦截的路径,/*表示拦截user目录下的任意controller方法-->
        <mvc:mapping path="/user/*"/>
        <!--exclude-mapping配置不需要拦截的路径-->
        <!-- <mvc:exclude-mapping path=""/>-->
        <!--配置用哪个拦截器来拦截-->
        <bean id="interceptor" class="com.zt.controller.interceptors.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。