前言
上回我们讲到,"门卫大叔"DispatcherServlet如何带客户去找人,那么本节就针对于DispatcherServlet这个角色继续分析,以及讲解SpringMVC的配置
DispatcherServlet解析
那继续我们上一节的分析,论DispatcherServlet何时生成的、如何生成的,以及花名册是怎么来的
通过之前的分析,我们的DispatcherServlet必须是在request请求之前就诞生了的,根据现实案例来分析,一般公司在创立招纳贤士的时候,出于安全考虑咱也不能少了"门卫"这个角色吧,我觉得还是很重要的,其实DispatcherServlet间接来说也保证了SpringMVC内部角色的安全性,那么它肯定是在服务器启动加载配置文件的时候就诞生了的,但是Sevlet默认是懒加载,DispatcherServlet也不例外,大家可以看DispatcherServlet的源码,它实际上就是一个Servlet,那么这种情况下,我们的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">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 注入核心配置文件的地址 -->
<init-param>
<!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等)
如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml)-->
<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>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>
大家可以看到,我们上面的配置,我加了一行<load-on-startup>1</load-on-startup>
,标识服务器加载时就创建DispatcherServlet,contextConfigLocation又是什么呢?我们来看看源码,发现DispatcherServlet继承了FrameworkServlet,在FrameworkServlet中有这么个属性contextConfigLocation。
根据上面的注释Explicit context config location翻译过来就是明确的上下文配置的位置。
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
好,又有新问题了,这里的上下文配置又指的是什么,回到上面的图我们发现,"门卫大叔"手上有一本花名册,这个花名册其实就是上下文配置springmvc.xml,那么我们来看看这个配置文件里面是什么内容吧
<?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.xsd">
<bean id="listUserController" class="com.marco.servlet.ListUserController" name="/user.action"></bean>
</beans>
欸,我们发现是不是和之前我们做MVC项目的时候写的url-parttern请求和对应的类关联的web.xml很相像,只不过这里我们是用name和class的bean标签的两个属性关联起来了,注意区分这里的name和id。
name就好比客户要找的那个人的英文名字,id就是真实名称,class就是它所在的具体位置信息。
这么一说大家应该就懂了吧~
SpringMVC的配置
导包
好,终于到了大家期待已久的枯燥乏味的配置阶段了,请系好安全带,在讲完配置之前不要跑了!
为了和之前的Spring Mybatis集成的内容联系起来,这里我还是将数据库连接和操作的相关配置都引入进来了
上面的这些jar包我之前都讲过,这里就不细讲啦,有两个新导入进来的包分别是
spring-web-4.3.24.RELEASE.jar
和spring-webmvc-4.3.24.RELEASE.jar
都是用于构建Web项目使用到的包
spring-webmvc 呢主要是一些view层的核心封装,提供各前端技术及标签支持。
spring-web 主要都是包含http(http协议的实现类)和web包(应用,上下文,会话,cookies,过滤器等等)
关于映射文件的相关补充
包导入之后我们回过头来再补充下之前我们已经配置好的文件中servlet-mapping这一部分的说明
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
为什么要单独拿出来说呢?因为在Spring中这一部分的配置已经不再是我们以往的专门配置访问请求和类映射的配置了,而是通过web.xml生成它的内部控制对象DispatcherServlet,因此之前有的写法可能在这里时不能使用的,这里我把三种方式都给大家列举出来了
第一种:*.action,访问以.action结尾 由DispatcherServlet进行解析
第二种:/,所以访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置不让DispatcherServlet进行解析,使用此种方式可以实现RESTful风格的url
第三种:/*,这样配置不对,使用这种配置,最终要转发到一个jsp页面时,仍然会由DispatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错。
可以看到,之前我们一直使用的第三种方式是不可取的,试想我们现在有一个请求,比如说login.jsp
那么DispatcherServlet拿到请求后和/*
匹配上了,就会把它当做一个controller去匹配,匹配不到就会返回404,而当我们写成/
的话,就不会被DispatcherServlet拦截!
构建MVC三层结构
那么我们继续沿用 Marco’s Java【Spring进阶(三) 之 Spring及Mybatis集成(下)】 中的MVC模型项目
只不过这次我们添加上servlet(controller)层,下面是我们这次项目的结构
先说说这次我们想达到什么样的效果吧,就是通过访问地址http://127.0.0.1:8080/spring/user.action
能够从数据库中拿到下面的这张表,是不是没啥难度~
首先要做成这样的一张表,我们肯定是需要数据的对吧?那么为此,我在UserMapper.xml里面多添加了一个方法
<sql id="Base_Column_List">
id, user_name, password, real_name
</sql>
<select id="selectAll" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/> from u_user
</select>
UserServiceImpl中添加了对应的方法
@Override
public List<User> selectAll() { //查询所有的用户信息
List<User> users = userMapper.selectAll();
return users.size() > 0 ? users:new ArrayList<User>();
}
编写控制器Controller
方式一:实现Controller接口
还记得我们上面的图走到第几步了吗?我们的"门卫大叔"在花名册上找到了客户想找的员工的信息(真实姓名、地址信息),接下来该我们的控制器(Controller)出马了,那么这个控制器是需要我们自己写的。
package com.marco.servlet;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import com.marco.domain.User;
import com.marco.service.UserService;
public class ListUserController implements Controller{
UserService userService;
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = context.getBean(UserService.class);
List<User> users = userService.selectAll();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("list.jsp");
modelAndView.addObject("users", users);
modelAndView.setStatus(HttpStatus.OK);
return modelAndView;
}
}
方式一:实现HttpRequestHandler接口
这种方式也可以创建我们自定义的控制器(处理器),和上面的方法略有区别
public class ListUserController implements HttpRequestHandler{
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = context.getBean(UserService.class);
List<User> users = userService.selectAll();
request.setAttribute("users", users);
request.getRequestDispatcher("list.jsp").forward(request, response);
}
}
是不是和Servlet的处理方式很像?如果非要选择的话,我个人还是比较偏好这一种的~
有的朋友肯定会吐槽我下面的写法,其实这种方式不规范,后面我们会讲到使用Spring监听器来完成这个操作
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
大家就先将就的用用吧~
后语
控制器这个角色就跟我们部门前台的小姐姐的角色差不都,门卫大叔最多把你带到这个部门的前台,或者说打电话帮你问问前台小姐姐吧,那么前台的小姐姐知道想找的员工的名字和部门号之后,肯定就可以定位到对应的人了。所以Controller也是至关重要的,不同的请求就对应一个Controller,由相应的Controller再调用ServiceImpl中的业务方法,返回我们想要的结果,然后将返回的结果和要跳转的位置封装到ModelAndView这个容器中,返回给DispatcherServlet来处理后续的问题(默认方式为内部转发,关于请求重定向我们后面会讲到)。
可谓是分工明确,不同的类做不同的事情,不会显得杂乱无章,也便于我们后期的管理。