从Servlet到Spring MVC再到Spring Boot的进化历程

从Servlet到Spring MVC再到Spring Boot的进化历程

J2EE、Java EE和Servlet

从Servlet开始说起

为了满足开发企业级应用程序的需求,Sun公司在早期J2SE的基础上,针对企业级应用的各种需求,提出了J2EE规范。自2005年J2EE 5.0版本推出后,Sun公司正式将J2EE的官方名称改为“Java EE(Java Enterprise edition)”。企业级应用具有大规模、多层、可伸缩、可靠以及网络安全等特点。目前Java EE规范中应用比较广泛的是其中Java Web相关的规范。

Servlet是Java EE规范中的定义的一个概念,它是处理用户请求的核心。

Servlet是一种独立于操作系统平台和网络传输协议的服务端的Java应用程序,他用来扩展服务器的功能,可以生成动态的Web页面。Servlet没有main()方法,它只能运行在Web服务器的Web容器中。Web容器负责管理Servlet,它装入并初始化Servlet,管理Servlet的多个实例;同时充当请求调度器,将客户端的请求传递到Servlet,并将Servlet的响应返回给客户端。

一个简单的Servlet示例

Servlet文件
public class MyFirstServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public static final String HTML_START="<html><body>";
    public static final String HTML_END="</body></html>";

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        PrintWriter out = response.getWriter();
        Date date = new Date();
        out.println(HTML_START + "<h2>Hi There!</h2><br/><h3>Date="+date +"</h3>"+HTML_END);
    }

}

Servlet要对外提供服务,就需要将它配置在web.xml上,web.xml也是Java EE规范的一部分,示例配置如下。

web.xml文件
<?xml version="1.0"?>
<web-app     xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">

    <welcome-file-list>
        <welcome-file>/my-first-servlet</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>MyFirstServlet</servlet-name>
        <servlet-class>com.ncepu.yun.MyFirstServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyFirstServlet</servlet-name>
        <url-pattern>/my-first-servlet</url-pattern>
    </servlet-mapping>

</web-app>

web.xml中的“<servlet>”和“<servlet-mapping>”配置块指定了Servlet类及其响应的路径,配置完后。用户在浏览器中访问/my-first-servlet时,用户请求就会被转发到MyFirstServlet中进行处理。

要想运行上面的代码,我们需要按照一定的目录结构放置我们的文件,将文件编译之后打成war包,再把war包放入到Servlet容器如tomcat中。

maven推荐的项目目录结构

目录说明
src/main/resources资源文件目录。例如application.xml、struts.xml
src/main/javaJava源代码目录
src/test/java测试用例代码目录
src/test/resources测试用例相关资源目录
src/main/webappWeb项目根目录
target编译构建输出目录

前述的web.xml文件放在webapp/WEB-INF目录下,java文件放入src/main/java目录下。

从Servlet进化到Spring MVC

上文提到了一个简单的web.xml配置加上一个Sevlet实现就能够对外提供服务了,但是这种做法有一个问题。随着时间的推移,当你需要处理的业务变得越来越复杂,当你展现给用户的界面变得越来越复杂,你的Sevlet中的代码实现就会变得越来越难以维护。如果我们能够让整个系统的有一个非常清晰的层次结构,而不是所有代码都在Servlet中,代码的可读性、可维护性就能够有显著提高,使用Spring MVC就能够达到这个目标。

先谈MVC

MVC(Model-View—Controller)是一种典型的软件设计模式,它将软件的结构分为三层,从而使得设计大型应用程序变得容易。
使用MVC有如下优点:

  1. 软件更加容易维护。系统拆分之后,各个部分实现的功能范围都很清晰,需要改什么功能直接到相应的部分修改即可。
  2. 代码复用率提高。系统拆分之后,功能实现会更加内聚,一个类的一个方法只负责某个固定的功能,所有用到这个功能的位置直接调用这个方法就行。
  3. 协同开发更加容易。系统拆分之后,视图的开发可以和模型、控制器的开发同时进行,提升软件交付速度。

Spring MVC项目示例

Spring MVC是利用Spring容器的和Servlet实现的一个MVC框架。下面会用一个简单的Spring MVC的例子来讲解Spring MVC是如何利用Spring容器和Servlet的来实现MVC框架的。

下面只截取了项中目的web.xml、dispatcher-servlet.xml两个配置文件来进行说明,完整的项目可见 https://github.com/muou0213/cloudyispringmvc

web.xml配置
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

  <display-name>Archetype Created Web Application</display-name>

  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

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

  <!--指定WebApplicationContext的配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
  </context-param>

</web-app>

简要的说明下这个配置文件中各个元素的作用。

<listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

这个linstener配置启动了Spring的容器,只有容器启动了Spring才能管理各个Bean。

 <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>

DispatcherServlet是Spring MVC实现MVC功能的关键Servlet

<init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>

这个配置指向了一个xml配置文件,这个配置文件配置了实现MVC功能所需的一些示例。后面对dispatcher-servlet.xml文件说明时会详细说到这一块。

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
  </context-param>

这个配置是告诉Spring要将哪些示例放入到容器中。这个配置跟上面的配置的功能是相似的,大多数应用场景下,这两个配置填写同一个xml文件就可以了。

<load-on-startup>1</load-on-startup>

根据Servlet的生命周期,Servlet的实例化是需要等到第一次有用户请求这个Servlet的时候才会进行。加了这个配置,DispatcherServlet就会在Servlet容器启动后就实例化,不需要用户去请求它。

dispatcher-servlet.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:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/mvc
   https://www.springframework.org/schema/mvc/spring-mvc.xsd
   http://www.springframework.org/schema/context
   https://www.springframework.org/schema/context/spring-context.xsd
   http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

    <context:component-scan base-package = "com.ncepu.yun" />
    <mybatis:scan base-package="com.ncepu.yun.mapper"/>

    <mvc:annotation-driven/>

    <context:property-placeholder location="classpath:db.properties" />

    <bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name = "prefix" value = "/WEB-INF/jsp/" />
        <property name = "suffix" value = ".jsp" />
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"  destroy-method="close">
        <property name="driverClassName" value="${db.driverClass}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath*:mappers/*.xml" />
    </bean>
</beans>

dispatcher-servlet.xml各个配置元素的说明。

<context:component-scan base-package = "com.ncepu.yun" />

这个配置告诉Spring容器应该去扫描哪个包来进行Bean管理,你的Controller、Service和Dao都放在这个包中。

<mybatis:scan base-package="com.ncepu.yun.mapper"/>

这个配置告诉MyBatis放SQL语句的接口所在的包的包名。

<mvc:annotation-driven/>

这个配置向Spring容器中注册了RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver等类的实例来处理Controller中的注解,例如@RequestMapping , @ExceptionHandler等注解,这些示例是完成MVC功能的重要依仗。

实际上,这个配置也可以省略掉,Dispatcher使用了一些默认的实现类来处理这些注解,这些默认实现配置在Dispatcher.properties文件中,而使用<mvc:annotation-driven/>可以修改这些默认实现为其他自定义的实现。想要进一步了解
可以参考 https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-servlet-config

<context:property-placeholder location="classpath:db.properties" />

这个配置项给出了xml可以读取配置的配置文件的位置。通过这个配置项目可以将一些属性值与xml文件分开存放。

<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name = "prefix" value = "/WEB-INF/jsp/" />
        <property name = "suffix" value = ".jsp" />
    </bean>

这个bena实际上是一个jsp的视图解析器,也就是解析MVC中V的相关部分。除了InternalResourceViewResolver外,Spring MVC还提供了多种视图解析器的实现。

总结一下,Spring MVC的就是在Servlet的基础上,通过各种注解、注解的解析类以及支持jsp视图等各种类型视图的类来完成MVC功能的一个MVC框架。

完整的示例项目可以在https://github.com/muou0213/cloudyispringmvc下载

从Spring MVC到Spring Boot

通过上一节对Spring MVC的配置文件的分析,我们能够发现使用Spring MVC进行业务开发的准备工作基本上就是写配置文件。而这些配置文件中的很大一部分都是固定不变的,理论上讲我们可以让框架去代替我们完成这些不变的部分,我们只需要给出变化部分的值就可以了。

使用Spring Boot就能够达到这个效果。

使用Spring Boot之后,我们就可以省去web.xml和dispacher-servlet.xml的配置,仅仅给出像数据库url这种每个项目都不一样的参数的值就可以运行整个项目了。下面是一个简单的在Spring Boot基础上使用Spring MVC的一个示例

Spring Boot项目示例

pom文件
<?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.ncepu.yun</groupId>
    <artifactId>cloudyispringboot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>cloudyispringboot Maven Webapp</name>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.7.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>9.0.35</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
        </dependency>
    </dependencies>
</project>

可以看到pom文件中添加了spring-boot-starter-parent,mybatis-spring-boot-starter,spring-boot-starter-web这三个spring boot相关的依赖,使用Spring Boot开发web项目这三个依赖是必须添加的。

特别需要说明的一点是,如果你需要在Spring Boot内嵌的tomcat中使用JSP,你就需要在pom中引入tomcat-embed-jasper依赖,这个依赖用于编译JSP文件。

application.properites文件
###mybatis###
mybatis.mapper-locations=mappers/*.xml

###datasource###
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring_demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.password=passwd
spring.datasource.username=root

###mvc###
spring.mvc.view.prefix=WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

前面单独使用Spring MVC时候web.xml、dispatcher-servlet.xml两个文件中的繁杂的配置都被上面这个简单的properties文件替代了。

Controller的文件
@Controller
public class HelloController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public ModelAndView sayHello() {
        ModelAndView modelAndView = new ModelAndView("hello");
        String username = userMapper.selectAllUser().get(0).getUsername();
        modelAndView.addObject("name", username);
        return modelAndView;
    }
}

Controller层、Service层、Dao层代码跟之前是一样的没有变化。就是经过这么简单的配置,整个项目就可以运行了。

完整的示例项目可以在https://github.com/muou0213/cloudyispringboot下载。

看到这里我们可能会有两个疑问:

  1. Spring Boot是如何消去web.xml的
  2. 没有dispacher-servlet.xml文件Spring Boot是如何把使用Mybati所必须的DataSource等Bean注入到Spring容器中的

下面我们就来解答这两个疑问。

Spring Boot是如何消去web.xml的

Spring Boot是通过使用Servlet3.0引入的新接口ServletContainerInitializer来消去web.xml配置文件的。实现了ServletContainerInitializer接口的类中的onStartup()方法会在Servlet容器启动的时候被调用。

在使用Spring Boot内嵌的tomcat时,Spring Boot中的类TomcatStarter的onStartup()方法会调用DispatcherServletRegistrationBean类的onStartup()方法。从类名我们就可以看出,DispatcherServletRegistrationBean这个类会把DispatcherServlet添加到Servlet容器中。

@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		return servletContext.addServlet(name, this.servlet);
	}

把DispatcherServlet添加到Servlet容器中的关键代码是servletContext.addServlet(name, this.servlet),这个addServlet方法也是Servlet3.0引入的,专门用来把servlet添加到Servlet容器中的。

通过使用Servlet3.0引入的ServletContainerInitializer接口和addServlet()方法,Spring Boot就完成了之前通过web.xml完成的DispatcherServlet配置过程。

没有dispacher-servlet.xml文件Spring Boot是如何将DataSource等Bean注入到Spring容器中的

Spring Boot是通过配合使用@EnableAutoConfiguration注解和SpringFactoriesLoader Bean注入机制来完成DataSource等Bean的注入的。

我们通常会在SpringBoot的启动类上使用注解@SpringBootApplication,如下面代码

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

而使用@SpringBootApplication注解等同于使用了@SpringBootConfiguration、@EnableAutoConfiguration等注解,这一点打开@SpringBootApplication注解的源码就能看到。
@EnableAutoConfiguration注解就是Spring Boot能够把DataSource等相关的Bean注入到Spring容器中的关键。

我们打开spring-boot-autoconfigure的源码,能找到一个spring.factories的properties文件,这个文件中有如下内容:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\

以EnableAutoConfiguration为key,以逗号分隔的Configuration类为value的一个映射关系。

在启动类上配置了@EnableAutoConfiguration后,Spring Boot的代码会利用SpringFactoriesLoader加载配置在spring.factories文件中EnableAutoConfiguration key后面所有的Configuration类,其中有个Configuration类会将DataSource注入到Spring容器中。

下面截取了DataSourceConfiguration的部分代码,从这部分代码就可以看出,这个Configuration提供了将tomcat提供的DataSource实现注入到Spring容器中的功能。

abstract class DataSourceConfiguration {

	@SuppressWarnings("unchecked")
	protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
		return (T) properties.initializeDataSourceBuilder().type(type).build();
	}

	/**
	 * Tomcat Pool DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
			matchIfMissing = true)
	static class Tomcat {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.tomcat")
		org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
			org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
					org.apache.tomcat.jdbc.pool.DataSource.class);
			DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
			String validationQuery = databaseDriver.getValidationQuery();
			if (validationQuery != null) {
				dataSource.setTestOnBorrow(true);
				dataSource.setValidationQuery(validationQuery);
			}
			return dataSource;
		}

	}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值