为了满足开发企业级应用程序的需求,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的响应返回给客户端。
public class MyFirstServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public static final String HTML_START="";
public static final String HTML_END="";
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
PrintWriter out = response.getWriter();
Date date = new Date();
out.println(HTML_START + "Hi There!
Date="+date +"
"+HTML_END);
}
}
Servlet要对外提供服务,就需要将它配置在web.xml上,web.xml也是Java EE规范的一部分,示例配置如下。
/my-first-servlet
MyFirstServlet
com.ncepu.yun.MyFirstServlet
MyFirstServlet
/my-first-servlet
web.xml中的“
要想运行上面的代码,我们需要按照一定的目录结构放置我们的文件,将文件编译之后打成war包,再把war包放入到Servlet容器如tomcat中。
maven推荐的项目目录结构
目录 | 说明 |
---|---|
src/main/resources | 资源文件目录。例如application.xml、struts.xml |
src/main/java | Java源代码目录 |
src/test/java | 测试用例代码目录 |
src/test/resources | 测试用例相关资源目录 |
src/main/webapp | Web项目根目录 |
target | 编译构建输出目录 |
前述的web.xml文件放在webapp/WEB-INF目录下,java文件放入src/main/java目录下。
上文提到了一个简单的web.xml配置加上一个Sevlet实现就能够对外提供服务了,但是这种做法有一个问题。随着时间的推移,当你需要处理的业务变得越来越复杂,当你展现给用户的界面变得越来越复杂,你的Sevlet中的代码实现就会变得越来越难以维护。如果我们能够让整个系统的有一个非常清晰的层次结构,而不是所有代码都在Servlet中,代码的可读性、可维护性就能够有显著提高,使用Spring MVC就能够达到这个目标。
MVC(Model-View—Controller)是一种典型的软件设计模式,它将软件的结构分为三层,从而使得设计大型应用程序变得容易。
使用MVC有如下优点:
Spring MVC是利用Spring容器的和Servlet实现的一个MVC框架。下面会用一个简单的Spring MVC的例子来讲解Spring MVC是如何利用Spring容器和Servlet的来实现MVC框架的。
下面只截取了项中目的web.xml、dispatcher-servlet.xml两个配置文件来进行说明,完整的项目可见 https://github.com/muou0213/cloudyispringmvc
Archetype Created Web Application
org.springframework.web.context.ContextLoaderListener
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/dispatcher-servlet.xml
1
DispatcherServlet
/
contextConfigLocation
/WEB-INF/dispatcher-servlet.xml
简要的说明下这个配置文件中各个元素的作用。
org.springframework.web.context.ContextLoaderListener
这个linstener配置启动了Spring的容器,只有容器启动了Spring才能管理各个Bean。
org.springframework.web.servlet.DispatcherServlet
DispatcherServlet是Spring MVC实现MVC功能的关键Servlet
contextConfigLocation
/WEB-INF/dispatcher-servlet.xml
这个配置指向了一个xml配置文件,这个配置文件配置了实现MVC功能所需的一些示例。后面对dispatcher-servlet.xml文件说明时会详细说到这一块。
contextConfigLocation
/WEB-INF/dispatcher-servlet.xml
这个配置是告诉Spring要将哪些示例放入到容器中。这个配置跟上面的配置的功能是相似的,大多数应用场景下,这两个配置填写同一个xml文件就可以了。
1
根据Servlet的生命周期,Servlet的实例化是需要等到第一次有用户请求这个Servlet的时候才会进行。加了这个配置,DispatcherServlet就会在Servlet容器启动后就实例化,不需要用户去请求它。
dispatcher-servlet.xml各个配置元素的说明。
这个配置告诉Spring容器应该去扫描哪个包来进行Bean管理,你的Controller、Service和Dao都放在这个包中。
这个配置告诉MyBatis放SQL语句的接口所在的包的包名。
这个配置向Spring容器中注册了RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver等类的实例来处理Controller中的注解,例如@RequestMapping , @ExceptionHandler等注解,这些示例是完成MVC功能的重要依仗。
实际上,这个配置也可以省略掉,Dispatcher使用了一些默认的实现类来处理这些注解,这些默认实现配置在Dispatcher.properties文件中,而使用
可以参考 https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-servlet-config
这个配置项给出了xml可以读取配置的配置文件的位置。通过这个配置项目可以将一些属性值与xml文件分开存放。
这个bena实际上是一个jsp的视图解析器,也就是解析MVC中V的相关部分。除了InternalResourceViewResolver外,Spring MVC还提供了多种视图解析器的实现。
总结一下,Spring MVC的就是在Servlet的基础上,通过各种注解、注解的解析类以及支持jsp视图等各种类型视图的类来完成MVC功能的一个MVC框架。
完整的示例项目可以在https://github.com/muou0213/cloudyispringmvc下载
通过上一节对Spring MVC的配置文件的分析,我们能够发现使用Spring MVC进行业务开发的准备工作基本上就是写配置文件。而这些配置文件中的很大一部分都是固定不变的,理论上讲我们可以让框架去代替我们完成这些不变的部分,我们只需要给出变化部分的值就可以了。
使用Spring Boot就能够达到这个效果。
使用Spring Boot之后,我们就可以省去web.xml和dispacher-servlet.xml的配置,仅仅给出像数据库url这种每个项目都不一样的参数的值就可以运行整个项目了。下面是一个简单的在Spring Boot基础上使用Spring MVC的一个示例
4.0.0
com.ncepu.yun
cloudyispringboot
1.0-SNAPSHOT
war
cloudyispringboot Maven Webapp
UTF-8
1.8
1.8
org.springframework.boot
spring-boot-starter-parent
2.2.2.RELEASE
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.2
org.mybatis
mybatis-spring
2.0.4
org.apache.commons
commons-dbcp2
2.7.0
mysql
mysql-connector-java
5.1.49
org.apache.tomcat.embed
tomcat-embed-jasper
9.0.35
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
junit
junit
4.11
test
org.projectlombok
lombok
1.18.6
可以看到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文件。
###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
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下载。
看到这里我们可能会有两个疑问:
下面我们就来解答这两个疑问。
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配置过程。
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 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;
}
}