MVC(Model View Controller),是一种设计模式。所有设计模式归的目的根结底都是解耦,MVC模式也不例外, 那么MVC模式要解的是什么耦合呢? MVC的设计意图是要将前展示与业务逻辑分离出来。在Spring MVC,将Web应用程序抽象成职责不同3种角色:Model, View和Controller。
Controller层不会负责具体的页面渲染(不包含页面如何展示的代码), 而是接收用户的URL请求(POST/GET),执行业务逻辑(如根据前端的请求的参数做为条件,通过JDBC从数据库中查出需要的数据),并通过Model(做为数据载体)将需要的数据, 返回给View渲染, 并最终生成html页面展现给用户。
Controller在处理完业务逻辑之后会返回一个View的名称(字符串), Spring MVC的ViewResolver根据View的名称找到对应的页面(文件),并使用Controller返回的Model对页面进行渲染,在view中显示渲染(加工), 返回最终的html页面, View中不包含任何业务逻辑,要做的只是将Model中的数据展示给用户。
Spring MVC内置了各种各样的视图解析器(ViewResolver接口)实现,每个ViewResolver都对应着一种view页面的实现技术,如InternalResourceViewResolver是专门针对JSP技术设计的解析器,可以用来解析JSP, TilesViewResolver用于Apache Tiles视图,…
我们只要使用Spring MVC框架进行Web开发,那么我们的Web应用就是符合MVC模式的, 是前端展示和业务逻辑分离的,这就是使用Spring MVC框架的意义,J2EE MVC框架并非Spring独有,常见的还有Struts框架。
Spring MVC框架内部使用了一系列的机制(类)来实现MVC,其中一个最核心的就是前端控制器(DispatcherServlet)。
Spring MVC框架除了使用MVC模式,还使用前端控制器模式(Front controller),DispatcherServlet是前端控制器设计模式的实现,他提供了一个Spring Web MVC的集中访问点,将匹配的请求, 分发给目标Controller来处理。
DispatcherServlet实际上是一个J2EE Servlet(它继承自HttpServlet基类), 我可以像普通的Servlet一样在J2EE的部署文件web.xml中进行配置,并通过指定"servlet-mapping"将想要的url请求映射给DispatcherServlet处理。
配置Spring MVC的第一步就是配置DispatcherServlet。在Spring MVC中,可以配置多个DispatcherServlet, 每个DispatcherServlet中包含一个Spring容器(WebApplicationContext),他们相互独立。DispatcherServlet做为子容器继承应用的根容器(Root WebApplicationContext) 中的beans的定义,从根容器继承的beans可以被DispatcherServlet的子容器定义覆盖。
In the Web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans defined can be overridden in the servlet-specific scope, and you can define new scope-specific beans local to a given servlet instance.
通常在DispatcherServlet的WebApplicationContext容器中装载Web相关的Beans,如Controllers,view resolvers,…,而在根容器中装载公共的beans,如middle-tier service,datasources,…
Spring MVC中根容器的配置是可选的,如果Web应用只配置有一个DispatcherServlet(没有需要共享的beans),也可以将把所有的beans定义都放在DispatcherServlet的容器中装载。但为了结构清晰, 也便于之后的扩展, 建议使用分层的容器结构。要注意的是,Web相关的Beans(如Controller, ViewResolver)只能在DispatcherServlet的容器中装载(配置), 不能配在根容器中。
根容器通过ContextLoaderListener装载,DispatcherServlet是一个Servlet,可以像普通Servlet一样,配置到web.xml中。而ContextLoaderListener则是一个ServletContextListener, 同样可以配置在web.xml部署文件中,在Web容器初始化我们的Web应用程序时,进行根容器的初始化。
继续使用之前的例子,将之前写的在线商城的案例(org.littlestar.learning.package6)目录拷贝到新建的webapp项目源代码目录下,做为公共服务。
在web.xml文件中配置ContextLoaderListener和DispatcherServlet,让Web容器初始化相关容器和前端控制器,将用户请求映射到前端控制器。我们可以通过Spring MVC定义的两个servlet初始化参数来指定:
Web应用部署描述文件 - web.xml:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>Spring MVC Learningdisplay-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:org/littlestar/learning/package6/package6-bean-config.xmlparam-value>
context-param>
<servlet>
<servlet-name>package7-dispatcher-servletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:org/littlestar/learning/package7/package7-dispatcher-servlet-config.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>package7-dispatcher-servletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
前端控制器配置文件 - package7-dispatcher-servlet-config.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-3.0.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-3.0.xsd">
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/" />
<context:component-scan base-package="org.littlestar.learning.package7.controller" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
beans>
编写Controller - BankServiceController :
package org.littlestar.learning.package7.controller;
import org.littlestar.learning.package6.BankingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class BankServiceController {
@Autowired
@Qualifier("bankingServiceImpl2")
BankingService bankService;
@RequestMapping("/")
public String requestIndex(
@RequestParam(value="username", defaultValue="Guest", required=false) String userName,
Model model) {
String bankName = bankService.getBankName();
model.addAttribute("bankName", bankName);
model.addAttribute("userName", userName);
// System.out.println("request index -> userName="+userName+", bankName=" + bankName);
return "index";
}
}
Servlet模板(index.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./resources/css/bootstrap.min.css" />
head>
<body>
<h2>${bankName}: Welcome, <small><c:out value="${userName}">c:out>small>h2>
<img src="./resources/images/hello.jpg" class="img-rounded">
body>
html>
在Eclipse选择项目, 执行Web应用,既可以看到结果:
从Servlet3.0开始, 引入了一个新接口javax.servlet.ServletContainerInitializer来支持基于代码配置Servlet容器,ServletContainerInitializer接口类似于JDBC的java.sql.Driver,使用了JDK内置的一种服务提供发现机制SPI(Service Provider Interface)。在Spring-MVC的spring-web-{version}.jar文件的META-INF/services/目录下,包含一个javax.servlet.ServletContainerInitializer文件,该文件将SpringMVC提供的ServletContainerInitializer接口的实现(服务) – org.springframework.web.SpringServletContainerInitializer暴露给Servlet容器(执行)。
在SpringServletContainerInitializer中,通过"@HandlesTypes(WebApplicationInitializer.class)"注解,将类路径中实现了WebApplicationInitializer接口的类名, 通过@Nullable Set
/*
javax.servlet.annotation.HandlesTypes:This annotation is used to declare an array of application classes which are
passed to a javax.servlet.ServletContainerInitializer.
*/
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
...
}
}
综上所述,在Spring MVC中,可以通过编写WebApplicationInitializer的实现类,来替代web.xml的功能。回到我们的项目,因为需要Servlet3.0以上的版本,先看看当前项目的依赖是否满足需求。
我们可以看到, 虽然包含了servlet-api, 是由jsp-api依赖传递过来的,但是版本2.4,所以我们需要先在jsp-api中排除servlet-api,再引入满足版本要求的servlet-api:
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
<exclusions>
<exclusion>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
exclusion>
exclusions>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
修改后,满足了Servlet版本要求。
继续我们的案例,将web.xml删除, 或者注释掉其内容,通过编写代码来进行配置。创建一个Web应用初始化类OnlineBankWebApplicationInitializer :
package org.littlestar.learning.package7;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class OnlineBankWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 初始化/创建Root WebApplicationContext
XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
rootContext.setConfigLocation("classpath:org/littlestar/learning/package6/package6-bean-config.xml");
servletContext.addListener(new ContextLoaderListener(rootContext));
// 初始化/创建DispatcherServlet WebApplicationContext, 通过Spring配置类配置;
// 通过配置类OnlineBankDispatcherServletConfig进行配置
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(OnlineBankDispatcherServletConfig.class);
dispatcherContext.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("package7-dispatcher-servlet",
new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
/* 也可以通过xml配置
XmlWebApplicationContext dispatcherContext = new XmlWebApplicationContext();
dispatcherContext.setConfigLocation("classpath:org/littlestar/learning/package7/package7-dispatcher-servlet-config.xml");
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("package7-dispatcher-servlet", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
*/
}
}
在这里前端控制器改由配置类OnlineBankDispatcherServletConfig来配置,而不是package7-dispatcher-servlet-config.xml:
package org.littlestar.learning.package7;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan("org.littlestar.learning.package7")
public class OnlineBankDispatcherServletConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
要使用thymeleaf替换JSP,首先需要添加thymeleaf依赖。
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
<version>3.0.14.RELEASEversion>
dependency>
在配置类中使用ThymeleafViewResolver替换掉InternalResourceViewResolver:
public class OnlineBankDispatcherServletConfig implements WebMvcConfigurer {
...
@Autowired
ApplicationContext applicationContext;
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/thymeleaf/");
templateResolver.setSuffix(".html");
templateResolver.setCacheable(false);
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
public ViewResolver thymeleafResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(0);
return viewResolver;
}
}
要同时使用jsp和thymeleaf模板,我的配置方法如下:
public class OnlineBankDispatcherServletConfig implements WebMvcConfigurer {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
//viewResolver.setSuffix(".jsp"); //去掉.jsp后缀
viewResolver.setViewClass(JstlView.class);
viewResolver.setViewNames(new String[] {"*.jsp"}); //添加viewnames
viewResolver.setOrder(2);
return viewResolver;
}
@Autowired
ApplicationContext applicationContext;
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/thymeleaf/");
//templateResolver.setSuffix(".html"); //去掉.html后缀
templateResolver.setCacheable(false);
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
...
@Bean
public ViewResolver thymeleafResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setViewNames(new String[] {"*.html"}); //添加viewNames
viewResolver.setOrder(1);
return viewResolver;
}
}
@Controller
public class BankServiceController {
@Autowired
@Qualifier("bankingServiceImpl2")
BankingService bankService;
@RequestMapping("/")
public String requestIndex(
@RequestParam(value="username", defaultValue="Guest", required=false) String userName,
Model model) {
String bankName = bankService.getBankName();
model.addAttribute("bankName", bankName);
model.addAttribute("userName", userName);
System.out.println("request index -> userName="+userName+", bankName=" + bankName);
return "index.jsp"; //添加.jsp后缀
}
@RequestMapping("/hello")
public String requestHello(
@RequestParam(value="username", defaultValue="Guest", required=false) String userName,
Model model) {
String bankName = bankService.getBankName();
model.addAttribute("bankName", bankName);
model.addAttribute("userName", userName);
return "hello_thymeleaf.html"; //添加.html后缀
}
}
在项目的根目录执行mvn package即可生成war包。
C:\Users\Think\eclipse-workspace\learning2>mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< org.littlestar:learning2 >----------------------
[INFO] Building learning2 Maven Webapp 1
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ learning2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
...
[INFO] Webapp assembled in [180 msecs]
[INFO] Building war: C:\Users\Think\eclipse-workspace\learning2\target\learning2.war --> 生成war包
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.686 s
[INFO] Finished at: 2022-01-27T17:22:19+08:00
[INFO] ------------------------------------------------------------------------
C:\Users\Think\eclipse-workspace\learning2>