SpringMVC

三层架构和MVC

三层架构

在 B/S 架构中,系统标准的三层架构包括:表现层、业务层、持久层

1.表现层:
	也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web层,web需要接收http请求,完成http响应。
	表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。
	表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。
	表现层的设计一般都使用MVC模型。
2.业务层:
	也就是我们常说的service层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web层依赖业务层,但是业务层不依赖web层。
	业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)
3.持久层:
	也就是我们是常说的dao层。负责数据持久化,包括数据层(即数据库)和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。持久层就是和数据库交互,对数据库表进行增删改查的。

MVC模型

MVC是一种用于设计创建Web应用程序表现层的模式。
MVC中每个部分各司其职:
1.Model(模型):
通常指的就是我们的数据模型。作用一般情况下用于封装数据。
2.View(视图):
通常指的就是我们的jsp或者html。作用一般就是展示数据的。通常视图是依据模型数据创建的。
3.Controller(控制器):
是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。

它相对于前两个不是很好理解,这里举个例子:
例如:
我们要保存一个用户的信息,该用户信息中包含了姓名,性别,年龄等等。
这时候表单输入要求年龄必须是 1~100 之间的整数。姓名和性别不能为空。并且把数据填充到模型之中。
此时除了js的校验之外,服务器端也应该有数据准确性的校验,那么校验就是控制器该做的。
当校验失败后,由控制器负责把错误页面展示给使用者。
如果校验成功,也是控制器负责把数据填充到模型,并且调用业务层实现完整的业务需求。

@面:MVC是什么、好处

mvc是一种设计模式。模型(model)、视图(view)、控制器(controller),三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。

mvc设计模式的好处
1.分层设计,实现了业务系统各个组件之间的解藕,有利于业务系统的可扩展性,可维护性。
2.有利于系统的并行开发,提升开发效率。

概述

1.SpringMVC 在三层架构的位置

@面:Spring MVC的优点

1)可以支持各种视图技术,而不仅仅局限于JsP;
2)与Spring框架集成(如IoC容器、AOP等);
3)清晰的角色分配:前端控制器(dispatcherServlet),请求到处理器映射(handlerMapping),处理器适配器(HandlerAdapter),视图解析器(ViewResolver)。
4)支持各种请求资源的映射策略。

@面:SpringMVC工作原理

1)客户端发送请求到DispatcherServlet
2)DispatcherServlet查询handlerMapping找到处理请求的Controller
3)Controller调用业务逻辑后,返回ModelAndView
4)DispatcherServlet查询ModelAndView,找到指定视图
5)视图将结果返回到客户端

入门案例

项目创建

1.具体的坐标

2.配置核心的控制器

在web.xml配置文件中核心控制器DispatcherServlet

<!--配置前端控制器-->
<servlet>
    <servlet-name>dispatcherServlet</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>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

3.编写springmvc.xml的配置文件

<!-- 开启注解扫描 -->
<context:component-scan base-package="cn.itcast"/>

<!-- 视图解析器对象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- 开启SpringMVC框架注解的支持 -->
<mvc:annotation-driven conversion-service="conversionService"/>

4.编写index.jsp和HelloController控制器类

1) index.jsp

<body>
    <h3>入门程序</h3>
    <a href="hello">入门程序</a>
</body>

2)HelloController

@Controller 
public class HelloController {
    
    /** * 接收请求 */ 
    @RequestMapping(path="/hello") 
    public String sayHello() {
    
        System.out.println("Hello SpringMVC!!"); 
        return "success"; 
    } 
}

5.在WEB-INF目录下创建pages文件夹,编写success.jsp的成功页面

SpringMVC工作流程

@面:SpringMVC的工作流程

1)用户发送请求至前端控制器DispatcherServlet;

2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;

4)DispatcherServlet调用HandlerAdapter处理器适配器;
5)HandlerAdapter经过适配调用具体处理器(Handler,也叫后端控制器);
6)Handler执行完成返回ModelAndView;
7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;

8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
9)ViewResolver解析后返回具体View;

10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
11)DispatcherServlet响应用户。
1. 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象,就会加载springmvc.xml配置文件
2. 开启了注解扫描,那么HelloController对象就会被创建
3. 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解找到执行的具体方法
4. 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
5. Tomcat服务器渲染页面,做出响应

组件分析

1 DispatcherServlet:前端控制器

用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

2 HandlerMapping:处理器映射器

HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3 Handler:处理器

它就是我们开发中要编写的具体业务控制器。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理。

4 HandlAdapter:处理器适配器

通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

5 View Resolver:视图解析器

View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

6 View:视图

SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

7 < mvc:annotation-driven>说明

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使 用< mvc:annotation-driven> 自动加载 RequestMappingHandlerMapping 处理映射器和RequestMappingHandlerAdapter 处理适配器,可用在SpringMVC.xml配置文件中使用< mvc:annotation-driven>替代注解处理器和适配器的配置。

@面:什么是Spring MVC框架的控制器

控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入井将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。

@面:SpringMVC的控制器是不是单例模式

是单例模式,所以在多线程访问的时候有线程安全问题;不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。

RequestMapping

1.作用:

用于建立请求URL和处理请求方法之间的对应关系。

2.出现位置:

1)类上:
请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的URL可以按照模块化管理:
例如:
账户模块:
**/account**/add
**/account**/update
**/account**/delete
...

**/account**部分就是把RequsetMappding写在类上,使我们的URL更加精细。

2)方法上:
请求 URL 的第二级访问目录。

3.属性:

1)value:用于指定请求的URL。它和path属性的作用是一样的。
2)method:用于指定请求的方式。
3)params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。
例如:params = {"accountName"},表示请求参数必须有 accountName

请求参数的绑定

绑定的机制

1.SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。

- 表单提交的数据都是k=v格式的 username=haha&password=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的

@面:怎样在方法里面得到Request或Session

直接在方法的形参中声明request,Spring MVC就自动把request对象传入。

支持的数据类型

1)基本数据类型和字符串类型

  • 提交表单的name和参数的名称是相同的
  • 区分大小写
<a href="param/testParam?username=hehe&password=123">请求参数绑定</a>
@RequestMapping("/testParam")
public String testParam(String username,String password){
   
    System.out.println("执行了...");
    System.out.println("用户名:"+username);
    System.out.println("密码:"+password);
    return "success";
}

2)实体类型(JavaBean)

  • 提交表单的name和JavaBean中的属性名称需要一致
  • 如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如:address.name
public class Account implements Serializable{
   
    private String username;
    private String password;
    private Double money;

   	private User user;
}
public class User implements Serializable{
   
    private String uname;
    private Integer age;
}
把数据封装Account类中
<form action="param/saveAccount" method="post">
    姓名:<input type="text" name="username" /><br/>
    密码:<input type="text" name="password" /><br/>
    金额:<input type="text" name="money" /><br/>
    用户姓名:<input type="text" name="user.uname" /><br/>
    用户年龄:<input type="text" name="user.age" /><br/>
    <input type="submit" value="提交" />
</form>

3)集合数据类型(List、map集合等)

  • 第一种:要求集合类型的请求参数必须在POJO中。在表单中请求参数名称要和 POJO 中集合属性名称相同。给 List 集合中的元素赋值,使用下标。给 Map 集合中的元素赋值,使用键值对。
  • 第二种:接收的请求参数是 json 格式数据。需要借助一个注解实现。
public class Account implements Serializable{
   
    private String username;
    private String password;
    private Double money;

    private List<User> list;
    private Map<String,User> map;
}
public class User implements Serializable{
   
    private String uname;
    private Integer age;
}
<form action="param/saveAccount" method="post">
    姓名:<input type="text" name="username" /><br/>
    密码:<input type="text" name="password" /><br/>
    金额:<input type="text" name="money" /><br/>

    用户姓名:<input type="text" name="list[0].uname" /><br/>
    用户年龄:<input type="text" name="list[0].age" /><br/>

    用户姓名:<input type="text" name="map['one'].uname" /><br/>
    用户年龄:<input type="text" name="map['one'].age" /><br/>
    <input type="submit" value="提交" />
</form>

4)请求参数乱码问题

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>

自定义类型转换器

1.表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行数据类型转换。

2.如果想自定义数据类型转换,可以实现Converter的接口

1)自定义类型转换器

/** * 把字符串转换日期 */
public class StringToDateConverter implements Converter<String,Date>{
   

    /** * String source 传入进来字符串 * @param source * @return */
    public Date convert(String source) {
   
        // 判断
        if(source == null){
   
            throw new RuntimeException("请您传入数据");
        }
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

        try {
   
            // 把字符串转换日期
            return df.parse(source);
        } catch (Exception e) {
   
            throw new RuntimeException("数据类型转换出现错误");
        }
    }

}

2) 注册自定义类型转换器,在springmvc.xml配置文件中编写配置,spring 配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。

<!-- 配置类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!-- 给工厂注入一个新的类型转换器 -->
    <property name="converters">
        <set>
            <!-- 配置自定义类型转换器 -->
            <bean class="cn.itcast.utils.StringToDateConverter"/>
        </set>
    </property>
</bean>


<!-- 开启SpringMVC框架注解的支持 在annotation-driven标签中引用配置的类型转换服务 -->
<mvc:annotation-driven conversion-service="conversionService"/>

ServletAPI对象作为参数

只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象

/** * 原生的API * @return */
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
   
    System.out.println("执行了...");
    System.out.println(request);

    HttpSession session = request.getSession();
    System.out.println(session);

    ServletContext servletContext = session.getServletContext();
    System.out.println(servletContext);

    System.out.println(response);
    return "success";
}

常用注解

RequestParam

作用:

把请求中指定名称的参数给控制器中的形参赋值,可以用于名称不同情况。

属性:

value:请求参数中的名称。
required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

<a href="anno/testRequestParam?name=哈哈">RequestParam</a>
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(name="name") String username){
   
    System.out.println("执行了...");
    System.out.println(username);
    return "success";
}

RequestBody

作用:

**用于获取请求体内容。**直接使用得到是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="text" name="age" /><br/>
    <input type="submit" value="提交" />
</form>
/** * 获取到请求体的内容 * @return */
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
   
    System.out.println("执行了...");
    System.out.println(body);
    return "success";
}

PathVariable

作用:

用于绑定url中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。

属性:

value:用于指定 url 中占位符名称。
required:是否必须提供占位符。

<a href="anno/testPathVariable/10">testPathVariable</a>
/** * PathVariable注解 * @return */
@RequestMapping(value="/testPathVariable/{sid}")
public String testPathVariable(@PathVariable(name="sid") String id){
   
    System.out.println("执行了...");
    System.out.println(id);
    return "success";
}

Restful风格的URL

@面:PathVariable和RequestParam的区别

请求路径上有个id的变量值,可以通过@PathVariable来获取
@RequestMapping(value ="/page/{id}", method=RequestMethod.GET)

@RequestParam用来获得静态的URL请求入参spring注解时action里用到。

HiddentHttpMethodFilter

作用:

由于浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添加了一个过滤器,可以将浏览器请求改为指定的请求方式,发送给我们的控制器方法,使得支持 GET、POST、PUT 与DELETE 请求。

使用方法:

第一步:在 web.xml 中配置该过滤器。
第二步:请求方式必须使用 post 请求。
第三步:按照要求提供_method请求参数,该参数的取值就是我们需要的请求方式。

RequestHeader

作用:

用于获取请求消息头。

属性:

value:提供消息头名称
required:是否必须有此消息头

<a href="anno/testRequestHeader">RequestHeader</a>
/** * 获取请求头的值 * @param header * @return */
@RequestMapping(value="/testRequestHeader")
public String testRequestHeader(@RequestHeader(value="Accept") String header, HttpServletRequest request,HttpServletResponse response) throws IOException {
   
    System.out.println("执行了...");
    System.out.println(header);
    // return "success";
    // response.sendRedirect(request.getContextPath()+"/anno/testCookieValue");
    return "redirect:/param.jsp";
}

CookieValue

作用:
用于把指定cookie名称的值传入控制器方法参数。

属性:
value:指定cookie的名称。
required:是否必须有此 cookie。

<a href="anno/testCookieValue">CookieValue</a>
/** * 获取Cookie的值 * @return */
@RequestMapping(value="/testCookieValue")
public String testCookieValue(@CookieValue(value="JSESSIONID") String cookieValue){
   
    System.out.println("执行了...");
    System.out.println(cookieValue);
    return "success";
}

ModelAttribute

作用:
它可以用于修饰方法和参数。

出现在方法上:
表示当前方***在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法(此时可能先存储到map中),也可以修饰有具体返回值的方法。

出现在参数上:
获取指定的数据给参数赋值。

属性:
value:用于获取数据的key。key可以是POJO的属性名称,也可以是map结构的key。

应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。

例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

1.修饰方法带返回值

<form action="anno/testModelAttribute" method="post">
    用户姓名:<input type="text" name="uname" /><br/>
    用户年龄:<input type="text" name="age" /><br/>
    <input type="submit" value="提交" />
</form>
/** * 该方法先执行 */
@ModelAttribute
public User showUser(String uname){
   
    System.out.println("showUser执行了...");
    // 通过用户查询数据库(模拟)
    User user = new User();
    user.setUname(uname);
    user.setAge(20);
    user.setDate(new Date());
    return user;
}

@RequestMapping(value="/testModelAttribute")
public String testModelAttribute(User user){
   
    System.out.println("testModelAttribute执行了...");
    System.out.println(user);
    return "success";
}

2.修饰方法不带返回值

@ModelAttribute
public void showUser(String uname, Map<String,User> map){
   
    System.out.println("showUser执行了...");
    // 通过用户查询数据库(模拟)
    User user = new User();
    user.setUname(uname);
    user.setAge(20);
    user.setDate(new Date());
    map.put("abc",user);
}

@RequestMapping(value="/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
   
    System.out.println("testModelAttribute执行了...");
    System.out.println(user);
    return "success";
}

SessionAttribute

作用:
用于多次执行控制器方法间的参数共享。

属性:
value:用于指定存入的属性名称
type:用于指定存入的数据类型
@Controller
@RequestMapping(path="/user") 
@SessionAttributes(value= {
   "username","password","age"},types= {
   String.class,Integer.class}) //把数据存入到session域对象中 
public class HelloController {
    
    /** * 向session中存入值 */ 
    @RequestMapping(path="/save") 
    public String save(Model model) {
    
        System.out.println("向session域中保存数据"); 
        model.addAttribute("username", "root"); 
        model.addAttribute("password", "123"); 
        model.addAttribute("age", 20); 
        return "success"; 
    }
    /** * 从session中获取值 */ 
    @RequestMapping(path="/find") 
    public String find(ModelMap modelMap) {
    
        String username = (String) modelMap.get("username"); 
        String password = (String) modelMap.get("password"); 
        Integer age = (Integer) modelMap.get("age"); 
        System.out.println(username + " : "+password +" : "+age); 
        return "success"; 
    }
    /** * 清除值 */ 
    @RequestMapping(path="/delete") 
    public String delete(SessionStatus status) {
   
        status.setComplete(); 
        return "success"; 
    } 
}

@面:把ModelMap的数据放入Session

可以在类上加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。

ResponseBody

作用:
该注解用于将Controller的方法【返回的对象】,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

使用时机:
返回的数据不是html标签的页面,而是其他某种格式的数据时(如json, xml等)使用;

@面:SpringMVC注解的优点

1、XML配置起来有时候冗长,此时注解可能是更好的选择;注解在处理一些不变的元数据时有时候比XML方便的多,比如springmvc的数据绑定,如果用xml写的代码会多的多;

2、注解最大的好处就是简化了XML配置;其实大部分注解一旦确定后很少会改变,所以在一些中小项目中使用注解反而提供了开发效率。

3、注解相对于XML的另一个好处是类型安全的,XML只能在运行期才能发现问题。

@面:注解原理是什么

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方***最终调用AnnotationInvocationHandler的invoke方法。该方***从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

@面:SpingMVC中的控制器的注解用哪个

一般用@Controller注解,也可以使用@RestController代替,@RestController注解相当于@ResponseBody+@Controller,表示是表现层,除此之外,一般不用别的注解代替。

@面:@Controller注解的作用

@Controller用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。@Controller只是定义了一个控制器类,而使用@RequestMapping注解的方法才是真正处理请求的处理器。单单使用@Controller标记在一个类上还不能真正意义上的说它就是SpringMVC的一个控制器类,因为这个时候Spring还不认识它,需要我们把这个控制器类交给Spring来管理。
有两种方式:
在Spring MVC的配置文件中定义MyController的bean对象。
在Spring MVC的配置文件中告诉Spring该到哪里去找标记为@Controller的Controller控制器。

(在SpringMVC中,控制器Controlle负责处理由DispatcherServelet分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model,然后再把该Model返回给对应的View进行展示。在Spring MVC中提供了一个非常简便的定义Controller的方法,无需继承特定的类或实现特定的接口,只需使用@Controller标记一个类是Controller,然后使用@RequestMapping和@RequestParam等一些注解用以定义URL请求和Controller方法之间的映射,这样的Controller就能被外界访问到。此外Controller不会直接依赖于HttpServletRequest和HttpServletResponse等HttpServlet对象,它们可以通过Controller的方法参数灵活的获取到。)

响应数据和结果视图

返回值分类

1、字符串

1)Controller方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。

@RequestMapping(value="/hello") 
public String sayHello() {
    
    System.out.println("Hello SpringMVC!!"); 
    // 跳转到XX页面 
    return "success"; 
}

2)具体的应用场景

<h3>修改用户</h3> 
${ requestScope } 
<form action="user/update" method="post"> 
    姓名:<input type="text" name="username" value="${ user.username }"><br> 
    密码:<input type="text" name="password" value="${ user.password }"><br> 
    金额:<input type="text" name="money" value="${ user.money }"><br> 
    <input type="submit" value="提交"> 
</form>
@Controller 
@RequestMapping("/user") 
public class UserController {
    
    /** * 请求参数的绑定 */ 
    @RequestMapping(value="/initUpdate") 
    public String initUpdate(Model model) {
    
        // 模拟从数据库中查询的数据 
        User user = new User(); 
        user.setUsername("张三"); 
        user.setPassword("123"); 
        user.setMoney(100d); 
        user.setBirthday(new Date()); 
        model.addAttribute("user", user); 
        return "update"; 
    } 
}

2、返回值是void

1)如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。

​ 默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。

2)可以使用请求转发或者重定向跳转到指定的页面

@RequestMapping(value="/initAdd") 
public void initAdd(HttpServletRequest request,HttpServletResponse response) throws Exception {
    
    System.out.println("请求转发或者重定向"); 
    // 请求转发 
    // request.getRequestDispatcher("/WEB-INF/pages/add.jsp").forward(request, response);
    // 重定向 
    // response.sendRedirect(request.getContextPath()+"/add2.jsp"); 
    response.setCharacterEncoding("UTF-8"); 
    response.setContentType("text/html;charset=UTF-8"); 
    // 直接响应数据 
    response.getWriter().print("你好"); 
    return; 
}

3、返回值是ModelAndView对象

ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图

/** * 返回ModelAndView * @return */
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
   
    // 创建ModelAndView对象
    ModelAndView mv = new ModelAndView();
    System.out.println("testModelAndView方法执行了...");
    // 模拟从数据库中查询出User对象
    User user = new User();
    user.setUsername("小凤");
    user.setPassword("456");
    user.setAge(30);

    // 把user对象存储到mv对象中,也会把user对象存入到request对象
    mv.addObject("user",user);

    // 跳转到哪个页面
    mv.setViewName("success");
    return mv;
}

@面:SpringMVC中函数的返回值

返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。

@面:SpringMVC用什么对象从后台向前台传递数据

通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过e1表达式拿到。

转发和重定向

1 forward 转发

使用forward关键字进行请求转发

“forward:转发的JSP路径”,不走视图解析器了,所以需要编写完整的路径

2 Redirect 重定向

它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不能写在 WEB-INF 目录中,否则无法找到。

/** * 使用关键字的方式进行转发或者重定向 * @return */
@RequestMapping("/testForwardOrRedirect")
public String testForwardOrRedirect(){
   
    System.out.println("testForwardOrRedirect方法执行了...");

    // 请求的转发
    // return "forward:/WEB-INF/pages/success.jsp";

    // 重定向
    return "redirect:/index.jsp";
}

ResponseBody响应json数据

1、DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置

<!-- 设置静态资源不过滤 --> 
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> 
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> 
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->

- mvc:resources标签配置不过滤
- location元素表示webapp目录下的包下的所有文件
- mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b

2、使用@RequestBody获取请求体数据

<script>
    // 页面加载,绑定单击事件
    $(function(){
        $("#btn").click(function(){
            // alert("hello btn");
            // 发送ajax请求
            $.ajax({
                // 编写json格式,设置属性和值
                url:"user/testAjax",
                contentType:"application/json;charset=UTF-8",
                data:'{"username":"hehe","password":"123","age":30}',
                dataType:"json",
                type:"post",
                success:function(data){
                    // data服务器端响应的json的数据,进行解析
                    alert(data);
                    alert(data.username);
                    alert(data.password);
                    alert(data.age);
                }
            });

        });
    });
</script>

4、使用@ResponseBody注解把JavaBean对象转换成json字符串,直接响应

/** * 模拟异步请求响应 */
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){
   
    System.out.println("testAjax方法执行了...");
    // 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
    System.out.println(user);
    // 做响应,模拟查询数据库
    user.setUsername("haha");
    user.setAge(40);
    // 做响应
    return user;
}

文件上传

文件上传的回顾

1、导入文件上传的jar包

<dependency> 
    <groupId>commons-fileupload</groupId> 
    <artifactId>commons-fileupload</artifactId> 
    <version>1.3.1</version> 
</dependency> 
<dependency> 
    <groupId>commons-io</groupId> 
    <artifactId>commons-io</artifactId> 
    <version>2.4</version> 
</dependency>

2、编写文件上传的JSP页面

<form action="/user/fileupload1" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload" /><br/>
    <input type="submit" value="上传" />
</form>

3、编写文件上传的Controller控制器

/** * 文件上传 * @return */
@RequestMapping("/fileupload1")
public String fileuoload1(HttpServletRequest request) throws Exception {
   
    System.out.println("文件上传...");

    // 使用fileupload组件完成文件上传
    // 上传的位置
    String path = request.getSession().getServletContext().getRealPath("/uploads/");
    // 判断,该路径是否存在
    File file = new File(path);
    if(!file.exists()){
   
        // 创建该文件夹
        file.mkdirs();
    }

    // 解析request对象,获取上传文件项
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload upload = new ServletFileUpload(factory);
    // 解析request
    List<FileItem> items = upload.parseRequest(request);
    // 遍历
    for(FileItem item:items){
   
        // 进行判断,当前item对象是否是上传文件项
        if(item.isFormField()){
   
            // 说明普通表单向
        }else{
   
            // 说明上传文件项
            // 获取上传文件的名称
            String filename = item.getName();
            // 把文件的名称设置唯一值,uuid
            String uuid = UUID.randomUUID().toString().replace("-", "");
            filename = uuid+"_"+filename;
            // 完成文件上传
            item.write(new File(path,filename));
            // 删除临时文件
            item.delete();
        }
    }
    return "success";
}

SpringMVC文件上传

SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的name属性名称相同。

配置文件解析器
<!--配置文件解析器对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10485760" />
</bean>
编写 jsp 页面
<h3>Springmvc文件上传</h3>
<form action="/user/fileupload2" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload" /><br/>
    <input type="submit" value="上传" />
</form>
编写控制器
/** * SpringMVC文件上传 * @return */
@RequestMapping("/fileupload2")
public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {
   
    System.out.println("springmvc文件上传...");

    // 使用fileupload组件完成文件上传
    // 上传的位置
    String path = request.getSession().getServletContext().getRealPath("/uploads/");
    // 判断,该路径是否存在
    File file = new File(path);
    if(!file.exists()){
   
        // 创建该文件夹
        file.mkdirs();
    }

    // 说明上传文件项
    // 获取上传文件的名称
    String filename = upload.getOriginalFilename();
    // 把文件的名称设置唯一值,uuid
    String uuid = UUID.randomUUID().toString().replace("-", "");
    filename = uuid+"_"+filename;
    // 完成文件上传
    upload.transferTo(new File(path,filename));

    return "success";
}

跨服务器文件上传

1、搭建图片服务器

2、导入开发需要的jar包

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

3、编写文件上传的JSP页面

<h3>跨服务器文件上传</h3>
<form action="/user/fileupload3" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload" /><br/>
    <input type="submit" value="上传" />
</form>

4、编写控制器

/** * 跨服务器文件上传 * @return */
@RequestMapping("/fileupload3")
public String fileuoload3(MultipartFile upload) throws Exception {
   
    System.out.println("跨服务器文件上传...");

    // 定义上传文件服务器路径
    String path = "http://localhost:9090/uploads/";

    // 说明上传文件项
    // 获取上传文件的名称
    String filename = upload.getOriginalFilename();
    // 把文件的名称设置唯一值,uuid
    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";
}

异常处理

异常处理思路

@面:SpringMVC的异常处理

可将异常抛给Spring框架,由Spring框架来处理,我们只需配置简单的异常处理器,在异常处理器中添视图页面。

代码实现

1、自定义异常类

/** * 自定义异常类 */
public class SysException extends Exception{
   
    // 存储提示信息的
    private String message;

    public String getMessage() {
   
        return message;
    }

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

    public SysException(String message) {
   
        this.message = message;
    }
}

7.3 自定义异常处理器

/** * 异常处理器 */
public class SysExceptionResolver implements HandlerExceptionResolver{
   

    /** * 处理异常业务逻辑 */
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
   
        // 获取到异常对象
        SysException e = null;
        if(ex instanceof SysException){
   
            e = (SysException)ex;
        }else{
   
            e = new SysException("系统正在维护....");
        }
        // 创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",e.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

3、配置异常处理器

<!--配置异常处理器-->
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>

拦截器

拦截器的概述

1、Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

2、可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。

3、拦截器和过滤器的功能比较类似,有区别

  • 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
  • 拦截器是SpringMVC框架独有的。
  • 过滤器配置了/*,可以拦截任何资源。
  • 拦截器只会对控制器中的方法进行拦截。

4、拦截器也是AOP思想的一种实现方式

5、想要自定义拦截器,需要实现HandlerInterceptor接口。

@面:拦截get方式提交的方法,怎么配置

可以在@RequestMapping注解里面加上method=RequestMethod.GET。

@面:在拦截的方法里面得到从前台传入的参数

直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样

自定义拦截器步骤

1、创建类,实现HandlerInterceptor接口,重写需要的方法

/** * 自定义拦截器1 */
public class MyInterceptor1 implements HandlerInterceptor{
   

    /** * 预处理,controller方法执行前 * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法 * return false不放行 * @param request * @param response * @param handler * @return * @throws Exception */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor1执行了...前1111");
        // request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }

    /** * 后处理方法,controller方法执行后,success.jsp执行之前 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
   
        System.out.println("MyInterceptor1执行了...后1111");
        // request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }

    /** * success.jsp页面执行后,该方***执行 * @param request * @param response * @param handler * @param ex * @throws Exception */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
   
        System.out.println("MyInterceptor1执行了...最后1111");
    }
}
/** * 自定义拦截器2 */
public class MyInterceptor2 implements HandlerInterceptor{
   

    /** * 预处理,controller方法执行前 * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法 * return false不放行 * @param request * @param response * @param handler * @return * @throws Exception */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor1执行了...前2222");
        // request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }

    /** * 后处理方法,controller方法执行后,success.jsp执行之前 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
   
        System.out.println("MyInterceptor1执行了...后2222");
        // request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }

    /** * success.jsp页面执行后,该方***执行 * @param request * @param response * @param handler * @param ex * @throws Exception */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
   
        System.out.println("MyInterceptor1执行了...最后2222");
    }
}

2、在springmvc.xml中配置拦截器类

<!--配置拦截器-->
<mvc:interceptors>
    <!--配置拦截器-->
    <mvc:interceptor>
        <!--要拦截的具体的方法-->
        <mvc:mapping path="/user/*"/>
        <!--不要拦截的方法 <mvc:exclude-mapping path=""/> -->
        <!--配置拦截器对象-->
        <bean class="cn.itcast.controller.cn.itcast.interceptor.MyInterceptor1" />
    </mvc:interceptor>

    <!--配置第二个拦截器-->
    <mvc:interceptor>
        <!--要拦截的具体的方法-->
        <mvc:mapping path="/**"/>
        <!--不要拦截的方法 <mvc:exclude-mapping path=""/> -->
        <!--配置拦截器对象-->
        <bean class="cn.itcast.controller.cn.itcast.interceptor.MyInterceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

HandlerInterceptor接口中的方法

1、preHandle方法是controller方法执行前拦截的方法

  • 可以使用request或者response跳转到指定的页面
  • return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
  • return false不放行,不会执行controller中的方法。

2、postHandle是controller方法执行后执行的方法,在JSP视图执行前。

  • 可以使用request或者response跳转到指定的页面
  • 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。

3、postHandle方法是在JSP执行后执行

  • request或者response不能再跳转页面了