Spring MVC(Model-View-Controller)

当你看到本博文时,我猜你可能正面临着我已探索过的问题。

同其他博主一样,我先按照书上详细的介绍一下 Spring MVC,也是为了自己能理解的更深刻一些。

一、跟踪 Spring MVC 的请求

每当我们碰到各种问题时,我们总是会向浏览器求助。

正如你在百度里查找资料一样,搜索一下问题,点击一个链接,再查看里面的内容。

1、在请求离开浏览器时,总会带有你发出请求的信息,比如你可能刚刚在搜 Spring MVC,这就是信息。

2、请求的第一站是前端控制器,在 Spring MVC 中,它是 DispatcherServlet。

它的作用是把请求发送到合适的 Spring MVC 控制器。

因为有各种各样的请求需要控制器去处理,所以前端控制器是把请求发送到一个合适的控制器中。

3、用户发送了一个请求,当然想看的是结果了。所以控制器处理完请求后将会返回一些信息以便能让用户看见。

这些希望被用户看见的信息称为模型(Model)。

当然用户肯定是想看的舒服一点,所以这些信息需要发送到视图(View)时显示,通常是 JSP。

4、控制器的事情还没完,它的实际返回值是一个视图名,将视图名和模型数据传递给前端控制器(DispatcherServlet)。

5、既然知道由哪个视图显示用户希望看到的结果,那么请求的最后一站也就是视图的实现了。

二、搭建 Spring MVC

既然原理已经讲清楚了,那么接下来看个例子才能更好理解!

我们第一步也就是来配置上面所说到的东西,按照传统方式,通常是使用 web.xml 来配置的。

但是作者说的使用 JavaConfig 更好一些。那我们可以对比一下这两种方式。

1、配置 DispatcherServlet

package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{ @Override
protected Class<?>[] getRootConfigClasses()
{
return new Class<?>[] { RootConfig.class };
} @Override
protected Class<?>[] getServletConfigClasses()
{
return new Class<?>[] { WebConfig.class };
} @Override
protected String[] getServletMappings()
{
return new String[] { "/" };
} }

正如书本提到的,扩展 AbstractAnnotationConfigDispatcherServletInitializer 类的任意类---

都会自动配置 DispatcherServlet 和 Spring 应用上下文。

至于怎么配置可以看到下面重写的三个函数。

函数 getServletMappings 会处理所有的请求,也就是用户发出的请求都交由 SpringMVC 来处理。

函数 getRootConfigClasses 会根据 RootConfig 类来配置 ContextLoaderListener 创建的应用上下文的 bean。

函数 getServletConfigClasses 会根据 WebConfig 类来配置 DispatcherServlet 应用上下文的 bean。

另外配置类都要带有 @Configuration 注解,为了能让组件扫描知道这是配置类。

2、接下来看下这两个类的内容

package spittr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig
{
@Bean
public ViewResolver viewResolver()
{
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
}

该类的 Configuration 需要导入 org.springframework.context.annotation.Configuration;

@EnableWebMvc 说明需要启用 SpringMVC,

需要导入 org.springframework.web.servlet.config.annotation.EnableWebMvc;

@ComponentScan 注解说明启用组件扫描,扫描的包为 spittr.web,

需要导入 org.springframework.context.annotation.ComponentScan;

书中说的处理静态资源可能不太好,需要继承 WebMvcConfigureAdapter 并重写 configureDefaultServletHanding 方法。

目前我还不太清楚哪里不好,所以这里把它删掉。

在这里可以看到有个视图解析器,它的作用是给控制器返回的视图名加个前缀和后缀,以便知道视图文件在哪里。

在该函数前面加了个@bean 注解,是为了将起加入到 spring 容器中去,不然该视图解析器不会生效。

3、接下来看下 RootConfig 的内容

package spittr.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration
@ComponentScan(basePackages = { "spittr" }, excludeFilters = {
@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })
public class RootConfig
{ }

在这里我详细的说一下@ComponentScan的作用:
在配置类上配置 @ComponentScan 注明扫描的包名,该包及其子包下的类中,

只要代码中的类标注了 @Controller、@Service、@Repository、@Component 都会将其加载到 spring容器中。

当然 @bean 是显示的声明,可以直接加载到 spring容器中。

basePackages 表面组件扫描的包是那个,从Packages中可以看出,该属性的值可以是一个数组。

excludeFilters 指定扫描的时候需要排除的组件,includeFilters 指定扫描的时候只包含的组件,

useDefaultFilters 是否开启默认扫描规则。

例如:@ComponentScan(basePackages = "spittr",includeFilters = {},excludeFilters = {},useDefaultFilters = true)

excludeFilters 和 includeFilters 都是 Filter 数组 ,Filter中我们需要指定类型,分别为:

1、ANNOTATION 注解类型

2、ASSIGNABLE_TYPE 指定类型

3、ASPECTJ ASPECTJ 表达式

4、REGEX 正则表达式

5、CUSTOM 自定义

书上的例子是过滤 spittr 包中的注解类型的组件,后面跟的值我猜可能是过滤那个类里面的注解,使其不被加载的 Spring 容器中。

三、编写基本控制器

1、HomeController

package spittr.web;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; @Controller
@RequestMapping("/home")
public class HomeController { @RequestMapping(method = GET)
public String home(Model model) {
return "home";
} }

该控制器会处理 "/home" 的 GET 请求,返回的视图名为 home。

我们来看下 Spittr 的首页 index.jsp 以及 home.jsp:

<%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Spittr</title>
<link rel="stylesheet" type="text/css" href="/resources/styles.css">
</head>
<body>
<h1>Welcome to Spittr</h1>
<a href="<c:url value="/home"/>">Home</a> |
<a href="<c:url value="/spittles"/>">Spittles</a> |
<a href="<c:url value="/spitter/register"/>">Register</a> </body>
</html>

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Welcome to you!
</body>
</html>

当我们运行 index.jsp 后点击 Home 就会显示 Home 页面。

四、传递模型数据到视图中

在我们的Home 控制器中并没有把数据返回到视图中,仅仅只返回了一个视图名,可以说是一个超级简单的控制器了。

现在让我们给视图加点数据进去。

我们先定义一个 Spittle 类:

package spittr;
import java.util.Date;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; public class Spittle
{
private final Long id;
private final String message;
private final Date time;
private Double latitude;
private Double longitude; public Spittle(String message, Date time)
{
this(message, time, null, null);
} public Spittle(String message, Date time, Double latitude, Double longitude)
{
this.id = null;
this.message = message;
this.time = time;
this.latitude = latitude;
this.longitude = longitude;
} public long getId() {
return id;
} public String getMessage()
{
return message;
} public Date getTime()
{
return time;
} public Double getLongitude()
{
return longitude;
} public Double getLatitude()
{
return latitude;
} @Override
public boolean equals(Object that)
{
return EqualsBuilder.reflectionEquals(this, that, "id", "time");
} @Override
public int hashCode()
{
return HashCodeBuilder.reflectionHashCode(this, "id", "time");
}
}

然后假设我们的数据都在一个仓库里:

定义一个接口 SpittleRepository:

package spittr.data;
import java.util.List;
import org.springframework.stereotype.Component;
import spittr.Spittle; @Component
public interface SpittleRepository
{
List<Spittle> findSpittles(long max, int count); }

再实现它:

package spittr.data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Repository;
import spittr.Spittle;
@Repository
public class JdbcSpittleRepository implements SpittleRepository
{ private List<Spittle> createSpittleList(int count)
{
List<Spittle> spittles = new ArrayList<Spittle>();
for (int i = 0; i < count; i++)
{
spittles.add(new Spittle("Spittle" + i, new Date()));
}
return spittles;
} @Override
public List<Spittle> findSpittles(long max, int count)
{
// TODO Auto-generated method stub return createSpittleList(count);
} }

现在我们的仓库实现了,可以生成 Spittle 数据。然后我们编写一个 Spittle 控制器:来把数据放到视图中。

package spittr.web;
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 spittr.data.SpittleRepository; @Controller
public class SpittleController
{
private SpittleRepository spittleRepository; @Autowired
public SpittleController(SpittleRepository spittleRepository)
{ // 注入SpittleRepository
this.setSpittleRepository(spittleRepository);
} @RequestMapping(value="/spittles",method = RequestMethod.GET)
public String spittles(Model model)
{
// 将spittle添加到模型中
model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20));
return "spittles"; // 返回视图名
} public SpittleRepository getSpittleRepository()
{
return spittleRepository;
} public void setSpittleRepository(SpittleRepository spittleRepository)
{
this.spittleRepository = spittleRepository;
} }

该控制器中的 @Autowired 注解会在 Spring容器中寻找有 @Repository 注解的bean,然后将该 bean 注入进去。

也就是在 Spring 容器中 寻找适当的 bean 注入到参数中去。

在控制器处理方法上,也就是带有 @RequestMapping 注解的方法的参数有个 Model 也就是模型,

控制器可以通过该参数将数据传递到视图上去。

现在我们看下 spittles.jsp 视图:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Spitter</title>
<link rel="stylesheet" type="text/css" href="<c:url value="../resources/style.css" />" >
</head>
<div class="listTitle">
<h1>Recent Spittles</h1>
<ul class="spittleList">
<c:forEach items="${spittleList}" var="spittle" >
<li id="spittle_<c:out value="spittle.id"/>">
<div class="spittleMessage"><c:out value="${spittle.message}" /></div>
<div>
<span class="spittleTime"><c:out value="${spittle.time}" /></span>
</div>
</li>
</c:forEach>
</ul> </div>
</body>
</html>

最后让我们来看下效果:

     



                               

如果是用 web.xml 来配置的话:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>Java_Web</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list> <servlet>
<servlet-name>SpringDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/SpringMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>SpringDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping> <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/AppContext.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

另外说明一下,如果是 SpringMVC.xml 文件放在src里的话:<param-value>classpath:SpringMVC.xml<param-value>。

同理:AppContext.xml也是一样。

接下来我们看下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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <context:component-scan base-package="spittr"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean> </beans>

该文件配置的是视图解析器,讲解了 javaConfig,这里就不多说了。

AppContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> </beans>

上面的包并没有单元测试和 mock 模拟测试包。

如果需要项目及相关包可以点此下载:Download

初学 Spring MVC(基于 Spring in Action)的更多相关文章

  1. spring mvc 基于注解 配置默认 handlermapping

    spring mvc 是类似于 Struts 的框架.他们都有一个最主要的功能就是URL路由.URL路由能将请求与响应请求处理逻辑的类(在Struts中即是action,在spring mvc 中即是 ...

  2. Spring MVC 和 Spring 总结

    1. 为什么使用Spring ? 1). 方便解耦,简化开发 通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合. 2). AOP编程的 ...

  3. callable与runable区别?switch char ?sql只查是否存在,sql复制表 ?反射 ? spring mvc 和spring 上下文区别?

    中化技术部  2018.4.16 1. callable 和 thread 区别 实现Callable接口的线程能返回执行结果,而Runable 不可以 . Callable 的call方法允许抛出异 ...

  4. Spring,Spring MVC及Spring Boot区别

    什么是Spring?它解决了什么问题? 我们说到Spring,一般指代的是Spring Framework,它是一个开源的应用程序框架,提供了一个简易的开发方式,通过这种开发方式,将避免那些可能致使代 ...

  5. Spring MVC和Spring Boot的理解以及比较

    Spring MVC是什么?(1)Spring MVC是Spring提供的一个强大而灵活的模块式web框架.通过Dispatcher Servlet, ModelAndView 和 View Reso ...

  6. Spring MVC 到 Spring Boot 的简化之路(山东数漫江湖)

    背景 从Servlet技术到Spring和Spring MVC,开发Web应用变得越来越简捷.但是Spring和Spring MVC的众多配置有时却让人望而却步,相信有过Spring MVC开发经验的 ...

  7. 【转】Spring,Spring MVC及Spring Boot区别

    对于一个Java开发者来说,Spring可谓如雷贯耳,无论是Spring框架,还是Spring引领的IOC,AOP风格,都对后续Java开发产生的深远的影响,同时,Spring社区总能及时响应开发者的 ...

  8. spring、spring mvc与spring boot的区别是什么?

    Spring 的功能 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 ao ...

  9. Spring,Spring MVC,Spring Boot 三者比较

    Spring,Spring MVC,Spring Boot 三者比较 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 io ...

随机推荐

  1. 帆软FineReport如何使用程序数据集

    大多数情况下,FineReport直接在设计器里使用“数据集查询”,直接写SQL就能满足报表要求,但对于一些复杂的报表,有时候SQL处理并不方便,这时可以把查询结果在应用层做一些预处理后,再传递给报表 ...

  2. GCD的使用

    什么是 GCD Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法.该方法在 Mac OS X 10.6 雪豹中首次推出,并随后被引入到了 iOS4 ...

  3. 历代诗词咏宁夏注释3----蔡升元:&lt;题大清渠&gt;

    题大清渠 蔡升元 为怜□□□□□,□□□□□□□. □□□□沙碛里,凿开峡口贺兰旁. 支分九堡通沟浍,鼎峙三渠并汉唐. 作吏尽如君任事,不难到处乐丰穰. 两渠中划大清渠,畚筑无劳民力纾.[1] 心画万 ...

  4. PHP 魔术方法(所有的魔术方法)

    慢慢长寻夜,明月高空挂. 目前PHP所有的魔术方法有一下这些 __construct() __destruct() __call() __callStatic() __get() __set() __ ...

  5. Android(java)学习笔记181:利用Service在后台播放背景音乐

    1.在android应用程序里,有一种没有UI的类(android.app.Service)——Service.简单来说,Service是一个 background process(背景程序),通过背 ...

  6. linux之SQL语句简明教程---ORDER BY

    到目前为止,我们已学到如何藉由 SELECT 及WHERE 这两个指令将资料由表格中抓出.不过我们尚未提到这些资料要如何排列.这其实是一个很重要的问题.事实上,我们经常需要能够将抓出的资料做一个有系统 ...

  7. 数据结构之B进制(确定进制)

    #include <cstdio> int max_num(int n) { int max=0; while(n) { int k=n%10; if(k>max) max=k; n ...

  8. 使用SQL语句从数据库一个表中随机获取数据

    -- 随机获取 10 条数据 SQL Server:SELECT TOP 10 * FROM T_USER ORDER BY NEWID() ORACLE:SELECT * FROM (SELECT ...

  9. ubuntu14静态ip配置

    0.配置ip需要掌握的一些基本指令 打开/创建文件      sudo vim ... 插入信息      i 保存并强制退出      先按Esc,再键入:wq!,回车 1.使用命令 sudo vi ...

  10. JAVA程序 从命令行接受多个数字,求和之后输出结果

    源程序代码: public class sum{ public static void main(String[] args){ double[] a=new double[4]; a[0]=Doub ...