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

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 开发中的位置。

    无论是 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. 程序结构

对于刚学 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. 程序结构

这是一个传统的 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://stackoverflow.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. 程序结构

这个程序非常类似 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://stackoverflow.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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值