laitimes

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

author:Lao Cheng is not bug

Interviewer: What is the difference between a filter and an interceptor? This question interview question bank is relatively classic, these two I believe many students have been in contact with in the work, but if it has not been systematically sorted, it is really difficult to say a 123, then the old wet machine on this side will put it together with the commonly used AOP, ControllerAdvice, take you to do a more comprehensive understanding.

1. Understand the execution order of the 4 interception methods

Let's start with a chestnut and see who comes first when the four interception methods go hand in hand:

/**
 * ============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;
    }
}
复制代码           

Define the demo example, and then send it: http://localhost:8080/demo/test?user.name=Gong Sangongzi:

结果:
Filter 进入
Interceptor preHandle 进入
ControllerAdvice init 进入
Aop 进入
业务:user.name: 宫三公子
ControllerAdvice handleException 进入
Interceptor afterCompletion 进入
Filter 退出
复制代码           

Well, we seem to have seen that the order of execution of the 4 interception methods looks like this:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

2. Know the application scenarios of 4 interception methods

So for these 4 interception methods, they involve different application scenarios, implementation technologies, and strength, in order to have a clearer comparison, the old driver I am simple and rude, directly sorted out, everyone:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

3. Master the principles of the 4 interception methods

If you only know the execution order and application scenarios may not be able to pass the big poisonous eye of the interview, know it and know the reason, let's waste 10 hairs to study their principles.

3.1 Filters

Although a filter can only be called once in a request, but according to the specific business needs, multiple different types of filters can be generated and executed in turn, this is the chain call of the filter, you can sort by specifying Order, the smaller the value and the higher the front (the default is natural sorting according to the name of the Filter), 4 new Filters are created, Order is 0-3 in turn, demonstrating a wave, in line with expectations:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

Next, we focus on the core of the entire chained call: the FilterChain interface, which internally defines the doFilter interface method, and the tomcat container provides ApplicationFilterChain as a specific implementation of 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 filter configuration list is defined internally, each ApplicationFilterConfig holds a Filter instance internally, another important is the servlet, which corresponds to the native HttpServlet or SpringMVC DispatcherServlet after instantiation, when the interceptor link execution is completed, Will call the service method in the servlet to do the subsequent URL route mapping, business processing and view response processes (this will be analyzed in detail by studying the request process of SpringMVC), Well, we can see through Debug that in addition to some of the default request filters in the filters, the 4 filters we define ourselves are also arranged in the order of definition:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

The overall filter execution process is like this:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

3.2 Interceptors

The interceptor call process is more complicated, I have sorted out the core request process and brief description according to the source code, interested students can continue to explore: the source code is located at: org.springframework.web.servlet.DispatcherServlet#doDispatch method

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

Tip: Some friends on the Internet say that the interceptor is based on reflection, dynamic proxy to achieve, through the driver's analysis of the source code, reflection is useful, mainly from Spring's IOC container to obtain the interceptor object, and put it in the adaptedInterceptors global object of AbstractHandlerMapping, in the second step of the figure above to match the satisfied interceptor as the interceptor list of the current request, there is no shadow without dynamic proxy! .

3.3 ControllerAdvice

In fact, ControllerAdvice and interceptor implementation are similar, if you say what technical means are used, it should only be said to be reflection, in the doDispatch method that is also mainly in the previous step, it is mainly in the distribution step 4t through reflection of the InitBinder parameter setting and step 6 for unified exception catching, focus on step 6: inside the processDispatchResult processing result method, Call the processHandlerException method for exception-related processing logic, we can see that its main job is to traverse the handlerExceptionResolvers to handle the exception, our custom global exception ExceptionHandlerExceptionResolver instance controls all Controller classes, debug source code:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

So when and how to set it, it is worth thinking about it, with curiosity, we click on ExceptionHandlerExceptionResolver to see, it turns out that the focus is to implement the afterPropertiesSet method of InitializingBean. Detect classes with ControllerAdvice annotations and methods with ExceptionHandler annotations in classes at container startup and add them to the exceptionHandlerCache we just saw:

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 this piece of thinking that many students on the Internet have actually sorted out very well, in the end whether to sort it out or not, but considering the completeness, but also to avoid students from jumping around with additional articles, it was simplified and sorted out, and then continued: As mentioned earlier, AOP has two implementation methods, I believe everyone is familiar with it: JDK dynamic agent and CGLib, take you to review, first come to a chestnut of JDK dynamic agent:

//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("宫三公子");
    }
}

复制代码           

Test results:

before-------JDK Proxy
用户名:宫三公子
after-------JDK Proxy
复制代码           

CGLib's proxy method is to generate a subclass for the specific class we need to be proxied, i.e. override the method to be proxied:

// 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());
   }
}
复制代码           

Printing the result, we can see that the method being proclaimed has executed the enhancement logic:

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);
复制代码           

Okay, now that we've seen two demos based on Jdk dynamic proxies and CGLib proxies, let's move on to see how Spring plays. In the Spring startup process, there are many extension points, compared to the well-known BeanPostPorcessor, mainly after the Bean initializeBean initialization, call org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory #applyBeanPostProcessorsAfterInitialization, all BeanPostPorcessor objects are internally traversed, calling postProcessAfterInitialization for processing, and Aop is based on this extensibility point:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

Debug along this link, you can finally locate the specific decision to call the JDK dynamic proxy or use CGLib, my proxy is DemoController, then the generated is the CGLig proxy:

Cliché: Do you really understand filters, interceptors, ControllerAdvice, and AOP?

OK, about filters, interceptors, ControllerAdvice and AOP, this article summarizes from the execution order, application scenarios, implementation techniques and principles, etc., after reading, friends if they have gained is the greatest affirmation of me.

Source: https://juejin.cn/post/7212175872091029559