Spring MVC简介

  1. MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。<br /> MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。<br /> 模型-视图-控制器(MVC)是Xerox PARC在二十世纪八十年代为编程语言Smalltalk80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到越来越多的使用ColdFusionPHP的开发者的欢迎。

Spring MVC

  1. Spring MVC属于Spring FrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web应用程序的全功能 MVC 模块。<br /> Spring MVC是当前最优秀的MVC框架,自从Sp。+3ring 2.5版本发布后,由于支持注解配置,易用性有了大幅度的提高。Spring 3.0更加完善,实现了对Struts 2的超越。现在越来越多的开发团队选择了Spring MVC

入门案例(springmvc整合thymeleaf)

步骤1:创建web项目,导入对应依赖。

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-webmvc</artifactId>
  4. <version>5.2.8.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.thymeleaf</groupId>
  8. <artifactId>thymeleaf</artifactId>
  9. <version>3.0.11.RELEASE</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.thymeleaf</groupId>
  13. <artifactId>thymeleaf-spring5</artifactId>
  14. <version>3.0.11.RELEASE</version>
  15. </dependency>

步骤2:创建springMVC配置文件springmvc.xml。

<?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">
    <!-- 配置spring MVC扫描的包,spring MVC只扫描@Controller -->
    <context:component-scan base-package="com.woniu.controller">

    <mvc:annotation-driven/>

    <!--thymeleaf模板解析器-->
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
        <!--前缀配置-->
        <property name="prefix" value="/page/"></property>
        <!--后缀配置-->
        <property name="suffix" value=".html"></property>
        <!--模板类型-->
        <property name="templateMode" value="HTML"></property>
        <!--不使用缓存-->
        <property name="cacheable" value="false"></property>
        <!--编码类型-->
        <property name="characterEncoding" value="UTF-8"></property>
    </bean>
    <!--模板引擎配置-->
    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver"></property>
    </bean>
    <!--视图处理器-->
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine"></property>
        <property name="characterEncoding" value="UTF-8"></property>
    </bean>
    <!--配置静态资源映射-->
    <mvc:resources mapping="/page/**" location="/page/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>
</beans>

步骤3:在web.xml中配置web容器启动时创建DispatcherServlet,同时解析spring MVC配置文件生成spring MVC容器(该容器是spring容器的子容器,也就是说spring容器中的内容spring MVC容器都可以使用)。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!-- 配置字符编码过滤器 -->
     <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>

    <!-- 配置web容器启动时创建前端控制器DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 修改springmvc配置文件的路径和名称 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- web容器启动即创建 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

步骤4:创建控制器类TestController.java。

//注解配置本类为控制器
@Controller
public class TestController {
    //解析用户请求映射处理器
    @RequestMapping("/")
    public String index(){
        //返回逻辑视图
        return "index";
    }
}

注:@RequestMapping可以注解在类上,也可以注解在方法上,使用方式如下例:

//localhost:8080/user/
@Controller
@RequestMapping("/user")
public class TestController {

  //解析用户请求映射处理器
    @RequestMapping("/")
    public String index(){
        //返回逻辑视图
        return "index";
    }
}
    当方法上配置的路径过长时,可以使用@RequestMapping将路径分为两段,公共的一级路径配置在控制器类上,独有的二级路径配置在方法上,可以减少路径配置代码。

步骤5:在webapp目录下创建static目录,在该目录下创建index.html。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    这是首页!!!
</body>
</html>

步骤6:测试
部署项目到tomcat并运行,http://localhost:9080/,可正常访问登录页面。

spring mvc也可以使用jsp模板,如果使用jsp模板,在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">
    <!-- 配置spring MVC扫描的包,spring MVC只扫描@Controller -->
    <context:component-scan base-package="com.woniu">
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <mvc:annotation-driven/>
    <!--配置视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/page/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--配置静态资源映射-->
    <mvc:resources mapping="/static/**" location="/static/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>

</beans>
需要注意的是,如果前端全部使用ajax方式对后台发送请求,那么springmvc的视图解析器将不再是必要的。因为前后端会使用json进行数据交互,而不必使用视图进行数据渲染,所以无需使用视图解析器进行解析,从而可以不再配置视图解析器。

入门案例分析

springMVC运行原理图.jpg
通过入门案例,可以分析出springmvc运行流程。
学习spring MVC最重要的是弄懂其运行流程!!!
前端控制器DispatcherServlet是spring MVC的核心组件。DispatcherServlet是一个Servlet,可以拦截HTTP发送过来的请求。应用启动时,tomcat会根据web.xml中的配置,对DispatcherServlet进行初始化。
应用启动后,当用户发送请求时,DispatcherServlet会拦截用户请求,获取请求URI,将其分发至处理器映射器HandlerMapping。
HandlerMapping解析用户请求后,将生成的处理器执行链返回给DispatcherServlet。处理器Handler不能直接运行,必须在与该处理器对应的环境中运行,因此DispatcherServlet将执行处理器的请求分发给处理器适配器,由处理器适配器负责调用对应的处理器Handler进行处理。
处理器Handler完成操作后将返回模型和视图ModelAndView,处理器适配器将其返回给DispatcherServlet。
DispatcherServlet得到ModelAndView之后,会判断其是否为逻辑视图:

  • 如果是逻辑视图,DispatcherServlet调用视图解析器ViewResolver,ViewResolver将逻辑视图解析后,将模型Model渲染到视图View,返回给DispatcherServlet,DispatcherServlet响应用户请求;
  • 如果不是逻辑视图,DispatcherServlet不会调用视图解析器ViewResolver进行处理,而是直接通过视图渲染数据模型,响应用户请求。

绑定请求参数

基本数据类型和String

    用户发送的请求URL上带有参数时,spring MVC会自动将URL的参数值注入到控制器中映射方法的同名参数中,即要求**方法形参名必须与请求参数名相同**。<br />        用户请求中的参数传递是以键值对的方式进行传递,spring MVC获取请求后,使用key取得请求的参数的value值并注入到方法的同名形参中(根据名称注入,与参数位置无关)。<br />        虽然请求中传递的参数都是字符串,但spring MVC底层会自动对其进行转换,以符合形参的类型进行注入(前提是可以进行转换)。

例:方法形参是Integer类型的,此时请求参数中的“123”就可以注入成功,而“ab”则不行,原因是ab无法转换成数字,又或是“12.34”,也不能注入,因为转换后是Double类型,无法注入到Integer类型参数中。 需要注意的是:如果请求中传递的是整形数值字符串,方法形参采用Double类型,是可以进行注入的。也就是说,绑定请求参数时还得注意java数据类型转换的问题。

简单类型

需求:获取用户请求中的参数,将其封装到控制器方法的形参中。
步骤1:修改index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<form method="post" th:action="@{/register}">
    <table>
        <tr>
            <td>用户名</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>年龄</td>
            <td><input type="text" name="age"></td>
        </tr>
        <tr>
            <td>生日</td>
            <td><input type="text" name="birthday"></td>
        </tr>
        <tr>
            <td>爱好</td>
            <td>
                <input type="checkbox" name="hobbies" value="电视">电视
                <input type="checkbox" name="hobbies" value="游戏">游戏
                <input type="checkbox" name="hobbies" value="睡觉">睡觉
                <input type="checkbox" name="hobbies" value="美食">美食
            </td>
        </tr>
        <tr>
            <td>账号</td>
            <td><input type="text" name="cardNo"></td>
        </tr>
        <tr>
            <td>余额</td>
            <td><input type="text" name="balance"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" value="登录"></td>
        </tr>
    </table>
</form>
</body>
</html>

步骤2:修改控制器TestController.java

@Controller
public class TestController {
    @RequestMapping("/")
    public String index(){
        return "index";
    }

    @RequestMapping("register")
    public void register(String username,String password,Integer age,Date birthday){
        System.out.println(username+":"+password+":"+age+":"+birthday);
    }    
}

集合类型

    使用@RequestParam可使用集合接收多个同名参数的值。<br />        需求:为多个同名参数传值。<br />修改TestController.java
@Controller
public class TestController {
    @RequestMapping("/")
    public String index(){
        return "index";
    }
    @RequestMapping("register")
    public void register(String username,String password,Integer age,Date birthday,@RequestParam List<String> hobbies,String cardNo,Double balance){
        System.out.println(username+":"+password+":"+age+":"+birthday+":"+hobbies+":"+cardNo+":"+balance);
    }
}

javaBean

javaBean属性:基本数据类型或String

当参数个数太多的时候,spring MVC也可以通过给javaBean来注入对应属性,此时要求javaBean的属性名与请求参数名相同
需求:将用户请求中的参数注入到javaBean中。

步骤1:创建javaBean
User.java

@Data
@ToString
public class User {
    private String username;
    private String password;
    private Integer age;
    private Date birthday;
}

步骤2:修改控制器TestController.java

@Controller
public class TestController {
    @RequestMapping("/")
    public String index(){
        return "index";
    }
    @RequestMapping("register")
    public void register(User user){
        System.out.println(user);
    }
}

javaBean属性:引用数据类型

需求:给javaBean中的引用数据类型注入值。
步骤1:自定义引用数据类型Account.java,修改User.java

@Data
@ToString
public class Account {
    private String cardNo;
    private Double balance;
}

@Data
@ToString
public class User {
    private String username;
    private String password;
    private Integer age;
    private Date birthday;
    private Account account;//用于接收自定义类型数据
}

步骤2:修改index.html

        <tr>
            <td>账号</td>
            <td><input type="text" name="account.cardNo"></td>
        </tr>
        <tr>
            <td>余额</td>
            <td><input type="text" name="account.balance"></td>
        </tr>

javaBean属性:集合类型

需求:为javaBean的集合类型属性注入值。<br />步骤1:修改User.java
@Data
@ToString
public class User {
    private String username;
    private String password;
    private Integer age;
    private Date birthday;
    private List<String> hobbies;//用于接收前端表单同名参数的值
    private Account account;
}

自定义类型转换器

在spring mvc中,如果给日期类型的参数注入值,需要按照特定的形式输入,比如“2000/10/10”,如果输入“2000-10-10”,则会注入失败,因为spring mvc默认不支持以上形式的日期进行类型转换。要解决这个类型的问题(即用户请求中出现了spring mvc默认类型转换解决不了的值),可以使用自定义类型转换器。<br />    需求:自定义日期类型转换器,输入“2000/10/10”或“2000-10-10”都可以实现日期值的成功注入。<br />步骤1:创建工具类String2Date.java
@Component
public class String2Date implements Converter<String, Date> {
    @Override
    public Date convert(String s) {
        if (StringUtils.isEmpty(s)) {
            throw new RuntimeException("参数不正确");
        }
        try {
            String pattern1="[0-9]{4}-[0-9]{2}-[0-9]{2}";
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
            String pattern2="[0-9]{4}/[0-9]{2}/[0-9]{2}";
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy/MM/dd");
            Date date=null;
            if (s.matches(pattern1)) {
                return sdf1.parse(s);
            }
            if (s.matches(pattern2)) {
                return sdf2.parse(s);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

步骤2:修改springmvc.xml

      <!-- 配置自定义类型转换器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <!-- 将自定义类型转换器注册到spring MVC类型转换器集合 -->
                <bean class="com.wsjy.converter.String2Date"/>
            </set>
        </property>
    </bean>
    <!-- 注册自定义类型转换器,让自定义类型转换器生效 -->
    <mvc:annotation-driven conversion-service="conversionService"/>

获取Servlet API

需求:在控制器中需要获取servlet API。<br />    实现方式:在控制器方法参数中使用对应的形参接收即可。<br />步骤1:修改控制器TestController.java
@Controller
public class TestController {
    ......
    @RequestMapping("servletapi")
    public void testServletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
        System.out.println(session.getServletContext());
    }
}

常用注解

@RequestParam

在实际开发中,前后端开发是分离的。也就是说前端页面由负责前端开发的程序员编写,后台代码由负责后台开发的程序员编写。在这种情况下,极有可能产生以下的情况:前端页面上表单控件的name属性名称和后端控制器方法中的形参名称不匹配。<br />    一般来说,在开发中,负责后台的程序员不可能去修改前端页面上的name属性,因为该属性有可能会影响到js或css部分,此时有两种办法解决:1、将控制器的形参名称取得跟表单控件的name属性名称一样;2、选择使用@RequestParam来解决。<br />    此外,也可以使用@RequestParam来解决前端同名参数与后端集合之间的数据传递问题。<br />    需求:使用@RequestParam解决表单控件的username属性值与控制器中的形参user名称不符。
    <input type="text" name="username">
    @RequestMapping("register")
    public void register(@RequestParam("username") String user){
        System.out.println(user);
    }

@RequestBody

当项目中使用Ajax时,如果前端采用post方式向后台提交json字符串,此时后台无法直接获取请求体中json数据。直接接收时,一般会响应400(错误的请求),此时可以使用@RequestBody来解决。需要注意的是,该注解用于post请求,get请求不适用(get请求的数据都拼接到URL中了)。<br />    需求:获取请求体中的内容。<br />方式一:序列化表单<br />步骤1:创建ajax.html
    <form id="form" method="post">
        <table>
            <tr>
                <td>用户名</td>
                <td><input id="username" type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input id="password" type="password" name="password"></td>
            </tr>
            <tr>
                <td colspan="2"><input type="button" value="登录" id="btn"></td>
            </tr>
        </table>
    </form>

    <script src="../js/jquery-3.3.1.js" type="text/javascript"></script>
    <script>
        $(function () {
            $("#btn").click(function () {
                //序列化表单
                var params=$("#form").serialize()
                alert(params)
                $.ajax({
                    url:"ajax",
                    type:"post",                    
                    data:params,                    
                    dataType:"text",
                    success:function (data) {
                        alert(data)
                    }
                })
            })
        })
    </script>

步骤2:修改TestController.java

    @RequestMapping("ajax")
    @ResponseBody
    public String ajax(User user){
        System.out.println(user);
        return "success";
    }

方式二:传递json字符串
步骤1:导入jackson-core、jackson-databind、jackson-annotations依赖
步骤2:contentType:”application/json;charset=UTF-8”,提交方式必须是post

    <script src="../js/jquery-3.3.1.js" type="text/javascript"></script>
    <script>
        $(function () {
            $("#btn").click(function () {
                //获取json对象,此处可使用插件来获取
                var params={
                    username:$("#username").val(),
                    password:$("#password").val(),
                }
                alert(params)
                $.ajax({
                    url:"ajax",
                    type:"post",
                    //以json字符串传递,需要在请求头的contentType设置为json方式提交
                    contentType:"application/json;charset=UTF-8",
                    //将json对象转换为真正的json字符串 
                    data:JSON.stringify(params),
                    dataType:"text",
                    success:function (data) {
                        alert(data)
                    }
                })
            })
        })
    </script>

步骤3:@RequestBody

    //使用@RequestBody注解接json字符串参数
    @RequestMapping("ajax")
    @ResponseBody
    public String ajax(@RequestBody User user){
        System.out.println(user);
        return "success";
    }

@PathVariable

项目开发过程中使用了restful风格时,可以使用@PathVariable注解获取restful风格传递的参数值。<br />步骤1:创建restful.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试ajax</title>
</head>
<body>
    <a th:href="@{/rest/tom/111}">测试restful传参</a>
</body>
</html>

步骤2:修改TestController.java

@Controller
public class TestController {
    @RequestMapping("/")
    public String index(){
        return "restful";
    }

    @GetMapping("rest/{username}/{password}")
    public String rest(@PathVariable String username,@PathVariable String password){
        System.out.println(username+":"+password);
        return "index";
    }
}

响应返回值

spring MVC控制器的方法返回值有5种:字符串、void、关键字(forward、redirect)、ModelAndView、Json。

返回字符串

入门案例中使用的就是返回字符串方式,在配置文件中配置视图解析器将返回的字符串解析为一个具体的路径,实现内部跳转,使用的是请求转发的方式。

返回void

如果控制器方法不设返回值,根据不同的模板,操作不同。<br />    使用jsp模板,无法跳转。如果需要跳转,只能使用servlet API来实现,通过HttpServletRequest实现请求转发,HttpServletResponse实现重定向。<br />    使用thymeleaf模板时,会将请求路径作为视图名进行解析并跳转,如果没有对应的视图(html页面),则会报如下异常:<br />    Request processing failed; nested exception is  org.thymeleaf.exceptions.TemplateInputException: An error happened  during template parsing (template: "ServletContext resource  [/static/r.html]")

返回关键字

所谓关键字,是指forward和redirect,可以使用这两个关键字来实现转发和重定向,控制器方法的返回值实质上还是返回的字符串形式,如果不写关键字,默认请求转发。<br />步骤1:修改TestController.java
@Controller
public class TestController {
    @RequestMapping("servletapi")
    public void testServletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
        System.out.println(session.getServletContext());
    }

    /**
     * 请求转发:转发到视图,url不变
     * @param response
     * @return
     */
    @RequestMapping("testForwardView")
    public String forwardView(HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        return "forward:/static/index.html";
    }

    /**
     * 请求转发:转发到请求路径,url不变
     * @return
     */
    @RequestMapping("testForwardPath")
    public String forwardPath(){
        return "forward:servletapi";
    }

    /**
     * 重定向:视图,url发生变化
     * @param response
     * @return
     */
    @RequestMapping("testRedirectView")
    public String redirectView(HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        return "redirect:/static/index.html";
    }

    /**
     * 重定向:请求路径,url发生变化
     * @return
     */
    @RequestMapping("testRedirectPath")
    public String redirectPath(){
        return "redirect:servletapi";
    }    
}

返回ModelAndView

spring MVC使用ModelAndView对模型和视图进行了封装,也就是说,使用ModelAndView对象不但可以保存数据,而且可以设置视图,之后spring MVC会调用视图解析器对ModelAndView进行解析,返回结果到相应的视图。<br />步骤1:修改TestController.java
    @RequestMapping("testModelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView modelAndView = new ModelAndView();
        //存放数据
        modelAndView.addObject("username","tom");
        //设置跳转的视图名称
        modelAndView.setViewName("info");
        return modelAndView;
    }

步骤2:创建info.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试ModelAndView</title>
</head>
<body>
    <h1 th:text="${username}"/>
</body>
</html>

返回Json

spring MVC可以将数据封装成Json并将该结果响应给视图,只需要通过两个步骤即可实现:
1.将控制器方法的返回值设为希望返回的类型;
2.在控制器方法上使用@ResponseBody注解修饰即可;
需求:返回Json数据给前端页面。
修改TestController.java

    @RequestMapping("getUser")
    @ResponseBody
    public List<User> getUser(){
        List<User> users= Arrays.asList(new User().setUsername("tom"),new User().setUsername("kate"),new User().setUsername("jack"));
        return users;
    }

一般来说,在项目开发过程中,前端会希望后台返回一个固定格式的值以方便其使用。因此,后台返回json数据时,一般会设计一个通用返回类用于限定返回给前端的数据。
创建Result.java,用于向前端传递数据。

@Data
public class Result<T> implements Serializable {
    private boolean flag;//是否成功
    private Integer code;//响应状态码
    private String message;//返回消息
    private T data;//返回数据

    public Result(boolean flag, Integer code, String message, Object data) {
        this.flag = flag;
        this.code = code;
        this.message = message;
        this.data = (T) data;
    }

    public Result(boolean flag, Integer code, String message) {
        this.flag = flag;
        this.code = code;
        this.message = message;
    }

    public Result() {
        this.flag = true;
        this.code = StatusCode.OK;
        this.message = "操作成功!";
    }    
}

如果不希望直接返回http响应状态码,也可以自定义响应状态码类:StatusCode.java

public class StatusCode {
    public static final int OK = 20000;//成功
    public static final int ERROR = 20001;//失败
    public static final int LOGINERROR = 20002;//用户名或密码错误
    public static final int ACCESSERROR = 20003;//权限不足
    ......
}

以通用返回类型向前端传递数据。

    @RequestMapping("getUser")
    @ResponseBody
    public Result<List<User>> getUser(){
        List<User> users= Arrays.asList(new User().setUsername("tom"),new User().setUsername("kate"),new User().setUsername("jack"));
        return new Result<>(true, StatusCode.OK,null,users);
    }

Restful风格接口设计

目前来说,实际项目开发中大部分都采用了前后端分离的设计,前端会通过ajax或提交表单的形式向后台发送rest请求,后台则会根据rest方式以restful风格进行接口设计。<br />    rest请求方法有4种,包括get,post,put,delete。分别对应获取资源,添加资源,更新资源及删除资源。<br />    restful是符合rest架构风格的网络API接口,完全承认Http是用于标识资源。restful风格的URL是面向资源的,可以唯一标识和定位资源。 对于该URL标识的资源做何种操作是由Http方法决定的。<br />    不使用restful的url:<br />[http://www.xxxxx.com/user?a=1&b=2&c=3](http://www.xxxxx.com/user?a=1&b=2&c=3)     url:  [http://www.xxxxx.com/user](http://www.xxxxx.com/user)   参数: ?a=1&b=2&c=3<br />    使用restful的url:<br />[http://www.xxxxx.com/user/1/2/3](http://www.xxxxx.com/user/1/2/3)                     url:  [http://www.xxxxx.com/user/1/2/3](http://www.xxxxx.com/user/1/2/3)<br />    使用restful风格设计时应注意的是:url中不应出现动词,而应该使用rest请求方式来决定做哪种操作(查询、新增、修改、删除)。<br />错误示例:
    @RequestMapping(value = "addUser",method = RequestMethod.POST)
    public void addUser(User user){

    }

正确示例

    @RequestMapping(value = "user",method = RequestMethod.POST)
    public void addUser(User user){

    }
    springmvc为支持restful,单独提供了4个注解:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping,分别对应查询、新增、修改、删除。<br />    因此上例中的代码可以简写为以下形式:
    @PostMapping(value = "user")
    public void addUser(User user){

    }

restful 风格接口设计示范:

@RestController
public class RestfulController {
    @GetMapping("user")
    public Result<User> get(){
        return new Result<>(true, StatusCode.OK,
                "查询用户信息成功!",new User("tom","111"));
    }
    @DeleteMapping("user")
    public Result delete(){
        return new Result(true, StatusCode.OK,
                "删除用户成功!");
    }
    @PostMapping("user")
    public Result save(@RequestBody User user){
        System.out.println(user);
        return new Result(true, StatusCode.OK,
                "新增用户成功!");
    }
    @PutMapping("user")
    public Result update(@RequestBody User user){
        System.out.println(user);
        return new Result(true, StatusCode.OK,
                "修改用户成功!");
    }
}

注意:大多数浏览器不支持put和delete,无法通过浏览器进行测试,可通过postman工具来进行测试。
幂等性
对一个资源不论发送多少次相同的请求都不会更改数据或造成破坏,这就是幂等性。对于rest请求来说,除post请求外,get、put、delete都是幂等性的。

文件上传和文件下载

spring MVC使用了MultipartFile接口的实现类CommonsMultipartFile实现文件上传,相对于传统方式实现文件上传,大大简化了文件上传中的解析过程,需要在spring配置文件中配置文件解析器CommonsMultipartResolver支持该类的使用。

文件上传

需求:实现文件上传。                <br />步骤1:创建upload.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试文件上传</title>
</head>
<body>
    <form th:action="@{/upload}" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username" ><br>
        密码:<input type="password" name="password" ><br>
        文件上传:<input type="file" name="upload" id="upload"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

步骤2:修改springmvc.xml

    <!--配置文件解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--使用spEL设置文件上传最大尺寸-->
        <property name="maxUploadSize" value="#{1024*1024*100}"/>
    </bean>

步骤3:修改TestController.java

    /**
     * 测试文件上传
     * 文件上传后存放的位置:web服务器中,本地文件系统中,第三方文件服务器中
     * web服务器:
     * 需要借助于servlet-api实现,因为要获取项目在服务器中的路径,参数中需要传入HttpServletRequest
     * 本地文件系统:
     * 直接使用本地路径即可
     * 第三方文件服务器:阿里云(要收费)、七牛云(免费10G)等
     * 需要整合第三方依赖(自行查阅相关资料)
     * @param upload :上传文件名称,必须与<input type="file"/>标签中的name属性值相等
     * @return
     */
    @RequestMapping("upload")
    public String testUpload(User user,MultipartFile upload) throws IOException {
        System.out.println(user);
        //设置文件上传后的存放路径
        //1、存放在tomcat中
//        String path = request.getSession().getServletContext().getRealPath("/uploads");
        //2、存放在本地文件系统中
        String path="D:/uploads".replace("/",File.separator);
        File file=new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        String fileName = upload.getOriginalFilename();
        //避免文件同名,使用UUID+系统时间对文件名进行处理
        String uuid = UUID.randomUUID().toString().replace("-", "");
        long currentTimeMillis = System.currentTimeMillis();
        String uploadFileName=uuid+currentTimeMillis+fileName;
        try {
            //执行上传操作
            upload.transferTo(new File(path,uploadFileName));
            System.out.println("上传成功");
            return "success";
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("上传失败");
            return "error";
        }
    }

文件下载

在常规的HTTP应答中,**Content-Disposition** 响应头指示回复的内容该以何种形式展示,是以**内联**(默认)的形式(即网页或者页面的一部分),还是以**附件**的形式下载并保存到本地。<br />    如果希望所有文件都进行下载,需要设置响应头Content-Disposition属性:
Context-Disposition=”attachment;filename=下载后生成的文件名”

spring MVC提供了ResponseEntity类,用于将响应头信息、响应状态码与响应实体封装到一起,可以使用ResponseEntity类实现对Content-Disposition属性的设置。
步骤1:在D:/uploads目录中创建文件(学员的自我修养.txt)
步骤2:创建download.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试文件下载</title>
</head>
<body>
    <a th:href="@{/download/学员的自我修养/txt}">学员的自我修养</a>
</body>
</html>

步骤3:修改TestController.java

    @GetMapping("download/{filename}/{filetype}")
    public ResponseEntity download(@PathVariable("filename") String filename, 
                           @PathVariable("filetype")String filetype) throws IOException {
        String downloadFileName=filename+"."+filetype;
        String path="D:/uploads".replace("/",File.separator);
        File downloadFile=new File(path,downloadFileName);
        //创建HttpHeaders对象,向响应头中添加Content-Disposition属性
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(downloadFile.getName(),"UTF-8"));//文件名编码成utf-8,否则文件名为中文时会乱码
        //http请求响应状态码
        HttpStatus statusCode = HttpStatus.OK;
        //使用FileUtils读取文件并转换为二进制流,得到字节数组
        byte[] bytes = FileUtils.readFileToByteArray(downloadFile);
        //使用ResponseEntity封装响应头,响应码及字节数组
        ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(bytes, headers, statusCode);
        //将被ResponseEntity封装的字节数组返回给浏览器,浏览器就会使用附件的形式实现下载
        return entity;
    }

简单实现方式,不使用ResponseEntity,也可实现同样效果。

 @RequestMapping("/download/{filename}/{filetype}")
 public void download(@PathVariable("filename")String filename,
                      @PathVariable("filetype")String filetype,
                         HttpServletRequest request, HttpServletResponse response) throws IOException {
        String downloadFileName = filename + "." + filetype;
        String path="D:/uploads".replace("/",File.separator);
        File downloadFile = new File(path, downloadFileName);
           //设置响应头信息
        response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(downloadFile.getName(),"UTF-8"));
           //获取字节输出注
     ServletOutputStream os = response.getOutputStream();
           //使用commons组件FileUtils读取下载文件获得字节数组
     byte[] bytes = FileUtils.readFileToByteArray(downloadFile);
           //使用输出将字节数组写到浏览器
     os.write(bytes);
           //清空输出流
     os.flush();
           //关闭输出流
     os.close();
    }
}

自定义异常处理

    在实际开发过程中,代码底层产生的异常一般都不会立即处理,而是层层上抛,即从持久层->业务层->表现层。如果表现层不进行处理,则会将异常抛给浏览器,在浏览器中打印异常信息。这种方式对用户来说极不友好,因为用户看不懂,从而使用户体验变得很差,因此需要在表现层对异常进行处理,给用户一个友好的提示页面。<br />        spring MVC提供了异常处理器HandlerExceptionResolver,可以在表现层捕获了底层代码抛出的异常后,先对异常进行处理,然后跳转到对应的提示页面。<br />        需求:使用自定义异常处理器解决直接向浏览器抛出异常信息问题。<br />步骤1:创建错误页面,error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误页面</title>
</head>
<body>
    程序执行出错!!!
</body>
</html>

步骤2:创建自定义异常处理类
MyExceptionHandler.java

//如果是纯XML配spring容器中注册全局异常处理器
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) {
        e.printStackTrace();//打印异常信息,如果这里不打印,则异常信息无法打印,从而无法准备定位异常
        ModelAndView view = new ModelAndView();        
        //如果是ajax请求
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            response.setStatus(500);
            FastJsonJsonView jsonView = new FastJsonJsonView();
            HashMap<String, String> json = new HashMap<>();
            json.put("message","服务器异常");
            jsonView.setAttributesMap(json);
            view.setView(jsonView);
        }else{//如果是普通请求
            view.setViewName("error.html");
        }
        return view;
    }
}

步骤3:测试自定义异常处理类

    @RequestMapping("/")
    public String index(){
        int i=1/0;
        return "download";
    }

实际开发过程中,一般会定义全局异常总类,通过该类来实现对异常的精准控制。
步骤1:创建全局异常总类

@RestControllerAdvice//返回json格式
public class GlobalExceptionHandler{
    @ExceptionHandler({NullPointerException.class})
    public Result handlerNullPointException(){
        return new Result(false, StatusCode.ERROR,"空指针异常",null);
    }
    @ExceptionHandler({Exception.class})
    public Result handlerException(){
        return new Result(false, StatusCode.ERROR,"服务器异常",null);
    }
}
    @RequestMapping("/")
    public String index(){
        User user=null;
        user.getUsername();
        int i=1/0;
        return "download";
    }

拦截器

拦截器是spring MVC中强大的控件,可以在进行处理器之前做一些操作,也可以在处理器完成之后进行操作,甚至在渲染视图后还能进行操作。<br />    拦截器的功能类似于过滤器,但与过滤器相比,它们之间有以下区别:
  • 过滤器是servlet API的一部分,任何java Web项目都可以使用,拦截器是spring MVC的组件,只能在使用了spring MVC框架的项目中使用;
  • 过滤器在url-pattern中配置/可以拦截所有请求的资源,*拦截器只拦截控制器中的方法

    与过滤器类似,拦截器也可以配置多个,从而形成一个拦截器链,拦截器链的执行顺序以其配置顺序来决定。
    spring MVC中拦截器必须实现HandlerInterceptor接口。
    需求:测试spring MVC拦截器的使用。
    步骤1:创建拦截器类
    FirstInterceptor.java

    public class FirstInterceptor implements HandlerInterceptor {
      /**
       * 拦截器的preHandle方法会在控制器的方法执行之前执行
       * 如果有多个拦截器形成拦截器链,第一个拦截器的preHandle方法执行完成后
       * 会按照在XML中的配置顺序向后执行其他拦截器的preHandle方法,最后执行控制器方法
       * 该方法的返回值决定了是否放行
       * true:放行
       * false:拦截,不放行
       * 可以使用参数request和response进行跳转
       */
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          System.out.println("第一个拦截器预处理方法执行了。。。");
          return true;
      }
    
      /**
       * postHandle方法会在控制器方法执行之后执行
       * 如果有多个拦截器形成拦截器链,最后一个拦截器的preHandle方法执行完成后
       * 会按照在XML中的配置顺序倒序执行其他拦截器的preHandle方法,最后跳转到对应的视图
       */
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          System.out.println("第一个拦截器后处理方法执行了。。。");
          request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
      }
    
      /**
       * 视图渲染完成后,拦截器才执行的方法
       */
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          System.out.println("第一个拦截器最终方法执行了。。。");
      }
    }
    

    SecondInterceptor.java

    public class SecondInterceptor implements HandlerInterceptor {
    
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          System.out.println("第二个拦截器预处理方法执行了。。。");
          return true;
      }
    
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          System.out.println("第二个拦截器后处理方法执行了。。。");
          request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
      }
    
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          System.out.println("第二个拦截器最终方法执行了。。。");
      }
    }
    

    步骤2:修改springmvc.xml

      <!-- 配置拦截器 -->
      <mvc:interceptors>
          <!-- 配置具体的拦截器,通过子标签指定用哪个拦截器拦截用户请求中映射的哪个控制器方法,
              如有多个拦截器,需要配置多个<mvc:interceptor>
          -->
          <mvc:interceptor>
              <!-- 配置拦截器拦截哪个方法
                  path为拦截器拦截请求中映射控制器方法部分,可使用通配符
                  /**:代表拦截任意层路径下的任意方法
                  /user/*:代表拦截@RequestMapping设置了一级路径为student的控制器中所有方法
               -->
              <mvc:mapping path="/user/*"/>            
              <!-- 配置拦截器 -->
              <bean class="com.woniu.interceptor.FirstInterceptor"/>
          </mvc:interceptor>
    
          <mvc:interceptor>
              <!-- 拦截所有 -->
              <mvc:mapping path="/**"/>
              <!-- exclude为排除,也就是不拦截哪个方法
              <mvc:exclude-mapping path="/page/*"/>-->
              <!-- 放行部分 -->
              <mvc:exclude-mapping path="/page/*"/>
              <bean class="com.woniu.interceptor.SecondInterceptor"/>
          </mvc:interceptor>
      </mvc:interceptors>
    

    拦截器应用

    登录验证

    使用拦截器可以方便的实现页面访问时的登录验证。
    需求:使用拦截器实现登录验证。
    步骤1:环境准备。
    index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="UTF-8">
      <title>index</title>
    </head>
    <body>
      <h1 th:text="这是首页"/>
    </body>
    </html>
    

    WEB-INF/pages/login.jsp

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="UTF-8">
      <title>login</title>
    </head>
    <body>
    <form method="get" th:action="@{/user}">
      <table>
          <tr>
              <td>用户名</td>
              <td><input type="text" name="username"></td>
          </tr>
          <tr>
              <td>密码</td>
              <td><input type="password" name="password"></td>
          </tr>
          <tr>
              <td colspan="2"><input type="submit" value="登录"></td>
          </tr>
      </table>
    </form>
    </body>
    </html>
    

    Result.java

    @Data
    public class Result<T> implements Serializable {
      private boolean flag;//是否成功
      private Integer code;//返回码
      private String message;//返回消息
      private T data;//返回数据
    
      public Result(boolean flag, Integer code, String message, Object data) {
          this.flag = flag;
          this.code = code;
          this.message = message;
          this.data = (T) data;
      }
    
      public Result(boolean flag, Integer code, String message) {
          this.flag = flag;
          this.code = code;
          this.message = message;
      }
    
      public Result() {
          this.flag = true;
          this.code = StatusCode.OK;
          this.message = "操作成功!";
      }
    }
    

    StatusCode.java

    public class StatusCode {
      public static final int OK = 20000;//成功
      public static final int ERROR = 20001;//失败
      public static final int LOGINERROR = 20002;//用户名或密码错误
      public static final int ACCESSERROR = 20003;//权限不足
      public static final int REMOTEERROR = 20004;//远程调用失败
      public static final int REPERROR = 20005;//重复操作
      public static final int NOTFOUNDERROR = 20006;//没有对应的抢购数据
    }
    

    步骤2:创建拦截器,验证登录状态。

    public class LoginInterceptor implements HandlerInterceptor {
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          Object loginUser = request.getSession().getAttribute("loginUser");
          if (!ObjectUtils.isEmpty(loginUser)) {
              System.out.println("已经登录");
              return true;
          }else{
              System.out.println("尚未登录");
              response.sendRedirect("/toLogin");
              return false;
          }
      }
    }
    

    步骤3:创建控制器,处理对应请求。

    @Controller
    public class UserHandler {
      @RequestMapping("toLogin")
      public String toLogin(){
          return "login";
      }
    
      @RequestMapping("user")
      @ResponseBody
      public Result login(User user, HttpSession session){
          if (!ObjectUtils.isEmpty(user)) {
              if (user.getUsername().equals("tom") && user.getPassword().equals("111")) {
                  session.setAttribute("loginUser",user);
                  return new Result(true, StatusCode.OK,"登录成功!",user);
              }
          }
          return new Result(false, StatusCode.LOGINERROR,"登录失败!");
      }
    
      @RequestMapping("toIndex")
      public String toIndex(){
          return "index";
      }
    }
    

    步骤4:配置拦截器,设置拦截规则和放行规则。

      <mvc:interceptors>
          <mvc:interceptor>
              <mvc:mapping path="/**"/>
              <mvc:exclude-mapping path="/user"/>
              <mvc:exclude-mapping path="/toLogin"/>
              <bean class="com.woniu.interceptor.LoginInterceptor"/>
          </mvc:interceptor>
      </mvc:interceptors>
    

    SSM整合

    对于三层架构的项目而言,表现层使用的是spring MVC,持久层使用mybatis,而业务层则需要使用到spring。
    在项目中同时使用三个框架,此时如何来进行整合?可以从框架的使用范围来判断。
    mybatis只作用于持久层,spring MVC只作用于表现层,而spring是一套完整的解决方案,并不单单只作用于业务层。
    由此可知,进行整合时,一定是使用spring去整合mybatis和spring MVC。在进行整合时,一定要先确保每个部分都可以正常运行,才去进行整合操作。
    需求:使用spring整合spring MVC、mybatis,实现对数据库增删改查操作,使用声明式事务完成事务处理。
    整合前的准备:
    创建数据库表t_account,表中字段(aid,userName,balance),准备数据。
    导入依赖 ```xml

    junit junit 4.11 test org.springframework spring-webmvc 5.2.8.RELEASE org.springframework spring-tx 5.2.8.RELEASE org.springframework spring-jdbc 5.2.8.RELEASE org.aspectj aspectjweaver 1.9.6 mysql mysql-connector-java 5.1.43 org.mybatis mybatis 3.4.6 org.mybatis mybatis-spring 1.3.2 org.projectlombok lombok 1.18.16 com.alibaba druid 1.1.22 org.thymeleaf thymeleaf 3.0.11.RELEASE org.thymeleaf thymeleaf-spring5 3.0.11.RELEASE com.fasterxml.jackson.core jackson-core 2.11.3 com.fasterxml.jackson.core jackson-databind 2.11.3 com.fasterxml.jackson.core jackson-annotations 2.11.3 com.alibaba fastjson 1.2.62

整合第一步:搭建spring环境。 创建项目基本结构(domain层、dao层、service层、controller层) domain:Account.java //实体类 @Data public class Account implements Serializable { private Integer aid; private String userName; private Double balance; }

//通用返回类型 @Data public class Result implements Serializable { private boolean flag;//是否成功 private Integer code;//返回码 private String message;//返回消息 private T data;//返回数据

public Result(boolean flag, Integer code, String message, Object data) {
    this.flag = flag;
    this.code = code;
    this.message = message;
    this.data = (T) data;
}

public Result(boolean flag, Integer code, String message) {
    this.flag = flag;
    this.code = code;
    this.message = message;
}

public Result() {
    this.flag = true;
    this.code = StatusCode.OK;
    this.message = "操作成功!";
}

}

//自定义响应状态码 public class StatusCode { public static final int OK = 20000;//成功 public static final int ERROR = 20001;//失败 public static final int LOGINERROR = 20002;//用户名或密码错误 public static final int ACCESSERROR = 20003;//权限不足 public static final int REMOTEERROR = 20004;//远程调用失败 public static final int REPERROR = 20005;//重复操作 public static final int NOTFOUNDERROR = 20006;//没有对应的抢购数据 }

service: AccountService.java public interface AccountService { //转账 void transfer(String origin,String target,Double money); }

AccountServiceImpl.java @Service(“accountService”) public class AccountServiceImpl implements AccountService { @Override public void transfer(String origin,String target,Double money){ System.out.println(“业务层:开始转账”); } }

步骤4:搭建spring环境,并测试。 创建spring核心配置文件applicationContext.xml <?xml version=”1.0” encoding=”UTF-8”?>


创建测试类:SpringTest.java public class SpringTest { @Test public void testSrping(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”); AccountService accountService = applicationContext.getBean(“accountService”, AccountService.class); accountService.transfer(“tom”,”jack”,500.0); } }

整合第二步:整合springmvc 搭建springmvc环境 修改web.xml <?xml version=”1.0” encoding=”UTF-8”?>

dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc.xml dispatcherServlet / characterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 characterEncodingFilter /*

创建springmvc.xml <?xml version=”1.0” encoding=”UTF-8”?>

<mvc:annotation-driven/>

<!-- 配置视图解析器 -->
<!--thymeleaf模板解析器-->
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <!--前缀配置-->
    <property name="prefix" value="/static/"></property>
    <!--后缀配置-->
    <property name="suffix" value=".html"></property>
    <!--模板类型-->
    <property name="templateMode" value="HTML"></property>
    <!--不使用缓存-->
    <property name="cacheable" value="false"></property>
    <!--编码类型-->
    <property name="characterEncoding" value="UTF-8"></property>
</bean>

<!--模板引擎配置-->
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver"></property>
</bean>

<!--视图处理器-->
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine"></property>
    <property name="characterEncoding" value="UTF-8"></property>
</bean>

<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:resources mapping="/js/**" location="/js/"/>

创建首页index.html
<a name="ZrfgZ"></a>
####         转出账户名:
<a name="Gy5QA"></a>
####         转入账户名:
<a name="h0b1z"></a>
####         转账金额:
```java
创建控制器
//首页处理器
@Controller 
public class IndexHandler {
//url为"/"时,直接跳转首页
@RequestMapping("/")
public String index(){
return "index";
}
}
//账户处理器
@RestController 
public class AccountHandler {
    @Autowired 
    private AccountService accountService;
    @PostMapping("account")
    @ResponseBody
    public Result transfer(String origin,String target,Double money){
        try {
            accountService.transfer(origin,target,money);
            return new Result(true,StatusCode.OK,"转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(true,StatusCode.OK,"转账失败");
        }
    }
}

#### spring整合springmvc
修改web.xml
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!--配置初始化参数,让ContextLoaderListener到classpath下加载spring配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
<!--配置格式-->
  <filter>
    <filter-name>encodingFilter</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>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- 配置监听器, 当web容器启动时,监听器会自动读取<context-param>中指定的配置文件,创建spring容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

整合第三步:整合mybatis

修改applicationContext.xml,将mybatis的配置整合到spring容器中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--扫描service层注解-->
    <context:component-scan base-package="com.woniu.service"/>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/taotao"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--配置sqlsessionfactory-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置xml映射文件所在位置,
        如果是maven项目,在resources目录中创建了mapper接口包名相同的目录,则可省略该配置-->
        <property name="mapperLocations" value="classpath:com/woniu/mapper/*.xml"/>
        <property name="typeAliasesPackage" value="com.woniu.domain"/>
    </bean>

    <!--扫描数据访问层接口,生成代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.woniu.mapper"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切面-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.woniu.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

    <!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
创建数据访问层接口AccountMapper.java
public interface AccountMapper {
    //根据id查询账户
    Account findByUserName(String username);
    //修改账户
    void updateAccount(Account account);
}

在resources下创建com/woniu/mapper目录,在目录中添加AccountMapper.xml

<select id="findByUserName" resultType="account" parameterType="string">
    select * from t_account where username=#{username}
</select>

<update id="updateAccount" parameterType="account">
    update t_account set balance=#{balance} where username=#{username}
</update>
修改业务层接口实现类AccountServiceImpl.java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource 
    private AccountMapper accountMapper;
    public void transfer(String origin,String target,Double money) throws Exception{
        //1、查询两个账户信息
        Account originAccount = accountMapper.findByUserName(origin);//转出账户
        System.out.println(originAccount);
        Account targetAccount = accountMapper.findByUserName(target);//转入账户
        //2、分别设置两个账户的余额
        originAccount.setBalance(originAccount.getBalance()-money);
        targetAccount.setBalance(targetAccount.getBalance()+money);
        //3、调用方法修改数据库表的信息
        accountMapper.updateAccount(originAccount);
        int i=1/0;
        accountMapper.updateAccount(targetAccount);
    }
}
至此,ssm整合结束,可以通过对业务层方法异常的注释与否来测试声明式事务是否成功。