SpringMVC
SpringMVC故名思議,是Spring提供的MVC框架,那么自然有其他MVC框架,例如Struts等等。目前Struts正在陷入漏洞風(fēng)波,SpringMVC越來(lái)越受到大家的歡迎。
一個(gè)HTTP請(qǐng)求的旅程
要了解SpringMVC的原理首先我們從最基本的著手。一個(gè)HTTP請(qǐng)求到底要經(jīng)歷什么才能從請(qǐng)求發(fā)起到返回至瀏覽器中?一個(gè)請(qǐng)求被HTTP協(xié)議封裝之后,從OSI應(yīng)用層出發(fā),歷經(jīng)傳輸層,網(wǎng)絡(luò)層,鏈路層,物理層,直到與服務(wù)端Servlet容器(Tomcat)建立連接。這時(shí)候Tomcat管理的Servlet為該請(qǐng)求建立一個(gè)新線程或者從線程池中拿出線程做處理。如果請(qǐng)求是首次發(fā)出,我們需要為該請(qǐng)求創(chuàng)建一個(gè)Session;如果非首次訪問(wèn),且Session沒(méi)有過(guò)期,那么就通過(guò)Cookies訪問(wèn)對(duì)應(yīng)的Session。請(qǐng)求經(jīng)過(guò)一系列的處理,一般包括過(guò)濾器、監(jiān)聽(tīng)器,結(jié)果通過(guò)response返回。需要注意的是Servlet、過(guò)濾器、監(jiān)聽(tīng)器,在一般情況下只有一個(gè)實(shí)例,但是如果你在web.xml中聲明了多個(gè)Servlet,或者一個(gè)Servlet實(shí)現(xiàn)了SingleThreadMode接口,又或者在分布式的環(huán)境中,那么就存在多個(gè)實(shí)例。
服務(wù)端程序的進(jìn)化
JSP
起初我們只需要一個(gè)web.xml加上一個(gè)jsp文件,請(qǐng)求到達(dá)時(shí)直接返回url指定頁(yè)面。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>Welcome!I'm a baby web</title>
</head>
<body>
Hello, I'm still young.
</body>
</html>
后來(lái)覺(jué)得靜態(tài)頁(yè)面不夠酷炫編程也不方便,我們要把Java庫(kù)應(yīng)用到JSP中。
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>Welcome!I'm a baby web</title>
</head>
<body>
Hello, I'm still young.<br/>
現(xiàn)在時(shí)間為:<%= (new java.util.Date()).toString()%>
<!--JSP中聲明變量-->
<%!int i = 0;%>
<!--聲明函數(shù)-->
<%!public String f(int i) {
if (i < 3)
return "i小于3";
else
return "i大于等于3";
}%>
你的值是:<%= f(1)%><<br/>
<!--使用Bean-->
<jsp:useBean id="test" class="com.book.web.SimpleBean"/>
<jsp:setProperty name="test" property="name" value="Baby"/>
你叫什么:<jsp:getProperty name="test" property="name"/>
<!--獲取Session-->
<%! int number = 0;
synchronized void countPeople(){
number++;
}
%>
<%
if(session.isNew()){
countPeople();
String str=String.valueOf(number);
session.setAttribute("count", str);
application.setAttribute("count",str);
}
%>
<p>
你是第<%=(String) application.getAttribute("count")%>個(gè)訪問(wèn)本網(wǎng)站的人。
</p>
<!--幾個(gè)默認(rèn)對(duì)象 application:應(yīng)用啟動(dòng)的時(shí)候存在,應(yīng)用停掉消失,用戶(hù)在哥哥頁(yè)面瀏覽都是這一個(gè)對(duì)象。
pageContext范圍局限在本頁(yè)面內(nèi)。
Request從請(qǐng)求到頁(yè)面之后。
Session用戶(hù)持續(xù)連接服務(wù)器的時(shí)間,連接斷開(kāi)則Session無(wú)效。
-->
</body>
</html>
java代碼嵌入在樣式代碼中,雖然可用,但是凌亂。因此有部分Java程序員提出,不用把java嵌入到網(wǎng)頁(yè)中,而要把網(wǎng)頁(yè)嵌入到j(luò)ava中,這種方案就是servlet。
Servlet
jsp出現(xiàn)以后出現(xiàn)了有兩種編程方式,一種程序員啥代碼都往JSP里面堆積,為此擴(kuò)展了各種各樣的標(biāo)簽;一派覺(jué)得這種程序非常丑陋,堅(jiān)持用Servlet技術(shù),在純粹的Java類(lèi)中接收請(qǐng)求,填充網(wǎng)頁(yè)樣式,并返回。
下面是Servlet的一個(gè)例子:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException{
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD><TITLE>Servlet實(shí)例</TITLE></HEAD>");
out.println("<body>");
out.println("Servlet實(shí)例");
out.println(this.getClass());
out.println("</body>");
out.println("</HTML");
out.flush();
out.close();
}
}
我們將JSP用String的方式傳遞給response,并在前端展現(xiàn)。這種方式可以在服務(wù)端做純粹的java編程,雖然有點(diǎn)奇怪,但是這個(gè)程序沒(méi)有涉及任何標(biāo)簽(額,除了String里面的)。然而,在構(gòu)建大型工程的時(shí)候,一方面樣式代碼會(huì)占用大量代碼且不可以重用,另一方面我們無(wú)法對(duì)樣式做擴(kuò)展了。
MVC
當(dāng)當(dāng)當(dāng)當(dāng)...MVC在這JSP方案和servlet方案中取得了平衡。MVC將網(wǎng)頁(yè)樣式和java代碼分離,樣式我們叫“View”,java代碼我們叫“Controller”,兩者之間交換數(shù)據(jù)我們叫“Model”。這樣樣式可以自由擴(kuò)展,用JSP、HTML/CSS、甚至PHP都沒(méi)有問(wèn)題;Controller只需要專(zhuān)心于業(yè)務(wù)邏輯實(shí)現(xiàn);Model既可以用來(lái)做持久化也可以用來(lái)做接收表單的對(duì)象。
SpringMVC來(lái)了
上文說(shuō),MVC是將Model、View、Controller三者分離。那么如何做的分離呢?可以看一下上面的圖,一個(gè)攜帶URL和表單信息的請(qǐng)求首先會(huì)發(fā)送到DispatcherServlet。DispatcherServlet是前端控制器,在應(yīng)用中為一個(gè)單例。DispatcherServlet通過(guò)處理器映射得知請(qǐng)求的下一站是在哪個(gè)控制器。控制器接收請(qǐng)求,并將數(shù)據(jù)打包進(jìn)模型中,返回一個(gè)視圖名,傳遞給DispatcherServlet。DispatcherServlet通過(guò)視圖解析器定位到視圖實(shí)現(xiàn)。請(qǐng)求到達(dá)視圖,視圖用模型數(shù)據(jù)渲染輸出,寫(xiě)入到response對(duì)象中。
DispatcherServlet的配置
有兩種方法,第一種是直接按照Servlet的配置方法在web.xml中配置:
<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
另一種是基于Servlet3.0提供的機(jī)制,Servlet3.0環(huán)境中容器會(huì)查找javax.servlet.ServletContainerInitialize的實(shí)現(xiàn)類(lèi),Spring實(shí)現(xiàn)了這個(gè)接口SpringServletContainerInitializer,這個(gè)類(lèi)查找WebApplicationInitializer類(lèi)并將配置任務(wù)交給它,AbstractAnnotationConfigDispatcherServletInitializer是WebApplicationInitializer的一個(gè)基礎(chǔ)實(shí)現(xiàn),于是我們可以做出如下的實(shí)現(xiàn):
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//用來(lái)配置ContextLoaderListener創(chuàng)建的應(yīng)用上下文中的bean
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
//定義DispatcherServlet應(yīng)用上下文中的bean
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
//所有請(qǐng)求都會(huì)被攔截
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
實(shí)現(xiàn)webconfig類(lèi):
@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
super.addResourceHandlers(registry);
}
}
在Spring MVC中我們其實(shí)是可以創(chuàng)建出多個(gè)DispatcherServlet的(只要?jiǎng)?chuàng)建多個(gè)繼承自AbstractAnnotationConfigDispatcherServletInitializer的類(lèi)即可)。而每個(gè)DispatcherServlet有自己的應(yīng)用上下文(WebApplicationContext),這個(gè)應(yīng)用上下文只針對(duì)這個(gè)DispatcherServlet有用。這也就是getServletConfigClasses的作用,獲取這個(gè)DispatcherServlet的應(yīng)用上下文的配置類(lèi)。
而除了每個(gè)DispatcherServlet配置類(lèi)的應(yīng)用上下文之外,還有一個(gè)根應(yīng)用上下文,這個(gè)應(yīng)用上下文的作用是為了在多個(gè)DispatcherServlet之間共享Bean,比如數(shù)據(jù)源Bean,這就是getRootConfigClasses的作用,用于返回根應(yīng)用上下文的配置類(lèi)。Spring框架的機(jī)制會(huì)保證如果在當(dāng)前DispatcherServlet的應(yīng)用上下文中沒(méi)有找到想要的bean時(shí),會(huì)去根應(yīng)用上下文中去找。
這里我們只需要一個(gè)DispatcherServlet配置,因此可以不對(duì)RootConfig.class做復(fù)雜配置。
@Configuration
@Import(DataConfig.class)
@ComponentScan(basePackages={"spittr"},
excludeFilters={
@Filter(type=FilterType.CUSTOM, value=WebPackage.class)
})
public class RootConfig {
public static class WebPackage extends RegexPatternTypeFilter {
public WebPackage() {
super(Pattern.compile("spittr\\.web"));
}
}
}
編寫(xiě)控制器:
@Controller
@RequestMapping("/")
public class HomeController {
@RequestMapping(method = GET)
public String home(Model model) {
return "home";
}
}
home.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Spitter</title>
<link rel="stylesheet"
type="text/css"
href="<c:url value="/resources/style.css" />" >
</head>
<body>
<h1>Welcome to Spitter</h1>
<a href="<c:url value="/spittles" />">Spittles</a> |
<a href="<c:url value="/spitter/register" />">Register</a>
</body>
</html>
表單處理
Spring提供了數(shù)據(jù)驗(yàn)證功能。
public class Spitter {
private Long id;
@NotNull
@Size(min=5, max=16)
private String username;
@NotNull
@Size(min=5, max=25)
private String password;
@NotNull
@Size(min=2, max=30)
private String firstName;
@NotNull
@Size(min=2, max=30)
private String lastName;
@NotNull
@Email
private String email;
//get set略
}
我們發(fā)起一個(gè)表單:
<html>
<head>
<title>Spitter</title>
<link rel="stylesheet" type="text/css"
href="<c:url value="/resources/style.css" />" >
</head>
<body>
<h1>Register</h1>
<form method="POST">
First Name: <input type="text" name="firstName" /><br/>
Last Name: <input type="text" name="lastName" /><br/>
Email: <input type="email" name="email" /><br/>
Username: <input type="text" name="username" /><br/>
Password: <input type="password" name="password" /><br/>
<input type="submit" value="Register" />
</form>
</body>
</html>
后臺(tái)接收這個(gè)請(qǐng)求的Controller如下:
@Controller
@RequestMapping("/spitter")
public class SpitterController {
private SpitterRepository spitterRepository;
@Autowired
public SpitterController(SpitterRepository spitterRepository) {
this.spitterRepository = spitterRepository;
}
//接收Get請(qǐng)求的函數(shù)映射
@RequestMapping(value="/register", method=GET)
public String showRegistrationForm() {
return "registerForm";
}
//接收Post請(qǐng)求的映射
@RequestMapping(value="/register", method=POST)
public String processRegistration(
@Valid Spitter spitter,
Errors errors) {
if (errors.hasErrors()) {
return "registerForm";
}
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}
//動(dòng)態(tài)映射地址
@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
Spitter spitter = spitterRepository.findByUsername(username);
model.addAttribute(spitter);
return "profile";
}
}