慢慢来比较快,虚心学技术
前言:前面我们学习了关于Spring核心的IOC和AOP知识,除此之外,以此为基础,Spring的MVC框架还经常被用于Web开发(SpringMVC)
一、什么是SpringMVC框架?
在了解SpringMVC之前,我们先回顾一下Spring基础架构:
Spring MVC 是Spring的一部分,基于模型 - 视图 - 控制器( Model-View-Controller , MVC )模式实现,它能够帮你构建像 Spring 框架那样灵活和松耦合的 Web 应用程序。在实际开发中,接收浏览器的请求响应,对数据进行处理,然后返回页面进行显示。
二、SpringMVC组成以及运行原理
Ⅰ、SpringMVC的组成
- DispatcherServlet:前端控制器 (SpringMVC的核心)-----相当于MVC中的C,作为中心调用其他组件,降低其他组件之间的耦合性
- HandlerMapping:处理器映射器 ----------------------------------根据用户请求找到对应路径的处理器(相当于处理器的名单)
- HandlAdapter:处理器适配器 --------------------------------------调用执行处理器方法(适配器模式的应用)
- Handler:处理器 --------------------------------------------------------处理用户请求的类,相当于传统意义上的Servlet
- ViewResolver:视图解析器 ------------------------------------------处理返回结果,将处理器适配器返回的数据模型转换成具体视图,并进行渲染输出(实际上就是将处理器返回的名称补充成具体的路径也就是一个视图,同时从数据模型中提取数据进行填充)
- View:视图 ---------------------------------------------------------------视图是数据最终需要展现给客户的地方,Spring支持多种类型的视图:jstlVies,freemarkerView等,最常用的是JSP和使用模板实现的html等
Ⅱ、SpringMVC请求响应流程
①用户发起请求,携带请求信息到前端控制器进行调度
②前端控制器(DispatcherServlet)调用处理器映射器,根据请求信息从处理器映射器中找到访问路径的目标处理器
③前端控制器(DispatcherServlet)根据得到的目标处理器映射,调用处理器适配器方法(处理器适配器将处理器方法包装成适配器模式)
④处理器适配器(HandlerAdapter)调用处理器(Handler)相应功能方法,并将结果返回给前端控制器
⑤前端控制器(DispatcherServlet)根据得到的数据结果和目标视图名称,调用视图解析器(ViewResolver)返回目标视图完整路径
⑥前端控制器(DispatcherServlet)根据得到的视图路径,对目标视图(view)进行渲染(数据填充等),得到目标视图
⑦前端控制器(DispatcherServlet)将目标视图展现给用户
从上述流程可以看到,SpringMVC的功能流转是围绕前端控制器(DispatcherServlet)实现的,这样的好处是使得各个组件之间的耦合性大大降低,各个组件只做自己应该做的事情。其实这是大部分框架想要实现的目标。
分析DispatcherServlet,从Spring官网查看到的结构图如下:
从结构图可以看到,DispatcherServlet包含了两个Web应用上下文,用于独立控制,其中:
- Servlet WebApplicationContext:管理用于网络请求的处理器适配器,视图解析器,处理器映射器和处理器
- Root WebApplicationContext:管理基本的数据库操作类,业务逻辑类等Bean
通过两个应用上下文管理基本Bean和网络Bean,互不干扰,但是其中管理的Bean之间可以互相使用
三、SpringMVC的简单使用(注解方式)
首先我们应该先引入Spring对WebMvc的支持,maven引入如下:
<!--引入网络Servlet支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--引入SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
①创建DispatcherServlet类,继承并重载AbstractAnnotationConfigDispatcherServletInitializer类的三个方法:
//定义DispatcherServlet类名为WebAppInitializer
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 指定DispatcherServlet的基本Bean应用上下文配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 指定DispatcherServlet的网络类应用上下文配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 将DispatcherServlet映射到“/”,即应用内所有访问都会经过DispatcherServlet的处理
* @return
*/
@Override
protected String[] getServletMappings() {
logger.debug("DispatcherServlet获取匹配的前端控制器。。。。。。");
return new String[]{"/"};
}
}
②创建上述代码的两个配置类:RootConfig.java和WebConfig.java,其中,RootConfig只扫描除了WebConfig扫描范围外的基本类,而WebConfig只扫描基本的网络类,同时配置视图解析器和处理器映射器,并开启mvc配置
//定义WebConfig配置类
@Configuration
@ComponentScan(basePackages = {"com.my.spring.controller"})//WebConfig扫描包的范围
@EnableWebMvc /*<mvc:annotation-driven> 开启mvc配置*/
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 定义一个视图解析器
*
* @return org.springframework.web.servlet.ViewResolver
*
* @author xxx 2019/3/5
* @version 1.0
**/
@Bean
public ViewResolver viewResolver(){
//基本的视图解析器
InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
//视图前缀,指向WEB-INF目录下的view目录,意思是所有的视图名称进入视图解析器的时候都会被加上前缀
resourceViewResolver.setPrefix("/WEB-INF/view/");
//视图后缀,此处指定后缀为jsp,意思是所有的视图名称进入视图解析器的时候都会被加上后缀,前缀+view名+后缀得到完整路径
resourceViewResolver.setSuffix(".jsp");
//可以在JSP页面中通过${}访问beans
resourceViewResolver.setExposeContextBeansAsAttributes(true);
return resourceViewResolver;
}
/**
* 配置一个默认的处理器,实现父类接口,自动处理静态资源的映射
* @param configurer
*/
@Override
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
//定义RootConfig配置类
@Configuration
//指定扫描范围,排除过滤掉使用了@EnableWebMvc注解扫描范围的bean,不进行扫描
@ComponentScan(basePackages ={"com.my.spring"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {EnableWebMvc.class})})
public class RootConfig {
}
③编写基本的Bean类
@Data//lombok的注解,编译添加setter和getter方法
public class BaseBean {
private Integer id;
private String name;
}
④编写逻辑操作类(暂时没有用到数据库,所以只是模拟)
//定义基本Dao接口
public interface BaseRepository {
/**
* 根据id获取BaseBean
* @param id 目标id
* @return
*/
BaseBean findOne(Integer id);
}
//定义基本Dao实现类,@Repository注解使用了@Component,可以被当作组件装配
@Repository
public class BaseRepositoryImpl implements BaseRepository {
@Override
public BaseBean findOne(Integer id) {
if(id!=0){
return null;
}
BaseBean baseBean = new BaseBean();
baseBean.setId(0);
baseBean.setName("测试bean");
return baseBean;
}
}
//定义基本Service接口
public interface BaseService {
/**
* 根据id获取BaseBean
* @param id 目标id
* @return
*/
BaseBean findBean(Integer id);
}
//定义基本操作实现类,使用@Service注解,标明该类是一个service,该注解使用了@Comonnet注解,所以可被作为组件进行装配
@Service
public class BaseServiceImpl implements BaseService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
//注入基本操作dao
@Autowired
private BaseRepository baseRepository;
@Override
public BaseBean findBean(Integer id) {
return this.baseRepository.findOne(id);
}
}
⑤创建处理类HomeController,使用@Controller注解标明当前类为一个处理类,同样使用@Component注解,可被装配
@Controller
public class HomeController {
@Autowired
private BaseService baseService;
/**
*使用@RequestMapping注解,将当前方法作为可访问路径,value值指定了访问路径,而method值指定了访问方式
*/
@RequestMapping(method = RequestMethod.GET,value = "/home")
public String home(){
//返回试图名为home的视图
return "home";
}
}
看到上述代码,我们首先做最简单的测试,通过访问/home路径,访问具体的静态资源:
根据WebConfig中的视图解析器,我们在相应路径下创建:/WEB-INF/view/home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
路径如下:
启动项目,页面访问如下:本项目名为SpringAction05
从访问结果可以看到,访问被转向了home.jsp,而我们在controller方法中只是return了一个home,也就是说,视图解析器为我们补充了完整的路径并将视图返回给浏览器
注:
AbstractAnnotationConfigDispatcherServletInitializer 会同时创建 DispatcherServlet 和 ContextLoaderListener 。
GetServlet-ConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来定义 DispatcherServlet 应用上下文中的 bean 。
getRootConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来配置 ContextLoaderListener 创建的应用上下文中的 bean 。
四、信息交互
Ⅰ、传递模型数据到视图中
有时候我们并不只是需要对访问进行转发,同时可能需要携带一些信息给浏览器端,SpringMVC提供了Model类对返回信息进行封装返回:
下面我们在上述controller方法home()返回之前封装信息返回给视图,并从视图中获取到对应的数据:
@Controller
public class HomeController {
@Autowired
private BaseService baseService;
@RequestMapping(method = RequestMethod.GET,value = "/home")
public String home(Model model){
//往model中放置信息(key,value)
model.addAttribute("Message","I am HomePage!!!");
//返回试图名为home的视图
return "home";
}
}
修改home.jsp获取目标数据,此处我们使用JSTL标签库获取,所以需要先引入JSTL标签库的jar包:
<!-- jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><!--引入JSTL标签库,并以c为标签前缀-->
<%@ page isELIgnored="false" %><!--禁用tomcat自带的EL表达式,否则无法获取对应的数据-->
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello World</h1>
<!--通过EL表达式获取key为Message的属性值-->
<c:out value="${Message}"></c:out>
</body>
</html>
测试结果如下:
Ⅱ、接受请求的输入
Spring MVC 允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:
- 查询参数( Query Parameter )。
- 路径变量( Path Variable )
- 表单参数( Form Parameter )。
①查询参数形式
我们在controller中编写一个getBean方法,要求接收一个参数,参数名为id,通过拿到的id进行查询,将查询到的基本bean封装到数据模型并返回给视图
@RequestMapping(method = RequestMethod.GET,value = "/getBean")
public String getBeanByParam(@RequestParam("id")Integer beanId, Model model){
BaseBean bean = this.baseService.findBean(beanId);
//以bean为key将目标对象封装到数据模型
model.addAttribute("bean",bean);
//返回试图名为home的视图
return "showMessage";
}
然后创建一个showMessage.jsp作为目标视图
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>信息主页</title>
</head>
<body>
<!--判空-->
<c:if test="${bean==null}">
<li>bean不存在</li>
</c:if>
<c:out value="${bean.id}"></c:out><!--提取目标对象的信息-->
<c:out value="${bean.name}"></c:out>
</body>
</html>
浏览器访问路径如下:http://locahost:8080/SpringAction05/getBean?id=0
注:其实getBeanByParam(@RequestParam("id")Integer id, Model model)方法中的@RequestParam("id")可以省略不写,如果不写的话,那么访问参数必须与参数位的名称一致,即:http://locahost:8080/SpringAction05/getBean?beanId=0
②表单参数形式
将查询参数通过form表单提交到前端控制器,此时,Spring允许通过对象接收查询参数,要求form表单提交的字段与对象属性字段对应
首先创建一个form表单,表单提交到/getBean
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h2>Hello World!</h2>
<form action="./getBean" method="post">
提交id:<input name="id" type="text"/><br>
<button type="submit">提交</button>
</form>
</body>
</html>
Contorller接收参数(以对象形式):
@RequestMapping(method = RequestMethod.POST,value = "/getBean")
public String getBeanByForm(BaseBean baseBean, Model model){
BaseBean bean = this.baseService.findBean(baseBean.getId());
model.addAttribute("bean",bean);
//返回试图名为home的视图
return "showMessage";
}
浏览器访问结果如下:
③路径变量方式
SpringMVC可以通过@PathVariable从路径中提取参数变量:
@RequestMapping(method = RequestMethod.GET,value = "/getBean/{id}")//指定getBean/后的参数位id参数占位
public String getBeanByPath(@PathVariable("id") Integer id, Model model){//通过@PathVariable注解提取变量
BaseBean bean = this.baseService.findBean(id);
model.addAttribute("bean",bean);
//返回试图名为home的视图
return "showMessage";
}
访问结果如下: