不过,还存在以下问题:

掌握器层和模型层尚未分离;如果要利用JSP以外的展示层技能,则还要做很多事情;等等

而要办理这些问题,MVC框架和IoC框架是一个很好的选择,Spring MVC和Spring IoC便是个中一个很盛行的组合。

我们前面已经先容过Spring MVC的核心事理、利用XML配置和整合IoC、利用Java配置和整合IoC、整合基于表明和Java的IoC、基于表明的掌握器。

jsp中获取action的值Java Web轻松学42  Spring MVC和Spring IoC初步应用 HTML

也先容过Spring IoC的核心事理、基于XML生产和装置Bean、利用表明自动装置Bean、利用表明生产Bean、利用Java生产和装置Bean

以是,我们连续利用Spring MVC和Spring IoC来改造租房网运用吧。

分层思维和单一职责思维

分层思维和单一职责思维紧密联系、密不可分。

分层思维,也可以叫做层级、级别等等;

单一职责思维,便是只干一件事;

它们都可谓是无处不在。

自然界中,蚂蚁、蜜蜂这两个团队都是依据分层和单一职责来划分的。

国家行政机关有省部厅司局等,组织机构也有类似的分层,这些都是管理范畴的。

公路网、铁路网、电信网络等都有主干、次级主干、分支等,这也是一种分层。

干工作的方案也可以分层,百年方案、五十年方案、三十年方案、十年方案、五年方案。

在我们IT行业中,操作系统中也有硬件层、内核层、运用层等;打算机网络中有物理层、数据链路层、网络层、传输层、会话层、表示层、运用层的OSI七层模型;而运用程序开拓中,又具有更细的分层,比如MVC,乃至每一个类/接口、每一个方法/函数都可以视之为分层和单一职责的表示。

分层最大的好处是什么?便是每一层只关心和解决一件事,这实际上又是单一职责思维、高内聚的表示。
而层与层之间的通信只通过规范好的接口来实现,这便是低耦合的表示。

我们依据分层思维(详细到这便是MVC了),将JSP页面用作展示层,即只是取数据进行展示,而不关心数据是如何取得的。

而数据的查找、打算和存储是由模型层来办理。

掌握器层拦截要求、分派要求、串联模型层和展示层进行要求的处理(转换、绑定、校验、调用模型层、转发到展示层或其他组件)、天生相应并发送,我们采取Servlet来实现。
详细到Spring MVC,又分为三个层级(DispatcherServlet、Controller、Handler)。
我们把对一个要求的处理叫做一个action,以是规定提交给掌握器层的要求必须以 .action 为后缀。

再加上JSP页面因此 .jsp 为后缀的,它们都可以称之为资源,只不过JSP页面是数据的展示,掌握器层是数据的处理。
即要求某个资源,实际上是指要求某些数据进行展示,或者要求某些数据进行处理。

当然,处理结果末了还是要进行展示,以是,终极返回给浏览器真个都是某种数据的某种展示。

思路

展示层每每是从终极用户(可能是普通用户,也可能是开拓者用户哦)利用的角度去剖析,我们这里紧张是JSP页面,仍旧是登录页面login.html、登录后会展示用户感兴趣的房源列表页面houses.jsp、可以查看某个房源详情的页面house-details.jsp、可以修正某个房源信息的编辑页面house-form.jsp。

这几个JSP页面由于之前已经改造成了纯粹展示,以是代码基本不用改变。
不过,这几个JSP页面内部的链接须要修正为 .action 为后缀的,由于每个链接都须要要求做事端实行查找或处理某些数据的动作啊。

我们再来看看掌握器层如何来设计。

Spring MVC的核心是DispatcherServlet,首先要在支配描述符web.xml中配置它。
当然,必须配置它拦截所有 .action 为后缀的要求(上面设计的:))。
而它又依赖于Spring IoC来配置和管理各种组件,比如掌握器、视图解析器等等,以是要供应Spring IoC的配置元数据(基于XML、表明、Java均可)。

那么进一步看看须要几个Controller和Handler。
由于我们的租房网运用现在还比较大略,以是把所有动作都放在一个Controller中就行了。

由于要求登录页面仍旧是不须要任何数据,以是不须要为它设计任何动作,利用静态页面就可以了。
但是,提交登录要求的时候须要做事端验证用户名和密码,这须要一个动作,就叫login.action吧。

登录之后,就须要做事端找到我感兴趣的房源啊,这须要一个动作,就叫houses.action吧。

然后,我想看某个房源的详细信息,也须要做事端去查找那个房源的详细信息(为什么不在找房源列表的同时把每一个房源的详细信息就找到呢?大家也可以想一想),这也须要一个动作,就叫house-details.action吧。

末了,我想编辑某个房源的详细信息,首先须要做事端把该房源的详细信息找到,并以表单的形式展示出来,这须要一个动作,就叫house-form.action吧;而修正完表单中该房源的详细信息之后,须要提交给做事端保存起来,这也须要一个动作,就叫house-form.action吧。
哎,等等,怎么这两个动作都叫house-form.action啊?!
那该怎么映射到Spring MVC中的Handler呢?这就须要将这两个动作在HTTP层面利用HTTP方法来区分,前者利用GET方法将house-form.action映射到一个Handler;后者利用POST方法映射到另一个Handler。
实质上还是两个动作。

租房网目前只用到了仿照的房源数据,连最基本的用户数据都还没有,实在是不太妥当的。
不过,这用来做演示已经足够了,何况我们可以逐步的添加其他数据,一步一步来,这便是蜕变/迭代思维。

只管如此,我们把仿照的房源数据也抽象出来,作为我们的模型层(按照目前盛行的分层技能,又可以分为做事层和数据访问层,但我们暂时还没有必要这么分)。

根据上面剖析,模型层中最紧张的做事便是查找某个用户感兴趣的房源列表、根据房源ID查找该房源的详细信息。

下面,我们就一步一步来利用Spring MVC和Spring IoC来改造我们的租房网运用。

配置Spring MVC和Spring IoC

首先,我们要在我们的租房网运用中配置Spring MVC和Spring IoC。

配置的第一步,是把它们的JAR包先引入到工程构造中,可以参考这篇文章(实际上便是直接拷贝)。

第二步,是要配置Spring MVC的DispatcherServlet,可以利用XML(参考这篇文章),也可以利用Java(参考这篇文章),我这里选择的是利用XML的办法,web.xml的内容如下:

<?xml version=\"大众1.0\"大众 encoding=\"大众UTF-8\公众?><web-app xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\"大众xmlns=\"大众http://java.sun.com/xml/ns/javaee\公众xsi:schemaLocation=\"大众http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"大众id=\公众WebApp_ID\"大众 version=\公众3.0\"大众><display-name>house-renter</display-name><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file><welcome-file>default.html</welcome-file><welcome-file>default.htm</welcome-file><welcome-file>default.jsp</welcome-file></welcome-file-list><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>.action</url-pattern></servlet-mapping></web-app>

须要关注的是:

DispatcherServlet的名称可以随意取,不过最好取个好听的名字;整合Spring IoC,利用的也是基于XML的办法;DispatcherServlet拦截的要求是:.action,可千万不能写成:/.action,否则Tomcat会启动不了。
详细的映射规则可以参考Servlet规范(比如,servlet-4_0_FINAL.pdf)中的“Mapping Requests to Servlets”这一部分。

第三步是整合Spring IoC,利用的也是基于XML的办法。
根据DispatcherServlet的配置,创建dispatcher.xml文件:

<?xml version=\"大众1.0\公众 encoding=\"大众UTF-8\公众?><beans xmlns=\"大众http://www.springframework.org/schema/beans\"大众xmlns:context=\公众http://www.springframework.org/schema/context\公众xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\公众xsi:schemaLocation=\公众http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd\"大众><context:component-scan base-package=\"大众houserenter\"大众/></beans>

对付Spring IoC的配置,我开启了组件扫描,这样我们就能够利用Spring MVC的基于表明的掌握器了。
当然,我把所有组件都放在houserenter这个包和它的子包当中。

展示层

展示层的几个HTML、JSP页面的代码只需将链接和表单的URL改为 .action 后缀即可。

login.html中表单的action属性值改为:login.action;include.jsp不用修正;houses.jsp中链接的href属性值改为:house-details.action(后面的参数仍旧不变,下同);house-details.jsp中有两个链接,编辑链接的href属性值改为:house-form.action;回到列表链接的href属性值改为:houses.action;house-form.jsp中表单的action属性值改为:house-form.action,同时,method属性值必须是:post,以免跟上面的编辑链接冲突!

改为 .action 后缀的意义对开拓职员来说还是挺清晰的,便是要要求做事端实行某个动作啊,当然,这种 .action 后缀的要求都将由DispatcherServlet拦截并分派给各个Controller中的各个Handler。

每个JSP页面中的数据(即绑定到该页面的数据工具的名称)仍旧沿用原来的,比如房源列表页面houses.jsp利用mockHouses(它是一个House工具的List);房源详情页面和房源编辑页面利用target(它是一个House工具)。

模型层

由于掌握器层依赖于模型层,以是我们还是先来实现模型层吧。

根据前面剖析,我们只须要实现一个房源做事即可,就叫HouseService吧,它有两个方法,一个是查找用户感兴趣的房源列表,就叫findHousesInterested();一个是根据房源ID查找房源详情的方法,就叫findHouseById()吧。

由于HouseService将为持续到来的要求供应做事,以是该当在租房网运用运行期间初始化一个实例之后常驻内存,这样才能担保要求更加快速地得到处理。
我们不就可以利用Spring IoC的@Service表明自动天生Bean并交给Spring IoC容器来管理了吗?!

它利用仿照的房源数据,当然在该组件初始化的时候装载它们即可。

当然,它仍旧利用的是原来entity包中的House类。
实际上,实体类也该当属于模型层。
而掌握器层该当将传输工具转换为实体工具,但我这里都是利用的实体工具,由于它们是同等的。

同时,为了表示分层思维,须要其余建立一个service包,将HouseService类放入此包中。

package houserenter.service;import java.util.ArrayList;import java.util.List;import org.springframework.stereotype.Service;import houserenter.entity.House;@Servicepublic class HouseService {private List<House> mockHouses;public HouseService() {mockHouses = new ArrayList<House>();mockHouses.add(new House(\"大众1\"大众, \公众金科嘉苑3-2-1201\"大众, \公众详细信息\"大众));mockHouses.add(new House(\"大众2\公众, \"大众万科橙9-1-501\"大众, \"大众详细信息\"大众));}public List<House> findHousesInterested(String userName) {// 这里查找该用户感兴趣的房源,省略,改为用仿照数据return mockHouses;}public House findHouseById(String houseId) {for (House house : mockHouses) {if (houseId.equals(house.getId())) {return house;}}return null;}}掌握器层

根据前面的思路,我把所有动作都放在了一个掌握器中,就叫HouseRenterController吧。
当然,运行时须要天生它的Bean,我们仍旧可以利用Spring IoC。
由于我们已经开启了组件扫描,以是只要为该类加上表明@Controller即可。

然后,它依赖于模型层的HouseService,而HouseService也利用Spring IoC来自动天生Bean,以是可以利用@Autowired表明自动装置它们。

然后,便是各个Handler的声明,可以参考这篇文章。
Handler的声明采取@RequestMapping及其衍生表明如@GetMapping和@PostMapping等。

各个Handler与动作是逐一对应的:

处理登录要求:@PostMapping(\"大众/login.action\"大众)获取房源列表:@GetMapping(\"大众/houses.action\"大众)获取房源详情:@GetMapping(\"大众/house-details.action\公众)获取房源编辑表单:@GetMapping(\"大众/house-form.action\公众)处理房源更新要求:@PostMapping(\公众/house-form.action\"大众)

虽然,获取房源编辑表单和处理房源更新要求的URL是一样的,但是可以通过HTTP方法来匹配这两个不同的要求。

而各个Handler的实现,也可以参考这篇文章。
我紧张是利用ModelAndView这个类来作为Handler的返回值;而要求中携带的参数直策应用字符串类型,以是可以省略@RequestParam表明,只要担保参数名与要求中的参数名同等即可。

ModelAndView可以绑定我们希望视图层展示的数据工具,也是担保数据的名字与视图中(这里是JSP页面)中利用的名字同等即可。
它利用的是addObject()等方法。

ModelAndView可以设置希望将要求转发到哪一个视图(这里是JSP页面)或动作,或者重定向到哪一个视图(这里是JSP页面)或动作。
它利用的是setViewName()等方法,重定向的视图名须要带一个前缀:“redirect:”。

至于各个Handler详细的业务处理规则,还是相称大略的。

package houserenter.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import houserenter.entity.House;import houserenter.service.HouseService;@Controllerpublic class HouseRenterController {@Autowiredprivate HouseService houseService;@GetMapping(\"大众/test.action\"大众)@ResponseBodypublic String test() {return \"大众hello\"大众;}@PostMapping(\"大众/login.action\"大众)public ModelAndView postLogin(String userName, String password) {//这里须要验证用户是否已经注册,省略System.out.println(\公众userName: \公众 + userName + \公众, password: \"大众 + password);ModelAndView mv = new ModelAndView();//重定向到查找感兴趣房源列表的动作mv.setViewName(\"大众redirect:houses.action?userName=\"大众 + userName);return mv;}@GetMapping(\"大众/houses.action\"大众)public ModelAndView getHouses(String userName) {//这里须要验证用户是否登录,省略ModelAndView mv = new ModelAndView();//查找感兴趣房源并绑定到相应JSP页面,然后将要求转发到该页面mv.addObject(\公众mockHouses\"大众, houseService.findHousesInterested(userName));mv.setViewName(\公众houses.jsp?userName=\"大众 + userName);return mv;}@GetMapping(\公众/house-details.action\"大众)public ModelAndView getHouseDetails(String userName, String houseId) {// 这里须要验证用户是否登录,省略ModelAndView mv = new ModelAndView();//查找房源详情并绑定到相应JSP页面,然后将要求转发到该页面mv.addObject(\"大众target\"大众, houseService.findHouseById(houseId));mv.setViewName(\"大众house-details.jsp?userName=\公众 + userName);return mv;}@GetMapping(\"大众/house-form.action\"大众)public ModelAndView getHouseForm(String userName, String houseId) {// 这里须要验证用户是否登录,省略ModelAndView mv = new ModelAndView();//查找房源详情并绑定到相应JSP页面,然后将要求转发到该页面mv.addObject(\"大众target\"大众, houseService.findHouseById(houseId));mv.setViewName(\公众house-form.jsp?userName=\"大众 + userName);return mv;}@PostMapping(\"大众/house-form.action\公众)public ModelAndView postHouseForm(String userName, String houseId, String houseName, String houseDetail) {// 这里须要验证用户是否登录,省略//更新指定房源的详情House target = houseService.findHouseById(houseId);target.setName(houseName);target.setDetail(houseDetail);//将要求转发到查找房源详情的动作ModelAndView mv = new ModelAndView();mv.setViewName(\公众redirect:house-details.action?userName=\公众 + userName + \公众&houseId=\公众 + houseId);return mv;}}

在Handler方法的取名上,我也是根据HTTP方法加了get、post等前缀。

须要把稳的是,处理登录要乞降处理房源更新要求这两个Handler末了都采取了重定向。
由于它们的目标资源都是GET动作,而这两个要求都是POST要求,如果采取转发,则这两个要求直接交由目标资源的GET动作来处理,这就不匹配了;重定向则是将这两个要求先返回相应给浏览器,浏览器再重新发起对目标资源的GET要求。

验证

我们可以在Eclipse中发布租房网运用到Tomcat做事器,然后启动Tomcat做事器(可以参考这篇文章)。

我们打开浏览器,输入登录页面的URL:

http://localhost:8080/house-renter/login.html

登录后即可看到房源列表页面,URL也相应变为:

http://localhost:8080/house-renter/houses.action?userName=a

点击某个房源,可以打开该房源的详细信息页面,然后可以进一步编辑该房源的详细信息,提交后,却创造可恶的中文乱码问题再次涌现!

中文乱码问题再现

这次涌现中文乱码问题还是在提交房源详情要求的处理上,我们可以先通过日志打印的办法把提交的数据(房源名称、详情等)打印出来,看是否是乱码。

果真,在这一步已经是乱码,我们可以大胆猜想Spring MVC和Servlet一样,对数据的编码办法默认都是ISO-8859-1,而String类默认编码和解码是UTF-8。
即Spring MVC收到的数据的二进制是ISO-8859-1编码方案的二进制,而输出的时候却利用UTF-8去解码。

以是,我们可以利用以下办法进行转换:

new String(houseName.getBytes(\"大众iso-8859-1\"大众), \公众utf-8\"大众)

结果还真是办理了乱码问题。
唯一的问题是把这个转换代码放到此处有点不太得当,我们是否可以像之前一样利用Filter来办理这种通用维度的问题?

答案当然是可以的。
不过这个Filter不须要我们来实现了,Spring MVC已经供应了这么一个Filter,便是:

org.springframework.web.filter.CharacterEncodingFilter

我们只须要在支配描述符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><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/</url-pattern></filter-mapping>总结

这次我们真正的把Spring MVC和Spring IoC用到了看起来有用一点的项目上,从代码上来看也还不赖(可以把原来的filter包和servlet包删除),层次还是比较清晰的。

controller包entity包service包展示层的HTML页面和JSP页面放在WebContent下配置文件放在WebContent/WEB-INF下

代码也比较干净:

展示层和掌握层的数据都通过名字来绑定和解绑,Spring MVC帮我们做了,我们就无需利用getParameter()这样的方法来自己转换了。
一些通用维度的功能比如字符编码,Spring MVC也供应了相应的Filter,我们只要配置即可,无需在代码中实现。
等等。

当然,也还存在很多改进之处:

非常处理还没有;用户登录验证也没有;像房源编辑的数据能否直接绑定到一个House工具中;利用的还是仿照数据;等等。

不过,往后我们再连续先容其他技能的时候,我们就可以一贯利用租房网这个运用,逐步改进和完善吧。