从SpringMVC学习笔记(1)中,我们了解了SpringMVC的主要作用,并一步步的指导完成了第一个基于SpringMVC的web应用。
SSM与三层架构的关系
到这里,我们可以将SSM框架与三层架构之间的关系搞清:
- SpringMVC:界面层,接收用户请求,显示处理结果
- Spring:业务层,处理业务逻辑,Spring会负责Service,DAO,工具类等对象的创建和管理
- MyBatis:持久层,实现数据库的访问,对数据进行增删改查等。
SpringMVC中的MVC模式应用
从上面可以看出,SpringMVC实现界面层的功能,其框架的各组成部分,也是按MVC模型来进行设计的。这些类也分别实现MVC各层的功能。即三层架构中的界面层,再可以按MVC模式,分成M(Model),V(View),C(Controller)这三部分,其中:
- C(Controller),在SpringMVC中,分为两部分:
- 前端控制器(front controller),就是DispatcherServlet,也叫中央控制器,或中央处理器。所有的用户请求,都由这个前端控制器实行分配,通过查找请求映射,来将请求再交给具体的后端控制器。
- 后端控制器(back controller),是我们自己定义的Controller类,用@Controller注解,springMVC框架会自动创建该对象,在后端控制器中,可以定义使用@RequestMapping注解处理器方法或控制器方法来真正处理用户请求。处理完成后,可以通过返回值,来返回Model和View
- M(Model),后端处理器定义的处理器方法在处理完用户请求后,可以通过返回ModelAndView类型或Object类型来返回数据,这个数据就是Model。这个Model(数据)可以在View(视图)中进行显示。
- V(View),后端处理器定义的处理器方法在处理完用户请求后,可以通过返回ModelAndView类型或String类型来返回View的路径,通过springMVC框架会通过请求转发来将request域转发给View。比如上例中的show.jsp(最后显示处理结果的页面),在View中,可以显示Model中的各数据。
所以,从SpringMVC处理用户请求的过程,我们也可以很好的去理解三层架构与MVC模式之间的关系。
下面继续后面的知识。
配置视图解析器
在上一节学习中,我们发现一个问题,就是View视图文件(show.jsp),放在了webapp目录下面。
我们可以直接通过地址栏输入对这个视图文件的访问。
由于这个访问没有通过MyController控制器,所以,并没有任何数据。我们需要避免。
方法为将View视图页面放在用户不能直接访问到的WEB-INF目录下(在学习java web开发时,应该知道这一点,WEB-INF目录下的所有目录或文件对用户不开放,用户是直接访问不了)。
常用作法是,在WEB-INF目录下创建一个子目录,命名为view,或pages等。当然,还可以根据需要创建子目录结构。并将视图(show.jsp)放在该目录下(在idea中,可以直接使用鼠标拖动来移动文件,移动文件属于重构(refactor)操作,按提示确定即可)
移动后,show.jsp文件处于WEB-INF目录下的view目录中。
这时,如果用户想自己去访问show.jsp文件,是不被允许的,因为WEB-INF目录下的所有资源或文件,不允许用户直接访问。
但是,我们只要修改MyController类中的控制器处理方法doSome(),将里面指定视图的路径修改成新的路径即可。
//mv.setViewName("/show.jsp");
mv.setViewName("/WEB-INF/view/show.jsp");
这时重新启动Tomcat,再将访问首页index.jsp
点击链接
说明View视图页访问正常。
我们往往会在一个Controller中定义多个控制器处理方法,来处理不同的请求,比如我们一个Controller处理增删改查四种不同的请求。这就会有四个不同的控制器处理方法。在每个方法处理完成后,可能会指定不同的视图路径,来打开不同的View视图页面。这时,我们会发现,在路径字符串中,有许多信息是重复的,比如View所在的目录(例如我们这里是/WEB-INF/view
目录),还有扩展名都是.jsp
。
下图代码并不作为实例代码,只是作为示例,便于理解视图解析器的作用。
我们可以在springMVC框架中声明视图解析器来减少这种重复工作。
具体作法是,修改springMVC的配置文件springmvc.xml。添加视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀:视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/" />
<!--后缀:视图文件的扩展名-->
<property name="suffix" value=".jsp" />
</bean>
修改MyController的控制器处理方法中的指定View路径的代码
//当配置了视图解析器后,可以使用逻辑名称(文件名)来指定视图
//mv.setViewName("/WEB-INF/view/show.jsp");
mv.setViewName("show");
可以看出,当配置了视图解析器后,可以使用逻辑名称(文件名)来指定视图,而且,当配置了视图解析器后,只能使用逻辑名称来指定视图,如果还继续使用全路径来指定,则视图解析器也会帮你加上前缀和后缀,这样,最后的路径将会不正确。
关于请求映射的属性(@RequestMapping)
value属性
前面我们通过设置value值,来实现请求与控制器处理方法之间的映射关系。这种映射可以是
- 一对一,即一个请求有一个处理方法;
- 多对一,即多个请求,可以由一个处理方法来处理;
- 不能一对多,即一个请求不可能对应有多个处理方法。
对于一个控制器处理方法,我们可以让多个不同的请求都让该方法来处理。例如:
@RequestMapping(value = {"/some.do","/first.do"})
public ModelAndView doSome(){
...
}
value属性的值为一个数组,每个元素对应一种请求,所以,当是some.do或first.do请求,都会被中央处理器交给这个控制器处理方法dosome()来处理。
另外,请求名称(some.do和first.do)和控制器处理方法名称(dosome())不需要相同, 彼此没有任何关系。只是所有请求名称(包括路径在内的URI)必须唯一。
method属性
method属性用来指定请求的方法类型;如果不指定该属性值,则请求可以是任意类型。默认是对请求类型不限制。
但如果想限制请求只能是某一种类型时,则需要设置method属性。其值为枚举类型RequestMethod,下面代码是其定义。
public enum RequestMethod {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
private RequestMethod() {
}
}
例如:我们希望请求只能是POST请求,可以如下设置:
@RequestMapping(value = "/some.do",method = RequestMethod.POST)
public ModelAndView doSome(){ //功能上类似doGet()
...
}
通过设置属性value = "/some.do",method = RequestMethod.POST
,限定请求/some.do必须为POST请求,如果不是,则会出错。
例如通过链接来提交该请求为GET方式。
我们看到,会出现405错误码,方法不允许,即要求的是POST方法,但发起的是GET方法。客户端请求类型有错。同学们有兴趣,可以修改index.jsp文件,增加一个form,提交方法为POST。
<form action="some.do" method="post">
<button type="submit" >提交</button>
</form>
点击表单提交按钮,发起POST请求
请求类型正确,处理正常。
指定模块名称
通常情况下,我们进行开发时,会按功能划分模块,即某一功能下的所有请求,都会在一个模块下面。
比如前面我们的所有请求都在user模块下,那么,控制器的请求映射中,请求名称应该包括模块名称。如下:
为了方便对于模块名称的管理,避免重复输入模块名,也避免在修改模块名时,需要修改多个地方,我们可以通过在控制类的上面添加@RequestMapping注解。
当@RequestMapping注解放在类的上面时,是设置对于该控制类中所有控制器处理方法公共的部分。
修改MyController类代码,在类定义上添加@RequestMapping注解,并添加value属性,值为所有映射到处理器方法的请求的公共部分,一般为模块名。
@RequestMapping(value = "/user")
public class MyController {
@RequestMapping(value = "/some.do",method = RequestMethod.POST)
public ModelAndView doSome(){
...
}
@RequestMapping(value = {"/listAll.do","/second.do"})
public ModelAndView doListAll(){
...
}
@RequestMapping(value = "/add.do")
public ModelAndView doAdd(){
...
}
这时,实际与处理器方法映射的请求名称为/user/some.do,/user/listAll.do,/user/second.do,/user/add.do。
所以,如果有模块名,则可以通过在类上添加@RequestMapping注解来定义该类的准备映射的所有请求的模块名称。
当然,index.jsp文件中,发起请求的链接地址也需要添加上模块名称。
<p>第一个SpringMVC项目</p>
<p><a href="user/some.do">发起some.do请求</a></p>
<form action="user/some.do" method="post">
<button type="submit" >提交</button>
</form>
下一节,我们再继续讨论关于处理器方法的参数。