SpringMVC
SpringMVC的核心是DispatcherServlet,所有請求都要通過它轉(zhuǎn)發(fā),當(dāng)一個(gè)用戶發(fā)起一個(gè)請求,DispatcherServlet先找到處理器映射,根據(jù)映射找到類中對應(yīng)的控制器,然后調(diào)用控制器中對應(yīng)的方法,返回?cái)?shù)據(jù)模型(即各種需要被前端用到的數(shù)據(jù),比如實(shí)體類)以及視圖名稱,然后DispatcherServlet在根據(jù)控制器返回的視圖名稱找到視圖解析器解析視圖,最后輸出到前端響應(yīng)
- DispatcherServlet找到處理器映射
- 根據(jù)映射結(jié)果找到控制器中對應(yīng)的方法
- 根據(jù)方法返回的模型和視圖名找到視圖解析器解析
- 然后根據(jù)視圖解析器找到視圖使用模型渲染結(jié)果
在這里模型指的是返回給用戶并在瀏覽器顯示的數(shù)據(jù)
1.通過java配置取代web.xml配置
servlet3.0規(guī)范定義了一個(gè)ServletContainerInitializer接口來初始化類,用于替代web.xml,在servlet容器啟動(dòng)時(shí),會(huì)加載該接口的實(shí)現(xiàn)類用于加載相關(guān)配置,SpringMVC的AbstractAnnotationConfigDispatcherServletInitializer實(shí)現(xiàn)了這個(gè)接口,繼承這個(gè)接口即可實(shí)現(xiàn)不通過web.xml配置文件,
//當(dāng)web程序啟動(dòng)時(shí),tomcat會(huì)加載實(shí)現(xiàn)了ServletContainerInitializer,而下面這個(gè)就實(shí)現(xiàn)了
//所以。這個(gè)就是一個(gè)配置類,用于代替web.xml,在該類被初始化時(shí),會(huì)同時(shí)初始化Dispatcher核心類和ConTextLoaderListener
//后面ContextLoaderListener個(gè)類用于getServletConfigClasses方法
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//根配置,配置數(shù)據(jù)庫等 其他要用到的組件信息
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
//Dispatcher配置
//使用conTextLoaderListener加載前端控制器的上下文
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
//實(shí)現(xiàn)ServletContainerInitializer是用于替代web.xml,而Abstra....是SpringMVC 在替代web.xml的基礎(chǔ)上加入MVC特有的組件
//而這個(gè)ServletContainer是servlet3.0以后的產(chǎn)物,servlet容器tomcat7或者更高的版本才能這么使用
//攔截
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
RootConfig
package spitter.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages = {"spitter"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig {
}
WebConfig
package spitter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
resourceViewResolver.setPrefix("/WEB-INF/view/");
resourceViewResolver.setSuffix(".jsp");
resourceViewResolver.setExposeContextBeansAsAttributes(true);
return resourceViewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
其中g(shù)etServletConfigClass用于獲取配置Dispatcher相關(guān)屬性,而rootConfig用于配置其他類,例如mybatis redies等
最后在創(chuàng)建一個(gè)控制器
package spitter.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
static {
System.out.println("初始化成功");
}
@RequestMapping(value = "/",method = RequestMethod.GET)
public String home(){
return "home";
}
}
然后在對應(yīng)設(shè)置的目錄創(chuàng)建一個(gè)jsp,最后測試就能成功跳轉(zhuǎn)到頁面
傳遞模型數(shù)據(jù)到視圖中
在控制器的方法中,可以指定模型參數(shù),就可以把模型數(shù)據(jù)帶到前端顯示,實(shí)際這個(gè)參數(shù)就是一個(gè)map,具體如下,在這里傳遞了一個(gè)對象集合
/**
* 給定一個(gè)模型參數(shù),用于傳遞 需要顯示在前端的數(shù)據(jù),實(shí)際上model是一個(gè)map,
* 當(dāng)不給其傳遞key時(shí),他會(huì)自己推斷,在這里名字為spittles,如果你不希望使用Model 作為參數(shù)
* 你也可以使用Map model作為參數(shù),效果已有
*/
public String home(Model model){
model.addAttribute(createSpittleList(20));
return "home";
}
接收請求的輸入
SpringMVC自然也需要支持前端傳參數(shù)到后臺(tái),SpringMVC中可以在方法參數(shù)前面添加一個(gè)@RequestParam表示要接受的參數(shù)的name,代碼如下
/**
* 在這里@RequestParam代表必須要傳的參數(shù),也可以指定一個(gè)默認(rèn)值,在沒有傳入?yún)?shù)的時(shí)候
* 將賦值為默認(rèn)值,默認(rèn)值都必須是String類型的,不過在賦值給max的時(shí)候會(huì)做對應(yīng)的轉(zhuǎn)換
* @param max
* @param count
* @return
*/
public List<Spittle> spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
System.out.println(max +" " +count);
return findSpittle(max,count);
}
如果在一個(gè)頁面的form表單上不寫action,那么這個(gè)表單提交時(shí),就會(huì)默認(rèn)提交到 跳轉(zhuǎn)到這個(gè)頁面上的路徑,意思就是 如果你訪問 /index,而Controller跳轉(zhuǎn)到index.jsp,那么這個(gè)jsp就會(huì)提交到該控制器中去,即可指定一個(gè)POST同名方法,參數(shù)不一致即可(重載)
如下,其中redirect代表重定向,直接輸入controller的url路徑
@RequestMapping(value = "view",method = RequestMethod.POST)
public String spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
System.out.println(max +" " +count);
return "redirect:/view";
}
@RequestMapping(value = "view",method = RequestMethod.GET)
public String spittles(){
return "form";
}
校驗(yàn)表單
普通的后臺(tái)校驗(yàn)表單是獲取到前端的值,然后做很多邏輯判斷,這樣既寫了很多重復(fù)代碼,而且如果字段比較多,寫起來也比較麻煩,從Spring3.0開始,Spring提供了對java校驗(yàn)api,又稱之為JSR-303,實(shí)際上是一個(gè)接口,而其實(shí)現(xiàn)類就是Hibernate Validator,下面是相關(guān)注解
- @AssertFalse:所注解的屬性必須是false
- @AssertTrue:所注解的屬性必須是true
- @DecimalMax:所注解的屬性必須是數(shù)字,且小于給定的值
- @DecimalMin:所注解的屬性必須是數(shù)字,且大于給定的值
- @Digits:必須是數(shù)字,且必須有指定的數(shù)字
- @Future:必須是將來的日期
- @Max:必須是數(shù)字,且值要小于或者等于給定的值
- @Min:同上,大于給定的值
- @NotNULL:不能為空
- @NUll:必須為null
- @Past:必須是過去的日期
- @Pattern:必須匹配正則表達(dá)式
- @Size:值必須是String、集合、數(shù)組,且長度符合要求
用法如下
package spitter;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
public class Spittle {
private Long id;
@NotNull(message = "不能為空")
@Size(min=5,max = 16,message = "不能超過5-16")
private String message;
@NotNull
@Size(min=5,max=25,message = "不能超過5-25")
private String time;
@NotNull
@Size(min=2,max=30,message = "不能超過2-30")
private String latitude;
@NotNull
@Size(min=2,max = 30,message ="不能超過2-30")
private String longitude;
public Spittle(Long id, String message, String time, String latitude, String longitude) {
this.id = id;
this.message = message;
this.time = time;
this.latitude = latitude;
this.longitude = longitude;
}
public Spittle() {
}
public Spittle(String message, String time) {
this.message = message;
this.time = time;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return "Spittle{" +
"id=" + id +
", message='" + message + '\'' +
", time='" + time + '\'' +
", latitude='" + latitude + '\'' +
", longitude='" + longitude + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
}
@RequestMapping(value = "registry",method = RequestMethod.POST)
public String processRegistration( @Valid Spittle spittle, Errors error){
System.out.println(spittle);
if(error.hasErrors()){
System.out.println(error.getAllErrors());
return "form";
}
return "home";
}
建議不要使用高版本,我使用6.0.9報(bào)錯(cuò),版本兼容報(bào)錯(cuò),顯示初始化失敗,換成5.3版本以后正常執(zhí)行
使用Apache Tiles視圖定義布局
如果需要對每一個(gè)布局添加頭部和尾部,常規(guī)做法是為每一個(gè)jsp模版設(shè)置頭部和尾部,這樣明顯擴(kuò)展性不好且增加日后維護(hù)成本,所以就需要布局引擎Apache Tiles
沒什么卵用,多了些莫名其妙的配置,實(shí)際也就和一般的頭部引用jsp,沒什么差別
文件上傳
Spring的文件上傳比較簡單,只要在Spring 中注冊一個(gè)文件上傳的解析器就行了,Spring中有兩個(gè)解析器
- ComonsMultipartResolver:通用
- StandardServletMultipartResolver:適用于servlet3.0以后
基本用法
- 在Spring配置文件中實(shí)例化一個(gè)上傳解析器
- 接收用MultipartFile
使用java配置的一般例子
在主配置類中重寫方法
/**
* 設(shè)置上傳文件的臨時(shí)文件
* @param registration
*/
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//設(shè)置零時(shí)文件夾,以及其他相關(guān)設(shè)置
registration.setMultipartConfig(new MultipartConfigElement("/Users/f7689386/PycharmProjects",2097152,4194304,0));
}
Controller寫法
public String processRegistration(@RequestPart("profilePicture")MultipartFile profilePicture){
//獲取名字
System.out.println(profilePicture.getOriginalFilename());
return "home";
}
MultipartFile接口相關(guān)方法
public abstract interface MultipartFile {
String getName();
String getOriginalFilename();
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws java.io.IOException;
InputStream getInputStream() throws java.io.IOException;
void transferTo(File arg0) throws IOException,IllegalStateException;
}
定義異常通知切面
如果有多個(gè)控制器拋出同樣的異常,要對每一個(gè)方法都要進(jìn)行處理跳轉(zhuǎn)到error頁面,這樣會(huì)有大量存在代碼,可以抽取成一個(gè)切面,對每一個(gè)拋出該異常的方法 作為一個(gè)切點(diǎn)
在SpringMVC中,可以給類添加@ControllerAdvice代表這個(gè)類是一個(gè)通知類,@ExceptionHandler代表這個(gè)方法是一個(gè)異常通知方法
java
@ControllerAdvice
public class AppWideExceptionHandler{
@ExceptionHandler(DuplicateSprittleException.class)
public String duplicateSplittleHandler(){
return "error";
}
}
上面這個(gè)類代表如果有某個(gè)類拋出DulicateSprittleException異常,則執(zhí)行上面那個(gè)方法,其中ControllerAdvice里面包含的@Component,所以在啟動(dòng)的時(shí)候它也會(huì)被掃描進(jìn)來被Spring管理
重定向傳遞數(shù)據(jù)
當(dāng)用戶提交post請求以后,如果不使用重定向跳轉(zhuǎn)而使用請求轉(zhuǎn)發(fā),那么在刷新頁面或者后退可能會(huì)產(chǎn)生多次提交數(shù)據(jù)等危險(xiǎn)操作,但是在SpringMVC中Model的生命周期都是一次請求,在重定向model存儲(chǔ)的數(shù)據(jù)都會(huì)消失
有個(gè)方案是把需要訪問的數(shù)據(jù)存儲(chǔ)在會(huì)話Session中,Spring也認(rèn)為這是一個(gè)不錯(cuò)的方式,但是Spring認(rèn)為我們不需要管理這些數(shù)據(jù),Spring提供了RedirectAttributes設(shè)置屬性,在跳轉(zhuǎn)到頁面以后數(shù)據(jù)消失,類似一次請求存儲(chǔ)的數(shù)據(jù)
@RequestMapping(value = "redirect",method = RequestMethod.GET)
public String redirect(RedirectAttributes model){
model.addFlashAttribute("a","a");
return "redirect:/spitter/(username)";
}
如果傳遞的是一個(gè)變量名,還可以不設(shè)置key,他會(huì)默認(rèn)以變量名作為key