springmvc基于java配置的实现

该案例的github地址:https://github.com/zhouyanger/demo/tree/master/springmvc-noxml-demo

1.介绍

之前搭建SpringMvc项目要配置一系列的配置文件,比如web.xml,applicationContext.xml,dispatcher.xml。Spring 3.X之后推出了基于JavaConfig方式以及注解的形式的配置。在一定程度上简化了Spring项目的配置。近几年特别火的SpringBoot,大大的简化了创建项目,基本不需要配置配置文件,就可以快速的创建一个项目。其中一个重要的原因就是采用JavaConfig和注解帮我们做了很多配置的事。今天演示下如何通过JavaConfig和注解方式快速创建一个Springmvc项目,为以后深入学习SpringBoot打点基础。

(1) Spring MVC对 request 做了什么?

当一个 Request 离开浏览器(下图标号1)时,携带了用户的请求信息,包括url以及表单等,它到达的第一站就是 DispatcherServlet 。像大多数基于 Java 的 web 框架一样,Spring MVC 首先通过一个前端控制器处理,把 Request 交给其他组件处理(类似于前台姐姐把客户带到要去的各种部门),在 Spring MVC 里面,充当前端控制器这一角色的就是 DispatcherServlet 。

springmvc基于java配置的实现

DispatcherServlet 目的是把 Request 送到其他的 Controller,对 Request 作进一步处理,但是一般情况下程序中会有很多的 Controller,它们各自负责处理不同的 Request ,于是 DispatcherServlet 需要一些协助来决定一个 Request 应该送到哪个 Controller 。因此 DispatcherServlet 会询问 Handler mappings (上图标号2),一个 Request 应该何去何从,Handler mappings 谢邀之后不敢怠慢,通过验看 Request 携带的 URL 进行抉择。

Handler mappings 的结果出来之后,DispatcherServlet 欢天喜地地把 Request 送给对应的 Controller (如上图标号3)。到达之后,Request 卸下重担(携带着的由用户提交的信息)并耐心地等待 Controller 处理这些信息(而实际上,一个机智的Controller 自己几乎不做任何处理,而是把活儿交给其他负责业务逻辑的 service 对象)。

Controller 处理完后得到的结果往往需要返回给用户并在浏览器中显示,这个结果被称作 model, 它比较粗糙,需要补充额外信息转换成用户友好型的格式,例如 HTML 。出于这个目的呢,这一 model 需要交给一个 view , 典型的有 JavaServer Page (JSP)。

所以,Controller 最后需要做的就是把 model 数据打包并且确定一个到时候用来渲染这个 model 的 view,然后把 Request、model 数据以及 view 的名字一块发送回 DispatcherServlet(如上图标号4)。

因此 Controller 实现了与特定 view 的解耦, DispatcherServlet 拿到的 view 名也并不直接确定这个 view 是 JSP,DispatcherServlet 只知道凭借这一 view 名找到的 view 可以处理手头上的 model 数据,于是它把 view 名给 view resolver 来帮它找到对应的 view(如上图标号5) 。

DispatcherServlet 终于得到了渲染 model 数据的 view,Request 的旅程也即将走到尽头, 它的最后一站是在 view 的实现部分(上图标号6),而view 的实现典型的有 JSP ;这一步使用 model 数据进行输出结果的渲染,之后通过响应对象把渲染结果送回到客户端。


(2) Spring MVC 入门工程实现的准备工作;

需要的工具

Eclipse IDE for Java EE Developers ; 
Apache Tomcat ; 
Spring framework jar包 ; 
以及其他一些依赖包:commons-logging-1.1.3.jar,jstl-1.2.jar,log4j-1.2.17.jar

建立 maven 工程

完了之后,导入相应的mvcjar包,这和之前的xml配置的jar包一样

<?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.zy</groupId>
  <artifactId>springmvc-noxml-demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>springmvc-noxml-demo Maven Webapp</name>
  <!-- FIXME change it to the project‘s website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <!--spring 版本号-->
    <spring.version>5.1.6.RELEASE</spring.version>
    <!--mybatis 版本号-->
    <mybatis.version>3.2.4</mybatis.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!--上边介绍的的版本号等等 将下面的代码放到dependencies标签中即可-->

    <!--spring 核心包-->
    <!-- spring start -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>



    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!-- spring end -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--日志-->
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.8.0-alpha0</version>
      <scope>test</scope>
    </dependency>


    <!--j2ee相关包 servlet、jsp、jstl-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <!--mybatis核心包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>${mybatis.version}</version>
    </dependency>



    <!--mybatis/spring 包-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.2.2</version>
    </dependency>

    <!--MySQL 驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.39</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>springmvc-noxml-demo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

这一切做完应该就可以开始写代码了…

(3) Spring MVC的Java配置;

经过开头那幅图的一通介绍,看起来 Spring MVC 需要的配置会很复杂…相对来说,以前确实很复杂(用 xml 配置),然而现在只需简单几步就能搞定。

配置 DispatcherServlet

DispatcherServlet 是 Spring MVC 的核心组件,它是一个 request 首先到达的地方,负责 request 在其他各个组件间的传递加工,在过去,像DispatcherServlet 这样的 servlets 是使用 web.xml 文件配置的,当然现在也可以这样做….但是由于时代的进步,基于 Servlet 3 和 Spring 3.1 的一些新特性,我们可以用更简单的方式来配置,即使用 Java 代码。

public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 指定spring容器的配置类
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MySpringConfig.class};
    }

    /**
     * springmvc容器的配置类
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyServletConfig.class};
    }

    /**
     * 配置拦截请求路径
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

或许建工程的时候你就很在意 spittr 是干嘛的,但还是那句话,下文有介绍…先看代码…代码里是一个叫做 SpittrWebAppInitializer 的类,在spittr.config包里,顾名思义,它是起一个初始化配置的作用,并且相当于一个程序的入口类,在整个程序启动时加载。

首先需要介绍的就是这个名字很拉风的类,即被 SpittrWebAppInitializer 继承的 AbstractAnnotationConfigDispatcherServletInitializer ,乍看一头雾水…..哥们你干嘛的….简单来说,它自动被加载,负责应用程序中 servlet 上下文中的 DispatcherServlet 和 Spring 其他上下文的配置。

关于 AbstractAnnotationConfigDispatcherServletInitializer

各位好,如果以上介绍不过瘾,这里是复杂来说…

在 Servlet 3.0 环境下,Servlet 容器会在 classpath 下搜索实现了 javax.servlet 
.ServletContainerInitializer 接口的任何类,找到之后用它来初始化 Servlet 容器。

Spring 实现了以上接口,实现类叫做 SpringServletContainerInitializer, 它会依次搜寻实现了 WebApplicationInitializer的任何类,并委派这个类实现配置。之后,Spring 3.2 开始引入一个简易的 WebApplicationInitializer 实现类,这就是 AbstractAnnotationConfigDispatcherServletInitializer。

所以 SpittrWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer之后,也就是间接实现了 WebApplicationInitializer,在 Servlet 3.0 容器中,它会被自动搜索到,被用来配置 servlet 上下文。

当然可以直接实现 WebApplicationInitializer,但作为入门来说,AbstractAnnotationConfigDispatcherServletInitializer 是一个不错的选择。

回到代码,SpittrWebAppInitializer 需要重写3个方法,第一个,getServletMappings(),为 DispatcherServlet 提供一个或更多的Servlet 映射;这里是被映射到 /,指示它为默认的 servlet,用来操作所有来到程序的 Request。为了理解另外两个方法的作用,需要先明白 DispatcherServlet 和一个叫做 ContextLoaderListener 的 servlet 监听器之间的关系。

A TALE OF TWO APPLICATION CONTEXTS1

DispatcherServlet 开始启动时,会产生一个 Spring 应用程序上下文,把它和配置文件中声明的 bean 或者类一起加载进来。通过getServletConfigClasses() 方法,设置 DispatcherServlet 通过 WebConfig 配置类来完成 Spring 上下文和 bean 的加载。

但是在 Spring web 程序中,往往还有另外一个应用程序上下文,它是由 ContextLoaderListener 产生的。通过调用 getRootConfigClasses()方法返回的类就是用来配置 ContextLoaderListener 产生的上下文。

其中,DispatcherServlet 是用来加载涉及 web 功能的 beans,例如 controllers, view resolvers, 和 handler mappings;而 ContextLoaderListener 则是用来载入程序中其余的 beans,例如一些中间层和数据层组件,完成的是程序后端功能。

于是,我们知道,AbstractAnnotationConfigDispatcherServletInitializer 产生了一个 DispatcherServlet 和一个 ContextLoaderListener,以上代码清单1通过两个方法分别得到两个上下文的配置类(使用@Configuration注解)。和 web.xml 的配置方法不一样的是,这种方法只适用于使用了 Servlet 3.0 的服务器,例如 Apache Tomcat 7 或 7+。不过 Servlet 3.0 规范在 2009 年 12 月形成最终版本, 几乎不用担心遇到不支持 Servlet 3.0 的 servlet 容器 。

但是,如果你的服务器实在是不能支持 Servlet 3.0,那么 Java 配置的方法不适用于你了,你适合使用 web.xml 的配置方法,在原作者书中,第七章有介绍,不过本文不讨论这种方法,因为这种方法已经很成熟…网上一搜一大堆,请自行搜索….

启用 Spring MVC 功能

基于 Java 的方式超级简单,你的配置类只需要带有一个@EnableWebMvc注解,就像这样子

/**
 * 有关springmvc的配置都在这里面,可以重写其很多方法,比如拦截器,编码等
 */
@Configuration
@EnableWebMvc
@ComponentScan("com.zy.controller")
public class MyServletConfig implements WebMvcConfigurer {
    //配置视图解析器
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }
}

是不是很简洁?这样就可以开始使用 Spring MVC 框架了,但是各种组件还没配置:

  • View Resolver:虽然 Spring 会默认使用 BeanNameViewResolver,它会搜索 ID 和 view 名一致并且实现了 view 接口的 bean。

  • Controller:要让 Spring 能够加载各种 Controllers,必须对它们进行显式声明,然后启用 Component-scanning 功能自动搜索。

  • 完善 Request 的映射: 按照之前的配置,对于一些静态资源(图片、样式表等)也是映射到默认的 Servlet,而这显然是不合理的。

首先可以发现的是,代码中添加了@ComponentScan注解,因此spitter.web 包将会被扫描以搜索 Controller。

然后,代码添加了一个 ViewResolver bean, 具体来说是一个 InternalResourceViewResolver,再具体的话这里不再讨论,请查阅其他资料….或参看原书第六章,就这里而言,只需要知道它是用来查找 view(JSP)的,并且会对 view 的名字使用前缀和后缀进行包裹就行了 ( 举个栗子,在上述代码中,名字为 home 的 view 将被转换为 /WEB-INF/views/home.jsp )。

最后, WebConfig 类继承 WebMvcConfigurerAdapter 并重写了它的 configureDefaultServletHandling() 方法,通过调用所给的DefaultServletHandlerConfigurer 对象的 enable() 方法,告诉DispatcherServlet 转发对静态资源的 Request 到 Servlet 容器的默认Servlet, 而不是自己处理。

搞定了 WebConfig ,接下来搞定MySpringConfig,由于这里集中讨论 Spring 的 web 开发,所以简单贴出MySpringConfig的代码,这相当于配置application.xml,用来配置数据库等等,不做额外解释了:

@Configuration
@ComponentScan(value = "com.zy",excludeFilters = {@Filter(type = FilterType.ANNOTATION,classes = {Controller.class})})
public class MySpringConfig {
}

Spittr 的介绍

是的,终于到了这里。

之前所做的已经足够了,现在可以开始去构思一个 web 程序的实现了,按照原作者的说法,原作者打算做一个简易的 Twitter,于是,对 Twitter 和 Spring 各取一半就有了 Spitter,为了逼格更高一点,参考热门的 Flickr,扔掉一个 e 就有了 Spittr。Spittr 的用户叫做 spitters,他们发布的状态叫做spittles,是不是很有意思?

好了,就到这里,接下来写完一个简单的 Controller 就收工…..

(4) 一个简单的Controller;

在 Spring MVC中,Controller 就是类,只是类中的方法们带有@RequestMapping注解,以表明它们各自处理什么样的 Request。

让我们看一个简单的Controller示例,它用来处理目标路径是/的 Request:

@Controller
public class HelloController {
    //请求是/hello,返回的试图名是index,dispatcherServlet会根据springmvc的配置去找WEB-INF/views/index.jsp页面
    @GetMapping("/hello")
    public String hello(){
        return "index";
    }

}

第一眼你注意到的应该就是这个@Controller注解,显然它是用来声明一个 Controller,然而它和 Spring MVC 却没有什么关系,它的功能和 @Component 一致,都是便于component-scanning 能够查找到它所注解的类。唯一不用 @Component 的原因是,它的表现力没有这么强,你不会一眼就知道这个它所标注的类是在起到一个 Controller 的作用。

HomeController 唯一的方法是 hello(), 使用了@RequestMapping注解,其中 value 属性指定了方法所操作的 Request 路径,即“/”;method 属性则说明了它所接受的HTTP访问方式。

每当一个 HTTP GET Request 访问 / 路径的时候,home() 方法就会被调用:返回一个“hello”的String字符串。接着 Spring MVC 将“hello”作为view 名,之前说过,DispatcherServlet 会找 view resolver ,把逻辑上的 view 名映射到一个实际的 view。

按照已配置的 InternalResourceViewResolver,“home”将被映射到 /WEB-INF/views/hello.jsp,所以接下来的工作就是 hello.jsp 的编写:

<html>
<body>
<h2>Hello World!No web.xml</h2>
</body>
</html>

最后测试:在浏览器中输入,http://localhost:8080/hello

springmvc基于java配置的实现

总结:java配置的更简洁,取代了web.xml的繁琐,但是注意这是必要要servlet3.0之后的才能使用。

该案例的github地址:https://github.com/zhouyanger/demo/tree/master/springmvc-noxml-demo

相关推荐