SpringBoot學習-第四章 SpringMVC基礎-
阿新 • • 發佈:2019-01-31
SpringMvc 快速搭建
- 依賴 : 這裡直接使用SpringBoot的快速搭建
<!-- 包含常用的web/mvc等依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用於管理spring相關的依賴版本 -->
<dependency>
<groupId >org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
日誌 : Spring4推薦使用logback
簡歷一個logback.xml檔案進行日誌配置 ,內容與log4j差不多
頁面 : SpringBoot習慣把頁面放置在resources下面
快速配置
用SpringBoot(註解風格)配置代替web.xml和spring-mvc.xml
- web.xml
/**
* @WebApplicationInitializer 用來配置Servlet3.0的介面 ,也就代替了web.xml ,裡面配置的內容和xml配置基本一致 ,部署在tomcat時容器會自動尋找並載入這個實現
* <p>
* SpringBoot方式啟動的話 ,可以通過配置類(類裡定義servlet bean ,然後import到Application)
*/
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MvcConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
- SpringMvc
@Configuration
@EnableWebMvc //開啟一些預設配置MessageConverters,ViewResolvers等
@ComponentScan("demo2.springboot.mvc")
public class MvcConfig {
/**
* 註冊檢視轉換器 ,為mvc返回的頁面路徑新增前後綴
*/
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
頁面和Controller
@Controller
public class HelloController {
@RequestMapping("/index")
public String hello() {
System.out.println("進入controller");
return "index";
}
}
省略index.jsp
部署
SpringBoot(-web)有自帶的Tomcat ,先排除再關聯Servlet3.0需要的包
用Maven打包成war ,部署在tomcat即可
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
SpringMvc常用註解
@Controller
宣告控制器Bean ,容器的DispatcherServlet會把控制器和url繫結@RequestMapping
指定訪問路徑 ,produces
可指定返回資源的型別@ResponseBody
支援返回值放在response內 ,用於AJAX返回資料而非頁面@RequestBody
允許引數在request內 ,通常處理POST體@PathVarible
接受路徑中的引數 ,例如:/user/add/10002
-> (/user/add/{id}
) -> id=10002@RestController
等效@Controller + @ResponseBody
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(produces = "text/plain;charset=UTF-8")
@ResponseBody
public String index(HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access";
}
@RequestMapping(value = "/login/{userId}", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String demoLogin(@PathVariable Integer userId, HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access , id is :" + userId;
}
@RequestMapping(value = "/register", produces = "application/json;charset=UTF-8")
@ResponseBody
public String demoRegister(UserBean user, HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access , User:[" + user.getId() + "," + user.getName() + "]";
}
@RequestMapping(value = {"/name1", "/name2"}, produces = "application/xml;charset=UTF-8")
@ResponseBody
public String demoMultiPath(HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access";
}
}
MVC基本配置
DispatcherServlet
通常攔截所有URL ,而靜態資源 js/html/css 需要直接訪問 ,需要對Mvc進行配置
- 靜態資源放置在
resources
(根目錄)下 - 配置類繼承
WebMvcConfigurerAdapter
- 新增靜態資源路徑 ,覆蓋
addResourceHandlers(ResourceHandlerRegistry registry)
方法
- 靜態資源放置在
public class MvcConfig extends WebMvcConfigurerAdapter {
//...
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
}
}
- 攔截器配置
- 實現
HandlerInterceptor
介面 或者 繼承HandlerInterceptorAdapter
類 ,實現自定義攔截器 - 在
WebMvcConfigurerAdapter
中新增interceptor
- 實現
public class DemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
}
public class MvcConfig extends WebMvcConfigurerAdapter {
//...
@Bean
public DemoInterceptor demoInterceptor() {
return new DemoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor());
}
}
- ControllerAdvice - 控制器行為總控 (類似AOP)
- 使用
@ControllerAdvice
註解一個類 - 註解ControllerAdvice類中的方法 ,對所有
@RequesMapping
的方法生效
- 使用
@ControllerAdvice //註解啟動了一個總控的Controller ,裡面的方法會應用到所有@RequestMapping方法 ,並根據註解的不同產生不同作用
public class DemoControllerAdvice {
@ModelAttribute //在目標方法執行前 , 產生一個物件 , 並setAttribute
public UserBean addAttribute() {
System.out.println("============應用到所有@RequestMapping註解方法,在其執行之前把返回值放入Model");
return new UserBean(1, "admin");
}
@InitBinder // 針對WebDataBinder的預處理
public void initBinder(WebDataBinder binder) {
System.out.println("============應用到所有@RequestMapping註解方法,在其執行之前初始化資料繫結器");
}
@ExceptionHandler(NoClassDefFoundError.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView handleException(WebRequest request, NoClassDefFoundError e) {
System.out.println("===========應用到所有@RequestMapping註解的方法,在其丟擲NoClassDefFoundError異常時執行");
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMsg", e.getMessage());
return modelAndView;
}
}
- 路徑引數預設忽略”.”後的 ,例如 /user/{xx.yy} ,接收到的只有 xx , 需要在Mvc配置中手動關閉
public class MvcConfig extends WebMvcConfigurerAdapter {
//...
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseRegisteredSuffixPatternMatch(false);
}
}
檔案上傳
- 引入commons-fileupload包
- MVC配置對媒體資源的處理
- Controller處理 ,儲存收到的檔案
public class MvcConfig extends WebMvcConfigurerAdapter {
//...
/**
* 對multipart型別 (檔案)的預設處理設定
* 由commons-upload實現 需要引入
*/
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
multipartResolver.setDefaultEncoding("UTF-8");
return multipartResolver;
}
}
@Controller
@RequestMapping("/file")
public class FileController {
@RequestMapping(value = "upload", method = RequestMethod.POST)
@ResponseBody
public String uploadFile(MultipartFile file) {
try {
File newFile = new File("D:/upload/" + file.getOriginalFilename());
FileUtils.writeByteArrayToFile(newFile, file.getBytes());
return "上傳成功";
} catch (Exception e) {
e.printStackTrace();
return "上傳失敗 : " + e.getMessage();
}
}
}
MessageCovertor
- Spring內建了很多 ,預設Jackson
- 如果需要自定義 ,繼承 AbstractHttpMessageCovertor
服務端推送
- Ajax心跳 : 頻率不好控制 ,伺服器壓力
- WebSocket
- 非同步等待 ,伺服器抓住請求 ,等待推送時再返回(Server Send Event) - 實質還是瀏覽器不斷請求 非同步處理
SSE : 需要瀏覽器支援 ,使用SourceEvent去不斷地請求伺服器(非同步 ,監聽到返回再進行下一步) ,
伺服器可以hold這個連線直到合適的時候
@Controller
public class SSEController {
@RequestMapping(value = "/push", produces = "text/event-stream")
@ResponseBody
public String push() {
Random r = new Random();
try{
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
return "data:Testing 1,2,3"+r.nextInt()+"\n\n";
}
}
if (!!window.EventSource) {
//設定連線後端的方法(url)
var source = new EventSource('push');
source.addEventListener('message', function (e) {
//監聽正常返回的訊息
});
source.addEventListener('open', function (e) {
//監聽開啟連線時
}, false);
source.addEventListener('error', function (e) {
//監聽error
}, false);
}
servlet 3.0+ 開啟非同步方法
- 使用DeferredResult ,非同步返回 ,頁面使用Ajax迴圈訪問即可
/**
* 控制器呼叫 具有非同步特性的service層 ,在呼叫結束後控制器就完成任務
* 由service(實質上時DeferredResult)去控制何時返回響應給客戶端
*/
@Controller
public class AysncController {
@Autowired PushService pushService;
@RequestMapping(value = "/defer")
@ResponseBody
public DeferredResult<String> defer() {
return pushService.getAysncUpdate();
}
}
@Service
public class PushService {
/**
* @DeferredResult 是用來實現非同步請求的(業務邏輯耗時很長)
* 原servlet流程: request->servlet.service()->執行業務邏輯(servlet阻塞)->response
* 新的servlet流程: request->建立子執行緒執行業務邏輯->servlet結束(但不反悔response)->子執行緒結束返回response(子執行緒中有req,res)
*/
DeferredResult<String> deferredResult;
/**
* 方法建立了一個新的DeferredResult 並直接返回 ,servlet接受到這個result過後就結束任務並返回執行緒池
* 而request和response移交到了DeferredResult內 ,待setResult後 ,才會返回
*/
public DeferredResult<String> getAysncUpdate() {
deferredResult = new DeferredResult();
return deferredResult;
}
@Scheduled(fixedDelay = 3000)
public void refresh() {
deferredResult.setResult(String.valueOf(System.currentTimeMillis()));
}
}
SpringMVC的測試
- Spring-test + Junit:使用一些模擬的元件對MVC部分進行單元測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringMVCConfig.class})
@WebAppConfiguration("src/main/resources")//標示web資源位置,預設webapp,spring一般是resources
public class TestControllerIntegrationTests {
private MockMvc mockMvc;//模擬的mvc物件,使用MockMvcBuilders構造
//測試時注入bean和各種模擬的部件
@Autowired private DemoService demoService;
@Autowired WebApplicationContext context;
@Autowired MockHttpSession session;
@Autowired MockHttpServletRequest request;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.build();
}
@Test
public void testNormalController() throws Exception {
//模擬傳送請求
mockMvc.perform(MockMvcRequestBuilders.get("/normal"))
//各種預期結果
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andExpect(MockMvcResultMatchers.view()
.name("index"))
.andExpect(MockMvcResultMatchers.forwardedUrl("/WEB-INF/classes/views/index.jsp"))
.andExpect(MockMvcResultMatchers.model()
.attribute("msg", demoService.saySomething()));
}
@Test
public void testRestController() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/rest/testRest"))
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andExpect(MockMvcResultMatchers.content()
.contentType("text/plain;charset=UTF-8"))
.andExpect(MockMvcResultMatchers.content()
.string(demoService.saySomething()));
}
}