美文网首页Spring Boot
拦截机制中Aspect、ControllerAdvice、Int

拦截机制中Aspect、ControllerAdvice、Int

作者: 叫我胖虎大人 | 来源:发表于2018-12-16 14:08 被阅读774次



            在项目的开发中,在某些情况下,我们需要对客户端发出的请求进行拦截,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect。

    上图是spring中的拦截机制,如果出现异常的话,异常的顺序是从里面到外面一步一步的进行处理,如果到了最外层都没有进行处理的话,就会由tomcat容器抛出异常。下面我将详细的解释这四个拦截方式的不同。因为只是演示就不是所有的使用了日志记录。这里相关的依赖自己可以去https://mvnrepository.com/下载,我就不一一添加了

    先做一个总的总结吧

    1.过滤器:Filter

    :可以获得Http原始的请求和响应信息,但是拿不到响应方法的信息

    2.拦截器:Interceptor

      :可以获得Http原始的请求和响应信息,也拿得到响应方法的信息,但是拿不到方法响应中参数的值

    3.ControllerAdvice(Controller增强,自spring3.2的时候推出):

    主要是用于全局的异常拦截和处理,这里的异常可以使自定义异常也可以是JDK里面的异常

    用于处理当数据库事务业务和预期不同的时候抛出封装后的异常,进行数据库事务回滚,并将异常的显示给用户

    4.切片:Aspect

       主要是进行公共方法的

      可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法


    Filter(过滤器)

    可以获得Http原始的请求和响应信息,但是拿不到响应方法的信息

    filter是属于Servlet规范的,不属于Spring

    springboot中的配置方法:

    自定义一个Filter

    import javax.servlet.*;

    import java.io.IOException;

    public class TimeFilter implements Filter {

    /**Filter接口中的部分方法添加了default关键字,这样的方法就不是一定要重写*/

    @Override

        public void init(FilterConfig filterConfig)throws ServletException {

    System.out.println("Time Filter init");

        }

    @Override

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {

    System.out.println("time filter start");

            /**这里说明一下,在JDK8以及之后的JDK版本中都不再建议使用new Date().getTime()这种方式来获得时间*/

            long start = System.currentTimeMillis();

            chain.doFilter(request,response);

            System.out.println("time filter:"+(System.currentTimeMillis()-start));

            System.out.println("time filter finish");

        }

    @Override

        public void destroy() {

    System.out.println("time filter destroy");

        }

    }

    方式一:通过Bean注入的方式

    注册Filter,springboot当中提供了FilterRegistrationBean类来注册Filter

    import com.imooc.Filter.TimeFilter;

    import com.imooc.Interceptor.TimeInterceptor;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.boot.web.servlet.FilterRegistrationBean;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    import java.util.ArrayList;

    import java.util.List;

    @Configuration

    public class WebConfigimplements WebMvcConfigurer {

    /**WebMvcConfigurerAdapter在JDK8中这个类已经过时,我们直接继承这个类所继承的接口*/

    @Bean

        public FilterRegistrationBeantimeFilter(){

    FilterRegistrationBean registrationBean =new FilterRegistrationBean();

            TimeFilter timeFilter =new TimeFilter();

            registrationBean.setFilter(timeFilter);

            /**添加拦截的地址*/

            List urls =new ArrayList<>();

            urls.add("/*");

            return registrationBean;

        }

    }

    方式二:通过@WebFilter注解实现

    import org.springframework.stereotype.Component;

    import javax.servlet.*;

    import javax.servlet.annotation.WebFilter;

    import java.io.IOException;

    @Component

    @WebFilter(filterName ="TimeFilter",urlPatterns ="/*")

    public class TimeFilterimplements Filter {

    @Override

        public void init(FilterConfig filterConfig)throws ServletException {

    System.out.println("Time Filter init");

        }

    @Override

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {

    System.out.println("time filter start");

            /**这里说明一下,在JDK8以及之后的JDK版本中都不再建议使用new Date().getTime()这种方式来获得时间*/

            long start = System.currentTimeMillis();

            chain.doFilter(request,response);

            System.out.println("time filter:"+(System.currentTimeMillis()-start));

            System.out.println("time filter finish");

        }

    @Override

        public void destroy() {

    System.out.println("time filter destroy");

        }

    }

    @WebFilter 的常用属性

    属性名                  类型                              描述

    filterName             String                          指定过滤器的 name 属性,等价于 <filter-name>

    value                      String[]                       该属性等价于 urlPatterns 属性。但是两者不应该同时使用。

    urlPatterns             String[]                         指定一组过滤器的 URL 匹配模式。等价于 <url-pattern> 标签。

    servletNames          String[]                      指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是

                                                                       web.xml 中 <servlet-name> 的取值。

    dispatcherTypes     DispatcherType             指定过滤器的转发模式。具体取值包括:

                                                                          ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。

    initParams                 WebInitParam[]             指定一组过滤器初始化参数,等价于 <init-param> 标签。

    asyncSupported         boolean                         声明过滤器是否支持异步操作模式,等价于 <async-supported> 标签。

    description                 String                             该过滤器的描述信息,等价于 <description> 标签。

    displayName                 String                         该过滤器的显示名,通常配合工具使用,等价于 <display-name> 标签。

    相较而言,方式一会更加的灵活,而方式二更加的方便,但是两者在实质上是一样的


    Interceptor (拦截器) :

    可以获得Http原始的请求和响应信息,也拿得到响应方法的信息,但是拿不到方法响应中参数的值

    在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等。在spring中拦截器有两种,第一种是HandlerInterceptor,第二种是MethodInterceptor。HandlerInterceptor是SpringMVC中的拦截器,它拦截的是Http请求的信息,优先于MethodInterceptor。而MethodInterceptor是springAOP的。前者拦截的是请求的地址,而后者是拦截controller中的方法,因为下面要将Aspect,就不详细讲述MethodInterceptor

    在springboot中HandlerInterceptor的配置

    1.首先定义自己的Interceptor

    import org.springframework.stereotype.Component;

    import org.springframework.web.method.HandlerMethod;

    import org.springframework.web.servlet.HandlerInterceptor;

    import org.springframework.web.servlet.ModelAndView;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

    /**

    * 拦截器会拦截所有的控制器,不管是spring的还是自定义的

    */

    @Component

    public class TimeInterceptorimplements HandlerInterceptor {

    /**

        *控制器方法调用之前会进行

        *和上面的Filter一样,继承的某些接口方法中也加了default关键字,可以不用重写,这里为了演示就都写了

    */

        @Override

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

    System.out.println("proHandle");

            System.out.println(((HandlerMethod)handler).getBean().getClass().getName());

            System.out.println(((HandlerMethod)handler).getMethod().getName());

            request.setAttribute("startTime",System.currentTimeMillis());

    return true;

            /**true的话 就是选择可以调用后面的方法  也就是controller中的getInfo方法*/

        }

    /**控制后方法执行之后会进行,抛出异常则不会被执行*/

        @Override

        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {

    System.out.println("postHandle");

            Long start = (Long)request.getAttribute("startTime");

            System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-start));

        }

    /**方法被调用或者抛出异常都会被执行*/

        @Override

        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {

    System.out.println("afterCompletion");

            Long start = (Long)request.getAttribute("startTime");

            System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-start));

        }zai

    }

    2.在配置类中配置自定义的Interceptor

    import org.springframework.context.annotation.Configuration;

    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    import com.imooc.Interceptor.TimeInterceptor;

    import org.springframework.beans.factory.annotation.Autowired;

    @Configuration

    public class WebConfigimplements WebMvcConfigurer {

    /**WebMvcConfigurerAdapter在JDK8中这个类已经过时,我们直接继承这个类所继承的接口*/

        @Autowired

        private TimeInterceptortimeInterceptor;

        @Override

        public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(timeInterceptor);

        }

    }

    ControllerAdvice(Controller增强,自spring3.2的时候推出):

    用于处理当数据库事务业务和预期不同的时候抛出封装后的异常,进行数据库事务回滚,并将异常的显示给用户

    1.定义自己的异常类

    import lombok.Data;

    @Data

    public class UserNotExistExceptionextends RuntimeException{

    private static final long serialVersionUID =4820951478405122770L;

        private Stringid;

        public UserNotExistException(String id) {

    super("user not exist.......");

            this.id = id;

        }

    }

    2.编写全局异常处理类

    import com.imooc.Exception.UserNotExistException;

    import org.springframework.http.HttpStatus;

    import org.springframework.web.bind.annotation.ControllerAdvice;

    import org.springframework.web.bind.annotation.ExceptionHandler;

    import org.springframework.web.bind.annotation.ResponseBody;

    import org.springframework.web.bind.annotation.ResponseStatus;

    import java.util.HashMap;

    import java.util.Map;

    @ControllerAdvice

    public class ControllerExceptionHandler {

    /**指定抛出的异常类*/

    @ExceptionHandler(UserNotExistException.class)

    /**如果全部异常处理返回json格式,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody.@ResponseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。*/

    @ResponseBody

        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

    /**当出现该Http状态码的时候抛出异常*/

        public MaphandlerUserNotExistException(UserNotExistException exception){

    Map result =new HashMap<>();

            result.put("id",exception.getId());

            result.put("message",exception.getMessage());

            return result;

        }

    }

    3、controller中抛出异常进行测试

    import com.imooc.Exception.UserNotExistException;

    import com.imooc.dto.User;

    import com.imooc.dto.UserQueryCondition;

    import lombok.extern.slf4j.Slf4j;

    import org.springframework.validation.BindingResult;

    import org.springframework.web.bind.annotation.*;

    @RestController

    public class UserController {

        @GetMapping(value ="/user/{id}")

        public UsergetInfo(@PathVariable String id)throws Exception{

        throw new UserNotExistException(id);

            }

    }


    Aspect(切面):

          可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。

    可以参照https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html,spring的官方文档

    1.创建Controller

    2.创建一个Aspect切面

    import lombok.extern.slf4j.Slf4j;

    import org.aspectj.lang.ProceedingJoinPoint;

    import org.aspectj.lang.annotation.Around;

    import org.aspectj.lang.annotation.Aspect;

    import org.springframework.stereotype.Component;

    @Aspect

    @Component

    @Slf4j

    public class TimeAspect {

    @Around("execution(* com.imooc.controller.UserController.*(..))")

    public ObjecthandleControllerMethod(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {

    log.info("Time aspect start");

            Long start = System.currentTimeMillis();

            Object[] args = proceedingJoinPoint.getArgs();

            for(Object object:args)

    {

    log.info("arg is:"+String.valueOf(object));

            }

    Object object =proceedingJoinPoint.proceed();

            System.out.println("time filter:"+(System.currentTimeMillis()-start));

            log.info("Time aspect finish");

            return object;

        }

    }

    切面的方法说明:

    @Aspect

    作用是把当前类标识为一个切面供容器读取

    @Before

    标识一个前置增强方法,相当于BeforeAdvice的功能

    @AfterReturning

    后置增强,相当于AfterReturningAdvice,方法退出时执行

    @AfterThrowing

    异常抛出增强,相当于ThrowsAdvice

    @After

    final增强,不管是抛出异常或者正常退出都会执行

    @Around

    环绕增强,相当于MethodInterceptor

    除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX"

    关于切面PointCut的切入点

    execution切点函数

    execution函数用于匹配方法执行的连接点,语法为:

    execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选)) 

    例如:execution(* com.imooc.controller.UserController.*(..))

    第一个*代表的是所有的返回值类型,com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController类的所有方法,(..)代表的是所有的参数

    参数部分允许使用通配符:

    *  匹配任意字符,但只能匹配一个元素

    .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

    +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

    除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询

     @annotation()

    表示标注了指定注解的目标类方法

    例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法

    args()

    通过目标类方法的参数类型指定切点

    例如 args(String) 表示有且仅有一个String型参数的方法

    @args()

    通过目标类参数的对象类型是否标注了指定注解指定切点

    如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法

    within()

    通过类名指定切点

    如 with(examples.chap03.Horseman) 表示Horseman的所有方法

    target()

    通过类名指定,同时包含所有子类

    如 target(examples.chap03.Horseman)  且Elephantman extends Horseman,则两个类的所有方法都匹配

    @within()

    匹配标注了指定注解的类及其所有子类

    如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配

    @target()

    所有标注了指定注解的类

    如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

     this()

    大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

    关于Aspect参考了https://www.cnblogs.com/bigben0123/p/7779357.html,如有侵权,请作者联系删除

    具体使用哪种拦截机制还是要根据项目开发的需求来决定。

    相关文章

      网友评论

        本文标题:拦截机制中Aspect、ControllerAdvice、Int

        本文链接:https://www.haomeiwen.com/subject/yyytkqtx.html