面试官:说说过滤器和阻拦器的差异? 这个问题面试题库算是比较经典的,这两个我信任许多同学在作业中都有触摸过,但假如没有经过体系的收拾,还真的不好说出个123来,那老湿机在此这面就把它俩和常用的AOP、ControllerAdvice放一同,带你做一个比较全面的认识。

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

1. 了解4种阻拦办法的履行次序

先上一个栗子,看四种阻拦办法并驾齐驱运用时,谁先谁后:

/**
 * ============Filter过滤器============
 */
@Slf4j
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        log.info("Filter 进入");
        filterChain.doFilter(request, response);
        log.info("Filter 退出");
    }
}
l
/**
 * ============Interceptor过滤器============
 */
public class DemoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("Interceptor preHandle 进入");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("Interceptor postHandle 进入");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("Interceptor afterCompletion 进入");
    }
}
/**
 * ============ControllerAdvice============
 */
@ControllerAdvice
public class DemoControllerAdvice {
    @InitBinder
    public void init(WebDataBinder binder) {
        log.info("ControllerAdvice init 进入");
        binder.setFieldDefaultPrefix("user.");
    }
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        log.info("ControllerAdvice handleException 进入");
        return "error";
    }
}
/**
 * ============AOP============
 */
@Aspect
@Component
public class DemoAspect {
    @Pointcut("(@target(org.springframework.web.bind.annotation.RestController)) " +
            "&& within(cn.demo.api..*) && execution(public * *(..))")
    public void pointcut() {
    }
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("Aop 进入");
        Object proceed = pjp.proceed();
        log.info("Aop 退出");
        return proceed;
    }
}
/**
 * ============操控器入口============
 */
@RestController
@RequestMapping("/demo")
public class DemoController {
    @RequestMapping("/test")
    public Map<String, String> test(@ModelAttribute("user")User user) {
        log.info("事务:user.name: {}", user.getName());
        Map<String, String> result = new HashMap<>(2);
        result.put("code", "200");
        int i  = 1;
        i = i / 0;
        return result;
    }
}

界说好Demo示例,接着来一发:http://localhost:8080/demo/test?user.name=宫三令郎 :

成果:
Filter 进入
Interceptor preHandle 进入
ControllerAdvice init 进入
Aop 进入
事务:user.name: 宫三令郎
ControllerAdvice handleException 进入
Interceptor afterCompletion 进入
Filter 退出

好了,咱们似乎现已看到,4种阻拦办法的履行次序是这姿态:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

2. 知晓4种阻拦办法运用场景

那么针对这4种阻拦办法,它们触及的运用场景、完成技能、效果力度都各不相同,为了有一个比较清晰的比照,老司机我简单粗犷,直接收拾好啦,大伙干:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

3. 掌握4种阻拦办法的原理

假如只晓得履行次序和运用场景或许过不了面试大大狠辣的毒眼,知其然并知其所以然,咱们就自废10根头发,来研究一下它们的原理吧。

3.1 过滤器

尽管一个过滤器在一次恳求中只能调用一次,可是依据详细事务需求,能够生成多个不同类型的Filter并顺次履行,这就是过滤器的链式调用,能够经过指定Order排序,值越小越靠前(默认依据Filter的名称进行天然排序),新建了4个Filter,Order顺次为0-3,演示一波,契合预期:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

接下来咱们重视整个链式调用的中心: FilterChain接口,内部界说了doFilter接口办法,tomcat容器提供了ApplicationFilterChain作为FilterChain的详细完成:

public final class ApplicationFilterChain implements FilterChain {
// 
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private Servlet servlet;
public void doFilter(ServletRequest request, ServletResponse response) {
	internalDoFilter(request, response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response) {
	if (this.pos < this.n) {
		ApplicationFilterConfig filterConfig = this.filters[this.pos++];
		Filter filter = filterConfig.getFilter();
		filter.doFilter(request, response, this);             
	} else {
		this.servlet.service(request, response);
	}
 }
}

内部界说了ApplicationFilterConfig[] filters 过滤器装备列表,每一个ApplicationFilterConfig内部持有一个Filter实例,另一个比较重要的是Servlet,实例化后对应原生HttpServlet或SpringMVC的DispatcherServlet,当阻拦器链路履行完成后,会调用Servlet中service办法做后续的Url路由映射、事务处理以及视图响应等流程了(这个后边研究SpringMVC的恳求流程来详细再分析), 好了,咱们经过Debug能够看到,filters中除了服务默认的一些恳求filter,咱们自己界说的4个filter也以界说好的次序排入其中了:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学
过滤器整体履行流程Like this:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

3.2 阻拦器

阻拦器调用流程比较复杂,我这面依据源码整理了中心恳求流程和扼要阐明,感兴趣的同学能够持续一探终究:源码位于:org.springframework.web.servlet.DispatcherServlet#doDispatch办法

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

Tip一下:网上有些朋友说阻拦器是根据反射、动态署理来完成的,经过司机对源码分析,反射倒是有用到,首要从Spring的IOC容器中获取了阻拦器方针,并放在AbstractHandlerMapping的adaptedInterceptors大局方针中,在上图的第二步就匹配满足的阻拦器作为当时恳求的阻拦器列表,没有动态署理就没有影子!

3.3 ControllerAdvice

其实,ControllerAdvice和阻拦器完成有异曲同工之处,要是说用什么技能手段,那应该也只能说是反射吧,在也首要在上一步的doDispatch办法中,它首要是在散布第4步t经过反射对InitBinder的参数的设置和第6步进行统一的反常捕获,要点看看第6步:在processDispatchResult处理成果办法内部,调用processHandlerException办法进行反常相关的处理逻辑,咱们能够看到它的首要作业就是遍历handlerExceptionResolvers来进行反常对应的处理,咱们自界说的大局反常ExceptionHandlerExceptionResolver实例操控着一切的Controller类,debug源码:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学
那什么时分进行设置的以及如何设置的呢,值得咱们思考一下,怀着好奇心,咱们点开ExceptionHandlerExceptionResolver来看看,本来要点是完成了InitializingBean的afterPropertiesSet办法,在容器发动时分检测带ControllerAdvice注解的类和类中带ExceptionHandler注解的办法,并加入到咱们刚才看到的exceptionHandlerCache中:

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean {
    private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap(64);
   // 要点完成了afterPropertiesSet
    public void afterPropertiesSet() {
        this.initExceptionHandlerAdviceCache();
    }
    private void initExceptionHandlerAdviceCache() {
        if (this.getApplicationContext() != null) {
	   // 寻觅一切带有ControllerAdvice注解的类
            List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
            Iterator var2 = adviceBeans.iterator();
            while(var2.hasNext()) {
                ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
                Class<?> beanType = adviceBean.getBeanType();
		// 寻觅该类下面的办法是否有ExceptionHandler注解,假如有,则收集到mappedMethods列表中
                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
		// 假如不为空,则加入到大局反常阻拦缓存中
                if (resolver.hasExceptionMappings()) {
                    this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                }            
            }     
        }
    }
}

3.4 AOP

AOP这块想着网上其实许多同学整理得很好了,究竟要不要收拾,但考虑到完整性,也避免同学们再额外找文章跳来跳去,就一并简化收拾了,接着持续: 前面说到,AOP是有两种完成办法,信任大家都耳熟能详:JDK动态署理和CGLib,带大家温习一下,先来一个JDK动态署理的栗子:

//1.  动态署理是根据接口拜访的,先界说用户服务接口
public interface UserService {
    User getUser(String name);
}
// 2. 详细用户服务完成类,也即被署理方针
public class UserServiceImpl implements UserService{
    @Override
    public User getUser(String name) {
        User user = new User();
        user.setName("宫三令郎");
        System.out.println("用户名:" + user.getName());
        return user;
    }
}
// 3. 署理工具,用于服务增强
public class JDKProxyr implements InvocationHandler {
    private Object target;
    public JDKProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before-------JDK Proxy");
        Object invoke = method.invoke(target, args);//经过反射履行,方针类的办法
        System.out.println("after-------JDK Proxy");
        return invoke;
    }
}
// 4. 测验办法
public class JDKProxyTest {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        JDKProxy handler = new JDKProxy(userService);
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass()
                        .getInterfaces(), handler);
        User user = proxyInstance.getUser("宫三令郎");
    }
}

测验成果:

before-------JDK Proxy
用户名:宫三令郎
after-------JDK Proxy

CGLib的署理办法是为咱们需要被署理的详细类生成一个子类,即将需被署理的办法进行Override重写:

// CGLib署理工具,用于生成事务增强
public class CGLibProxy implements MethodInterceptor {
   public Object intercept(Object arg0, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
       System.out.println("before-------CGLib Proxy");
       Object result = proxy.invokeSuper(arg0, objects);
       System.out.println("after-------CGLib Proxy");
       return result;
   }
}
// 测验办法
public class CGLibProxyTest {
   public static void main(String[] args) {
       CGLibProxy proxy = new CGLibProxy();
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(UserServiceImpl.class);
       //回调办法的参数为署理类方针CglibProxy,最后增强方针类调用的是署理类方针CglibProxy中的intercept办法
       enhancer.setCallback(proxy);
       UserServiceImpl userService = (UserServiceImpl) enhancer.create();
       userService.getUser("宫三令郎");
       // 打印增强效果
       System.out.println("打印userService的增强成果:");
       ReflectUtils.printMethods(userService.getClass());
   }
}

打印成果,咱们能够看到被署理的办法现已履行了增强逻辑:

before-------CGLib Proxy
用户名:宫三令郎
after-------CGLib Proxy
打印userService的增强成果:
 public final getUser(java.lang.String);
  public setCallback(int,net.sf.cglib.proxy.Callback);
  public static CGLIB$findMethodProxy(net.sf.cglib.core.Signature);
  public static CGLIB$SET_THREAD_CALLBACKS([Lnet.sf.cglib.proxy.Callback;);
  public static CGLIB$SET_STATIC_CALLBACKS([Lnet.sf.cglib.proxy.Callback;);
  public getCallback(int);
  public getCallbacks();
  public setCallbacks([Lnet.sf.cglib.proxy.Callback;);
  private static final CGLIB$BIND_CALLBACKS(java.lang.Object);
  static CGLIB$STATICHOOK1();
  final CGLIB$hashCode$3();
  final CGLIB$clone$4();
  final CGLIB$toString$2();
  final CGLIB$equals$1(java.lang.Object);
  final CGLIB$getUser$0(java.lang.String);
  public final equals(java.lang.Object);
  public final toString();
  public final hashCode();
  protected final clone();
  public newInstance([Lnet.sf.cglib.proxy.Callback;);
  public newInstance([Ljava.lang.Class;,[Ljava.lang.Object;,[Lnet.sf.cglib.proxy.Callback;);
  public newInstance(net.sf.cglib.proxy.Callback);

好了,咱们看完了根据Jdk动态署理和CGLib署理的两种Demo,那咱们持续看看Spring是怎样玩的吧。 在Spring的发动过程中,有许多的扩展点,比较让咱们熟知的BeanPostPorcessor,首要在Bean进行initializeBean初始化后,调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization,内部对一切的BeanPostPorcessor方针进行遍历,调用postProcessAfterInitialization进行处理,而Aop就是根据该扩展点完成的:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

沿着这条链路Debug调试,终究能够定位到详细决议调用JDK动态署理还是运用CGLib,我这署理的是DemoController,那生成的就是CGLig署理啦:

全网最全:过滤器、拦截器、ControllerAdvice和AOP,先点赞收藏再慢学

OK,关于过滤器、阻拦器、ControllerAdvice以及AOP,本文从履行次序、运用场景、完成技能以及原理等进行了总结,看完了,朋友们假如有收成就是对我最大得必定,有疑问评论区留言或私信交流。需要Demo代码的@我吧

参考:

  1. spring aop原理 JDK动态署理和CGLIB动态署理