慢慢來比較快,虛心學技術
前言:前面我們學習了關於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";
}
訪問結果如下: