只要是同网络应用相关的,无论是有同用户互动的(带UI的)或者没有互动的情况,spring mvc都是种成熟、功能齐全的架构。阅读下面的内容需要对Java、spring(依赖注入dependency injection)、servlet需要有些基本的了解。
Spring的MVC框架如下图所示:
在Spring MVC中前端的控制器就是DispatcherServlet
这个Servlet来掌管着用户的请求及最后的系统回应。这个DispatcherServlet
同具体的业务逻辑一点都不着边,而是把所有的事情委派给控制器去做(Controller),当然DispatcherServlet
是知道该把当前的事情交个那个控制器去做,这个后面会讲;然后当控制器把事情都做完了后,这个时候轮到视图(View)上场了,简单的理解好比我们做PPT,那么这里的视图好比PPT里面的模板,它可以把数据以不同的展现形式交给客户,可以是jsp、xml、json等等。下面来看看如何具体实现的:
1、Servlet部分,上面说到前端控制器是DispatcherServlet
这个Servlet,那很显然需要在web.xml中加入一个servlet,然后把用户的请求都让DispatcherServlet去处理,下面是web.xml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web-app
version
=
"2.5"
xmlns
=
"http://java.sun.com/xml/ns/javaee"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
>
<!-- Processes application requests -->
<
servlet
>
<
servlet-name
>appServlet</
servlet-name
>
<
servlet-class
>org.springframework.web.servlet.DispatcherServlet</
servlet-class
>
<
init-param
>
<
param-name
>contextConfigLocation</
param-name
>
<
param-value
>/WEB-INF/spring/appServlet/servlet-context.xml</
param-value
>
</
init-param
>
<
load-on-startup
>1</
load-on-startup
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>appServlet</
servlet-name
>
<
url-pattern
>/</
url-pattern
>
</
servlet-mapping
>
</
web-app
>
|
关于上下文的配置,这里使用的是xml的配置方式,上面代码指定了配置文件是/WEB-INF/spring/appServlet/servlet-context.xml,这个也可以使用java的配置方式,这里需要在DispatcherServlet的初始参数中加入AnnotationConfigWebApplicationContext。另外在下面的例子中我们会看到组件即可以在XML中配置但也可以使用java代码来做。接下来看看第一个controller
2、Controller类,下面是一个最mini的控制类了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package xyz.sample.baremvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "WEB-INF/views/home.jsp";
}
}
|
1、使用了@Controller这个annotation(这个可以翻译成“注释”,表示下面的类的作用),来表示HomeController这个类是作为Spring MVC中的Controller(控制器),根据上面的那个图也就是表示这个类具有处理用户请求的能力。另外@Controller是@Component这个annotation中的特定annotation(有点类似@Component是@Controller的父类一样,@Component相当于抽象程度比较高,@Controller是属于一种特定的@Component),所以这里HomeController也是作为一个组件(Component),在Spring初始化扫描的时候它会被自动检测到并且加入到Spring Container中(Spring容器或者叫Spring上下文,都是类似的概念),然后生成对应的类实例,最后像其他任何Spring组件一样允许注入到系统中。
2、home这个方法使用了@RequestMapping这个注释,表示home这个方法可以用来处理对应于路径“/”(根路径)的用户请求
3、这里home的处理就是在log中打印一个字符串,然后返回WEB-INF/views/home.jsp,这个是交给View去处理的,这里就是个jsp文件,默认情况下,如果我们没有设置特定的View的话,Spring会使用默认的View来处理WEB-INF/views/home.jsp这个Response(回应);对应View在后面将详细讲,这里只要知道View会把系统的home.jsp这个文件呈现给客户就好
下面是对应的home.jsp文件,文件很简单,只是个Hello:
1
2
3
4
5
6
7
8
9
10
|
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<
html
>
<
head
>
<
title
>Home</
title
>
</
head
>
<
body
>
<
h1
>Hello world!</
h1
>
</
body
>
</
html
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<?
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:mvc
=
"http://www.springframework.org/schema/mvc"
xmlns:context
=
"http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Scans within the base package of the application for @Components to configure as beans -->
<!-- @Controller, @Service, @Configuration, etc. -->
<
context:component-scan
base-package
=
"xyz.sample.baremvc"
/>
<!-- Enables the Spring MVC @Controller programming model -->
<
mvc:annotation-driven
/>
</
beans
>
|
2、这里一共有三个名字空间,第一个是默认的beans、然后是mvc和context。
3、<context:component-scan/>表示要求Spring 容器(Spring container)对类进行扫描、检查,这样所有代码中包含有属于@Component子类型的注释(例如上面的@Controller就是属于@Component的子类型)都会被检查到,然后为了提高效率,这里自动检查的范围只在xyz..sample.baremvc这个包下面进行。
4、<mvc:annotation-driven />,表示可以使用annotation来进行工作,也就是支持Spring MVC将请求转给@Controller去处理;当然还可以涉及到如转换、格式化、有效性
通过上面的几个步骤后,启动服务器,在地址栏输入http://localhost:8080/baremvc
,可以看到下面的效果:
上面的这个例子十分简单吧,但是却涉及到了Spring MVC应用的关键部分,来看看这个应用的调用流程:
A、当Tomcat启动,我们的Spring MVC应用被启动起来,DispatcherServlet被加载进来,这是因为我们在Web.xml中定了这个Servlet。
B、DispatcherServlet去加载基于注释(annotation)的Spring应用上下文,这里是通过对指定的包(指定的包可以使用正则表达式)进行scan的方式来获得带注释的组件。
C、这些带注释的组件被Spring 的 container检测到。
D、http://localhost:8080/baremvc
的HTTP请求匹配了我们DispatcherServlet中的servlet-mapping,所以DispatcherServlet将去处理这个HTTP请求
E、在上面的HTTP请求中暗示着用户的请求路径为“/”,然后DispatcherServlet发现他里面有相应该路径请求的映射(这个其实是我们第二张图里面的那个Handler Mapping)
F、Dispatcher进行决定要如何处理请求,它使用到的策略叫做HandlerAdapter
,这个可以这么理解,对于request(请求)来说必须要有对应的handler(处理办法)去处理,而这些handler可能根据当前所处的不同情况,而使用不同类别的handler,所以这里还需要一个HandlerAdapter来作为不同情况下的来转换不同类别的handler去处理request。这个HandlerAdapter(或者多个,可以将它们链接起来)我们可以去指定,但是没有指定的情况下将使用系统默认的HandlerAdapter,这里就是使用注释驱动(annotation-based)的策略了。这里就是把请求转给@Controller,然后找到匹配对应路径的@RequestMapping方法,将输入的参数给该方法,并让该方法处理请求。使用其他HandlerAdapter的例子还没做,但是暂时我们不去关心这些,只要有个了解就ok了
G、然后home这个方法做它的事情,打印一个log,然后返回一个字符串,这个字符串暗示了要呈现给用户的东西,也帮助View选择合适呈现方式。
H、然后从第一个图中我们知道,Controller做完事情后,任务又交到DispatcherServlet手上了,对于如何选择合适的视图,同样DispatcherServlet需要策略,这次的策略叫做ViewResolver,通过这个ViewResolver(视图仲裁员)去仲裁那个视图(View)将回应呈现给用户,这个ViewResolver也是可以自己定义的。如果默认没有配置的情况下系统将使用InternalResourceViewResolver
,这个ViewResolver将创建一个JSTLView,而这个View只是简单的把任务委托给Servlet内部的RequestDispatcher
去呈现资源,所以这种View很适合于使用JSP或者HTML页面的情况
I、最后Servlet将回应通过制定的jsp呈现给用户
4、对View进行抽象,让Controller返回一个纯粹的逻辑性的名称。
上面的home函数中我们看到返回的是个纯粹的物理地址,为了减少耦合,让应用有更好的弹性,当然是不能使用这种硬编码的方式;要让Controller返回一个逻辑的名称,而最终的物理路径由View来决定,这样让Controller只关心逻辑上的处理,把展示留给View去做,真正做到C和V分离。看下改造了得Controller:
1
2
3
4
5
6
7
8
9
|
@Controller
public
class
HomeController {
@RequestMapping
(value =
"/"
)
public
String home() {
System.out.println(
"HomeController: Passing through..."
);
return
"home"
;
}
}
|
接下来我们要做的就是把”home“这个名称映射到”WEB-INF/views/home.jsp“这个文件上面。上面我们说过默认情况下这种映射是通过InternalResourceViewResolver
类来处理的。我们可以使用自己的ViewResolver来处理这种逻辑到物理的映射,但是这个例子里面我们只需要对InternalResourceViewResolver
做一点点调整就可以达到我们的目的了。
InternalResourceViewResolver
对Controller返回过来(其实最后是DispatcherServlet发过来的)的名称,只是在其前后加上可选的前后缀(默认情况为空),然后把这个字符串交给由它自己创建的JSTLView,然后JSTLView再委托Servlet引擎中的RequestDispatcher去干真正的活,呈现视图模板,这里是jsp做视图的模板。这个例子里比较简单,只要对View的名称返回增加相应的前后缀就可以了,前缀”WEB-INF/views/“,后缀".jsp"。一种简单的配置ViewResolver的方式是使用以java为基本的容器配置(另外就是以XML为基础的容器配置/上下文配置),把我们的Resolver定义为一个Bean,下面是对应的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package
xyz.sample.baremvc;
import
org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration;
import
org.springframework.web.servlet.ViewResolver;
import
org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
public
class
AppConfig {
// Resolve logical view names to .jsp resources in the /WEB-INF/views directory
@Bean
ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new
InternalResourceViewResolver();
resolver.setPrefix(
"WEB-INF/views/"
);
resolver.setSuffix(
".jsp"
);
return
resolver;
}
}
|
由于@Configuration也是一个@Component,所以扫描的时候也会被扫描到,然后这个配置组件(Configuration)会被加入到Spring容器中,Spring容器还会扫描所有的Bean并发现ViewResolver(也就是我们的容器里多了个由viewResolver函数里面创建的resolver,作为一个bean),然后使用这个ViewResolver来处理逻辑的View名称。
上面的使用的是java的方式,有些人喜欢xml的方式,如下面:
1
2
3
4
5
|
<!-- Resolve logical view names to .jsp resources in the /WEB-INF/views directory -->
<
bean
class
=
"org.springframework.web.servlet.view.InternalResourceViewResolver"
>
<
property
name
=
"prefix"
value
=
"/WEB-INF/views/"
/>
<
property
name
=
"suffix"
value
=
".jsp"
/>
</
bean
>
|
5、处理用户输入
我们总是要和用户互动的,让我们看看如何处理些简单的用户输入,然后再输出结果给用户。在Spring MVC中有多种输入、输出的方式,让我们看看最简单的一种。这里我们把上面的HomeController中增加一个的处理请求的方法,让它能获得两个用户输入的字符,然后进行比较,并且把结果通过jsp输出给用户,看看增加的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
xyz.sample.baremvc;
import
java.util.Comparator;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod;
import
org.springframework.web.bind.annotation.RequestParam;
/**
* Handles requests for the application home page.
*/
@Controller
public
class
HomeController {
@Autowired
Comparator<String> comparator;
@RequestMapping
(value =
"/"
)
public
String home() {
System.out.println(
"HomeController: Passing through..."
);
return
"home"
;
}
@RequestMapping
(value =
"/compare"
, method = RequestMethod.GET)
public
String compare(
@RequestParam
(
"input1"
) String input1,
@RequestParam
(
"input2"
) String input2, Model model) {
int
result = comparator.compare(input1, input2);
String inEnglish = (result <
0
) ?
"less than"
: (result >
0
?
"greater than"
:
"equal to"
);
String output =
"According to our Comparator, '"
+ input1 +
"' is "
+ inEnglish +
"'"
+ input2 +
"'"
;
model.addAttribute(
"output"
, output);
return
"compareResult"
;
}
}
|
2、这个函数希望用户通过GET请求的方式传入两个字符作为其参数,如果用户没有传两个指定名称(input1和input2)的字符串来,则会提示HTTP 400的错误,然后这里compare函数获得用户GET请求中的参数是通过@RequestParam这个注释来实现的。
3、我们使用自己定义的比较器来比较这两个字符串。这里注意到@Autowired,也就Spring通过在container中寻找类型同comparator相同(Comparator<String>)的组件,然后wired(连接)到comparator上面(可以理解为调用setComparator这样)
4、最后我们使用了Model来放置比较的结果,这样View将可以获得放在Model中的结果,这里只要把Model看做一个HashMap就可以了,名称->数据的一个对应。放到Model中的数据主要为了View能够访问这些有用的数据。上面的例子中只是个字符串,将来呈现到jsp上面。
我们创建一个对应的jsp文件来显示结果,WEB-INF/views/compareResult.jsp
:
1
2
3
4
5
6
7
8
9
10
|
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<
html
>
<
head
>
<
title
>Result</
title
>
</
head
>
<
body
>
<
h1
><
c:out
value
=
"${output}"
></
c:out
></
h1
>
</
body
>
</
html
>
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package
xyz.sample.baremvc;
import
java.util.Comparator;
import
org.springframework.stereotype.Component;
@Service
public
class
CaseInsensitiveComparator
implements
Comparator<String> {
public
int
compare(String s1, String s2) {
assert
s1 !=
null
&& s2 !=
null
;
return
String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
}
}
|
现在我们可以输入一个地址看看 http://localhost:8080/baremvc/compare?input1=Donkey&input2=dog
:
最后想了解更多Spring MVC的特性的话,可以看看这个 show case
转自:http://blog.csdn.net/rdarda/article/details/7922154