springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

SpringBoot中可以使用Intercept和Filter两种方式来实现拦截器。本文主要给大家讲解一下这两种方式的实现方式以及Filter和Intercept的区别。

Intercept实现方式

先定义一个拦截器类,需要继承HandlerInterceptor接口,点击这个接口可以看到有三个方法可以被实现,之所以说可以被实现,因为这三个方法被default修饰了,也就是非必须实现,接口里面已经有了这个接口的默认实现,这是java8的新特性。

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

所以当我们创建一个自定义的拦截器类的时候,是可以选择是否去实现其中的方法,比如大多数的情况下,我们只需要实现preHandler这个方法就可以满足我们的业务了,那么postHandle和afterCompletion方法就不需要实现了:

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

定义好拦截器类后,需要把这个拦截器加载到拦截器链中,所以我们需要WebConfig类,这个类需要实现WebMvcConfigurer接口类:

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

ok,一个拦截器就添加好了~

WebMvcConfigurer的作用就是为我们实现了通用的拦截器,我们如果想添加一些业务相关的拦截器,那么就实现这个接口类,然后像上面这样操作即可。

Filter实现方式

Filter实现还是之前的Servlet方式,先定义一个TestFilter类继承Filter接口:

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

然后再定义一个FilterConf类,把这个TestFilter注册到过滤器链中:

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

大家可以看到这里面有一个FilterRegistrationBean类,这个类就是SpringBoot为大家提供的过滤器,只要把你自定义的Filter添加到这里来就可以了,大家也可以自己去实现以下,然后点击上面这些方法看看FilterRegistrationBean为我们做了哪些事情。

如果有多个过滤器怎么办呢?

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

这个方法就是设置过滤器的优先级,可以根据业务需要定制自己的拦截器的执行顺序。

Filter和Intercept的区别

上面就是为大家带来的在SpringBoot中实现拦截器的两种方式,通常我们在做API项目的时候,可以在接口请求之前校验前端传递过来的token是否合法,也可以在这里设置是否允许跨域。跨域的知识大家有兴趣也可以学习一下,还是很有意思的。

这里再和大家一起学习下过滤器和拦截器的区别,面试中也经常会有这样的面试题:

  1. Filter是基于函数回调,而Intercept是基于java的反射机制;

  2. Filter是servlet的技术,而Intercept是注册在spring容器中,它不依赖servlet容器

  3. Filter可以拦截几乎所有的请求(包含对静态资源的请求),而Intercept只拦截action请求(不拦截静态资源请求)

  4. Filter不能访问action上下文及值栈里的对象,而Intercept都是可以的;

  5. Intercept可以获取spring容器里的对象,而Filter是不行的;

  6. Intercept在action的生命周期内是可以多次调用,而Filter只在容器初始化时被调用一次。

了解了以上的区别之后,相信大家在实际开发使用过程中便可以根据不同的业务场景具体是选择Filter还是Intercept就能心中有数了。


以上就是我为大家带来的如何在SpringBoot中定制拦截器的相关知识,如有问题大家可以随时私信我。

我是【java架构设计】,欢迎大家评论、点赞!关注我,持续为您提供科技领域优质内容!

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

Spring是如何运用设计模式的?

关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,工厂方法模式以及Builder模式。

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

1. 策略模式(Java自学网【javazx.com】)

关于策略模式的使用方式,在Spring中其实比较简单,从本质上讲,策略模式就是一个接口下有多个实现类,而每种实现类会处理某一种情况。我们以发奖励为例进行讲解,比如我们在抽奖系统中,有多种奖励方式可供选择,比如积分,虚拟币和现金等。在存储时,我们必然会使用一个类似于type的字段用于表征这几种发放奖励的,那么这里我们就可以使用多态的方式进行奖励的发放。比如我们抽象出一个PrizeSender的接口,其声明如下:

public interface PrizeSender {

/**

* 用于判断当前实例是否支持当前奖励的发放

*/

boolean support(SendPrizeRequest request);

/**

* 发放奖励

*/

void sendPrize(SendPrizeRequest request);

}

该接口中主要有两个方法:support()和sendPrize(),其中support()方法主要用于判断各个子类是否支持当前类型数据的处理,而sendPrize()则主要是用于进行具体的业务处理的,比如这里奖励的发放。下面就是我们三种不同类型的奖励发放的具体代码:

// 积分发放

@Component

public class PointSender implements PrizeSender {

@Override

public boolean support(SendPrizeRequest request) {

return request.getPrizeType() == PrizeTypeEnum.POINT;

}

@Override

public void sendPrize(SendPrizeRequest request) {

System.out.println("发放积分");

}

}

// 虚拟币发放

@Component

public class VirtualCurrencySender implements PrizeSender {

@Override

public boolean support(SendPrizeRequest request) {

return PrizeTypeEnum.VIRTUAL_CURRENCY == request.getPrizeType();

}

@Override

public void sendPrize(SendPrizeRequest request) {

System.out.println("发放虚拟币");

}

}

// 现金发放

@Component

public class CashSender implements PrizeSender {

@Override

public boolean support(SendPrizeRequest request) {

return PrizeTypeEnum.CASH == request.getPrizeType();

}

@Override

public void sendPrize(SendPrizeRequest request) {

System.out.println("发放现金");

}

}

这里可以看到,在每种子类型中,我们只需要在support()方法中通过request的某个参数来控制当前request是否是当前实例能够处理的类型,如果是,则外层的控制逻辑就会将request交给当前实例进行处理。关于这个类的设计,有几个点需要注意:

使用@Component注解对当前类进行标注,将其声明为Spring容器所管理的一个bean;

声明一个返回boolean值的类似于support()的方法,通过这个方法来控制当前实例是否为处理目标request的实例;

声明一个类似于sendPrize()的方法用于处理业务逻辑,当然根据各个业务的不同声明的方法名肯定是不同的,这里只是一个对统一的业务处理的抽象;

无论是support()方法还是sendPrize()方法,都需要传一个对象进行,而不是简简单单的基本类型的变量,这样做的好处是后续如果要在Request中新增字段,那么就不需要修改接口的定义和已经实现的各个子类的逻辑;

2. 工厂方法模式

上面我们讲解了如何使用Spring来声明一个策略模式,那么如何为不同的业务逻辑来注入不同的bean呢,或者说外层的控制逻辑是什么样的,这里我们就可以使用工厂方法模式了。所谓的工厂方法模式,就是定义一个工厂方法,通过传入的参数,返回某个实例,然后通过该实例来处理后续的业务逻辑。一般的,工厂方法的返回值类型是一个接口类型,而选择具体子类实例的逻辑则封装到了工厂方法中了。通过这种方式,来将外层调用逻辑与具体的子类的获取逻辑进行分离。如下图展示了工厂方法模式的一个示意图:

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

可以看到,工厂方法将具体实例的选择进行了封装,而客户端,也就是我们的调用方只需要调用工厂的具体方法获取到具体的事例即可,而不需要管具体的实例实现是什么。上面我们讲解了Spring中是如何使用策略模式声明处理逻辑的,而没有讲如何选择具体的策略,这里我们就可以使用工厂方法模式。如下是我们声明的一个PrizeSenderFactory:

@Component

public class PrizeSenderFactory {

@Autowired

private List prizeSenders;

public PrizeSender getPrizeSender(SendPrizeRequest request) {

for (PrizeSender prizeSender : prizeSenders) {

if (prizeSender.support(request)) {

return prizeSender;

}

}

throw new UnsupportedOperationException("unsupported request: " + request);

}

}

这里我们声明一个了一个工厂方法getPrizeSender(),其入参就是SendPrizeRequest,而返回值是某个实现了PrizeSender接口的实例,可以看到,通过这种方式,我们将具体的选择方式下移到了具体的子类中的,因为当前实现了PrizeSender的bean是否支持当前request的处理,是由具体的子类实现的。在该工厂方法中,我们也没有任何与具体子类相关的逻辑,也就是说,该类实际上是可以动态检测新加入的子类实例的。这主要是通过Spring的自动注入来实现的,主要是因为我们这里注入的是一个List,也就是说,如果有新的PrizeSender的子类实例,只要其是Spring所管理的,那么都会被注入到这里来。下面就是我们编写的一段用于测试的代码来模拟调用方的调用:

@Service

public class ApplicationService {

@Autowired

private PrizeSenderFactory prizeSenderFactory;

public void mockedClient() {

SendPrizeRequest request = new SendPrizeRequest();

request.setPrizeType(PrizeTypeEnum.POINT); // 这里的request一般是根据数据库或外部调用来生成的

PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);

prizeSender.sendPrize(request);

}

}

在客户端代码中,首先通过PrizeSenderFactory获取一个PrizeSender实例,然后通过其sendPrize()方法发放具体的奖励,通过这种方式,将具体的奖励发放逻辑与客户端调用进行了解耦。而且根据前面的讲解,我们也知道,如果新增了一种奖励方式,我们只需要声明一个新的实现了PrizeSender的bean即可,而不需要对现有代码进行任何修改。

3. Builder模式

关于Builder模式,我想使用过lombok的同学肯定会说builder模式非常的简单,只需要在某个bean上使用@Builder注解进行声明即可,lombok可以自动帮我们将其声明为一个Builder的bean。关于这种使用方式,本人不置可否,不过就我的理解,这里主要有两个点我们需要理解:

Builder模式就其名称而言,是一个构建者,我更倾向于将其理解为通过一定的参数,通过一定的业务逻辑来最终生成某个对象。如果仅仅只是使用lombok的这种方式,其本质上也还是创建了一个简单的bean,这个与通过getter和setter方式构建一个bean是没有什么大的区别的;

在Spring框架中,使用设计模式最大的问题在于如果在各个模式bean中能够注入Spring的bean,如果能够注入,那么将大大的扩展其使用方式。因为我们就可以真的实现通过传入的简单的几个参数,然后结合Spring注入的bean进行一定的处理后,以构造出我们所需要的某个bean。显然,这是lombok所无法实现的;

关于Builder模式,我们可以以前面奖励发放的SendPrizeRequest的构造为例进行讲解。在构造request对象的时候,必然是通过前台传如的某些参数来经过一定的处理,最后生成一个request对象。那么我们就可以使用Builder模式来构建一个SendPrizeRequest。这里假设根据前台调用,我们能够获取到prizeId和userId,那么我们就可以创建一个如下的SendPrizeRequest:

public class SendPrizeRequest {

private final PrizeTypeEnum prizeType;

private final int amount;

private final String userId;

public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {

this.prizeType = prizeType;

this.amount = amount;

this.userId = userId;

}

@Component

@Scope("prototype")

public static class Builder {

@Autowired

PrizeService prizeService;

private int prizeId;

private String userId;

public Builder prizeId(int prizeId) {

this.prizeId = prizeId;

return this;

}

public Builder userId(String userId) {

this.userId = userId;

return this;

}

public SendPrizeRequest build() {

Prize prize = prizeService.findById(prizeId);

return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);

}

}

public PrizeTypeEnum getPrizeType() {

return prizeType;

}

public int getAmount() {

return amount;

}

public String getUserId() {

return userId;

}

}

这里就是使用Spring维护一个Builder模式的示例,具体的 维护方式就是在Builder类上使用@Component和@Scope注解来标注该Builder类,这样我们就可以在Builder类中注入我们所需要的实例来进行一定的业务处理了。关于该模式,这里有几点需要说明:

在Builder类上必须使用@Scope注解来标注该实例为prototype类型,因为很明显,我们这里的Builder实例是有状态的,无法被多线程共享;

在Builder.build()方法中,我们可以通过传入的参数和注入的bean来进行一定的业务处理,从而得到构建一个SendPrizeRequest所需要的参数;

Builder类必须使用static修饰,因为在Java中,如果内部类不用static修饰,那么该类的实例必须依赖于外部类的一个实例,而我们这里本质上是希望通过内部类实例来构建外部类实例,也就是说内部类实例存在的时候,外部类实例是还不存在的,因而这里必须使用static修饰;

根据标准的Builder模式的使用方式,外部类的各个参数都必须使用final修饰,然后只需要为其声明getter方法即可。

上面我们展示了如何使用Spring的方式来声明一个Builder模式的类,那么我们该如何进行使用呢,如下是我们的一个使用示例:

@Service

public class ApplicationService {

@Autowired

private PrizeSenderFactory prizeSenderFactory;

@Autowired

private ApplicationContext context;

public void mockedClient() {

SendPrizeRequest request = newPrizeSendRequestBuilder()

.prizeId(1)

.userId("u4352234")

.build();

PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);

prizeSender.sendPrize(request);

}

public Builder newPrizeSendRequestBuilder() {

return context.getBean(Builder.class);

}

}

上述代码中,我们主要要看一下newPrizeSendRequestBuilder()方法,在Spring中,如果一个类是多例类型,也即使用@Scope("prototype")进行了标注,那么每次获取该bean的时候就必须使用ApplicationContext.getBean()方法获取一个新的实例,至于具体的原因,读者可查阅相关文档。我们这里就是通过一个单独的方法来创建一个Builder对象,然后通过流式来为其设置prizeId和userId等参数,最后通过build()方法构建得到了一个SendPrizeRequest实例,通过该实例来进行后续的奖励发放。

4. 小结

本文主要通过一个奖励发放的示例来对Spring中如何使用工厂方法模式,策略模式和Builder模式的方式进行讲解,并且着重强调了实现各个模式时我们所需要注意的点。

Spring中如何使用责任链模式?

关于责任链模式,其有两种形式,一种是通过外部调用的方式对链的各个节点调用进行控制,从而进行链的各个节点之间的切换;另一种是链的每个节点自由控制是否继续往下传递链的进度,这种比较典型的使用方式就是Netty中的责任链模式。本文主要讲解我们如何在Spring中使用这两种责任链模式。

1. 外部控制模式

对于外部控制的方式,这种方式比较简单,链的每个节点只需要专注于各自的逻辑即可,而当前节点调用完成之后是否继续调用下一个节点,这个则由外部控制逻辑进行。这里我们以一个过滤器的实现逻辑为例进行讲解,在平常工作中,我们经常需要根据一系列的条件对某个东西进行过滤,比如任务服务的设计,在执行某个任务时,其需要经过诸如时效性检验,风控拦截,任务完成次数等过滤条件的检验之后才能判断当前任务是否能够执行,只有在所有的过滤条件都完成之后,我们才能执行该任务。那么这里我们就可以抽象出一个接口,其设计如下:

这里的方法只有一个参数,主要就是控制当前task是否需要被过滤掉,其有一个boolean类型的返回值,通过该返回值以告知外部控制逻辑是否需要将该task过滤掉。对于该接口的子类,我们只需要将其声明为Spring所管理的一个bean即可:

上面我们模拟声明了三个的子类,用于设计一系列的控制当前task是否需要被过滤的逻辑,结构上的逻辑其实比较简单,主要就是需要将其声明为Spring所管理的一个bean。下面是我们的控制逻辑:

在上述的控制逻辑中,对于过滤器的获取,只需要通过Spring的自动注入即可,这里注入的是一个,也就是说,如果我们有新的实例需要参与责任链的过滤,只需要将其声明为一个Spring容器所管理的bean即可。

这种责任链设计方式的优点在于链的控制比较简单,只需要实现一个统一的接口即可,其基本上能够满足大部分的逻辑控制,但是对于某些需要动态调整链的需求其就无能为力了。比如在执行到某个节点之后需要动态的判断是否执行下一个节点,或者说要执行某些分叉的节点等等。这个时候我们就需要将链节点的传递工作交由各个节点进行。

2. 节点控制模式

对于节点控制调用的方式,其主要有三个控制点:Handler,HandlerContext和Pipeline。Handler中是用于编写具体的业务代码的;HandlerContext则主要是用于对Handler进行包裹,并且用于控制进行下一个节点的调用的;Pipeline则主要是用于控制整体的流程调用的,比如对于任务的执行,其有任务的查询,任务的过滤和执行任务等等流程,这些流程整体的逻辑控制就是由Pipeline来控制的,在每个流程中又包含了一系列的子流程,这些子流程则是由一个个的HandlerContext和Handler进行梳理的。这种责任链的控制方式整体逻辑如下图所示:

从图中可以看出,我们将整个流程通过对象进行了抽象,这里主要分为了三个步骤:查询task,过滤task和执行task。在每个步骤中,我们都使用了一系列的链式调用。图中需要注意的是,在每次调用链的下一个节点的时候,我们都是通过具体的Handler进行的,也就是说是否进行链的下一个节点的调用,我们是通过业务实现方来进行动态控制的。

关于该模式的设计,我们首先需要强调的就是接口的设计,其设计如下所示:

这里的接口主要是对具体的业务逻辑的一个抽象,对于该主要有如下几点需要说明:

  • 在前面图中的每个层级中对应于该都有一个方法,在需要进行具体的业务处理的时候,用户只需要声明一个bean,具体实现某个当前业务所需要处理的层级的方法即可,而无需管其他的逻辑;
  • 每个层级的方法中,第一个参数都是一个类型的,该参数主要是用于进行流程控制的,比如是否需要将当前层级的调用链往下继续传递,这里链的传递工作主要是通过方法进行的;
  • 每个层级的方法都有默认实现,默认实现方式就是将链的调用继续往下进行传递;
  • 每个中都有一个方法和方法,这两个方法分别用于异常控制和所有调用完成后的清理的,这里的异常控制主要是捕获当前中的异常,而方法则会保证在所有步骤之后一定会进行调用的,无论是否抛出异常;
  • 对于的使用,我们希望能够达到的目的是,适用方只需要实现该接口,并且使用某个注解来将其标志为的bean即可,而无需管整个的组装和流程控制。通过这种方式,我们即保留了每个Spring提供给我们的便利性,也使用了模式的灵活性。

上述流程代码中,我们注意到,每个层级的方法中都有一个用于传递链相关的控制信息,这里我们来看一下其源码:

在中,我们需要说明如下几点:

  • 之前接口默认实现的方法,在这里都委托给了对应的方法进行调用,而且我们需要注意到,在传递给方法的参数里,传入的对象都是通过方法获取到的。也就是说我们在中调用方法时,都是在调用当前handler的下一个handler对应层级的方法,通过这种方式我们就实现了链的往下传递。
  • 在上一点中我们说到,在某个中如果想让链往下传递,只需要调用方法即可,也就是说,如果我们在某个中,如果根据业务,当前层级已经调用完成,而无需调用后续的,那么我们就不需要调用方法即可;
  • 在中,我们也实现了方法,该方法的主要作用是供给外部的进行调用的,以开启每个层级的链;
  • 在每个方法中,我们都使用try…catch将当前层级的调用抛出的异常给捕获了,然后调用方法处理该异常,这也就是我们前面说的,如果想处理当前中的异常,只需要实现该中的方法即可,异常捕获流程就是在这里的中进行处理的;
  • 在的声明处,我们需要注意到,其使用了和注解进行标注了,这说明我们的是由Spring所管理的一个bean,并且由于我们每一个实际上都由一个维护着,所以这里必须声明为类型。通过这种方式,我们的也就具备了诸如Spring相关的bean的功能,也就能够根据业务需求进行一些额外的处理了;

前面我们讲解了和的具体实现,以及实现的过程中需要注意的问题,下面我们就来看一下进行流程控制的是如何实现的,如下是接口的定义:

这里 主要是定义了一个接口,该接口定义了一系列的层级调用,是每个层级的入口方法。如下是该接口的一个实现类:

关于的实现,主要有如下几点需要说明:

  • 使用和注解进行了标注,前一个注解用于将其声明为一个Spring容器所管理的bean,而后一个注解则用于表征是一个多例类型的,很明显,这里的是有状态的。这里需要进行说明的是,"有状态"主要是因为我们可能会根据业务情况动态的调整个链的节点情况,而且这里的和对象都是与具体的业务相关的,因而必须声明为类型;
  • 上面的示例中,对象是通过构造对象的时候传进来的,而对象则是在的流转过程中生成的,这里比如通过完成链的调用之后,就需要通过外部请求得到一个对象,从而进行整个的后续处理;

这里我们已经实现了,和,知道这些bean都是被Spring所管理的bean,那么我们接下来的问题主要在于如何进行整个链的组装。这里的组装方式比较简单,其主要需要解决两个问题:

  • 对于后续写业务代码的人而言,其只需要实现一个接口即可,而无需处理与链相关的所有逻辑,因而我们需要获取到所有实现了接口的bean;
  • 将实现了接口的bean通过进行封装,然后将其添加到中。

这里的第一个问题比较好处理,因为通过ApplicationContext就可以获取实现了某个接口的所有bean,而第二个问题我们可以通过声明一个实现了BeanPostProcessor接口的类来实现。如下是其实现代码:

这里我们整个链的维护工作就已经完成,可以看到,现在基本上已经实现了前面图中整个链式流程的控制。这里需要说明的一点是,上面的方法的执行是在方法之后执行的,也就是说这里在执行时,整个是已经初始化完成了的。下面我们来看一下外部客户端如何进行整个链是流程的控制:

这里我们模拟了一个客户端的调用,首先创建了一个对象,然后依次调用其各个层级的方法,并且这里我们使用try…finally结构来保证方法一定会执行。如此我们就完成了整个责任链模式的构造。这里我们使用前面用到的时效性过滤的filter来作为示例来实现一个:

关于这里的具体业务我们需要说明的有如下几点:

  • 该必须使用注解来将其声明为Spring容器所管理的一个bean,这样我们前面实现的才能将其动态的添加到整个中;
  • 在每个中,需要根据当前的业务需要来实现具体的层级方法,比如这里是进行时效性检验,就是"任务过滤"这一层级的逻辑,因为时效性检验通过我们才能执行这个task,因而这里需要实现的是方法,如果我们需要实现的是执行task的逻辑,那么需要实现的就是方法;
  • 在实现完具体的业务逻辑之后,我们可以根据当前的业务需要看是否需要将当前层级的链继续往下传递,也就是这里的方法的调用,我们可以看前面方法就是会获取当前节点的下一个节点,然后进行调用。如果根据业务需要,不需要将链往下传递,那么就不需要调用;

3. 小结

如此,我们就通过两种方式实现了责任链模式,而且我们实现的责任链模式都是符合"开-闭"原则的,也就是说后续我们要为链添加新的节点的时候,只需要根据规范实现相应的接口即可,而无需处理链的维护相关的工作。关于第二种实现方式,这里我们并没有实现链节点的顺序控制功能,以及如何动态的添加或删除链的节点,更有甚者,如果控制每个Handler是单例的还是多例的。当然,有了前面的框架,这些点实现起来也比较简单,这里权当起到一个抛砖引玉的作用,读者朋友可根据自己的需要进行实现。

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

springboot自定义拦截器,Spring Boot项目中如何定制拦截器?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 xxx@163.com 举报,一经查实,本站将立刻删除。

发表评论

登录后才能评论