spring4 mvc 快速入门 - spring boot or not?

时间:2022-12-10 23:01:52

spring mvc 简洁易学。但自从 spring 4 开始,案例都采用 spring boot,无论老鸟菜鸟都懵了啊。本文介绍 mvc 的框架原理,使用简单案例分别使用spring boot 容器、其他 servlet 容器(jetty)的xml配置、无 xml 配置实现,讨论 spring4 mvc 不同环境实现程序之间的区别和联系。由于每种方式不是代码兼容的(蛋疼),这对 servlet 程序员来说是痛苦的负担,两套体系的挣扎。但对于新人,则可以选择自己喜欢的方式快速实现 web mvc 编程。 

一、 MVC 的概念

1. 什么是 MVC

    MVC是一种结构风格,就是将程序的输入、处理、输出这个顺序过程抽象为模型、视图、控制三个部分。程序员仅需关注业务的数据、展现、流程三个元素,将 Context,通讯协议、进程与线程、会话等复杂的要素交给框架处理,实现业务为中心的编程理念。在 web 应用中,MVC的含义是:

  • M(模型/Model):业务涉及的数据对象实例。例如,你显示一定订单,它包含用户、订单、订单项、支付等业务对象数据;
  • V(视图/View):展示业务人机交互界面的显示模板。例如,jsp 文件等,它能将模型中的数据填入显示模板,用户可看到界面元素丰富的界面;
  • C(控制器/Controller):用户输入处理单元。它检查输入的合法性,处理输入表单,按流程调用业务函数,生成输出需要的数据模型,选择视图模板,输出。

使用MVC结构编程,使得程序业务逻辑清晰,模块结构好。对提升开发效率,增强可维护性,促进团队内部按技能分工,起到关键作用,因此几乎所有语言都有自己若干不同的 MVC 支持框架实现。

2. MVC 在 web 程序中的位置

    对于新手,常会对某种语言或 MVC 框架产生崇拜,例如学了 MVC 就掌握了 web 编程,喜欢对比 struts 和 spring mvc 的效率。本节的目的是供新手了解 MVC 结构在 web 程序中的位置。附图表示了 MVC 结构在 web 开发中的位置。

spring4 mvc 快速入门 - spring boot or not?

    无论是 J2EE 或 轻量级 web 应用,大概分为三个层次:

  • 表示层(present layer): 处理 HTTP 输入(Request),然后调用业务服务,产生输出(Response)。这里,应用开发人员只需要考虑 MVC 三个编程元素。
  • 业务层(business layer): 提供满足 ACID 要求的业务服务。这里,开发人员只需声明对外的业务服务函数,spring 等提供事务(Transaction)支持。
  • 持久化层(persistence layer):提供数据表存取机制,主要是 ORM 框架实现以对象-关系数据库的映射。这里,开发人员只需声明表和对象的映射,特殊的 SQL。

因此,MVC结构仅是 web 开发中表示层技术。

    JAVA 所有框架都尊崇这样的理念,“应用开发人员关注于业务逻辑的实现而不是底层的实现机制”。JAVA 开发一般是最佳实践驱动,即文件名、类名、目录结构、包结构都有自己一套长期形成的默认规则,这是初学者最大的学习障碍(demo 程序不一定遵守这些规则)。最佳的办法就是在看靠谱的项目代码,学习 Java 设计模式。例如:一个类中出现 process(...) 方法,你立马就知道这时命令模式(command pattern)的实现,必定实现了一个 xxxCommand 接口。 

3. MVC 部件执行过程

    各种语言 web MVC 框架不可胜数,但执行过程都是一致的。

  1.  分配/映射:这时框架完成的,它根据客户端 URL 映射到你定义的控制器类(使用 @ 或 外部 xml),做部分 Valid 检查
  2. 控制器:执行业务流程,一般步骤是:
    1. 参数检查
    2. 业务服务访问
    3. 装配数据模型
    4. 选择输出模板
  3. 模板渲染:模板按数据模型提供的数据渲染,生成网页或特定数据格式

因此,你需要一些 HTTP 基础知识;URL 到函数的映射;模板渲染表达式。

4. 选择 MVC 框架

    尽管 spring MVC 的效率是一直被质疑,反射用的越多的框架效率也差一些,但使用会更方便。

    选择 MVC 框架第一准则是团队培训成本,即你团队最优秀的人会什么,大家都用什么。第二准则是简单实用,简单实用是相对感受=没标准,个人喜欢 HTTP Request 与 处理函数的 Mapping 模型(如 spring MVC),这与 Restful Service 开发泛型是一致的,是现代 web 开发最流行的方式。

二、用 spring boot 实现 spring4 mvc

这里使用的 Spring 官方案例,参见:http://spring.io/guides/gs/serving-web-content/  。可直接从 GIT 下载代码。

开发环境 JAVA JDK 1.8, Maven 3.3 +。 如果你使用 Centos 7,可参见:http://blog.csdn.net/pmlpml/article/details/53427164

1. 程序结构

spring4 mvc 快速入门 - spring boot or not?

对于刚学 Java 的来说,这与普通 Java 程序几乎没有区别。程序在 /src/main/java 源代码目录下;静态资源与视图模板都 /src/main/resources 目录下;maven 文件 pom.xml。

2. 控制器与模型

@Controller
public class GreetingController {

@RequestMapping("/greeting")
public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "greeting";
}

}

GreetingController 代码非常简单,要点:

  • @Controller 注释申明这个类是控制器
  • @RequestMapping("/greeting") 注释申明 URL 匹配 “/greeting” 给这个函数处理。
  • @RequestParam(value="name", required=false, defaultValue="World") 注释申明如果查询字符串中有 name=value 则 value 转为 String 给 name 这个参数。例如:URL 是  /greeting?name=User 则变量 name = “User” 。
  • model 是一个容器,存放变量供视图模板使用
  • 返回 “greeting” 是模板名称。

3. 视图

greeting.html 在资源文件夹模板目录下:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>

这是 thymeleaf 风格的模板。其中 th:text=“表达式” 表示修改 <p> 元素的 text 属性。 ${name} 从模型中获取对象 name。

4. 程序启动代码

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

非常简单,要点:

  • @SpringBootApplication 注释申明了这个类,等价于四个注释。
  • main()方法中,SpringApplication.run() 这个配置的程

其实,这个程序有许多默认的配置,如:模板的目录,静态文件的目录。如果要修改这些,可能要读 spring boot 上百页文档。例如,要添加一个过滤器?请阅读:http://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/htmlsingle/#boot-features-embedded-container

这对熟悉 servlet 的程序猿,无疑是痛苦的负担,除非你打算成为 spring boot 的专责程序猿。

5. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.springframework</groupId>
<artifactId>spring-mvc-demo</artifactId>
<version>0.1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
</properties>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

它最大好处就是依赖配置简单,几乎 spring 所有模块都下载了。

6. 运行

一般情况下,使用 maven 运行,debug 除外。在 pom.xml 目录输入命令:

mvn spring-boot:run

程序运行,web 服务器启动在 8080 端口。输入:

curl http://localhost:8080/greeting?name=User

三、用传统 XML 配置实现 spring4 mvc

1. 程序结构

spring4 mvc 快速入门 - spring boot or not?

这是一个传统的 Spring MVC 程序,也是标准的 web servlet 2.3 + 的程序结构。java 源代码目录 /src/main/java;默认资源目录变为 /src/main/webapp/WEB-INF,其中 web.xml 是 servlet 应用配置,hello-servlet.xml 是 spring beans 的配置。

2. web servlet 应用启动配置

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- spring mvc 静态资源 404问题 : http://blog.csdn.net/this_super/article/details/7884383 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

这对新手可能理解困难点。servlet 启动了 DispatcherServlet,它的名字 hello,处理的 url 是 “/” 开头的所有 URL,静态资源使用 default Servlet。

由于没有定义<listener>, DispatcherServlet 默认采用 name-servlet.xml 最为 spring 配置文件,这里是 hello-servlet.xml.

3. spring 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>
<mvc:annotation-driven/>
<context:component-scan base-package="hello"/>

<!-- SpringResourceTemplateResolver automatically integrates with Spring's own -->
<!-- resource resolution infrastructure, which is highly recommended. -->
<bean id="templateResolver"
class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<!-- HTML is the default value, added here for the sake of clarity. -->
<property name="templateMode" value="HTML" />
<!-- Template cache is true by default. Set to false if you want -->
<!-- templates to be automatically updated when modified. -->
<property name="cacheable" value="true" />
</bean>

<!-- SpringTemplateEngine automatically applies SpringStandardDialect and -->
<!-- enables Spring's own MessageSource message resolution mechanisms. -->
<bean id="templateEngine"
class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<!-- Enabling the SpringEL compiler with Spring 4.2.4 or newer can speed up -->
<!-- execution in most scenarios, but might be incompatible with specific -->
<!-- cases when expressions in one template are reused across different data -->
<!-- ypes, so this flag is "false" by default for safer backwards -->
<!-- compatibility. -->
<property name="enableSpringELCompiler" value="true" />
</bean>

<bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
</bean>

</beans>



这里配置略微复杂,要点:

  • <context:annotation-config/> 对应 @configuration
  • <mvc:annotation-driven/> 对应 @EnableWebMvc
  • <context:component-scan base-package="hello"/> 对应 @ComponentScan
  • 后面是 thymeleaf 与 spring4 集成需要的配置。 参见 thymeleaf 官网

这样,Spring Dispatcher 就能使用 @注释,分配/映射 URL 并使用 thymeleaf 引擎渲染页面。

 4. 控制器 

@Controller
public class GreetingController {

@RequestMapping("/greeting")
public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "greeting";
}

@RequestMapping("/hello")
public void sayHello(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello ,!!!!</h1>");
response.getWriter().println("session=" + request.getSession(true).getId());
}

// http://*.com/questions/7415084/spring-welcome-file-list-correct-mapping
@RequestMapping("/")
public String root() {
return "redirect:/index.html";
}

}

控制器程序几乎没有变化。只是为了支持 welcome 页面,以及使用传统 servlet 方式给出了映射。

  • /hello 可以让你*处理输入输出
  • / 可以定位到首页

视图,首页支持文件没有变化,只是改变了位置。

5. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.my_project</groupId>
<artifactId>spring-mvc-demo-jetty</artifactId>
<version>0.1.0</version>

<properties>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>

<jetty.version>9.3.7.v20160115</jetty.version>
<springframework.version>4.3.3.RELEASE</springframework.version>
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<java.version>1.8</java.version>
</properties>

<dependencies>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>${thymeleaf.version}</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
</plugin>
</plugins>
</build>

</project>
这个程序最大的缺点就是你必须知道 Java 包之间的依赖,并明白依赖的包是否在运行时需要或仅编译时需要。这对入门者非常不友好!!!

6.运行

运行命令:

mvn jetty:run

服务器也启动在 http://localhost:8080 端口。

四、无 XML 配置实现 spring4 mvc 使用 jetty

从 spring3 开始,Spring 实现了无 xml 配置,包括对 servlet 3+ 规范对 WebContext (web.xml)可编程支持。对 Spring3+ mvc 的核心接口是 WebApplicationInitializer 。实现该接口,就可以直接启动 web 应用,并进行 WebContext 的配置。一般继承该接口的 AbstractDispatcherServletInitializer 实现类,配置 web context。 

1. 程序结构

spring4 mvc 快速入门 - spring boot or not?

这个程序非常类似 spring-boot 的结构,仅是多了 config 包下两个 Java 类

2. 配置类(Java-based Configuration Class)

SpringServletInitializer 其实就是 web.xml 的 Java 形式。这里仅是比前面多了常用的 UTF-8 汉字支持的过滤器。

public class SpringServletInitializer extends AbstractDispatcherServletInitializer {

public static final String CHARACTER_ENCODING = "UTF-8";


public SpringServletInitializer() {
super();
}


protected WebApplicationContext createServletApplicationContext() {
final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringWebConfig.class);
// context.register(PersistenceJPAConfig.class);
return context;
}

protected WebApplicationContext createRootApplicationContext() {
return null;
}

protected String[] getServletMappings() {
return new String[] { "/" };
}

@Override
protected Filter[] getServletFilters() {
final CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding(CHARACTER_ENCODING);
encodingFilter.setForceEncoding(true);
return new Filter[] { encodingFilter };
}

}


SpringWebConfig 类就是 hello-servlet.xml 配置形式 

@Configuration
@EnableWebMvc
@ComponentScan("hello")
public class SpringWebConfig
extends WebMvcConfigurerAdapter implements ApplicationContextAware {

private ApplicationContext applicationContext;


public SpringWebConfig() {
super();
}


public void setApplicationContext(final ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}



/* ******************************************************************* */
/* GENERAL CONFIGURATION ARTIFACTS */
/* Static Resources, i18n Messages, Formatters (Conversion Service) */
/* ******************************************************************* */

/**
* Dispatcher configuration for serving static resources
*/
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/**/*.html").addResourceLocations("classpath:/static/");
}

/* **************************************************************** */
/* THYMELEAF-SPECIFIC ARTIFACTS */
/* TemplateResolver <- TemplateEngine <- ViewResolver */
/* **************************************************************** */

@Bean
public SpringResourceTemplateResolver templateResolver(){
// SpringResourceTemplateResolver automatically integrates with Spring's own
// resource resolution infrastructure, which is highly recommended.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
//templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
// HTML is the default value, added here for the sake of clarity.
templateResolver.setTemplateMode(TemplateMode.HTML);
// Template cache is true by default. Set to false if you want
// templates to be automatically updated when modified.
templateResolver.setCacheable(true);
return templateResolver;
}

@Bean
public SpringTemplateEngine templateEngine(){
// SpringTemplateEngine automatically applies SpringStandardDialect and
// enables Spring's own MessageSource message resolution mechanisms.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// Enabling the SpringEL compiler with Spring 4.2.4 or newer can
// speed up execution in most scenarios, but might be incompatible
// with specific cases when expressions in one template are reused
// across different data types, so this flag is "false" by default
// for safer backwards compatibility.
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}

@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}

}

你可能以注意到其中一些 "classpath:" 。这时找静态文件就在 resource 目录下了。

3. 控制器

@Controller
public class GreetingController {

@RequestMapping("/greeting")
public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "greeting";
}

@RequestMapping("/hello")
public void sayHello(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello ,!!!!</h1>");
response.getWriter().println("session=" + request.getSession(true).getId());
}

// http://*.com/questions/7415084/spring-welcome-file-list-correct-mapping
@RequestMapping("/")
public String root() {
return "redirect:/index.html";
}

}
你会发现这个程序与前面的控制器一样哦!

4. 运行

pom.xml 同 jetty 版本(XML 配置),资源文件没有变化。所以,运行程序依然是:

mvn jetty:run

服务器也启动在 http://localhost:8080 端口。

五、结论

本文给出了 Spring 官方 Hello world MVC 在 spring boot,jetty 容器中的实现。其中 jetty 中包含 xml 配置 和 无 xml 配置形式。总的感觉,没有常用案例支持,仅依靠官方文档(几乎都有说明,但难找到解决方案),不同形式的配置迁移是比较难配置和调试的。除了 Spring boot 应用, XML 与 java-based 配置几乎都是对应的。

  • XML 配置版本:可以使用 spring 2 - 4 任意版本,部署到任意 servlet 容器中运行,网上中文资料最丰富。缺点,程序变大后配置灾难在所难免;
  • 无 XML 配置版本:可以使用 spring 3 - 4 版本,部署到 servlet3+ 容器中运行,网上中文资料质量好坏难辨。非常合适以前的系统升级,也合适新手学习(配置类不同程序都类似,拷贝来就能用)
  • Spring boot 应用版本: 只能使用 sping 4 和 spring boot,网上中文资料几乎为零。胜在入门容易,几乎零配置,与 Node.js python 等比也算简单的。

【参考】

[1] Web MVC framework http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html