Springboot 系列(六)Spring Boot web 開發之攔截器和三大組件


1. 攔截器

Springboot 中的 Interceptor 攔截器也就是 mvc 中的攔截器,只是省去了 xml 配置部分。並沒有本質的不同,都是通過實現 HandlerInterceptor 中幾個方法實現。幾個方法的作用一一如下。

  1. preHandle
    進入 Habdler 方法之前執行,一般用於身份認證授權等。
  2. postHandle
    進入 Handler 方法之后返回 modelAndView 之前執行,一般用於塞入公共模型數據等。
  3. afterCompletion
    最后處理,一般用於日志收集,統一后續處理等。

1.1 引入依賴

 <!-- Spring Boot web 開發整合 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-json</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 阿里 fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

        <!-- Lombok 工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 導入配置文件處理器,在配置springboot相關文件時候會有提示 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 單元測試 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>

1.2 編寫攔截器

package net.codingme.boot.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * 攔截器
 *
 * @Author niujinpeng
 * @Date 2019/1/6 16:54
 */
@Slf4j
public class LogHandlerInterceptor implements HandlerInterceptor {

    /**
     * 請求方法執行之前
     * 返回true則通過
     *
     * @param request
     * @param response
     * @param handler
     * @return
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        StringBuffer requestURL = request.getRequestURL();
        log.info("preHandle請求URL:" + requestURL.toString());
        return true;
    }

    /**
     * 返回modelAndView之前執行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        log.info("postHandle返回modelAndView之前");
    }

    /**
     * 執行Handler完成執行此方法
     * @param request
     * @param response
     * @param handler
     * @param ex
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info("afterCompletion執行完請求方法完全返回之后");
    }
}

1.3 配置攔截器

省去了 XML 中的攔截器配置部分后,使用 springboot 推薦的方式配置自定義攔截器。

package net.codingme.boot.config;


import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 1.使用FastJSON
 * 2.配置時間格式化
 * 3.解決中文亂碼
 * 4.添加自定義攔截器
 *
 * @Author niujinpeng
 * @Date 2018/12/13 15:35
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 自定義JSON轉換器
     *
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        //日期格式化
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        //處理中文亂碼問題
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

        converter.setSupportedMediaTypes(fastMediaTypes);
        converter.setFastJsonConfig(fastJsonConfig);
        converters.add(converter);
    }

    /**
     * 添加自定義攔截器
     * .addPathPatterns("/**")  攔截的請求路徑
     * .excludePathPatterns("/user"); 排除的請求路徑
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user");
    }
}

2 切面編程

  1. AOP:面向切面(方面)編程,擴展功能不修改源代碼實現
  2. AOP采取橫向抽取機制,取代了傳統縱向繼承體系重復性代碼
  3. AOP底層使用動態代理實現
    • 有接口情況使用動態代理創建接口實現類代理對象
    • 沒有接口情況使用動態代理創建類的子類代理對象
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * <p>
 * 使用AOP記錄訪問日志
 * 使用@Before在切入點開始處切入內容
 * 使用@After在切入點結尾處切入內容
 * 使用@AfterReturning在切入點return內容之后切入內容(可以用來對處理返回值做一些加工處理)
 * 使用@Around在切入點前后切入內容,並自己控制何時執行切入點自身的內容
 * 使用@AfterThrowing用來處理當切入內容部分拋出異常之后的處理邏輯
 * <p>
 * 注解:
 * Aspect:AOP
 * Component:Bean
 * Slf4j:可以直接使用log輸出日志
 * Order:多個AOP切同一個方法時的優先級,越小優先級越高越大。
 * 在切入點前的操作,按order的值由小到大執行
 * 在切入點后的操作,按order的值由大到小執行
 *
 * @Author niujinpeng
 * @Date 2019/1/4 23:29
 */

@Aspect
@Component
@Slf4j
@Order(1)
public class LogAspect {
    /**
     * 線程存放信息
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定義切入點
     * 第一個*:標識所有返回類型
     * 字母路徑:包路徑
     * 兩個點..:當前包以及子包
     * 第二個*:所有的類
     * 第三個*:所有的方法
     * 最后的兩個點:所有類型的參數
     */
    @Pointcut("execution(public * net.codingme.boot.controller..*.*(..))")
    public void webLog() {
    }

    /**
     * 在切入點開始處切入內容
     *
     * @param joinPoint
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 記錄請求時間
        startTime.set(System.currentTimeMillis());
        // 獲取請求域
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        // 記錄請求內容
        log.info("Aspect-URL: " + request.getRequestURI().toLowerCase());
        log.info("Aspect-HTTP_METHOD: " + request.getMethod());
        log.info("Aspect-IP: " + request.getRemoteAddr());
        log.info("Aspect-REQUEST_METHOD: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("Aspect-Args: " + Arrays.toString(joinPoint.getArgs()));
    }

    /**
     * 在切入點之后處理內容
     */
    @After("webLog()")
    public void doAfter() {

    }

    /**
     * 在切入點return內容之后切入內容(可以用來對處理返回值做一些加工處理)
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        log.info("Aspect-Response: " + ret);
        Long endTime = System.currentTimeMillis();
        log.info("Aspect-SpeedTime: " + (endTime - startTime.get()) + "ms");
    }

}

訪問查看攔截器和 AOP 的日志輸出。

09:57:15.408  INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor    : preHandle請求URL:http://localhost:8080/
09:57:15.413  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-URL: /
09:57:15.413  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-HTTP_METHOD: GET
09:57:15.413  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-IP: 0:0:0:0:0:0:0:1
09:57:15.414  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-REQUEST_METHOD: net.codingme.boot.controller.HelloController.index
09:57:15.415  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-Args: []
09:57:15.424  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-Response: Greetings from Spring Boot!SpringBoot是一個spring應用程序
09:57:15.425  INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect       : Aspect-SpeedTime: 12ms
09:57:15.436  INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor    : postHandle返回modelAndView之前
09:57:15.437  INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor    : afterCompletion執行完請求方法完全返回之后

3. Servlet,Filter,Listener

Servlet, Filter, Listener 是 Java web 的核心內容,那么在 Springboot 中如何使用呢?

3.1 編寫 Servlet

package net.codingme.boot.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * <p>
 * @WebServlet(urlPatterns = "/myservlet") // 定義訪問路徑
 * @Author niujinpeng
 * @Date 2019/1/24 16:25
 */
@Slf4j
@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        log.info("Servlet 開始初始化");
        super.init();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("Servlet 開始處理 GET 方法");
        PrintWriter writer = resp.getWriter();
        writer.println("Hello Servlet");
        writer.flush();
        writer.close();
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    public void destroy() {
        log.info("Servlet 開始銷毀");
        super.destroy();
    }
}

3.2 編寫 Filter

package net.codingme.boot.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * <p>
 *
 * @Author niujinpeng
 * @Date 2019/1/24 16:35
 */
@Slf4j
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        log.info("攔截器開始攔截");
        filterChain.doFilter(request, response);
    }

}

3.3 編寫 Listener

package net.codingme.boot.listener;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * <p>
 *
 * @Author niujinpeng
 * @Date 2019/1/24 16:45
 */
@Slf4j
@WebListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("監聽器開始初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("監聽器開始銷毀");
    }
}

3.4 添加到容器

添加到容器有兩種方式,第一種使用注解掃描。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;

/**
 * @ServletComponentScan 掃描Servlet,Filter,Listener 添加到容器
 */
@SpringBootApplication
@ServletComponentScan
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }

}

或者使用配置類想容器中添加。

/**
 * <p>
 * 在這里注冊Servlet Filter Listener 或者使用 @ServletComponentScan
 *
 * @Author niujinpeng
 * @Date 2019/1/24 16:30
 */
@Configuration
public class WebCoreConfig {

    @Bean
    public ServletRegistrationBean myServlet() {
        return new ServletRegistrationBean<>(new MyServlet());
    }

    @Bean
    public FilterRegistrationBean myFitler() {
        return new FilterRegistrationBean<>(new MyFilter());
    }

    @Bean
    public ServletListenerRegistrationBean myListener() {
        return new ServletListenerRegistrationBean(new MyListener());
    }
    
}

啟動可以在控制台看到監聽器啟動。

 11:35:03.744  INFO 8616 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1364 ms
 11:35:03.798  INFO 8616 --- [           main] net.codingme.boot.listener.MyListener    : 監聽器開始初始化
 11:35:03.892  INFO 8616 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
 11:35:04.055  INFO 8616 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''

訪問 Servlet 可以看到攔截器和 Servlet 生效。

 11:36:55.552  INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.servlet.MyServlet      : Servlet 開始初始化
 11:36:55.556  INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.filter.MyFilter        : 攔截器開始攔截
 11:36:55.556  INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.servlet.MyServlet      : Servlet 開始處理 GET 方法

文章代碼已經上傳到 GitHub Spring Boot Web開發 - 攔截處理
文章代碼已經上傳到 GitHub Spring Boot Web開發 - Servlet,Filter,Listener

<完>
本文原發於個人博客:https://www.codingme.net 轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM