简述SpringAop以及拦截器和过滤器

简述

AOP是面向切面编程(Aspect-Oriented Programming)的简称。它不是一项技术,和平常说的OOP(Object-Oriented Programming)一样,它是一种编程思想。这里不再做更多的名词解释。上图:

从这个丑陋的图中可以看出,利用AOP后,OOP的Objects 都可以只专注于自己的事情,而不需要去管用户是否登录以及记录本次操作日志的事情了。 而且关于用户的判断以及日志记录的代码也只需要一份,不再关心需要将这些额外的操作加在哪里。

实现

aop的实现主要有两种方式,一种是通过回调函数,另一种是代理。

1、回调

  web中常见的通过回调的方式实现的aop有Filter(过滤器)和Interceptor(拦截器)。首先附上一张图,看一下在运用springMVC时,一个请求的部分生命周期。

  

  (1)、客户端发起请求到服务器,服务器(这里以tomcat为例)会接受到请求,经过内部一系列包装以后,会生成编程时候需要用到的HttpServletRequest和HttpServletResponse。这其中的包装细节,这里不多说,贴个学习地址:https://blog.csdn.net/sunyunjie361/article/details/60126398

 (2)(11)(3)(10)、tomcat在StandardWrapperValve.invoke()方法中并不是直接调用dispatherServlet(分发器),而是将项目中注册的FilterList和dispatherServlet一起构造一个ApplicationFilterChain对象,再调用filterChain.doFilter(request,response)。在FilterChain中,会依次回调所有的Filter的doFilter方法,每个方法又通过调用FilterChain的doFilter来继续往下走,直到调用DispathServlet后,再逆向执行每个fitler中在调用FilterChain的doFilter后的代码。逻辑大致分这么几部,但具体的实现这里不做具体研究。有机会再写一篇关于tomcat的文章再具体说这个。

  这个FilterChain是个挺有意思的算法,简单版本的:

复制代码

public class myChain implements FilterChain {
    int pos = 0;
    List<Filter> list = new ArrayList<Filter>();


    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        Filter filter = list.get(pos++);
        filter.doFilter(request,response,this);
    }
}

class MyFitler implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}

复制代码

Filter(SpringBoot版本)示例:

复制代码

@WebFilter
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CrosFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //这里填写你允许进行跨域的主机ip
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        //允许的访问方法
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        //Access-Control-Max-Age 用于 CORS 相关配置的缓存
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "content-type,sessionid,x-requested-with");
        if(!HttpUtils.isOptionsRequest(request)){
            filterChain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {

    }
}

复制代码

(4)(5)、在DispatherServlet第一次被调用的时候,它会先执行initStrategies(context) 方法,改方法会初始化分发器所需要的一些参数。包括了HandleMappings。获取需要初始化的handleMapping很简单,直接调用BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);方法,这个方法返回注册在SPringIOC中且实现了HandlerMapping接口的对象。当然handleAdapter也是这样做的。

  当DispatherServlet执行doService时,它会遍历的HandleMapping,依次调用每个handlerMapping的getHandler()。每个handleMapping根据自己的需求重写getHandle或者getHandlerInternal()方法。在该方法中,根据reqeust判断是否是该自己处理的请求,如果不是,返回null,遍历继续,如果是自己该处理的请求,则返回this,同时遍历也就结束了。当然这里有个重要的事情就是:如果对于同一个Request有多个HandleMapping符合时,只会执行第一个。而第一个也不是根据注册时的顺序来的,是根据AnnotationAwareOrderComparator.sort(this.handlerMappings)方法来排序。这个方法其实就是@Order注解了。

  HandlerExecutionChain在返回之前,会进行一些配置,其中就包括了适配Interceptor。这里以RequestMappingHandlermapping(springMVC中用于处理Controller的处理器)为例,它会根据url匹配规则,HandlerExecutionChain中会保存所有matches方法返回true的MappedInterceptor和直接实现HandleInterceptor的Interceptors。实现的具体请看代码:

复制代码

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

   String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
         }
      }
      else {
         chain.addInterceptor(interceptor);
      }
   }
   return chain;
}

复制代码

  下面是adaptedInterceptors列表的初始化

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        getApplicationContext(), MappedInterceptor.class, true, false).values());
    }

  它会加载所有的MappedInterceptor以及其子类的SpringBean。然而,我们并不能简单的通过编写一个继承自MappedInterceptor的实体类来实现一个拦截器,因为MappedInterceptor.class是用final修饰的。这是一个装饰者模式的设计。所以,我们需要两部来编写一个Interceptor。首先编写一个实现了HandlerInterceptor接口的类,然后将其一个实例加上urlPattern来装饰为一个mappedInterceptor,并交由springIOC管理即可。

  当然也可以用xml配置的方式来实现,让容器在启动时就加载Interceptor,这样就省去配置mappedInterceptor了:

复制代码

<mvc:interceptors>  
    <!--定义一个handleInterceptor , 拦截所有的请求 -->  
    <bean class="com.host.app.web.interceptor.AllInterceptor"/>  
  <!--定义一个mappedInterceptor 装饰了path属性-->
    <mvc:interceptor>  
        <mvc:mapping path="/test/number.do"/>  
        <bean class="com.host.app.web.interceptor.LoginInterceptor"/>  
    </mvc:interceptor>  
</mvc:interceptors> 

复制代码

  当然,最终SpringMVC还是会把handleInterceptor变成mappedInterceptor。

复制代码

protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
        //遍历handleInterceptor
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
          //通过adaptInterceptor方法将handlerInterceptor转化为MappedInterceptor,并加入到adaptedInterceptors中
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }

复制代码

 在SpringBoot中,我们也有两种方式配置Interceptor,第一种还是配置mappedInterceptor,第二种是自定义WebMvcConfigurerAdapter适配器,在适配器中添加Interceptor。

  

复制代码

@SpringBootApplication
@EnableTransactionManagement
@ServletComponentScan
public class DemoApplication extends WebMvcConfigurerAdapter {
    protected static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);
  
  //通过重写这个方法来添加拦截器
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
    //添加一个拦截器,并为它配置urlPath。  这是java中不常用的火车头式写法,不要被迷惑,注意addInterceptor方法的返回值
     registry.addInterceptor(new ValidatorInterception()).addPathPatterns("/**");
     registry.addWebRequestInterceptor(new TestWebRequestInterceptor()).addPathPatterns("/**");
     super.addInterceptors(registry);
  }
  public static void main(String[] args) { 
      SpringApplication.run(DemoApplication.class, args);
      logger.error("CONGRATULATIONS!!   demo effective!");
  }

}

复制代码

 

(6)(7)(8)(9) 在DispatherServlet中,获得了HandlerExecutionChain以后,通过回调来实现AOP,代码如下:(去掉了其他与拦截器无关的代码,源码在DispatherServlet.doDispatch()方法中。)

复制代码

try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {//获取handler 
                mappedHandler = getHandler(processedRequest);//获取adapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                // 执行所有Interceptor的preHandle()方法  对应图中第6步
          if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 通过适配器执行contoller 对于图中第 7 、8在这方法中执行,这里不具体研究
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                 //通过视图解析器解析modleAndView 对应途中第9步
          applyDefaultViewName(processedRequest, mv);
                 //执行所有Interceptor的PostHandle()方法 也在第9步
          mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
               ...
            }
        }catch (Throwable err) {
           ...
        }
        finally {
          //执行Interceptors的afterCompletion()方法         
       mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        }

复制代码

   通过代码发现,handleInterceptor接口中的三个方法都有被回调一次;其中:afterCompletion方法是一定会执行的,一般情况下preHandle方法也会执行,但PostHandle方法则不一定执行。

示例:

复制代码

public class ValidatorInterception implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if(HttpUtils.isOptionsRequest(httpServletRequest)){
            return true;
        }
        HandlerMethod method = (HandlerMethod)o;
        Method method1 = method.getMethod();
        List<Object> objs = new ArrayList<Object>();
        MethodParameter[] methodParameters = method.getMethodParameters();
        for(int i = 0 ; i < methodParameters.length; i++){
            MyValidator parameterAnnotation = methodParameters[i].getParameterAnnotation(MyValidator.class);
            String parameter = httpServletRequest.getParameter(methodParameters[i].getParameterName());
            if(!Objects.isNull(parameterAnnotation)){
            }
        }

        Parameter[] parameters = method1.getParameters();
        for(int i = 0 ; i < parameters.length; i++){
            MyValidator annotation = parameters[i].getAnnotation(MyValidator.class);
            if(!Objects.isNull(annotation)){
               /* objs.add()*/
            }

        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

复制代码

通过回调的方式,这里主要讲的就是Filter和Interceptor,从以上可以发现一个问题,那就是通过回调的方式实现的AOP需要一个回调的步骤,耦合度较高,如果框架没有提供这个回调的过程,那我们编写的时候是很难做到的。如果觉得这种方式有点low,那接下来我们就讨论一下另一种实现方式:

2、代理

首先说一下代理模式: 

定义:为另一个对象提供一个替身或者占位符以控制对这个对象的访问。

  名词解释啥的,本人不擅长。上面这句话引用自《Head First 设计模式 》。多说无益,上代码:

  

复制代码

//代理对象和实际对象一样都继承共同接口
public class PersonBeanProxy implements Person{
   //代理对象 
  PersonBean personBean;
  
    public SimpleProxy(PersonBean personBean) {
        this.personBean = personBean;
    }
  //控制对象的访问
    public void say() {
        personBean.say();
    }

}

interface Person{
    void say();
}
//被代理对象的类
class PersonBean implements Person{
    String name;

    public PersonBean(String name) {
        this.name = name;
    }

    @Override
    public void say() {
        System.out.println("my name is "+name);
    }
}

复制代码

这是静态代理的一个简单例子,这个模式很容易和装饰者模式以及适配器模式混淆,其实细细品味,概念上还是能分出个所以然来。笔者觉得这需要个人理解,很难解释。

  嗯哼~ 大好时光,何必纠结这些名词解释,说点实在的。在java中,动态代理模式已经有两种现成的实现,一种是jdk自带的(是不是很激动,jdk自带了,不用纠结怎么去实现了),而另一种就是CGlib了。至于动态代理的具体实现,这里就不说了。后面会出SpringIOC相关知识,再细说代理。接下来,我们直接说spring如何运用代理来实现AOP的。

SpringAop

 这里简单花了一下springAop实现的流程图:

  

默认情况下springIOC再启动后会利用DefaultListableBeanFactory来实例化所有的bean。在实例化bean的时候,springIOC中会有多个beanPostProcessors(实例化bean后的处理器),也就是说大部分的bean实际上都是经过代理后的代理对象,而不是实际的对象。所以在debug调试中经常会看到一些bean的名字结尾是$proxy或者$cglibProxy等等,说明它们都是经过包装后的代理类对象。源码再DefaultListableBeanFactory.class的父类AbstractAutowireCapableBeanFactory.class中:

复制代码

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
      throws BeansException {

   Object result = existingBean;
   for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
      result = beanProcessor.postProcessAfterInitialization(result, beanName);
      if (result == null) {
         return result;
      }
   }
   return result;
}

复制代码

在众多的处理器中,包括了一个AnnotationAwareAspectJAutoProxyCreator,它其实也只是众多代理bean类处理器中的一个,它主要用来助理@AspectJ自动代理的处理器。在它的内部存储了用@AspectJ注解标注的advisor(通知器),这里有个有趣的事情,其实这些处理器和通知器本身也是Bean,后面学习SpringIOC的时候,再细细研究这个。

这里给出部分类图:

在AnnotationAwareAspectJAutoProxyCreator内,会先构建一个ProxyFactory对象。源码:

复制代码

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);

        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            }
            else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        for (Advisor advisor : advisors) {
            proxyFactory.addAdvisor(advisor);
        }

        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

复制代码

ProxyFactory内部持有一个DefaultAopProxyFactory对象,利用DefaultAopProxyFactory.createAopProxy(AdvisedSupport config)来创建代理对象。源码:

复制代码

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

复制代码

这里第一个判断:代理服务器设置是否应该执行积极的优化,第二个判断:是否启动cglib,第三个为判断:bean是不是继承了SpringProxy。其中前两个判断可以利用参数来配置:

复制代码

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"  
        p:interceptorNames="before"  
        p:target-ref="userServiceImpl"  
        p:optimize="false"  
        p:proxyTargetClass="false"  
        >  
    </bean>  

复制代码

当然还有其他的一些判断,主要是因为两种aop的实现是不同的,jdk代理是基于接口,也就是说生成的代理类是实现了传入的接口,而cglib是基于类的,它生成代理类是继承了传入的类。所以两种实现所需的参数不同,也就适用于不同的情况。

先了解一下jdk版本的。Proxy类提供了newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 方法来获取代理对象。 它返回的对象实现了接口的所有方法,这样对这个代理对象的访问就像访问一个实例一样简单,而每个方法的具体实现,实际上jdk的Proxy是没有的。它并不会去管代理对象的方法该实现怎样的逻辑,而是通过调用传入的InvocationHandler  的的invoke方法来执行具体的细节。而invoke方法是需要用户自己实现的。当然在spring框架中,spring是已经实现了的。源码:

复制代码

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;

        TargetSource targetSource = this.advised.targetSource;
        Class<?> targetClass = null;
        Object target = null;

        try {
           
            Object retVal;// Get the interception chain for this method.
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            // Check whether we have any advice. If we don't, we can fallback on direct
            // reflective invocation of the target, and avoid creating a MethodInvocation.
            if (chain.isEmpty()) {
                // We can skip creating a MethodInvocation: just invoke the target directly
                // Note that the final invoker must be an InvokerInterceptor so we know it does
                // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            }
            else {
                // We need to create a method invocation... 
                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                // Proceed to the joinpoint through the interceptor chain.
                retVal = invocation.proceed();
            }return retVal;
        }
        finally {
          ...
    }

复制代码

这里就会执行我们aop中的代码了。由于spring的aop是可以多层的嵌套的,这里也是通过链式调用来完成逐层调用。

然后来看下spring自己的cglilb实现:个人感觉cglib的实现就是比较暴力的了,它先创建Enhancer 对象,源码:

复制代码

Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

            Callback[] callbacks = getCallbacks(rootClass);

复制代码

然后直接生产字节码的类文件,再调用classCLoad加载该类,然后生产一个代理对象。生产的类文件我还没办法找到,不过收藏了一篇 https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0

复制代码

@Aspect
@Configuration
public class ValidatorAop {
    @Autowired
    Validator validator;

    @Pointcut("execution(public * com.example.controller..*.*(..))")
    public void validate(){}

    @Before("validate()")
    @After("")
    @Around("")
    public void validating(JoinPoint point)throws Throwable{
        Object[] args = point.getArgs();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Before annotation1 = method.getAnnotation(Before.class);
        annotation1.value();
        
        Parameter[] parameters = method.getParameters();
        for(int i = 0 ; i < parameters.length; i++){
            Parameter parameter = parameters[i];
            Object value = args[i];
            MyValidator annotation = parameter.getAnnotation(MyValidator.class);
            if(!Objects.isNull(annotation)){
                ValidatorByType(args, i, value, annotation);
            }
        }
    }

    private void ValidatorByType(Object[] args, int i, Object value, MyValidator annotation) {
        if(Map.class.isAssignableFrom(value.getClass())){
            Map<Object,Object> newValue = (Map<Object,Object>) value;
            for(Map.Entry<Object,Object> entry : newValue.entrySet()){
                Object value1 = entry.getValue();
                if(!value1.getClass().isPrimitive() && !(value1 instanceof String)){
                    validatedUseHibernateValidator(value1, annotation);
                }
            }
        }else if(Collection.class.isAssignableFrom(value.getClass())){
            Collection newValue = (Collection) value;
            Iterator iterator = newValue.iterator();
            while (iterator.hasNext()){
                validatedUseHibernateValidator(iterator.next(), annotation);
            }
        }else{
            validatedUseHibernateValidator(args[i], annotation);
        }
    }

    private void validatedUseHibernateValidator(Object value, MyValidator annotation) {
        Set<ConstraintViolation<Object>> validate = validator.validate(value, annotation.value());
        if(!CollectionUtils.isEmpty(validate)){
            throw new ConstraintViolationException(validate);
        }
    }

}

复制代码

 两种实现的总结:

  不管是通过方法回调还是代理的方式,再使用的时候都需要遵守一些规范,Fitler和Interceptor都需要实现指定的接口,而代理的方式是通过注解来注册通知器,但是代理的方式就显得特别灵活,可以随意控制拦截的目标,而回调的方式拦截的只能是再回调方法调用的地方了。但对于新手来说,拦截器和过滤器如果用代理的方式来实现的话,压根就不知道该拦截谁,这就很尴尬。即使初级开发,也很多不知道该如何去拦截。所以Filter和Interceptor是框架为我们提供的一个便利,不让我们迷失在框架的源码中。

猜你喜欢

转载自blog.csdn.net/chuixue24/article/details/112797576