当前位置: 首页 > 编程日记 > 正文

SpringBoot 使用过滤器、拦截器、切面(AOP),及其之间的区别和执行顺序

首先了解一下SpringMVC的执行流程

img

具体流程如下

  1. 用户发起请求到前端控制器(Controller)
  2. 前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。
  3. HandlerMapping返回执行链,包含了2部分内容: ① Handler对象、② 拦截器数组
  4. 前端处理器通过处理器适配器包装后执行Handler对象。
  5. 处理业务逻辑。
  6. Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
  7. 将ModelAndView返回给前端控制器。
  8. 视图解析器(ViewResolver)返回真正的视图对象(View)。
  9. (此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
  10. 返回渲染后的视图(html/json/xml)返回。
  11. 给用户产生响应。

核心就是DispatcherServlet核心控制器,我们看源码可知道DispatcherServlet是Servlet的子类

img

下面用一张图说一下过滤器、Servlet容器、拦截器、AOP、Controller之间的关系

img

然后具体执行流程如下:

img

拦截器和过滤器的区别

1、拦截器不依赖与servlet容器是SpringMVC自带的,过滤器依赖于Servlet容器。

2、拦截器是基于java的反射机制的,而过滤器是基于函数回调。

3、拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。

4、拦截器可以访问controller上下文、值栈里的对象,而过滤器不能访问。

(拦截器的preHandle方法在进入controller前执行,而拦截器的postHandle方法在执行完controller业务流程后,在视图解析器解析ModelAndView之前执行,可以操控Controller的ModelAndView内容。而afterCompletion是在视图解析器解析渲染ModelAndView完成之后执行的)

( 过滤器是在服务器启动时就会创建的,只会创建一个实例,常驻内存,也就是说服务器一启动就会执行Filter的init(FilterConfig config)方法.当Filter被移除或服务器正常关闭时,会执行destroy方法)

5、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

(关于这句话的解读是:我们知道拦截器是SprinMVC自带的,而SpringMVC存在Controller层的,而controller层可以访问到service层,service层是不能访问service层的,而过滤器是客户端和服务端之间请求与响应的过滤)

6、过滤器和拦截器触发时机、时间、地方不一样

(过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是在servlet处理完后,返回给前端之前,如果看不懂可以看7完后再来理解)

7、过滤器包裹住servlet,servlet包裹住拦截器。




SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)

一、一切要从Servlet说起

1.1什么是Servlet

Servlet(Server Applet),全称是Java Servlet,是提供基于协议请求/响应服务的Java类。

在JavaEE中是Servlet规范,即是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的Java类,一般人们理解是后者。

1.2为什么需要Servlet

最重要的就是,提供动态的Web内容

当向一个Web服务器(如Nginx、IIS、Apache)请求一个资源时,一般提供都是一个静态页面,Web服务器不能做的两件事

不能提供动态即时网页
不能往服务库中保存数据

为了提升用户的体验度,有了Servlet实现动态内容的展示,进而有了JSP动态网页。

1.3Servlet如何响应用户请求

正如前面所说,Servlet是一个Java程序,一个Servlet应用有一个或多个Servlet程序,JSP页面会被转换和编译成Servlet程序。

Servlet应用无法独立运行,必须运行在Servlet容器中。Servlet容器将用户的请求传递给Servlet应用,并将结果返回给用户。

这个Servlet容器就是Tomcat,当然其他的,比如Jetty。

但是值得一提的是Tomcat只是实现了JavaEE13个规范中的Servlet/JSP规范,其他规范没有实现,所以不是一个JavaEE容器

1.4Servlet与Tomcat处理请求的流程

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring

不得不说,这位小哥很有才啊,简要的说下主要的步骤:

  • 1.用户发送一个HTTP请求到Tomcat
  • 2.根据URL找到对应的Servlet类
  • 3.Tomcat从磁盘加载Servlet类到内存,将HTTP请求解析封装成一个ServletRequest实例,且封装一个ServletResponse实例
  • 4.此时Servlet容器调用Servlet的Service方法,并将ServletRequest实例及ServletResponse实例传入方法中
  • 5.方法执行完后将ServletResonse响应给浏览器

1.5Servlet与Controller之间的关系

聪明的你可能已经发现在上述第二步,根据URL找到对应的Servlet类,现在都是通过URL锁定Controller中的方法进行执行,那么Controller是一个Servlet吗?
答案是不是的,这个要分为两个阶段,一个是没有引入SpringMVC框架时,一个是引入SpringMVC框架后

没有引入SpringMVC时,咱们通过在web.xml中配置URL和Serlvet类映射关系

如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <servlet>
        <!--servlet名称,与servlet-mapping中的servlet-name必须一致-->
        <servlet-name>LoginServlet</servlet-name>
        <!--Servlet类的位置-->
        <servlet-class>Jsp_Servlet_login.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <!--servlet名称,与上面中servlet-name必须一致-->
        <servlet-name>LoginServlet</servlet-name>
        <!--servlet名称,与上面中servlet-name必须一致-->
        <url-pattern>/LoginServlet.action</url-pattern>
    </servlet-mapping>
</web-app>

这时通过/LoginServlet.action就可以找到Jsp_Servlet_login.LoginServlet这个类

引入SpringMVC框架后,就有了著名的SpringMVC处理流程图

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_02

看图中标红的两处,DispatcherServlet,也叫前端控制器,是SpringMVC中最后一个Servlet类,Servlet容器将用户请求发送给DispatcherServlet,由DispatcherServlet根据用户的url找到Controller中的方法并执行,这个过程完全可以再写一篇博客的,后续完成,现在大家知道Controller不是Serlvet即可。

1.6敲黑板,重点来了!!

总结上述就是,Servlet容器将用户请求封装了ServletRequest实例及ServletResponse实例,而今天的主题,Filter、Intercepter、Aspect就是可以在用户请求到目标方法前拿到这两个实例,也就是拿到了用户的请求(我在网上查阅资料时,大家说Aspect不能拿到ServleRequest实例及ServletResonse实例,其实是可以拿到的)进行校验、增强,而Aspect更多的是对Controller中方法的增强。

二、过滤器、拦截器、Aspect概览

为什么需要上面三者

如果要回答这个问题,需要从它们三者的共同点入手,那么它们三个有什么共同点呢?没错,它们都是AOP编程思想的落地实现

在spring官方文档中是这样描述AOP的

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)
文档地址
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

大致的意思如下:

面向切面编程(AOP)是面向对象编程(OOP)的一个补充,面向对象编程的基石是类,面向切面编程的基石是切面(Aspect)。切面可以将多个类或者对象都要执行的代码进行模块化(比如事务管理)

再通俗一点的话:

可以用下面的图进行解释

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_用户认证_03

由上图可以看出,权限认证是每个方法都要执行的,并且不是业务代码,因此可以将权限认证的代码抽离出来成为一个切面,今天咱们讨论这三个都可以实现切面,这是它们三的共同点,下面也会围绕AOP展开分享

开始实践环节

三、搭建一个简单springboot项目

1.项目目录结构如下

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_用户认证_04

结构比较简单,新建一个maven工程即可

2.pom及application文件

pom依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.3.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>2.3.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>

application.yml

server:
  port: 8082

3.主启动类

@SpringBootApplication
public class SpringbootFilter {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootFilter.class);
    }
}

好了,一个简单的springboot项目就搭建成功了

四、Springboot中自定义过滤器

1.过滤器基本知识

是什么

过滤器Filter,是在Servlet规范中定义的,是Servlet容器支持的,该接口定义在javax.servlet包下,主要是对客户端请求(HttpServletRequest)进行预处理,以及对服务器响应(HttpServletResponse)进行后处理

Filter接口

package javax.servlet;
import java.io.IOException;
public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {}
}

该接口包含了Filter的3个生命周期:init、doFilter、destroy

init方法

Servlet容器在初始化Filter时,会触发Filter的init方法,一般来说是当服务程序启动时,而且这个方法只调用一次,用于初始化Filter

void init(FilterConfig filterConfig)

其中参数FilterConfig是由Servlet容器传入到init方法中,该参数封装了初始化Filter的参数值,类似于构造函数给对象初始值一样

doFilter方法

当init方法初始化Filter后,Filter拦截到用户请求时,Filter就开始工作了

void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3)

正如前面所说Servlet容器会将用户请求封装成ServletRequest,而doFilter方法参数中就有ServletRequest,这也就意味着允许给ServletRequest增加属性或者增加header,也可以修饰ServletReqest或者ServletResponse来改变其行为(装饰者模式的应用)

请注意最后一个参数FilterChain var3,该接口定义如下

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

该参数存在意味着,到达用户请求的真正方法之前,可能被多个过滤器进行过滤,这时Filter.doFilter()方法将触发Filter链条中下一个Filter。

值得注意的是:只有在Filter链条中最后一个Filter里调用FilterChain.doFilter(),才会触发处理资源的方法(值得验证),如果结尾处没有调用该方法,后面的处理就会中断

destroy方法

void destroy() {}

这个方法就比较简单了,顾名思义,该方法就是在Servlet容器要销毁Filter时触发,一般在应用停止的时候调用

好了,下面开始实践部分

2.springboot中自定义Filter

在springboot中自定义filter主要是两种方式
一个是使用配置类,一个是使用@WebFilter注解, 推荐使用配置类,和spring项目其他组件保持一致,其实配置类也就是@WebFilter注解的变形

2.1使用@WebFilter注解

该注解属于Servlet3.0中的注解,不属于Spring,因此需要在主启动类加上@ServletComponentScan。但是如果定义多个filter,filter的执行顺序需要配置在web.xml或者使用spring的注解order()定义filter执行顺序,所以建议大家还是用配置类

好现在用自定义filter实现一个登陆的小功能

新建一个LoginFilter类

package com.thinkcoer.filter;

import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*",filterName = "LoginFilter",initParams = {
        @WebInitParam(name="includeUrls",value = "/login")
})
public class LoginFilter implements Filter {

    //不需要登录就可以访问的路径(比如:注册登录等)
    private String includeUrls;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //获取初始化filter的参数
        this.includeUrls=filterConfig.getInitParameter("includeUrls");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        String uri = request.getRequestURI();

        System.out.println("filter url:"+uri);

        //不需要过滤直接传给下一个过滤器
        if (uri.equals(includeUrls)) { 
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            //需要过滤器
            // session中包含user对象,则是登录状态
            if(session!=null&&session.getAttribute("user") != null){
                System.out.println("user:"+session.getAttribute("user"));
                filterChain.doFilter(request, response);
            }else{
                response.setContentType("Application/json;charset=UTF-8");
                response.getWriter().write("您还未登录");
                //重定向到登录页(需要在static文件夹下建立此html文件)
                //response.sendRedirect(request.getContextPath()+"/user/login.html");
                return;
            }
        }
    }

    @Override
    public void destroy() {
        log.info("loginfilter销毁方法执行了");
    }
}

该类主要功能是除登陆外url进行拦截,如果登陆成功会产生一个session,并在客户端产生一个cookie,用户请求别的资源会携带cookie进行验证,如果验证通过则可以拿到该资源

新建一个LoginController

@RestController
public class LoginController {

    @PostMapping("/login")
    public String login(@RequestBody User user, HttpServletRequest request){
        HttpSession session = request.getSession();

        if(!user.getName().equals("root")&&!user.getPwd().equals("root")){
            return "用户名或者密码错误!";
        }
        session.setAttribute("user",user);
        return "登录成功";
    }

    @GetMapping("/test")
    public String loginTest(){
        return "登录校验成功";
    }
}

该类中的自定义User类可以自己建一个实体类,这里就不再赘述了

主启动类

加上@ServletComponentScan注解

@ServletComponentScan
@SpringBootApplication
public class SpringbootFilter {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootFilter.class);
    }
}

开始验证

postman发送请求

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_05

进行登录校验

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_06

2.2使用spring中的配置类方式

该方式使用FilterRegistrationBean类注册自定义的Filter类,并为自定义Filter设置初始化参数,下面自定义两个Filter类,一个是用户认证Filter,一个是打印日志Filter,设置优先级顺序用户认证在前,打印日志在后

用户认证Filter(AuthFilter)

@Slf4j
public class AuthFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("用户认证filter init方法执行");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("用户认证doFilter方法执行");
        log.info("处理业务逻辑,改变请求体对象和回复体对象");
        //调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }


    @Override
    public void destroy() {
        log.info("用户认证destroy方法执行");
    }
}

打印日志Filter(LogFilter)

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
        log.info("过滤器初始化时配置"+filterConfig);
        log.info("日志filter init方法执行");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("日志doFilter方法执行");
        log.info("处理业务逻辑,改变请求体对象和回复体对象");
        //调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }


    @Override
    public void destroy() {
        log.info("日志filter destroy方法执行");
    }
}

配置类FilterConfig

注册两个Filter

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean authFilterRegistation(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //注册bean
        registrationBean.setFilter(new AuthFilter());
        //设置bean name
        registrationBean.setName("AuthFilter");
        //拦截所有请求
        registrationBean.addUrlPatterns("/*");
        //执行顺序,数字越小优先级越高
        registrationBean.setOrder(1);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean logFilterRegistation(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new LogFilter());
        registrationBean.setName("LogFilter");
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(2);
        return registrationBean;
    }
}

新建一个LogController用于测试两个Filter类

@Slf4j
@RestController
public class LogController {

    @GetMapping("/log")
    public void testLog(){
        log.info("日志controller方法执行了");
    }
}

开始验证

启动项目

用户认证filter init方法执行
日志filter init方法执行

请求方法

用户认证doFilter方法执行
处理业务逻辑,改变请求体对象和回复体对象
日志doFilter方法执行
处理业务逻辑,改变请求体对象和回复体对象

关闭程序

用户认证destroy方法执行
日志filter destroy方法执行

小总结:

Filter是拦截Request请求的对象,在用户的请求访问资源前处理ServletRequest以及ServletResponse,可以用于日志记录、Session检查等,多个Filter协同工作时可以设置Filter的先后顺序,值得一说的是现在微服务的组件中,底层也是用到了Filter,比如gateway网关、zuul、spring
security等等

好了,关于自定义Filter暂搞一段落,现在用户的请求已经到达了DispatcherServlet(假设用的是SpringMVC),在真正到达Controller类中的方法前,还要经过拦截器

五、Springboot中自定义拦截器

1.拦截器基本知识

是什么

简单一点理解拦截器就是,能够在进行某个操作之前拦截请求,如果请求符合条件就允许向下执行

HandlerInterceptor接口

该接口提供了拦截器的功能,如果自定义拦截器要实现该接口

public interface HandlerInterceptor {

  default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

    return true;
  }

  default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      @Nullable ModelAndView modelAndView) throws Exception {
  }


  default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
      @Nullable Exception ex) throws Exception {
  }

}

该接口的作用,我把这个接口一段注释搬下来,理解一下

A HandlerInterceptor gets called before the appropriate HandlerAdapter
triggers the execution of the handler itself. This mechanism can be used
for a large field of preprocessing aspects, e.g. for authorization checks,
or common handler behavior like locale or theme changes. Its main purpose
is to allow for factoring out repetitive handler code.

大致的意思就是在handler(controller中的方法)执行之前拦截器,这个机制不会产生大量的重复性代码,比如授权检查啊等等,这个第2节写过,就不再赘述了。

下面说下三个方法的功能及执行顺序

(1).preHandle()方法

该方法会在控制器方法前执行,其返回值表示是否中断后续操作。当返回值为true时,表示继续向下执行;当返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。

(2).postHandle()方法

该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。

(3).afterCompletion()方法

该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。

大体执行顺序是preHandle→handler(controller中的方法)→postHandle→afterCompletion

具体可以看接口中方法的注释,写的比较清晰

2.springboot中自定义拦截器

(1)实现HandlerInterceptor接口

@Slf4j
public class AuthIntercepter implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        log.info("用户认证拦截器preHandle方法执行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("用户认证拦截器postHandle方法执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("用户认证拦截器afterCompletion方法执行");
    }
}

(2)向spring注册拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //需要拦截的路径,/**表示拦截所有请求
        String[] addPathPatterns={"/**"};
        //不需要拦截的路径
        String[] excludePathPatterns={"/boot/login","/boot/exit"};

        registry.addInterceptor(new AuthIntercepter())
                .addPathPatterns(addPathPatterns)
                .excludePathPatterns(excludePathPatterns);
    }
}

(3).测试

public class LoginController {

    @ResponseBody
    @GetMapping("/test")
    public void loginTest(){
        log.info("handler方法执行");
    }
}

(4).测试结果

2021-01-03 14:20:01.597  INFO 22664 --- [nio-8082-exec-2] c.thinkcoer.interceptor.AuthIntercepter  : 用户认证拦截器preHandle方法执行
2021-01-03 14:20:01.605  INFO 22664 --- [nio-8082-exec-2] c.thinkcoer.controller.LoginController   : handler方法执行
2021-01-03 14:20:01.616  INFO 22664 --- [nio-8082-exec-2] c.thinkcoer.interceptor.AuthIntercepter  : 用户认证拦截器postHandle方法执行
2021-01-03 14:20:01.617  INFO 22664 --- [nio-8082-exec-2] c.thinkcoer.interceptor.AuthIntercepter  : 用户认证拦截器afterCompletion方法执行

可以验证下面的执行顺序

preHandle→handler(controller中的方法)→postHandle→afterCompletion

其实在DispatcherServlet的doDispatch方法中也可以看出来

//如果preHandler方法返回false,则直接return结束请求
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  return;
}

// 执行controller中的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
  return;
}

applyDefaultViewName(processedRequest, mv);
//执行postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

3.过滤器与拦截器比较

相同点

  1. 都是AOP编程思想体现
  2. 都能实现权限检查、日志记录等

不同点:

  • 1.Filter(过滤器)属于Servlet规范,拦截器属于spring容器

从这里可以延伸出,拦截器可以拿到spring容器各种bean,而过滤器是拿不到的,除非将Filter本身交给spring管理,但是经过测试doFilter方法会执行两遍

  • 2.Filter(过滤器)和拦截器执行顺序不同,Filter要先于拦截器执行

4.多个过滤器与多个拦截器协同工作

(1)在上面代码基础上新建LogInterceptor类

@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("日志拦截器preHandle方法执行");
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        log.info("日志拦截器postHandle方法执行");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        log.info("日志拦截器afterCompletion方法执行");
    }
}

(2)在InterceptorConfig类中注册

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //需要拦截的路径,/**表示拦截所有请求
        String[] addPathPatterns={"/**"};
        //不需要拦截的路径
        String[] excludePathPatterns={"/boot/login","/boot/exit"};

        registry.addInterceptor(new AuthIntercepter())
                .addPathPatterns(addPathPatterns)
                .excludePathPatterns(excludePathPatterns);
        //新注册的过滤器
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns(addPathPatterns)
                .excludePathPatterns(excludePathPatterns);
    }
}

(3).总体项目结构

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_07

(4).测试

打印日志如下

用户认证doFilter方法执行
处理业务逻辑,改变请求体对象和回复体对象
日志doFilter方法执行
处理业务逻辑,改变请求体对象和回复体对象
用户认证拦截器preHandle方法执行
日志拦截器preHandle方法执行
handler方法执行
日志拦截器postHandle方法执行
用户认证拦截器postHandle方法执行
日志拦截器afterCompletion方法执行
用户认证拦截器afterCompletion方法执行
用户认证filter destroy方法执行
日志filter destroy方法执行

用下面的图表示

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_08

注意:

  • filter的init方法和destroy方法在应用程序整个生命周期(从启动到关闭)中,只执行一次
  • afterCompletion方法一个用户请求最后执行的方法

六、SpringBoot中使用Aspect

1.基本知识

AOP、Spring AOP、Aspect的关系
首先AOP是编程思想,SpringAOP是AOP的实现,实现AOP不止SpringAOP一种,而Aspect是SpringAOP的一种实现方式,还有一种是xml配置

2.AOP相关术语

AOP并不是Spring中特有的概念,所以AOP有相关的术语去描述AOP

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_09

对于导图左边部分了解即可,重点是右边部分,要理解切面、通知、连接点、切点之间的关系,所以对于Spring AOP切面的使用,可以总结如下

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_10

3.SpringAOP如何定位切点

通过切点表达式,SpringAOP支持的表达式类型还是比较多的,主要说下execution表达式

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_11

下面说下Spring官网上比较难理解的两个例子

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_12

当然还有其他表达式,详见spring官网

4.开始实践

终于到了实践部分,下面会使用上面的步骤,用AOP实现一个用户认证的小例子

(1)引入maven坐标

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
     <version>2.1.1.RELEASE</version>
 </dependency>

(2)定义切面

新建一个AuthAspect切面类,用于用户认证功能

@Slf4j
@Aspect
@Component
@Order(1) //指定切面类执行顺序,数字越小越先执行
public class AuthAspect {

    @Pointcut(value = "execution(* com.*.controller.*.*(..))")
    public void authPointCut(){ }

    @Before(value = "authPointCut()")
    public void doBefore(JoinPoint point){
        log.info("【用户认证切面:Before方法执行了】");
    }

    @Around(value = "authPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("【用户认证切面:执行目标方法前Around方法执行】");
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String serverName = request.getServerName();
        String queryString = request.getQueryString();
        //拿到HttpServletRequest对象就可以对权限进行校验
        //如果校验不通过,直接 return null即可,就不会请求到控制器方法
        Object proceed = joinPoint.proceed();
        log.info("【用户认证切面:执行目标方法后Around方法执行】");
        return proceed;
    }

    @After(value = "authPointCut()")
    public void doAfter(){
        log.info("【用户认证切面:After方法执行】");
    }

    @AfterReturning(returning = "ret",value = "authPointCut()")
    public void doAfterReturn(JoinPoint joinPoint,Object ret){
        log.info("【用户认证切面:AfterReturning方法执行】");
    }

    @AfterThrowing(value = "authPointCut()",throwing ="throwable")
    public void doAfterThrowing(Throwable throwable){
        log.info("【用户认证切面:AfterThrowing方法执行】");
    }
}

在上述代码Around方法中可以看出,是可以拿到用户请求的HttpServletRequest对象的

定义切面类的注意点

  • Around环绕通知中参数类型只能是ProceedingJoinPoint,不能是JoinPoint,因为JoinPoint中没有proceed方法,也就是说执行不了控制器中的方法
  • 注意在AfterThrowing及After注解中不能有JoinPoint参数

(3)测试类

@Slf4j
@RestController
public class LogController {

    @GetMapping("/log")
    public void testLog(String name,String age){
        log.info("日志controller方法执行了");
    }
}

(4)请求结果

【用户认证切面:执行目标方法前Around方法执行】
【用户认证切面:Before方法执行了】
日志controller方法执行了
【用户认证切面:执行目标方法后Around方法执行】
【用户认证切面:After方法执行】
【用户认证切面:AfterReturning方法执行】

值得注意的是,在切面中首先执行的不是Before前置通知,而是Around环绕通知proceed方法之前的代码

(5)用图表示

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_13

那么定义多个切面执行顺序又是怎样呢?

(6)多个切面协同工作

新建一个LogAspect,用于打印日志

@Aspect
@Slf4j
@Component
@Order(2)
public class LogAspect {

    @Pointcut(value = "execution(* com..controller..*(..)) ")
    public void logPointCut(){ }

    /***
     *方法前执行
     * @param joinPoint
     * @return
     */
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        log.info("【日志切面:Before方法执行了】");
        StringBuilder str = this.getMethodInfo(joinPoint);
        if (CollectionUtils.arrayToList(joinPoint.getArgs()).isEmpty()) {
            str.append("该方法无参数");
        } else {
            StringBuilder strArgs = new StringBuilder("【请求参数】:");
            for (Object o : joinPoint.getArgs()) {
                strArgs.append(o + ",");
            }
            str.append(strArgs);
        }
        log.info(str.toString());
    }


    /***
     * 于Before增强处理和AfterReturing增强,
     * Around增强处理可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标方法的执行
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        log.info("【日志切面:执行目标方法前Around方法执行】");
        StringBuilder sb = this.getMethodInfo(point);

        long startTime = System.currentTimeMillis();
        //执行方法
        Object returnVal = point.proceed();
        //计算耗时
        long elapsedTime = System.currentTimeMillis() - startTime;
        log.info("【日志切面:执行目标方法后Around方法执行】");
        sb.append("【请求消耗时长" + elapsedTime + "ms】");

        log.info(sb.toString());
        return returnVal;
    }

    //注意在AfterThrowing及After注解中不能有JoinPoint参数
    @After(value = "logPointCut()")
    public void doAfter(){
        log.info("【日志切面:After方法执行了】");
    }

    /***
     * 方法执行完后执行
     * @param point
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "logPointCut()")
    public void doAfterReturning(JoinPoint point,Object ret) {
        log.info("【日志切面:AfterReturning方法执行了】");
        StringBuilder sb = this.getMethodInfo(point);
        if(ObjectUtils.isEmpty(ret)){
            sb.append("【请求返回结果没有返回值】");
        }else{
            sb.append("【请求返回结果】:"+ret.toString());
        }

        log.info(sb.toString());
    }

    /***
     * 请求方法信息
     * @param point
     */
    private StringBuilder getMethodInfo(JoinPoint point){
        StringBuilder sb = new StringBuilder();
        sb.append("【方法名】"+point.getSignature().getDeclaringTypeName()+"."+point.getSignature().getName());
        return sb;
    }

    @AfterThrowing(value = "logPointCut()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        log.info("【日志切面:AfterThrowing方法执行了】");
        // 保存异常日志记录
        log.error("发生异常时间:{}" +new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
        log.error("抛出异常:{}" + throwable.getMessage());
    }
}

测试结果

【用户认证切面:执行目标方法前Around方法执行】
【用户认证切面:Before方法执行了】
【日志切面:执行目标方法前Around方法执行】
【日志切面:Before方法执行了】
日志controller方法执行了
【日志切面:执行目标方法后Around方法执行】
【日志切面:After方法执行了】
【日志切面:AfterReturning方法执行了】
【用户认证切面:执行目标方法后Around方法执行】
【用户认证切面:After方法执行】
【用户认证切面:AfterReturning方法执行】

咱们也来画一个图更加直观的看下效果

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_14

七、Filter、Intercepter、Spring AOP大总结

1.三者共同点与区别

共同点

  • 三者都是AOP思想体现
  • 都可以对HttpServletRequest对象进行处理,日志、权限控制等

区别

  • Filter属于Servlet规范,Intercepter、Spring AOP属于Spring框架
  • 实现AOP的方式不同,Filter用回调函数实现,一般情况下拿不到Spring bean对象,Intercepter用责任链实现,Spring AOP基于动态代理

2.三者应用场景

先大致说下下,用户的请求的顺序,下面有更详细的,先到Servlet容器,然后过滤器→servlet(DispatcherServlet)→拦截器→SpringAOP→Controller

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_拦截器_15

再写下在Spring AOP如何拿到http请求和响应对象

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

3.三者执行顺序

将上面的程序一起运行,得到下面的日志

用户认证doFilter方法执行
日志doFilter方法执行
用户认证拦截器preHandle方法执行
日志拦截器preHandle方法执行
【用户认证切面:执行目标方法前Around方法执行】
【用户认证切面:Before方法执行了】
【日志切面:执行目标方法前Around方法执行】
【日志切面:Before方法执行了】
日志controller方法执行了
【日志切面:执行目标方法后Around方法执行】
【日志切面:After方法执行了】
【日志切面:AfterReturning方法执行了】
【用户认证切面:执行目标方法后Around方法执行】
【用户认证切面:After方法执行】
【用户认证切面:AfterReturning方法执行】
日志拦截器postHandle方法执行
用户认证拦截器postHandle方法执行
日志拦截器afterCompletion方法执行
用户认证拦截器afterCompletion方法执行

用下面一幅图表示

SpringBoot中AOP实现落地——Filter(过滤器)、Intercepter(拦截器)、Aspect(Spring AOP)_spring_16

参考文献

【1】.扬俊的小屋

【2】.Servlet、JSP和Spring MVC初学指南 【加】Buid Kurniawan 【美】Paul Deck 著 林仪明 俞黎敏 译 中国工信出版社

【3】springboot 过滤器Filter vs 拦截器Interceptor vs 切片Aspect 详解

【4】Spring Aop实例@Aspect、@Before、@AfterReturning@Around 注解方式配置



listener、filter、servlet、interceptor在springboot中执行顺序

1.前提

需要在springboot中提前分别注册listenerfilterservletinterceptor,代码可参考以下文章:


2.测试

本地访问http://localhost:8080/my,查看日志信息,如下:

2020-11-18 21:07:09.523  INFO 6536 --- [ost-startStop-1] com.xzl.spire.filter.MyFilter            : MyFilter...init
2020-11-18 21:07:09.777  INFO 6536 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-11-18 21:07:10.065  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@168660: startup date [Wed Nov 18 21:07:06 CST 2020]; root of context hierarchy
2020-11-18 21:07:10.150  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/book],methods=[GET]}" onto public com.xzl.spire.model.Book com.xzl.spire.controller.BookController.book()
2020-11-18 21:07:10.152  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/delete/{id}],methods=[POST]}" onto public java.lang.String com.xzl.spire.controller.BookController.deleteBook(java.lang.Long)
2020-11-18 21:07:10.154  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getUserById],methods=[GET]}" onto public java.lang.String com.xzl.spire.controller.UserController.getUserById(java.lang.Integer)
2020-11-18 21:07:10.154  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/deleteUserById],methods=[GET]}" onto public void com.xzl.spire.controller.UserController.deleteUserById(java.lang.Integer)
2020-11-18 21:07:10.156  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2020-11-18 21:07:10.156  INFO 6536 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-11-18 21:07:10.219  INFO 6536 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-11-18 21:07:10.219  INFO 6536 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-11-18 21:07:10.474  INFO 6536 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-11-18 21:07:10.515  INFO 6536 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-11-18 21:07:10.520  INFO 6536 --- [           main] com.xzl.spire.SpireApplication           : Started SpireApplication in 4.597 seconds (JVM running for 8.211)
2020-11-18 21:09:18.635  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.listener.MyListener        : MyListener...requestInitialized
2020-11-18 21:09:18.650  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.filter.MyFilter            : MyFilter...doFilter
2020-11-18 21:09:18.651  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.servlet.MyServlet          : doGet...
2020-11-18 21:09:18.662  INFO 6536 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2020-11-18 21:09:18.663  INFO 6536 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2020-11-18 21:09:18.687  INFO 6536 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 24 ms
2020-11-18 21:09:18.695  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.interceptor.MyInterceptor  : MyInterceptor>>>preHandle
2020-11-18 21:09:18.731  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.interceptor.MyInterceptor  : MyInterceptor>>>postHandle
2020-11-18 21:09:18.790  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.interceptor.MyInterceptor  : MyInterceptor>>>afterCompletion
2020-11-18 21:09:18.815  INFO 6536 --- [nio-8080-exec-1] com.xzl.spire.listener.MyListener        : MyListener...requestDestroyed

再down掉项目后,新产生的日志信息如下:

2020-11-18 21:31:20.558  INFO 6536 --- [      Thread-18] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@168660: startup date [Wed Nov 18 21:07:06 CST 2020]; root of context hierarchy
2020-11-18 21:31:20 JRebel: Reconfiguring bean 'myWebMvcConfig' [com.xzl.spire.config.MyWebMvcConfig$$EnhancerBySpringCGLIB$$5db458ff]
2020-11-18 21:31:20.889  INFO 6536 --- [      Thread-18] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2020-11-18 21:31:21.039  INFO 6536 --- [ost-startStop-2] com.xzl.spire.filter.MyFilter            : MyFilter...destroy

3.结论

执行顺序如下:
1.项目 run时,filter开始init
2.浏览器发送get请求,listener开始init
3.filter执行doFilter方法
4.servlet执行doGet方法
5.interceptor开始依次执行preHandlepostHandleafterCompletion方法
6.listener执行requestDestroyed方法,get请求结束
7.项目down掉,filter执行destroy方法

相关文章:

并发编程下的集合:数组寻址、LinkedList、HashMap、ConcurrentHashMap

如果发现hash取模后的数组索引位下无元素则直接新增,若不是空那就说明存在hash冲突,则判断数组索引位链表结构中的第一个元素的key以及hash值是否与新的key一致则直接覆盖,若不一致则判断当前的数组索引下的链表结构是否为红黑树,若为红黑树则走红黑树的新增方法,若不为红黑树则遍历当前链表结构,遍历中发现某个节点元素的next为null是则直接将新元素指针与next进行关联,若在遍历到next为空前判断到,某个节点的key以及key的hash值与新的key与新的keyhash值一致时则走覆盖。

【日常开发之插件篇】IDEA plugins 神器助我!!

今早因为老代码的一些bug让我突然觉得Idea的一些插件特别好用,我准备将我平时所用到的一些插件做个推荐以及记录。

【日常开发之FTP】Windows开启FTP、Java实现FTP文件上传下载

FTP是一个专门进行文件管理的操作服务,一般来讲可以在任意的操作系统之中进行配置,但是如果考虑到简便性,一般来讲可以直接在Linux系统下进行安装。FTP (File Transfer Protocol、文件传输协议)是TCP/IP协议中的一部分,属于应用层协议。使用FTP最主要的功能是对文件进行管理,所以在FTP内部对于文件支持有两种传输模式:文本模式(ASCII、默认)和二进制模式(Binary),通常文本文件使用ASCIl模式,而对于图片、视频、声音、压缩等文件则会使用二进制的方式进行传输。

【Linux之升华篇】Linux内核锁、用户模式与内核模式、用户进程通讯方式

alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。字符设备描述符 struct cdev,cdev_alloc()用于动态的分配 cdev 描述符,cdev_add()用于注。外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD。从最初的原子操作,到后来的信号量,从。(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的。

【Mongdb之数据同步篇】什么是Oplog、Mongodb 开启oplog,java监听oplog并写入关系型数据库、Mongodb动态切换数据源

oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的。每个节点都有oplog,记录这从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。Oplog 可以说是Mongodb Replication的纽带了。

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载

下拉框选择你选择的用户点击添加,然后共享确定。创建一个文件夹然后点击属性界面,点击共享。maven版本存在于SMB协议的兼容问题。首先开启服务,打开控制面板点击程序。点击启用或关闭Windows功能。我这边是专门创建了一个用户。SMB1.0选中红框内的。

Rust XTask 模式介绍与应用

XTask(扩展任务)是一种在Rust项目中定义和执行自定义构建任务的方式。它通过创建一个独立的Rust库或二进制项目来封装这些任务,利用Rust语言的强类型、安全性和跨平台能力,使得构建流程更加健壮、可读和可维护。

CXFServlet类的作用

CXFServlet是Apache CXF框架中的一个核心组件,用于处理HTTP请求并将它们转换为Web服务调用。通过配置CXFServlet,你可以轻松地部署和管理SOAP和RESTful Web服务。

@Scheduled注解的scheduler属性什么作用

注解是 Spring Framework 提供的一种机制,用于定义计划任务,即周期性执行的任务。 注解可以应用于方法上,以指示 Spring 容器在特定的时间间隔或按照某种调度规则来调用该方法。 属性是 注解的一个可选属性,它的作用是允许开发者指定一个自定义的 对象来控制任务的调度方式。默认情况下, 注解使用 Spring 内部的 来执行任务,但如果需要更高级的定制化需求,可以通过 属性指定一个自定义的 实现。自定义调度器:共享调度器资源:高级调度需求:假设你想使用 作为调度器,并且希望所有带有

过滤器、拦截器、aop的先后顺序和作用范围&拦截器preHandle(),postHandle(),afterComplation()方法执行顺序

在Spring框架中,过滤器(Filter)、拦截器(Interceptor)和面向切面编程(AOP)都是用于处理请求和处理流程的组件,但它们的作用范围和触发时机有所不同。下面我会解释这三者的先后顺序和作用范围。执行顺序:请注意,这个顺序可能因具体的配置和使用的技术而有所不同。在实际应用中,建议根据项目的具体需求来合理配置和使用这些组件。拦截器执行流程图:实现拦截器需要实现这个接口,这个 接口中有三个默认方法,这三个方法的执行顺序:我们实现接口然后重写这三个方法,就会在对应的时机被自动执行。这里就是调用处理

Zookeeper概要、协议、应用场景

Zoopkeeper提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型,作为分布式系统的沟通调度桥梁。

spring.factories文件的作用

即spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。spring.factories文件是Spring Boot自动配置的核心文件之一,它的作用是。

Spring事务七大传播机制与五个隔离级别,嵌套事务

如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。

常见的七种加密算法及实现

**数字签名**、**信息加密** 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、`oauth` 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 **签名加密算法** 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。## 正文### 1. 数字签名**数字签名**,简单来说就是通过提供 **可鉴别** 的 **数字信息** 验证 **自身身份** 的一种方式。一套 **数字签名** 通常定义两种 **互补

7min到40s:SpringBoot 启动优化实践

然后重点排查这些阶段的代码。先看下。

SpringBoot系列教程之Bean之指定初始化顺序的若干姿势

之前介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?本文将介绍几种可行的方式来控制 bean 之间的加载顺序。

在Java中使用WebSocket

WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。

3种方案,模拟两个线程抢票

在多线程编程中,资源竞争是一个常见的问题。资源竞争发生在多个线程试图同时访问或修改共享资源时,可能导致数据不一致或其他并发问题。在模拟两个线程抢票的场景中,我们需要考虑如何公平地分配票,并确保每个线程都有机会成功获取票。本篇文章将通过三种方式来模拟两个线程抢票的过程,以展示不同的并发控制策略。使用 Synchronized 来确保一次只有一个线程可以访问票资源。使用 ReentrantLock 来实现线程间的协调。使用 Semaphore 来限制同时访问票的线程数量。

替代Druid,HakariCP 为什么这么快?

这次源码探究,真的感觉看到了无数个小细节,无数个小优化,积少成多。平时开发过程中,一些小的细节也一定要“扣”。

Java中volatile 的使用场景有哪些?

volatile是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性。所以使用volatilevolatile。

JDK22 正式发布了 !

Java 22 除了推出了新的增强功能和特性,也获得 Java Management Service (JMS) 的支持,这是一项新的 Oracle 云基础设施远程软件服务(Oracle Cloud Infrastructure, OCI) 原生服务,提供统一的控制台和仪表盘,帮助企业管理本地或云端的 Java 运行时和应用。使包含运行时计算值的字符串更容易表达,简化 Java 程序的开发工作,同时提高将用户提供的值编写成字符串,并将字符串传递给其他系统的程序的安全性。支持开发人员自由地表达构造器的行为。

Jackson 用起来!

你可以创建自定义序列化器和反序列化器以自定义特定字段或类的序列化和反序列化行为。为此,请创建一个实现或接口的类,并在需要自定义的字段或类上使用和注解。@Override// ...其他代码...优势性能优异:Jackson在序列化和反序列化过程中表现出优秀的性能,通常比其他Java JSON库更快。灵活性:通过注解、自定义序列化器/反序列化器等功能,Jackson提供了丰富的配置选项,允许你根据需求灵活地处理JSON数据。易于使用:Jackson的API设计简洁明了,易于学习和使用。

拜托!别再滥用 ! = null 判空了!!

另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。最终,项目中会存在大量判空代码,多么丑陋繁冗!,而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接。

详解Java Math类的toDegrees()方法:将参数从弧度转换为角度

Java Math 类的 toDegrees() 方法是将一个角度的弧度表示转换为其度表示,返回值为double类型,表示从弧度数转换而来的角度数。这就是Java Math 类的 toDegrees() 方法的攻略。我们已经了解了该方法的基本概念、语法、注意事项以及两个示例。希望这篇攻略对你有所帮助。

SpringBoot接口防抖(防重复提交)的一些实现方案

作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。啥是防抖所谓防抖,一是防用户手抖,二是防网络抖动。

公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!

前段时间,同事在代码中KW扫描的时候出现这样一条:上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?

每天一个摆脱if-else工程师的技巧——优雅的参数校验

在日常的开发工作中,为了程序的健壮性,大部分方法都需要进行入参数据校验。最直接的当然是在相应方法内对数据进行手动校验,但是这样代码里就会有很多冗余繁琐的if-else。throw new IllegalArgumentException("用户姓名不能为空");throw new IllegalArgumentException("性别不能为空");throw new IllegalArgumentException("性别错误");

SpringBoot请求转发与重定向

但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SSO 单点登录和 OAuth2.0 有何区别?

此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

TCP协议-TCP连接管理

TCP协议是 TCP/IP 协议族中一个非常重要的协议。它是一种面向连接、提供可靠服务、面向字节流的传输层通信协议。TCP(Transmission Control Protocol,传输控制协议)。