SpringMVC学习笔记

预备知识

http升级为WebSocket支持双向通信,服务器可以主动发起同客户端的通话。

HTTP请求包含三个部分:
1.方法-URI-版本
2.请求头信息
3.请求正文

示例:
这里写图片描述

1.请求方法为POST, URI为/example/default.jsp,协议版本为HTTP/1.1
2.HTTP请求头信息包含关于客户端环境以及实体内容等非常有用的信息,例如浏览器语言,实体内容长度等。每个header都用一个换行分割
3.HTTP请求头信息和请求正文用一行空格分隔,HTTP服务器以此来判断请求正文的起始位置。
本例中,请求正文是lastName=Blank&firstName=Mike
在正常的HTTP请求中,请求正文的内容不止如此。

HTTP响应也分为3个部分:
1.协议-状态码-描述
2.响应头信息
3.响应正文
举例:
这里写图片描述

1.响应报文第一行说明了HTTP的版本是1.1,并且请求结果是成功的(状态响应码200为响应成功)
2.响应报文头信息也包含大量有用的信息
3.响应正文是HTML文档。HTTP响应报文的头信息和响应正文也是用换行符来分隔的。
状态码:
200 服务器能正确响应所请求的资源。
401 访问未授权的资源
405 被禁用的请求方法
404 not found anything matching Request-URI

Servlet和Jsp
一个Servlet是一个Java程序,一个Servlet应用包含了一个或多个Servlet,一个Jsp页面被翻译并编译成一个Servlet。
一个Servlet应用运行在一个Servlet容器中,它无法独立运行。Servlet容器将来自用户的请求传递给Servlet应用,并将Servlet应用的响应返回给用户。
由于大部分Servlet应用会包含一些Jsp页面,所以称为Java/Jsp应用.
如图展示了一个典型的Servlet/Jsp应用架构:
这里写图片描述

Web服务器端和Web客户端基于HTTP通信。因此Web服务器也被称为HTTP服务器。
Servlet/Jsp容器是能够处理Servlet以及静态资源Web服务器。

第一章 Spring框架

这里主要介绍Core和Bean两个模块,以及依赖注入解决方案。
以前,依赖注入作为代码可测试性的一个解决方案。
什么是依赖注入技术?
举例:
有两个组件A和B,A依赖于B,A的一个方法使用到了B

public class A {
    public void importantMethod() {
    B b = ...// get an instance of B
    b.usefulMethod();
    }
}

要使用B,类A必须先获得组件B的实例引用。若B是一个具体类,可以通过new的方式创建B实例。但是如果b是接口(问题引入),且有多个实现,我们固然可以任意选择接口B的一个实现类,但是也意味着A的可重用性大大降低了,因为无法采用B的其他实现。
依赖注入是这样来处理此类情景的:接管对象的创建工作,并将该对象的引用注入到需要该对象的引用。依赖注入框架会创建对象A和对象B,将对象B注入到对象A中。
为了能让框架进行依赖注入,我们需要编写特定的set方法或者构建方法。
举例将B注入到A中:

public class A {
    private B b;
    public void importantMethod() {
        //no need to worry about creating B anymore
        //B b = ...//  get an instance of B
        b.usefulMethod();
        ...
    }
    public void setB(B b) {
        this.b=b;
    }
}

修改后的类A新增了一个set方法,该方法将会被框架调用,以注入一个B的实例。由于对象B通过依赖注入,类A的importantMethod方法不再需要调用B的usefulMethod方法前去创建一个B的实例。(就是把B对象的创建从A的方法内部转移为A的一个成员变量)

也可以通过构造器方式注入,如下所示:

public class A {
    private B b;

    public A(B b) {
        this.b=b;
    }

    public void importantMethod() {
        //no need to create B anymore
        //B b = ...//get an instance of B
        b.usefulMethod();
        ...
    }
}

1.1XML配置文件

Spring很早就支持XML的配置,2.5版本开始,增加了通过注解的配置支持。下面介绍如何配置XML文件。
配置文件的根元素通常为:
这里写图片描述
如果需要增强Spring的配置能力,可以在schema location属性中添加响应的schema。配置文件可以是一份,也可以分解为多份,以支持模块化配置。ApplicationContext的实现类支持读取多份配置文件。
另一种选择是,通过一份主配置文件,将其他配置文件导入到该文件中。
主配置文件:
这里写图片描述

1.2 Spring控制器反转容器的使用

本节主要介绍Spring如何管理bean和依赖关系。
本例中,Spring会先创建B的实例,再创建实例A,然后把B注入到A中。(Spring管理的对象称为beans)
通过提供一个控制反转容器(后者依赖注入容器),Spring为我们提供一种可以”聪明“管理java对象依赖关系的方法。这样,程序员无须了解Spring框架的存在,更不需要引入任何Spring类型。
从1.0版本开始,Spring就同时支持setter和构造器方式的依赖中注入。从2.5版本开始,通过Autowired注解,Spring支持基于filed方式的依赖注入,但是缺点是程序必须引入org.springframework.beans.factory.annotation.Autowired,这对Spring产生了依赖(因为要用到Spring的注解),程序无法直接迁移到另一个依赖注入容器间(why?不明白这句)。

使用Spring,程序几乎将所有重要对象的创建工作移交到Spring,并配置如何注入依赖。Spring支持XML或注解两种配置方式。此外,还需要创建一个ApplicationContext对象(用来使用Spring的配置),代表一个Spring控制反转容器,org.springframe.context.ApplatationContext接口有多个实现,包括ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。这两个实现都需要至少一个包含beans信息的XML文件。ClassPathXmlApplicationContext尝试在类加载路径中加载配置文件,而FileSystemXmlApplicationContext则从文件系统中加载。
下面为从类路径中加载config.xml和config2.xml的ApplicationContext创建的一个代码实例。

    ApplicationContext context = new CalssPathXmlApplicationContext(new String[]{"config.xml","config2.xml"});

也可以通过ApplicationContext的getBean方法获得对象:
(也可以是单个文件)

Product product = context.getBean("product",Product.class);

getBean方法会查询id为product且类型为Product的bean对象。
注意:T getBean(String ,ClassreqiredType)根据容器Bean中的id来获取指定Bean,但是该方法带一个泛型参数,因此获取Bean之后无须进行强制类型转换。因为用的是特定的对象的Class对象。

注意:理想情况下,我们仅需要在测试中创建一个ApplicationContext,应用程序本身无需处理。对于SpringMVC应用,可以通过一个Spring Servlet来处理ApplicationContext,而无需直接处理。

1.2.1 通过一个构造器创建一个bean实例
前面已经讲到通过调用ApplicationContext的getBean方法可以获取到一个bean的实例。下面的配置文件中定义了一个名为product的bean.
一个简单的配置文件:

<?xml version-="1.0" encoding="utf-8">
<beans xmlns>="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean name="product" class="app01a.bean.Product"/>
</beans>

该bean的定义告诉我们Spring通过默认无参的构造器来初始化Product。如果不存在该构造器(如果类中重载了构造器,且没有显示声明默认构造器),则Spring将抛出一个异常。(因为找不到合适的构造器)
注意,应采用id或者name属性表示一个bean。为了让一个Spring创建一个Product实例,应将bean定义的name值”product”(具体实现中也可以是id值)和Product类型作为参数传递到ApplicatonContext的getBean方法。(即调用getBean方法时要用到这个参数)

ApplicationContext context = new ClassPathXmlApplication(new String[]{"spring-config.xml"});
Product product1 = context.getBean("product",Product.class);
product1.setName("Excellent snale oil");
System.out.println("product1:"+product1.getName());

1.2.2 通过工厂方法创建一个bean实例

除了通过类的构造器方式,Spring还同样支持通过调用一个工厂的方法来初始化类。下面的bean展示了通过工厂方法来实例化java.util.Canlendar.

<bean id="calendar" class="java.util.Calendar" factory-method="getInstance"/>

本例中采用了id属性,而非name属性来标识bean,采用了getBean方法来获取Calendar实例。

ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring-config.xml"});
Calendar calendar = context.getBean("calendar",Calendar.class);

1.2.3 Destory Method的使用

有时候,我们希望一些类被销毁前能执行一些方法。Spring考虑到了这样的需求。可以在bean定义中配置destory-method属性,来指定在销毁前要被执行的方法。
下面的例子中,我们配置Spring通过java.util.concurrent.Executors的静态方法newCachedThreadPool来创建一个java.util.concurrent.ExecutorsService实例,并指定了destory-method属性值为shutdown方法。这样,Spring会在销毁ExecutorService实例前调用其shutdown方法。

<bean id="executorService" class="java.util.concurrent.Executors"
factory-method="newCachedThreadPool"
destory-method="shutdown"/>

1.2.4 向构造函数传递参数
Spring支持通过带参数的构造器来初始化类
Product类

package app01a.bean;
import java.io.Serializable;

public class Product implements Serializable {

    private static final long serialVersionUID = 748392348L;

    private String name;

    private String description;

    private float price;


    public Product() {
        super();
        // TODO Auto-generated constructor stub
    }

    public Product(String name, String description, float price) {
        super();
        this.name = name;
        this.description = description;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

}

如下定义展示了如何通过参数名传递参数。

<bean name="featuredProduct" class="app01a.bean.Product">
    <constructor-arg name="name" value="Ultimate Olive Oil"/>
    <constructor-arg name="description" value="The purest olive oil on the market"/>
    <constructor-arg name="price" value="9.95"/>
</bean>

这样,在创建Product实例时,Spring会调用如下构造器:

public Product (String name,String description,float price) {
    this.name = name;
    this.description = description;
    this.price = price;
}

除了通过名称传递参数外,Spring还支持通过指定参数方式传递参数,具体如下:

<bean name="featuredProduct2" clas="app01a.bean.Product">
    <constructor-arg index="0" value="Ultimate Olive Oil"/>
    <constructor-arg index="1" value="The purest olive oil on the market"/>
    <constructor-arg index="2" value="9.95">
</bean>

需要说明的是,采用这种方式,对应构造器的所有参数必须传递,缺一不可。

1.2.5 Setter方式依赖注入

下面以Employee类和Address类为例,介绍setter方式依赖注入

清单1.3
public class Employee {

    private String firstName;

    private String lastName;

    private Address homeAddress;


    public Employee() {
        super();
        // TODO Auto-generated constructor stub
    }


    public Employee(String firstName, String lastName, Address homeAddress) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.homeAddress = homeAddress;
    }


    public String getFirstName() {
        return firstName;
    }


    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }


    public String getLastName() {
        return lastName;
    }


    public void setLastName(String lastName) {
        this.lastName = lastName;
    }


    public Address getHomeAddress() {
        return homeAddress;
    }


    public void setHomeAddress(Address homeAddress) {
        this.homeAddress = homeAddress;
    }


    @Override
    public String toString() {
        return firstName + " " + lastName + " " + "\n" + homeAddress;
    }

}


清单1.4
public class Address {
    private String line1;

    private String line2;

    private String city;

    private String state;

    private String zipCode;

    private String country;

    public Address(String line1, String line2, String city, String state, String zipCode, String country) {
        super();
        this.line1 = line1;
        this.line2 = line2;
        this.city = city;
        this.state = state;
        this.zipCode = zipCode;
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address [line1=" + line1 + ", line2=" + line2 + ", city=" + city + ", state=" + state + ", zipCode="
                + zipCode + ", country=" + country + "]";
    }

    //gettter and setter omitted


}

Employee类依赖于Address类,可以通过如下配置来保证每个Employee实例都能包含Address实例。

<bean name="simpleAddress" class="app01a.bean.Address">
    <constructor-arg name="line1" value="151 Conner Street"/>
    <constructor-arg name="line2" value=""/>
    <constructor-arg name="city" value=""/>
    <constructor-arg name="state" value="Albany"/>
    <constructor-arg name="zipCode" value="9999"/>
    <constructor-arg name="country" value="US"/>
</bean>

<bean name="employee1" class="app01.bean.Employee">
    <property name="homeAddress" ref="simpleAddress"/>
    <property name="firstName" value="Junior"/>
    <property name="lastName" value="Moore"/>
</bean>

simpleAddress对象是Address类的一个实例,其通过构造器方式实例化。employee1对象则通过property元素来调用setter方法以设置值。需要注意的是,homeAddress属性配置的是simpleAddress对象的引用。
被引用对象的配置的定义无须早于引用其对象的定义。本例中,employee1对象可以出现在simpleAddress对象定义之前。

1.2.6 构造器方式依赖注入
清单1.3所展示的Employee类提供了一个可以传递参数的构造器,我们还可以将Address对象通过构造器注入,如下所示:

这里写图片描述

第二章 模型2和MVC模式

JavaWeb应用开发中有两种设计模型,分别称为模型1和模型2。模型1是页面中心,适合小应用开发。而模型2基于MVC开发,是JavaWeb应用的推荐架构。

2.1 模型1介绍
以JSP为中心,通过链接方式进行JSP页面间的跳转。缺点是,在大型项目中,修改一个jsp页面,到导致大量页面中的链接需要修正。

2.2 模型2介绍
模型2基于mvc这里写图片描述

每个HTTP请求都发给控制器,请求中的URI表示出对应的action.action代表了应用可以执行的一个*操作*。一个提供了Action的Java对象称为action对象。一个action类可以支持多个action(在Spring MVC和Struts2中),或者一个actijon(Struts1)
看似简单的操作肯恶搞需要多个action。如,向数据库添加一个产品,需要两个action。
1.显示一个”添加产品“的表单,以便用户可以输入产品信息。
2.将表单信息保存到数据库中。
如前述,我们需要通过URI方式告诉控制器执行响应的action。例如,通过发送类似如下URI,来显示”添加产品表单“。
http://domain/appName.product_input
通过类似如下URI,来保存产品。
http://domain/appName/product_save

控制器会解析URI并调用响应的action,然后将模型对象放到视图可以访问的区域,一面服务器端数据可以展示在浏览器上。最后,控制器利用RequestDispatcher跳转到视图(JSP页面)。在JSP页面中,用表达式语言以及定制标签显示数据/
注意:调用RequestDispatcher..forward方法并不会停止执行剩余的代码。因此,若forward方法不是最后一行代码,则应显示地返回。(即用return主动返回)

2.3 模型2之Servlet控制器
如下展示一个简单的模型2应用

这里写图片描述

实例应用支持如下两个action。
1.展示”添加产品”表单,该action发送图中的输入表单到浏览器上,则对应的URI包含字符串product_input。
2.保存产品并返回图2.3所示的完成页面,对应的URI必须包含字符串product_save。
示例应用由如下组件构成。
1.一个Product类,作为product的领域对象。
2.一个ProductForm类,封装了HTML表单的输入项。
3.一个ControllerServlet类,本例应用的控制器。
4.一个SaveProductAction类。
5.两个JSP页面作为view。
6.一个css文件,定义了两个JSP页面的显示风格。
目录结构如图所示

这里写图片描述

所示的JSP**文件都放置在WEB-INF目录下,因此无法被直接访问**(防止直接越过controller访问jsp,index就可以直接访问)。
下面介绍每一个组件

2.3.1 Product类

Product实例是一个封装了产品信息的JavaBean。Product类包含了3个属性。
这里写图片描述
注意:为什么要继承Serial?
1、将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;
2、按值将对象从一个应用程序域发送至另一个应用程序域。
实现serializable接口的作用是就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没实现序列化怎么才能进行网络传输呢,要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。

2.3.2 ProductForm类

表单类与HTML表单相映射,是后者在服务端的代表,ProductForm类包含了一个产品的字符串值。ProductForm类看上去同Product类类似,这就引出一个问题,ProductForm类是否有存在的必要。
实际上,表单对象会传递ServletRequest给其他组件,类似Validator而ServletRequest是一个Servlet层的对象,不应当暴露给应用的其他层
另一个原因是当,当数据交换失败时,表单对象将用于保存和展示用户在原始表单上的输入。
注意:大部分情况下,一个表单类不需要实现Serializable接口,因为表单对象很少保存在HttpSession中。
这里写图片描述

2.3.3 ControllerServlet类

ControllerServlet类继承自javax.servlet.http.HttpServlet类,其doGet和doPost方法最终会调用process方法,该方法是整个Servlet控制器的核心。
为什么这个Servlet控制器被命名为ControllerServlet,这里遵从了一个预定,所有Servlet的类名称都带有servlet后缀。

ControllerServlet类
package…
import …

public class ControllerServlet extends HttpServlet {
    private stetic
    final long serialVersionUID = 1579L;

    @Override
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException {
        process(request,response);  
    }

    @Override
    public void doPost(HttpServletRequest request,HttpServletResponse) throws IOException,ServletException {
        process(request,response);  
    }

    private void process(HttpServletRequest,HttpServletResposne) throws IOException,ServletException {
        String uri = request.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);   
        if(action.equals("product_input.action")){
            //no action class,there is nothing to be done       
        }else if (action.equals("product_save.action")){
        //create form
        ProductForm productForm = new ProductForm();             
        productForm.setName(request.getParameter("name"));
        productForm.setDescription(request.getParameter("description"));
        productForm.setPrice(request.getParameter("price"));
        //create model
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(product.getDescription());
        try {
            product.setPrice(Float.parseFloat(produceForm.getPrice()));
        }catch (NumberFormatException) {

        }       
        //code to save product

        //store model in a scope variable for the view request.setAttribute("product",product);
        request.setAttribute("product",product);


        //forward to a view 
        String dispatchUrl = null;
        if (action.equals("product_input.action")) {
            dispatchUrl = "/WEB-INF/jsp/ProductForm.jsp";
        } else if (action.equals("product_save.action")) {
            dispatchUrl = "/WEB-INF/jsp/ProductDetails.jsp";

        }
        if (dispatchUrl != null) {
            RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
            rd.forward(request,response);
        }
    }   
}

如果基于Servlet3.0规范,可以采用注解的方式,而无需在部署描述符中进行映射。

...
import javax.servlet.annotation.WebServlet;
...
@WebServlet(name="ControllerServlet",urlPatterns={"/product_input","product_save"})
public class ControllerServlet extends HttpServlet {
    ...
}

ControllerServlet的process方法处理所有输入请求。首先是获取请求URI和action名称。
String uri = request.getRequrstURI();
int lastIndex = uri.lastIndexOf(“/”);
String action= uri.substring(lastIndex + 1);

在本示例应用中,action值只会是product_input或product_save。
接着,process方法会执行如下步骤:
1.创建并根据请求参数构建一个表单对象。product_save操作设计3个属性:name、description和price。然后创建一个领域对象,并通过表单对象设置响应属性。
2.执行针对领域对象的业务逻辑,包括将对象持久化到数据库中。
3.转发请求到视图(JSP页面)

2.3.4 视图
示例中包含两个JSP页面。
如何避免用户通过浏览器直接访问JSP页面。
1.将JSP页面都放到WEB-INF目录下
2.利用一个srvlet filter过滤JSP页面(spring.xml)
3.在部署描述符文件中为JSP页面增加安全限制。(web.xml)

2.3.5 测试应用
访问应用
http:8080//localhost:8080/app02a/product_input.action

完成输入后,表单将提交到如下服务器URI上
http://localhost:8080/app02a/product_save.action
注意:可以将servlet控制器作为默认主页,这是一个非常重要的特性,使得在浏览器地址栏中仅输入域名(如http://example.com),就可以访问到servlet控制器,这是无法通过filter方式完成的。

2.4 解耦控制器代码
升级后
这里写图片描述
这两个Controller都实现了Controller接口。Controller接口只有handleRequest一个方法。Controller接口的实现类通过该方法访问到当前请求的HttpServletRequest和HttpSrervletResponse对象。

Controller接口


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Controller {
    String handleRequest(HttpServletRequest request,HttpServletResponse response);
}

InputProductController类直接返回了ProductForm.jsp的路径,而SaveProductController类则会读取请求参数来构造一个ProductForm对象,之后则用ProductForm对象来构造一个Product对象,并返回ProductDetail.jsp路径。
这里写图片描述
这里写图片描述
这里写图片描述

将业务逻辑代码迁移到controller类的好处很明显:Controller Servlet变得更加专注。现在作用更像一个dispatcher,而非一个controller,因此将其改名为DispatcherServlet。

所以

....
public class DispatcherServlet extends HttpServlet {
    ...
    doget()
    dopost()
    process(){
    if(...){
        InputProductController controller = new InputProductController()...
        dispatchUrl = controller.handleRequest(request,response);
        }
    else(...){
        SaveProductController controller = new SaveProductController();
        dispatchUrl = controller.handleRequest(request,response);
        }

        if(dispatchUrl!=null){
        RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
        rd.forward(request,response);
        }
    }
}

有了Spring,DispatcherServlet已经放在spring MVC.xml中了。

2.5 校验器
在web应用中执行action时,很重要的一个步骤就是进行输出校验。现在MVC框架通常同时支持编程式和申明式两种校验方法。在编程式中,需要通过编码进行用户输入校验,而在申明式中,则需要提供包含校验规则的XML文档或者属性文件。
这里写图片描述
与原来相比,这里多了一个ProductValidator类以及两个JSTLjar包本节,我们仅需知道JSTL的作用是在ProductFrom.jsp页面中展示输入校验的错误信息。
关于ProductValidator类:
这里写图片描述
注意:ProductValidator类中有一个操作ProductForm对象的validator方法,确保产品的名字非空,其价格是一个合理的数字。validate方法返回的是一个包含错误信息的字符串列表,若返回一个空列表,则表示输入合法。
应用中唯一需要用到产品校验的地方是保存产品时,即SaveProductController类,现在,我们为SaveProductController类引入一个ProductValidator类.

新版的SaveProductController类



public class SaveProductController implements Controller {

    @Override
    public String handleRequest(HttpServletRequest request,HttpServletResponse response) {
        // ....
        //validate ProductForm
        ProductValidator productValidator = new ProductValidator();
        List<String> errors = productValidator.validate(productForm);
        if(...){
            //
        }else{
            ///
        }
    }

}

新版的SaveProductController类新增了初始化的ProductValidator类,并调用其validate方法的代码

//validate ProductForm
ProductValidator productValidator = new ProductValidator();
List<String> errors = productValidator.validate(productFrom);

如果发现校验有错误,则SaveProductControlller的handleRequest方法会转发到ProductForm.jsp页面。若没有错误,则创建一个Product对象,设置属性,并转到/WEB-INF/jsp/ProdyctDeatails.jsp。



    if(errors.isEmpty()) {
        //create Product form ProductForm
        Product product = new Product();
        product.setName(productFrom.getName());
        product.setDescription(productFrom.getDescription());
        product.setPrice(Float.parseFloat(productForm.getPrice()));

        //no validation error ,execute action method
        //insert code to save product to the database

        //store product in a scope variable for the view
        request.setAttribute("product",product);
        return "/WEB-INF/jsp/ProductDetails.jsp";

    }else{
        //store errors and form in a scope variable for the view
        request.setAttribute("errors",errors);
        request.setAttribute("form",productForm);
        return "/WEB-INF/jsp/ProductForm.jsp";

    }

当然实际中,这里会把Product保存到数据库或者其他存储类型的代码,但现在我们仅关注输入校验。
现在,修改应用中的ProductForm.jsp页面,使其可以显示错误信息以及错误的输入。

...
    <p id="errors">
    Error(s)!
    <ul>
    <c:forRach var="error" items="${requestScope.errors}">
        <li>${error}</li>
    </c:forEach>
    <ul>
    </p>
...

现在可以访问product_input并测试
http://localhost:8080/app02c/product/product_input.action
若产品表单提交了非法数据,页面将显示相应的错误信息。如图,包含两条错误信息。
这里写图片描述

2.6 后端
前面演示了如何进行前端处理,那么后端如何处理呢?我们当然需要数据库。
应用MVC,可以在Controller类中调用后端业务逻辑。通常,需要若干封装了后端复杂逻辑的Service类。在Service类中,可以实例化一个DAO类来访问数据库。在Spring环境中,Service对象可以自动被注入到Controller实例中,而DAO对象可以自动被注入到Service对象中

Spring MVC介绍

Spring可以加速模型2的开发。

3.1 采用Spring MVC的好处
如果需要开发一个模型2的应用,我们需要写一个DispatcherServlet和控制类。
DispatcherServlet需要能够做如下事情:
1. 根据URI调用相应的action。
2. (在DispathcerServlet)实例化正确的控制类。
3. 根据请求参数来构造表单bean。
4. 调用控制器对象的i相应方法。
5. 转到一个视图(jsp)页面。

Spring MVC是一个包含了DispatcherSevlet的MVC框架,它调用控制器方法并转发到视图。所以SpringMVC,不需要编写DispatcherServlet。以下是Spring MVC具有的能加速开发的功能列表。
1.Spring MVC 提供了一个Dispatcher,无需额外开发。
2. Spring MVC使用基于XML的配置文件,可以编辑,无需重新编译应用程序。
3. Spring MVC实例化控制器,并根据用户输入来构造bean
4. Spring MVC可以绑定用户输入,并正确地转换数据类型。例如,能自动解析字符串,并设置float和decimal类型的属性。
5. Spring MVC可以校验用户输入,若校验不通过,则重定向回输入表单。输入校验是可选的,支持编程方式以及声明。关于这一点,SpringMVC内置了常见的校验器。
6. Spring MVC是Spring框架的一部分,可以利用Spring提供的其他能力。
7. 支持国际化和本地化,支持根据用户区域显示多国语言。
8. 支持多种视图技术,最常见的JSP技术以及其他技术包括Velocity和FreeMarker。

3.2 SPring MVC的DispathcerServlet
Spring MVC自带了一个开箱即用的DispathcerServlet,全名为org.springframework.web.servlet.DispatcherServlet。
要使用这个servlet,需要把它配置在部署描述符(web.xml文件),印共用servlet和servlet元素。

<servlet>
    <servlet-name> springmvc</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.Dispatcher
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--map all requests to the DispatcherServlet-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

servlet元素内的on-startup元素是可选的。有的话,它将在应用程序启动时装载servlet并调用它的init方法。若不存在,则在servlet的第一个请求时加载。
DispatcherServlet将使用SpringMVC诸多默认的组件。此外,初始化时,他会寻找一个在印共用程序的WEB-IN发目录下的配置文件,该配置文件的命名规则如下:
servletName-servlet.xml
其中,srevletName是在文件描述符中的DispatcherServlet的名称。如果这个sevlet的名字是SpringMVC,则在i也能够用程序的WEB-INF目录下对应的文件是SpringMVC-servlet.xml。
此外,也可以把SpringMVC的配置文件放在应用程序的任何地方,用servlet定义的init-param元素,以便Dispatcher servlet加载到该文件。init-param元素拥有一个值为contextConfigLocation的param-name元素,其param-value元素则包含配置文件的路径。例如,可以利用init-param元素更改默认的文件名和文件路径。

<servlet>
    <servlet-name>springmvc<servlet-name/>
    <servlet-class>
            org.springframework.web.servlet.DiapatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>任意你想放的地方</param-value>
        <load-on-startup>1</load-on-startup>
    </init-param>

3.3 Controller接口
在Spring2.5版本以前,开发一个控制器的唯一方法就是实现org.springframework.web.servlet.mvc.Controller接口。这个接口公开了一个handleRequest方法。

ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response)

其实现类可以访问对应请求的HeepServletRequest和HttpServletResponse,还必须返回一个包含视图路径和模型的ModelAndView对象。
Controller接口的实现类只能处理一个单一动作(Action),而基于一个注解的控制器可以同时支持多个请求处理动作,并且无需实现任何接口。

3.4 第一个Spring MVC接口
3.4.1 目录结构
注意WEB-INF/lib目录包含了所有的SpringMVC所有的jar文件,其中就包含了DispatcherServlet类。SpringMVC依赖于Apache Commons Logging组件。
目录结构

3.4.2 部署文件符文件和SpringMVC配置文件
web.xml和springmvc.xml

...
<bean name="/product_input.action" class="app03a.controller.InputProductController"/>
<bean name="/product_save.action" class="app03.controller.SaveProductController"/>
...

这里声明了两个控制器类,分别映射到/product_input.action和/product_save.action

3.4.3 Controller
接下来是两个控制器类

public class InputProductController implements Controller {
    private static final Log logger = LogFactory.getLog(InputProductController.class);
    @Override
    public ModelAndView handleRequest(HttpServletRequest,HttpServletResponse response) {
    logger.info("InputProdcutController called");
    return new ModelAndView("/WEB-INF/jsp/ProductForm.jsp");
    }
}

以上只是返回了一个ModelAndView,包含了一个视图,并没有模型,因此,该请求被转发到/WEB-INF/jsp/ProductForm.jsp页面。

public class SaveProductController implements Controller {
    private static final Log logger = LogFactory.getLog(SaveProductController.class);
    @Override
    //populate action properties 填充属性
    ....set属性;
    //create model
    Product product = new Prodcut();
    product.setName(productForm.getName());
    product.setDesciption(productForm.getDescription());
    try{
        ...
    }catch{
        ...
    }
    //insertt code to save Product
    return new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp","product",product); 
}

(用ProductFrom对象来创建Product对象)
SaveProductController类的handleRequest方法中,首先请求参数参加一个ProductForm对象;然后他根据ProductForm对象创建Product对象。由于ProductForm的price属性是一个字符串,而其在Product类对应的是一个float,所以类型转换是有必要的。其实,SpringMVC中可以省区ProductForm对象,以后学到。
SaveProductController的handleRequest方法最后返回的ModelAndView模型包含了视图的路径、模型名称以及模型(Product对象)。该模型将提供给目标视图,用于界面显示。

3.4.4 View
程序中包含两个JSP页面:ProductForm.jsp和ProductDetails.jsp页面。
ProductForm.jsp:

<body>
<form action="product_save.action" method="post">
    <fieldset>
        <label></label>
        <input>
        ......
        <input="reset">
        <input="submit">
    </fieldset>
</body>

ProfuctDetials.jsp:

    <p>
        Product name:${product.name}
        Description:${product.description}
    </p>

页面通过模型属性名”product”来访问由SaveProductController传入的Product对象。用的是JSP EL表达式。

3.4.5 测试应用
浏览器输入:
http://localhost:8080/app03/product_input.action
这里写图片描述

3.5 View Resolver
Spring MVC中的视图解析器负责解析视图,可以通过在配置文件中定义一个ViewResolver来配置视图解析器。

<bean id="viewResolver" class="org.springframe.web.servlet.view.InternetResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

上面的视图解析器配置有前缀和后缀两个属性。这样依赖,view的路径将缩短。例如,仅需提供”myPage”,而不必再设置视图路径为/WEB-INF/jsp/myPage.jsp,视图解析器将会自动增加前缀和后缀。

第四章,基于注解的控制器

4.1Spring MVC注解类型
使用基于注解的控制器的优点:
一个控制器可以处理多个动作(而一个实现了Controller接口的控制器只能处理一个动作)。这个允许将相关的操作写到一个控制器内,减少代码量。
基于注解的控制器的请求映射不需要存储在配置文件中,使用RequestMapping注释类型,可以对一个方法进行请求处理。
Controller和RequestMapping注释类型是SpringMVC API最重要的两个注释类型。

4.1.1 Controller注解类型
org.springframework.stereotype.Controller注解类型用于指示一个控制器。下面是一个带注解@Controller的例子。

package ...
import org.springframework.stereotype;
...
@Controller
public class CustomerController {
    //....
}

Spring使用扫描机制来找到所有基于注解的控制器类。为了保证Spring找到控制器,需要完成两件事。首先,在SpringMVC的配置文件中声明spring-context,如下:

<beans
    ...
    xmlns:context="http://www.org.springframework.org/schema/context"
    ....
>

然后需要应用元素,如下所示:

<context:component-scan base-package="basePackage"/>

请在元素中指定控制器类的基本表。例如,若所有的控制器都在com.example.controller及其子包下,则需要写一个如下所示的元素:
那么整个配置文件如下:

<?xml ...>
<beans xmlns="......"
    xmlns:xsi"......"
    xmlns:p"......"
    xmlns:context"http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    ...
    ...
    http://www.springframework.org/schema/context/springcontext.xsd">
    <context:component-scan base-package="com.example.controller"/>
</beans>

(注意:xmlns:xsi ——是指xml文件遵守xml规范,xsi全名:xml schema instance,是指具体用到的schema资源文件里定义的元素所准守的规范。即http://www.w3.org/2001/XMLSchema-instance这个文件里定义的元素遵守什么标准。
xsi:schemaLocation——是指本文档里的xml元素所遵守的规范,这些规范都是由官方制定的,可以进你写的网址里面看版本的变动。)

请指定一个不太广泛的基本包(采用com.example.controller而不是com.example),这样不至于扫描无关的包。

4.1.2 RequestMapping注解类型
采用@RequestingMapping注释的方法将称为一个请求处理方法,并由调度程序在接受到对应的URL请求时调用。
举例:

package...
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
...
@Controller
public class CustomerController{

    @RequestMapping(value="/customer_input")
    public String inputCustomer(){
    //do something
    return "CustomerForm";
    }
}

使用RequestMapping注解的value属性将URI映射到方法上。我们将customer_input(请求)映射到inputCustomer方法上。这样,可以使用如下URL访问inputCustomer方法。
http://domain/context/customer_input
由于value属性时RequestMapping注释的默认属性,因此,托只有唯一的属性,则可以省略属性名称。
@RequestMapping(value=”/customer_input”)
等同于
@RequestMapping(“/customer_input”)
但是如果有超过一个属性时,就必须写入属性名称。
请求映射的值可以时一个空字符串,此时方法映射到如下网址:
http://domain/context
RequsetMapping除了具有value属性外,还有其他属性。例如method用来指示仅处理哪些HTTP方法。
例如,仅当在HTTP POST或PUt方法时,采用访问到下面的ProcessOrder方法。

...
import ...
...
    @RequestMapping(**value**="/order_process",**method**={RequestMethod.POST,RequestMethod,PUT})
    public Strung processOrder() {
    //do something
    return "OrderForm";
    }

若method属性只有一个HTTP方法,则无需花括号。
@RequsetMapping(value=”/order_process”,method=RequestMethod.POST)
如果没有指定的method属性值,则请求处理方法可以处理**任意**HTTP方法。

此外,RequestMapping注释类型可以用来注释一个控制器类:

import ....
...
@Controller
@RequestMapping(value="/customer")
public **class** CustomerController

在这种情况下,所有方法都将映射称为相对于类级别的请求。
(二级请求):

import ....
....
@Controller
@RequestMapping("/customer")
public class CustometController {

    @RequestMapping(value="/delete",method={...POST,...PUT})
    public String deleteCustomer() {
    //do something
    return ...;
    }
}

由于控制器的映射使用”/customer”,而deleteCustomer方法映射为”/delete”,则如下的URL会映射到该方法上。
http://domain/context/customer/delete

4.2 编写请求处理方法

每个处理请方法可以有多个不同类型的参数,以及一个多种类型的返回结果。例如,如果在i请求处理方法中需要访问**HttpSession方法,则可以添加HttpSession作为参数,**Spring会将对象正确地传递给方法

@RequestingMapping("uri")
public String myMethod(HttpSession session) {
    ...
    session.addAttribute(key,value);
    ...
}

或者,若需要访问客户端语言环境和HttpServletRequest对象,则可以在方法签名上包括这样的参数:

@RequestMapping("/uri")
public String myMethod(HttpServletRequest request,Locale locale){
    ...
    //access locale and HttpSerlvetRequest here
    ...
}

下面是可以在请求处理方法中出现的参数类型
1. javax.servlet.ServletRequest或javax.servlet.http.HttpServletRequest
2. javax.servlet.ServletResponse或 javax.servlet.http.HttpSerlvetResponse
3. javax.servlet.http.HttpSession
4. org.springframework.web.context.request.WebRequest或org.springframework.web.context.request.NativeWebRequest
5. java.util.Locale (客户端语言环境)
6. java.io.InputStream或java.io.Reader
7. java.io.OutputStream或java.io.Writer
8. java.security.Principal
9. HttpEntity < ? >
10. 10. java.util.Map/org.springframework.ui.Model/
11. org.springframework.web.servlet.mvc.support.RedirectAttributes
12. org.springframework.validation.Errors/
13. org.springframework.validation.BindingResult
14. 命令或表单对象
15. org.springframework.web.bind.support.SessionStatus
16. org.springframework.web.util.UriComponentsBuilder
17. 带@PathVariable.@MatrixVariable注释的对象
18. @RequestParam,@RequestHeader,@RequestBody,@RequestPart
特别重要的是org.springframework.ui.Model类型,这不是一个ServletAPI类型,而是一个包含Map的SpringMVC类型。每次调用请求处理方法时,Spring MVC都创建Model对象并将其Map注入到各种对象。

请求处理方法可以返回如下类型的对象。
1. ModelAndView
2. Model
3. Map 包含模型的属性
4. View
5. 代表逻辑视图名的String
6. void
7. 提供对servlet的访问,以响应HTTP头部和内容HttpEntity或ResponseEntity对象
8. Callable
9. DeferredResult
10. 任意其他类型,Spring将其视作输出给View的对象模型

4.3 应用基于注解的控制器
4.3.1 目录结构
这里写图片描述
新增了一个HTML文件,SpringMVC Servlet的URL模式设置为”/“,可以访问静态资源。

4.3.2 配置文件
web.xml和springmvc-servlet.xml
放部署描述符中的元素,SpringMVC的dispatcher-servlet的URL模式设置为”/“,而不是第三章的.action。设置为”/“时,意味着所有请求都被映射到Dispatcher Servlet。为了正确处理静态资源(包括那些用于静态资源),需要在Spring MVC配置文件中添加一些元素
这里写图片描述
上面的清单中最主要的时元素,这是要指示SpringMVC扫描目标中的类,本例是app04a.controller包。接下去是一个元素和两个元素。元素做的事情包括注册用于支持基于注解的控制器的请求处理方法bean对象。元素则指示SpringMVC哪些静态资源需要单独处理(不通过dispatcher servlet)。
清单中的配置文件中有两个元素。第一个确保在/CSS目录下的所有文件可见,第二个则允许显示所有的.html文件。
注意:如果没有,元素会阻止任意控制器被调用。若不需要使用resources,则不需要元素。

4.3.3 Controller类
这里写图片描述

saveProduct方法的第二个参数是org.springframework.ui.Model类型。无论是否会使用,SpringMVC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值