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()));
}
}