SpringBoot 學習筆記_整合 Web 開發(二)
聲明:
本次學習參考 《SpringBoot + Vue 開發實戰》 · 王松(著) 一書。
本文的目的是記錄我學習的過程和遇到的一些問題以及解決辦法,其內容主要來源於原書。
如有侵權,請聯繫我刪除
文章目錄
SpringBoot 整合 Web 開發
CORS 支持
CORS (Cross-Origin Resource Sharing) 是一種跨域資源共享技術標準,其目的就是爲了解決前端跨域請求。在 Java EE 中,關於前端跨域請求最常見的解決方案是
JSONP
,但是,JSONP
最大的缺陷是隻支持GET
請求。而CORS
支持多種 HTTP 請求方法。
跨域有兩個地方可以配置:
-
請求方法配置
@PostMapping("/") @CrossOrigin(value = "https://localhost:8080", maxAge = 1800, allowedHeaders = "*") public String addBook(String name){ return "receive" + name; } @DeleteMapping("/{id}") @CrossOrigin(value = "https://localhost:8080", maxAge = 1800, allowedHeaders = "*") public String deleteBookById(@PathVariable Long id){ return String.valueOf(id); }
@CrossOrigin
中的value
表示支持的域,這裏表示https://localhost:8080
域的請求支持跨域maxAge
表示探測請求的有效期。在請求執行過程中會先發送探測請求,探測請求不是每次都發送,可以配置一個週期,過了有效期再次發送,默認 1800 秒allowedHeaders
表示允許的請求頭,*
表示所有請求頭都被允許
-
全局配置
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/book/**") .allowedHeaders("*") .allowedMethods("*") .maxAge(1800) .allowedOrigins("https://localhost:8080"); }
配置類和 XML 配置
SpringBoot 推薦使用 Java 來完成相關配置工作,這些配置類需要添加
@Configuration
註解,@ComponentScan
註解會掃描所有 Spring 組件,也包括@Configuration
。@ComponentScan
註解在項目入口類的@SpringBootApplication
註解中已經提供,因此在實際項目中只需要按需提供相關配置類即可。
註冊攔截器
SpringMVC 中提供了
AOP
風格的攔截器,擁有更加精細的攔截處理能力。 SpringBoot 的攔截器註冊更加方便。
- 創建攔截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor >>>>> preHandler");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor >>>>> postHandler");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor >>>>> afterCompletion");
}
}
按照 preHandle - Controller - postHandle - afterCompletion 的順序依次執行, 當且僅當 preHandler 返回 true 時,後面的纔會繼續
當攔截器鏈內有多個攔截器時,postHandler 在攔截器鏈內所有攔截器返回成功時纔會調用,而 afterCompletion 只有 preHandler 返回 true 才調用
- 配置攔截器
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
/**
* 攔截器配置
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") //攔截路徑
.excludePathPatterns("/hello"); //排除路徑
}
}
啓動系統任務
有一些需要在系統啓動時執行的任務,如配置文件加載、數據庫初始化等。
在不使用 SpringBoot 的情況下,這些問題一般在 Listener 中解決。
SpringBoot 對此提供了兩種方案
CommandLineRunner
和ApplicationRunner
,二者主要區別在參數不同。
-
CommandLineRunner
SpringBoot 會在啓動時按照
@Order
的順序遍歷所有的CommandLineRunner
的實現類並調用其中的run
方法,run
方法的參數是系統啓動時傳入的參數@Component @Order(1) public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("Runner >>> " + Arrays.toString(args)); } }
-
ApplicationRunner
整合 Servlet、Filter 和 Listener
-
Servlet
@WebServlet("/myServlet") public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("name" + req.getParameter("name")); super.doPost(req, resp); } }
-
Filter
@WebFilter("/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyFilter >>>>> init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("MyFilter >>>>> doFilter"); } @Override public void destroy() { System.out.println("MyFilter >>>>> destroy"); } }
-
Listener
@WebListener public class MyListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("MyListener >>>>> requestDestroyed"); } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("MyListener >>>>> requestInitialized"); } }
接下來,在項目入口類添加 @ServletComponentScan
註解,實現對 Servlet
、Filter
和 Listener
的掃描。
@SpringBootApplication
@ServletComponentScan
public class Chapter012Application {
public static void main(String[] args) {
SpringApplication.run(Chapter012Application.class, args);
}
}
啓動項目,訪問 https://localhost:8080/myServlet?name=Ambrose
測試
路徑映射
一般情況下,我們都是通過控制器訪問頁面,有時候,有些頁面不需要加載數據,只是完成簡單的跳轉,對於這種頁面,就可以直接配置路徑映射,提高訪問速度
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/* 這行代碼其實就相當於在控制器中對 login 頁面 和 /login 映射,提升了訪問速度,但無法進行數據處理 */
registry.addViewController("/login").setViewName("login");
}
}
配置 AOP
面向切面編程(Aspect-Oriented Programming, AOP),是一種通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。
AOP 技術中,有一些常見的概念需要了解。
- Joinpoint(連接點):類裏面可以被增強的方法即爲連接點。例如,想修改哪個方法,那麼該方法就是一個連接點。
- Pointcut(切入點):對
Joinpoint
進行攔截的定義即爲切入點。例如,攔截所有insert
開始的方法。,這個定義就是切入點。 - Advice(通知):攔截到
Joinpoint
之後要做的操作就是通知。通知分爲前置通知、後置通知、異常通知、最終通知和環繞通知。 - Aspect(切面):
Pointcut
和Advice
的結合。 - Target(目標對象):要增強的類稱爲
Target
。
SpringBoot 在 Spring 的基礎上對 AOP 的配置提供了自動化配置解決方案 spring-boot-starter-aop
。
- 引入
spring-boot-starter-aop
依賴
<!-- 引入 AOP 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 創建
UserService
類
@Service
public class UserService {
public String getUserById(Integer id){
System.out.println("get...");
return "user";
}
public void deleteUserById(Integer id){
System.out.println("delete...");
}
}
- 創建切面
/**
* AOP 切面
*/
// @Aspect 註解表明這是一個切面類
@Aspect
public class LogAspect {
// @Pointcut 註解定義切入點定義
// execution 中的第一個 * 表示方法返回任意值
// execution 中的第二個 * 表示 org.sang.aop.service 包下的任意類
// execution 中的第三個 * 表示類中的任意方法
// 括號中的兩個點表示地方法參數任意。
// 即,這裏描述的切入點爲 service 包下所有類中的所有方法
@Pointcut("execution(* org.sang.aop.service.*.*(..))")
public void pc1(){
}
// @Before 表示這是一個前置通知,該方法在目標方法執行前執行。通過 JoinPoint 參數可以獲取目標方法名、修飾符等
@Before(value = "pc1()")
public void before(JoinPoint jp){
//這裏是 org.aspectj.lang.JoinPoint
//還有一個 org.aopalliance.intercept.Joinpoint
//不要用錯了~
String name = jp.getSignature().getName();
System.out.println(name + "方法開始執行...");
}
// @After 表示這是一個後置通知,該方法在目標方法執行後執行。
@After(value = "pc1()")
public void after(JoinPoint jp){
String name = jp.getSignature().getName();
System.out.println(name + "方法執行結束...");
}
// @AfterReturning 表示這是一個返回通知,在該方法中可以獲取目標方法的返回值。 returning 參數指返回值的變量名
@AfterReturning(value = "pc1()", returning = "result")
public void afterReturning(JoinPoint jp, Object result){
String name = jp.getSignature().getName();
System.out.println(name + "方法返回值爲:" + result);
}
// @AfterThrowing 表示這是一個異常通知,即當目標方法發生異常時,該方法被調用。
@AfterThrowing(value = "pc1()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e){
String name = jp.getSignature().getName();
System.out.println(name + "方法發生了異常:" + e.getMessage());
}
// @Around 表示這是一個環繞通知。環繞通知是所有通知中功能最強大的,可以實現所有通知的功能。
// 目標方法進入環繞通知後,可以調用 ProceedingJoinPoint 對象的 proceed 方法使目標繼續執行;
// 開發者可以在這裏修改目標方法的執行參數、返回值等,並可以在此處理目標方法的異常。
@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
return pjp.proceed();
}
}
- 創建接口調用
UserService
中的方法測試。
package org.sang.controller;
import org.sang.bean.User;
import org.sang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
@Controller
public class UserController {
/* AOP 測試:調用 UserService 的兩個方法,即可以看到 LogAspect 中的代碼動態嵌入目標方法中的執行 */
@Autowired
UserService userService;
@GetMapping("/getUserById")
public String getUserById(Integer id){
return userService.getUserById(id);
}
@GetMapping("/deleteUserById")
public void deleteUserBuId(Integer id){
userService.deleteUserById(id);
}
}
自定義首頁
SpringBoot 啓動後會先去靜態資源路徑下查找
index.html
作爲首頁文件,如果未找到,則會取查找動態的index.html
作爲首頁文件。
-
如果使用靜態首頁,只需要在
resources/static
目錄下新建index.html
文件即可; -
如果使用動態首頁,需要在
resources/templates
目錄下新建index.html
文件,並在Controller
中返回邏輯視圖名稱。@RequestMapping("/index") public String Index(){ return "index"; }