1.Spring背景
1.1.Spring四大原則:
- 使用POJO進(jìn)行輕量級(jí)和最侵入式開發(fā);
- 通過(guò)依賴注入和基于借口編程實(shí)現(xiàn)松耦合;
- 通過(guò)AOP和默認(rèn)習(xí)慣進(jìn)行聲明式編程萌业;
- 使用AOP和模板(template)減少模塊化代碼
Spring所有功能的設(shè)計(jì)和實(shí)現(xiàn)都是基于這四大原則的稚虎。
1.2.依賴注入
容器負(fù)責(zé)創(chuàng)建對(duì)象和維護(hù)對(duì)象之間的依賴關(guān)系侧蘸,而不是通過(guò)對(duì)象本身負(fù)責(zé)自己的創(chuàng)建和解決自己的依賴裁眯。依賴注入的主要目的是解耦,體現(xiàn)一種“組合”的理念闺魏,如果你希望你的類具備某項(xiàng)功能的時(shí)候,是繼承自具有此項(xiàng)功能的父類好俯画,還是組合另一個(gè)具有這個(gè)功能的類好析桥,答案不言而喻。
Spring IoC容器負(fù)責(zé)創(chuàng)建Bean艰垂,并將功能類Bean注入到你需要的Bean中泡仗。Spring提供xml配置、注解猜憎、Java配置娩怎、groovy配置實(shí)現(xiàn)Bean的創(chuàng)建和注入。
聲明Bean的注解:
@Component組件:沒有明確的角色
@Service:業(yè)務(wù)邏輯層使用
@Repository:數(shù)據(jù)訪問(wèn)層(DAO)使用
@Controller:展現(xiàn)層使用
注入Bean的注解
@Autowire:Spring提供的注解
@Inject:JSR-330標(biāo)準(zhǔn)提供的注解
@Resource:JSR-250標(biāo)準(zhǔn)提供的注解
一般情況下通用
1.3.Java配置
Spring4.x推薦的配置方式胰柑,也是Spring Boot推薦的配置方式截亦,可以完全替代xml配置。
@Configuration:Java配置配置方式柬讨,聲明當(dāng)前類是一個(gè)配置類崩瓤,與使用xml配置文件效果一樣
@ComponentScan 自動(dòng)掃描報(bào)名下所有使用@Service @Component @Repository @Controller的類并注冊(cè)為Bean
@Bean注解在方法上,聲明當(dāng)前方法的返回值為一個(gè)Bean
2.MVC模型
MVC模型是一種架構(gòu)型的模式踩官,本身不引入新功能却桶,只是幫助我們將開發(fā)的結(jié)構(gòu)組織的更加合理,使展示與模型分離、流程控制邏輯颖系、業(yè)務(wù)邏輯調(diào)用與展示邏輯分離嗅剖。
Web端的開發(fā)發(fā)展流程如下圖
這里只講述服務(wù)到工作者:Front Controller + Application Controller + Page Controller + Context
運(yùn)行流程如下:
我們回顧了整個(gè)web開發(fā)架構(gòu)的發(fā)展歷程,可能不同的web層框架在細(xì)節(jié)處理方面不同嘁扼,但的目的是一樣的:
1信粮、 干凈的web表現(xiàn)層:
- 模型和視圖的分離;
- 控制器中的控制邏輯與功能處理分離(收集并封裝參數(shù)到模型對(duì)象偷拔、業(yè)務(wù)對(duì)象調(diào)用)蒋院;
- 控制器中的視圖選擇與具體視圖技術(shù)分離。
2莲绰、 輕薄的web表現(xiàn)層:
- 做的事情越少越好欺旧,薄薄的,不應(yīng)該包含無(wú)關(guān)代碼蛤签;
- 只負(fù)責(zé)收集并組織參數(shù)到模型對(duì)象辞友,啟動(dòng)業(yè)務(wù)對(duì)象的調(diào)用;
- 控制器只返回邏輯視圖名并由相應(yīng)的應(yīng)用控制器來(lái)選擇具體使用的視圖策略震肮;
- 盡量少使用框架特定API称龙,保證容易測(cè)試。
3.Spring MVC模型
Spring Web MVC是一種基于Java的實(shí)現(xiàn)了Web MVC設(shè)計(jì)模式的請(qǐng)求驅(qū)動(dòng)類型的輕量級(jí)Web框架戳晌,即使用了MVC架構(gòu)模式的思想鲫尊,將web層進(jìn)行職責(zé)解耦,基于請(qǐng)求驅(qū)動(dòng)指的就是使用請(qǐng)求-響應(yīng)模型沦偎,框架的目的就是幫助我們簡(jiǎn)化開發(fā)
Spring Web MVC也是服務(wù)到工作者模式的實(shí)現(xiàn)疫向,但進(jìn)行可優(yōu)化。
- 前端控制器是DispatcherServlet豪嚎;
- 應(yīng)用控制器其實(shí)拆為處理器映射器(Handler Mapping)進(jìn)行處理器管理和視圖解析器****(View Resolver)進(jìn)行視圖管理搔驼;
- 頁(yè)面控制器/動(dòng)作/處理器為Controller****接口(僅包含ModelAndView handleRequest(request, response) 方法)的實(shí)現(xiàn)(也可以是任何的POJO類);
- 支持本地化(Locale)解析侈询、主題(Theme)解析及文件上傳等舌涨;
- 提供了非常靈活的數(shù)據(jù)驗(yàn)證、格式化和數(shù)據(jù)綁定機(jī)制扔字;
- 提供了強(qiáng)大的約定大于配置(慣例優(yōu)先原則)的契約式編程支持囊嘉。
3.1.Spring Web MVC能做什么
- 讓我們能非常簡(jiǎn)單的設(shè)計(jì)出干凈的Web層和薄薄的Web層;
- 進(jìn)行更簡(jiǎn)潔的Web層的開發(fā)革为;
- 天生與Spring框架集成(如IoC容器哗伯、AOP等);
- 提供強(qiáng)大的約定大于配置的契約式編程支持篷角;
- 能簡(jiǎn)單的進(jìn)行Web層的單元測(cè)試焊刹;
- 支持靈活的URL到頁(yè)面控制器的映射;
- 非常容易與其他視圖技術(shù)集成,如Velocity虐块、FreeMarker等等俩滥,因?yàn)槟P蛿?shù)據(jù)不放在特定的API里,而是放在一個(gè)Model里(Map數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)贺奠,因此很容易被其他框架使用)霜旧;
- 非常靈活的數(shù)據(jù)驗(yàn)證、格式化和數(shù)據(jù)綁定機(jī)制儡率,能使用任何對(duì)象進(jìn)行數(shù)據(jù)綁定挂据,不必實(shí)現(xiàn)特定框架的API;
- 提供一套強(qiáng)大的JSP標(biāo)簽庫(kù)儿普,簡(jiǎn)化JSP開發(fā)崎逃;
- 支持靈活的本地化、主題等解析眉孩;
- 更加簡(jiǎn)單的異常處理个绍;
- 對(duì)靜態(tài)資源的支持;
- 支持Restful風(fēng)格浪汪。
3.2.Spring Web MVC處理請(qǐng)求的流程
具體執(zhí)行步驟如下:
1巴柿、 首先用戶發(fā)送請(qǐng)求—>前端控制器,前端控制器根據(jù)請(qǐng)求信息(如URL)來(lái)決定選擇哪一個(gè)頁(yè)面控制器進(jìn)行處理并把請(qǐng)求委托給它死遭,即以前的控制器的控制邏輯部分广恢;圖中的1、2步驟呀潭;
2钉迷、 頁(yè)面控制器接收到請(qǐng)求后,進(jìn)行功能處理蜗侈,首先需要收集和綁定請(qǐng)求參數(shù)到一個(gè)對(duì)象篷牌,這個(gè)對(duì)象在Spring Web MVC中叫命令對(duì)象睡蟋,并進(jìn)行驗(yàn)證踏幻,然后將命令對(duì)象委托給業(yè)務(wù)對(duì)象進(jìn)行處理;處理完畢后返回一個(gè)ModelAndView(模型數(shù)據(jù)和邏輯視圖名)戳杀;圖2-1中的3该面、4、5步驟信卡;
3隔缀、 前端控制器收回控制權(quán),然后根據(jù)返回的邏輯視圖名傍菇,選擇相應(yīng)的視圖進(jìn)行渲染猾瘸,并把模型數(shù)據(jù)傳入以便視圖渲染;圖2-1中的步驟6、7牵触;
4淮悼、 前端控制器再次收回控制權(quán),將響應(yīng)返回給用戶揽思,圖2-1中的步驟8袜腥;至此整個(gè)結(jié)束
3.3.疑惑(后面解答)
1、 請(qǐng)求如何給前端控制器钉汗?
2羹令、 前端控制器如何根據(jù)請(qǐng)求信息選擇頁(yè)面控制器進(jìn)行功能處理?
3损痰、 如何支持多種頁(yè)面控制器呢福侈?
4、 如何頁(yè)面控制器如何使用業(yè)務(wù)對(duì)象徐钠?
5癌刽、 頁(yè)面控制器如何返回模型數(shù)據(jù)?
6尝丐、 前端控制器如何根據(jù)頁(yè)面控制器返回的邏輯視圖名選擇具體的視圖進(jìn)行渲染显拜?
7、 不同的視圖技術(shù)如何使用相應(yīng)的模型數(shù)據(jù)?
4.Spring核心框架
核心架構(gòu)的具體流程步驟如下:
1瓷胧、 用戶發(fā)送請(qǐng)求 -> DispatcherServlet亚铁,前端控制器收到請(qǐng)求后自己不進(jìn)行處理,而是委托給其他的解析器進(jìn)行處理譬淳,作為統(tǒng)一訪問(wèn)點(diǎn),進(jìn)行全局的流程控制盹兢;
2邻梆、 DispatcherServle -> HandlerMapping:HandlerMapping將會(huì)把請(qǐng)求映射為HandlerExecutionChain對(duì)象(包含一個(gè)Handler處理器(頁(yè)面控制器)對(duì)象、多個(gè)HandlerInterceptor攔截器)對(duì)象绎秒,通過(guò)這種策略模式浦妄,很容易添加新的映射策略;
3见芹、 DispatcherServlet -> HandlerAdapter:HandlerAdapter將會(huì)把處理器包裝為適配器剂娄,從而支持多種類型的處理器,即適配器設(shè)計(jì)模式的應(yīng)用玄呛,從而很容易支持很多類型的處理器阅懦;
4、 HandlerAdapter -> 處理器:HandlerAdapter將會(huì)根據(jù)適配的結(jié)果調(diào)用真正的處理器的功能處理方法徘铝,完成功能處理耳胎;并返回一個(gè)ModelAndView對(duì)象(包含模型數(shù)據(jù)惯吕、邏輯視圖名);
5怕午、 ModelAndView -> ViewResolver:ViewResolver將把邏輯視圖名解析為具體的View混埠,通過(guò)這種策略模式,很容易更換其他視圖技術(shù)诗轻;
6扳炬、 渲染View :View會(huì)根據(jù)傳進(jìn)來(lái)的Model模型數(shù)據(jù)進(jìn)行渲染劝术,此處的Model實(shí)際是一個(gè)Map數(shù)據(jù)結(jié)構(gòu),因此很容易支持其他視圖技術(shù);
7、 DispatcherServlet****響應(yīng):返回控制權(quán)給DispatcherServlet月腋,由DispatcherServlet返回響應(yīng)給用戶寨躁,到此一個(gè)流程結(jié)束放钦。
此處我們只是講了核心流程颓屑,沒有考慮攔截器器腋、本地解析措左、文件上傳解析等钳枕。
現(xiàn)在可以回答上述提出的問(wèn)題:
1昔瞧、 請(qǐng)求如何給前端控制器酬荞?
答:這個(gè)應(yīng)該在web.xml中進(jìn)行部署描述
2陨亡、 前端控制器如何根據(jù)請(qǐng)求信息選擇頁(yè)面控制器進(jìn)行功能處理虐急?
答:我們需要配置HandlerMapping進(jìn)行映射
3、 如何支持多種頁(yè)面控制器呢滔迈?
答:配置HandlerAdapter從而支持多種類型的頁(yè)面控制器
4止吁、 頁(yè)面控制器如何使用業(yè)務(wù)對(duì)象?
答:利用Spring IoC容器的依賴注入功能
5燎悍、 頁(yè)面控制器如何返回模型數(shù)據(jù)敬惦?
答:使用ModelAndView返回
6、 前端控制器如何根據(jù)頁(yè)面控制器返回的邏輯視圖名選擇具體的視圖進(jìn)行渲染谈山?
答: 使用ViewResolver進(jìn)行解析
7俄删、 不同的視圖技術(shù)如何使用相應(yīng)的模型數(shù)據(jù)?
答:因?yàn)镸odel是一個(gè)Map數(shù)據(jù)結(jié)構(gòu)奏路,很容易支持其他視圖技術(shù)
org.springframework.web.servlet.DispatcherServlet # doDispatch :
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.(通過(guò)HandlerMapping映射獲取)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug(
"Last-Modified value for [" + getRequestUri(request) + "] is: "
+ lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified)
&& isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. 適配器執(zhí)行處理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
通過(guò)閱讀源碼畴椰,可以看出具體的核心開發(fā)步驟:
1、 DispatcherServlet在web.xml中的部署描述鸽粉,從而攔截請(qǐng)求到Spring Web MVC
2斜脂、 HandlerMapping的配置,從而將請(qǐng)求映射到處理器
3触机、 HandlerAdapter的配置帚戳,從而支持多種類型的處理器
4、 ViewResolver的配置儡首,從而將邏輯視圖名解析為具體視圖技術(shù)
5片任、 處理器(頁(yè)面控制器)的配置,從而進(jìn)行功能處理
5.注解式開發(fā)
Spring2.5引入注解式處理器支持蔬胯,通過(guò)@Controller 和 @RequestMapping注解定義我們的處理器類对供。并且提供了一組強(qiáng)大的注解:
需要通過(guò)處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter來(lái)開啟支持@Controller 和 @RequestMapping注解的處理器。
- @Controller:用于標(biāo)識(shí)是處理器類笔宿;
- @RequestMapping:請(qǐng)求到處理器功能方法的映射規(guī)則犁钟;
- @RequestParam:請(qǐng)求參數(shù)到處理器功能處理方法的方法參數(shù)上的綁定;
- @ModelAttribute:請(qǐng)求參數(shù)到命令對(duì)象的綁定泼橘;
- @SessionAttributes:用于聲明session級(jí)別存儲(chǔ)的屬性涝动,放置在處理器類上,通常列出模型屬性(如@ModelAttribute)對(duì)應(yīng)的名稱炬灭,則這些屬性會(huì)透明的保存到session中醋粟;
- @InitBinder:自定義數(shù)據(jù)綁定注冊(cè)支持,用于將請(qǐng)求參數(shù)轉(zhuǎn)換到命令對(duì)象屬性的對(duì)應(yīng)類型重归;
Spring3.0引入RESTful架構(gòu)風(fēng)格支持(通過(guò)@PathVariable注解和一些其他特性支持),且又引入了更多的注解支持:
- @CookieValue:cookie數(shù)據(jù)到處理器功能處理方法的方法參數(shù)上的綁定米愿;
- @RequestHeader:請(qǐng)求頭(header)數(shù)據(jù)到處理器功能處理方法的方法參數(shù)上的綁定;
- @RequestBody:請(qǐng)求的body體的綁定(通過(guò)HttpMessageConverter進(jìn)行類型轉(zhuǎn)換)鼻吮;
- @ResponseBody:處理器功能處理方法的返回值作為響應(yīng)體(通過(guò)HttpMessageConverter進(jìn)行類型轉(zhuǎn)換)育苟;
- @ResponseStatus:定義處理器功能處理方法/異常處理器返回的狀態(tài)碼和原因;
- @ExceptionHandler:注解式聲明異常處理器椎木;
- @PathVariable:請(qǐng)求URI中的模板變量部分到處理器功能處理方法的方法參數(shù)上的綁定违柏,從而支持RESTful架構(gòu)風(fēng)格的URI;
6.DispatcherServlet
DispatcherServlet主要用作職責(zé)調(diào)度工作香椎,本身主要用于控制流程漱竖,主要職責(zé)如下:
1、 文件上傳解析畜伐,如果請(qǐng)求類型是multipart將通過(guò)MultipartResolver進(jìn)行文件上傳解析馍惹;
2、 通過(guò)HandlerMapping玛界,將請(qǐng)求映射到處理器(返回一個(gè)HandlerExecutionChain万矾,它包括一個(gè)處理器、多個(gè)HandlerInterceptor攔截器)慎框;
3勤众、 通過(guò)HandlerAdapter支持多種類型的處理器(HandlerExecutionChain中的處理器);
4鲤脏、 通過(guò)ViewResolver解析邏輯視圖名到具體視圖實(shí)現(xiàn)们颜;
5、 本地化解析猎醇;
6窥突、 渲染具體的視圖等;
7硫嘶、 如果執(zhí)行過(guò)程中遇到異常將交給HandlerExceptionResolver來(lái)解析阻问。
DispatcherServlet主要負(fù)責(zé)流程的控制,而且在流程中的每個(gè)關(guān)鍵點(diǎn)都是很容易擴(kuò)展的沦疾。
DispatcherServlet默認(rèn)使用WebApplicationContext作為上下文称近,因此我們來(lái)看一下該上下文中有哪些特殊的Bean:
1第队、 Controller:處理器/頁(yè)面控制器,做的是MVC中的C的事情刨秆,但控制邏輯轉(zhuǎn)移到前端控制器了凳谦,用于對(duì)請(qǐng)求進(jìn)行處理;
2衡未、 HandlerMapping:請(qǐng)求到處理器的映射尸执,如果映射成功返回一個(gè)HandlerExecutionChain對(duì)象(包含一個(gè)Handler處理器(頁(yè)面控制器)對(duì)象、多個(gè)HandlerInterceptor攔截器)對(duì)象缓醋;如BeanNameUrlHandlerMapping將URL與Bean名字映射如失,映射成功的Bean就是此處的處理器;
3送粱、 HandlerAdapter:HandlerAdapter將會(huì)把處理器包裝為適配器褪贵,從而支持多種類型的處理器,即適配器設(shè)計(jì)模式的應(yīng)用抗俄,從而很容易支持很多類型的處理器竭鞍;如SimpleControllerHandlerAdapter將對(duì)實(shí)現(xiàn)了Controller接口的Bean進(jìn)行適配,并且掉處理器的handleRequest方法進(jìn)行功能處理橄镜;
4偎快、 ViewResolver:ViewResolver將把邏輯視圖名解析為具體的View,通過(guò)這種策略模式洽胶,很容易更換其他視圖技術(shù)晒夹;如InternalResourceViewResolver將邏輯視圖名映射為jsp視圖;
5姊氓、 LocalResover:本地化解析丐怯,因?yàn)镾pring支持國(guó)際化,因此LocalResover解析客戶端的Locale信息從而方便進(jìn)行國(guó)際化翔横;
6读跷、 ThemeResovler:主題解析,通過(guò)它來(lái)實(shí)現(xiàn)一個(gè)頁(yè)面多套風(fēng)格禾唁,即常見的類似于軟件皮膚效果效览;
7、 MultipartResolver:文件上傳解析荡短,用于支持文件上傳丐枉;
8、 HandlerExceptionResolver:處理器異常解析掘托,可以將異常映射到相應(yīng)的統(tǒng)一錯(cuò)誤界面瘦锹,從而顯示用戶友好的界面(而不是給用戶看到具體的錯(cuò)誤信息);
9、 RequestToViewNameTranslator:當(dāng)處理器沒有返回邏輯視圖名等相關(guān)信息時(shí)弯院,自動(dòng)將請(qǐng)求URL映射為邏輯視圖名辱士;
10、 FlashMapManager:用于管理FlashMap的策略接口听绳,F(xiàn)lashMap用于存儲(chǔ)一個(gè)請(qǐng)求的輸出颂碘,當(dāng)進(jìn)入另一個(gè)請(qǐng)求時(shí)作為該請(qǐng)求的輸入,通常用于重定向場(chǎng)景辫红。
ContextLoaderListener初始化的上下文加載的Bean是對(duì)于整個(gè)應(yīng)用程序共享的凭涂,不管是使用什么表現(xiàn)層技術(shù)祝辣,一般如DAO層贴妻、Service層Bean;
DispatcherServlet初始化的上下文加載的Bean是只對(duì)Spring Web MVC有效的Bean蝙斜,如Controller名惩、HandlerMapping、HandlerAdapter等等孕荠,該初始化上下文應(yīng)該只加載Web相關(guān)組件娩鹉。
Controller
Controller控制器,是MVC中的部分C稚伍,為什么是部分呢弯予?因?yàn)榇颂幍目刂破髦饕?fù)責(zé)功能處理部分:
1、 收集个曙、驗(yàn)證請(qǐng)求參數(shù)并綁定到命令對(duì)象锈嫩;
2、 將命令對(duì)象交給業(yè)務(wù)對(duì)象垦搬,由業(yè)務(wù)對(duì)象處理并返回模型數(shù)據(jù)呼寸;
3、 返回ModelAndView(Model部分是業(yè)務(wù)對(duì)象返回的模型數(shù)據(jù)猴贰,視圖部分為邏輯視圖名)对雪。
還記得DispatcherServlet嗎?主要負(fù)責(zé)整體的控制流程的調(diào)度部分:
1米绕、負(fù)責(zé)將請(qǐng)求委托給控制器進(jìn)行處理瑟捣;
2、根據(jù)控制器返回的邏輯視圖名選擇具體的視圖進(jìn)行渲染(并把模型數(shù)據(jù)傳入)栅干。
因此MVC中完整的C(包含控制邏輯+功能處理)由(DispatcherServlet + Controller)組成蝶柿。
Controller繼承關(guān)系圖
7.攔截器
請(qǐng)求的映射分為如下幾種:
- URL路徑映射:使用URL映射請(qǐng)求到處理器的功能處理方法;
- 請(qǐng)求方法映射限定:如限定功能處理方法只處理GET請(qǐng)求非驮;
- 請(qǐng)求參數(shù)映射限定:如限定只處理包含“abc”請(qǐng)求參數(shù)的請(qǐng)求交汤;
- 請(qǐng)求頭映射限定:如限定只處理“Accept=application/json”的請(qǐng)求。
可以通過(guò)在一個(gè)POJO類上放置@Controller或@RequestMapping,即可把一個(gè)POJO類變身為處理器芙扎;
@RequestMapping(value = "/hello") 請(qǐng)求URL(/hello) 到 處理器的功能處理方法的映射星岗;
模型數(shù)據(jù)和邏輯視圖名的返回。
package cn.javass.chapter6.web.controller;
//省略import
@Controller // 或 @RequestMapping ①將一個(gè)POJO類聲明為處理器
public class HelloWorldController {
@RequestMapping(value = "/hello") //②請(qǐng)求URL到處理器功能處理方法的映射
public ModelAndView helloWorld() {
//1戒洼、收集參數(shù)
//2俏橘、綁定參數(shù)到命令對(duì)象
//3、調(diào)用業(yè)務(wù)對(duì)象
//4圈浇、選擇下一個(gè)頁(yè)面
ModelAndView mv = new ModelAndView();
//添加模型數(shù)據(jù) 可以是任意的POJO對(duì)象
mv.addObject("message", "Hello World!");
//設(shè)置邏輯視圖名寥掐,視圖解析器會(huì)根據(jù)該名字解析到具體的視圖頁(yè)面
mv.setViewName("hello");
return mv; //③ 模型數(shù)據(jù)和邏輯視圖名
}
}
如果您使用的是Spring3.1之前版本,開啟注解式處理器支持的配置為:
<!—Spring3.1之前的注解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!—Spring3.1之前的注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
如果您使用的Spring3.1開始的版本磷蜀,建議使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter召耘。
<!--Spring3.1開始的注解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--Spring3.1開始的注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
運(yùn)行流程:
@Controller
public class HelloWorldController {
……
}
推薦使用這種方式聲明處理器,它和我們的@Service褐隆、@Repository很好的對(duì)應(yīng)了我們常見的三層開發(fā)架構(gòu)的組件污它。
@RequestMapping
@RequestMapping
public class HelloWorldController {
}
這種方式也是可以工作的,但如果在類上使用@ RequestMapping注解一般是用于
窄化功能處理方法的映射的庶弃,詳見6.4.3衫贬。
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①處理器的通用映射前綴
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相對(duì)于①處的映射進(jìn)行窄化
public ModelAndView helloWorld() {
}
}
窄化請(qǐng)求映射
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①處理器的通用映射前綴
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相對(duì)于①處的映射進(jìn)行窄化
public ModelAndView helloWorld() {
//省略實(shí)現(xiàn)
}
}
①類上的@RequestMapping(value="/user") 表示處理器的通用請(qǐng)求前綴;
②處理器功能處理方法上的是對(duì)①處映射的窄化歇攻。
因此http://localhost:9080/springmvc-chapter6/hello2 無(wú)法映射到HelloWorldController2的 helloWorld功能處理方法固惯;而http://localhost:9080/springmvc-chapter6/user/hello2是可以的。
窄化請(qǐng)求映射可以認(rèn)為是方法級(jí)別的@RequestMapping繼承類級(jí)別的@RequestMapping缴守,窄化請(qǐng)求映射還有其他方式葬毫,如在類級(jí)別指定URL,而方法級(jí)別指定請(qǐng)求方法類型或參數(shù)等等
Servlet
Servlet是一套Web應(yīng)用的開發(fā)規(guī)范斧散,我們按照這套規(guī)范編碼就可以實(shí)現(xiàn)一個(gè)Web應(yīng)用供常,使其在Web容器中運(yùn)行。我們最開始學(xué)習(xí)J2EE時(shí)鸡捐,學(xué)習(xí)和創(chuàng)建的就是Servlet的實(shí)現(xiàn)類栈暇,后來(lái)學(xué)習(xí)了MVC框架以后,尤其是SpringMVC箍镜,就很少直接創(chuàng)建Servlet的實(shí)現(xiàn)類了源祈。雖然SpringMVC簡(jiǎn)化和隱藏了Servlet,但是我們也要了解Servlet的運(yùn)行原理色迂,這樣對(duì)了解SpringMVC的原理也很有幫助香缺。
Servlet 容器
Tomcat 的容器等級(jí)中,Context 容器是直接管理 Servlet 在容器中的包裝類 Wrapper歇僧,所以 Context 容器如何運(yùn)行將直接影響 Servlet 的工作方式图张。
真正管理 Servlet 的容器是 Context 容器锋拖,一個(gè) Context 對(duì)應(yīng)一個(gè) Web 工程,在 Tomcat 的配置文件中可以很容易發(fā)現(xiàn)這一點(diǎn)祸轮,如下:
<Context path="/projectOne " docBase="D:\projects\projectOne"
reloadable="true" />
當(dāng) Context容器初始化狀態(tài)設(shè)為init 時(shí)兽埃,添加在 Contex 容器的 Listener 將會(huì)被調(diào)用。ContextConfig 繼承了 LifecycleListener 接口适袜,它是在調(diào)用Tomcat的addWebapp方法時(shí)被加入到 StandardContext 容器中柄错。ContextConfig 類會(huì)負(fù)責(zé)整個(gè) Web 應(yīng)用的配置文件的解析工作。
ContextConfig 的 init 方法將會(huì)主要完成以下工作:
- 創(chuàng)建用于解析 xml 配置文件的 contextDigester 對(duì)象
- 讀取默認(rèn) context.xml 配置文件苦酱,如果存在解析它
- 讀取默認(rèn) Host 配置文件售貌,如果存在解析它
- 讀取默認(rèn) Context 自身的配置文件,如果存在解析它
- 設(shè)置 Context 的 DocBase
ContextConfig 的 init 方法完成后疫萤,Context 容器的會(huì)執(zhí)行 startInternal 方法颂跨,這個(gè)方法啟動(dòng)邏輯比較復(fù)雜,主要包括如下幾個(gè)部分:
- 創(chuàng)建讀取資源文件的對(duì)象
- 創(chuàng)建 ClassLoader 對(duì)象
- 設(shè)置應(yīng)用的工作目錄
- 啟動(dòng)相關(guān)的輔助類如:logger给僵、realm毫捣、resources 等
- 修改啟動(dòng)狀態(tài)详拙,通知感興趣的觀察者(Web 應(yīng)用的配置)
- 子容器的初始化
- 獲取 ServletContext 并設(shè)置必要的參數(shù)
- 初始化“l(fā)oad on startup”的 Servlet
為什么要將 Servlet 包裝成 StandardWrapper 而不直接是 Servlet 對(duì)象帝际。這里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征饶辙,而 Servlet 為了一個(gè)獨(dú)立的 web 開發(fā)標(biāo)準(zhǔn)蹲诀,不應(yīng)該強(qiáng)耦合在 Tomcat 中。除了將 Servlet 包裝成 StandardWrapper 并作為子容器添加到 Context 中弃揽,其它的所有 web.xml 屬性都被解析到 Context 中脯爪,所以說(shuō) Context 容器才是真正運(yùn)行 Servlet 的 Servlet 容器。一個(gè) Web 應(yīng)用對(duì)應(yīng)一個(gè) Context 容器矿微,容器的配置屬性由 應(yīng)用的 web.xml 指定痕慢,這樣我們就能理解 web.xml 到底起到什么作用了。
那服務(wù)器是如何根據(jù)這個(gè) URL 來(lái)達(dá)到正確的 Servlet 容器中的呢涌矢?
Tomcat7.0 中這件事很容易解決掖举,因?yàn)檫@種映射工作有專門一個(gè)類來(lái)完成的,這個(gè)就是 org.apache.tomcat.util.http.mapper娜庇,這個(gè)類保存了 Tomcat 的 Container 容器中的所有子容器的信息塔次,當(dāng) org.apache.catalina.connector. Request 類在進(jìn)入 Container 容器之前,mapper 將會(huì)根據(jù)這次請(qǐng)求的 hostnane 和 contextpath 將 host 和 context 容器設(shè)置到 Request 的 mappingData 屬性中名秀。所以當(dāng) Request 進(jìn)入 Container 容器之前励负,它要訪問(wèn)那個(gè)子容器這時(shí)就已經(jīng)確定了。
Session 工作的時(shí)序圖
Servlet 中的 Listener
用@Configuration注解該類匕得,等價(jià) 與XML中配置beans继榆;用@Bean標(biāo)注方法等價(jià)于XML中配置bean。