SpringBoot 學習筆記_整合 Web 開發(二)

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 對此提供了兩種方案 CommandLineRunnerApplicationRunner,二者主要區別在參數不同。

  • 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 註解,實現對 ServletFilterListener 的掃描。

@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(切面):PointcutAdvice 的結合。
  • Target(目標對象):要增強的類稱爲 Target

SpringBoot 在 Spring 的基礎上對 AOP 的配置提供了自動化配置解決方案 spring-boot-starter-aop

  1. 引入 spring-boot-starter-aop 依賴
<!--    引入 AOP 依賴    -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 創建 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...");
    }
}
  1. 創建切面
/**
 * 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();
    }
}
  1. 創建接口調用 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";
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章