簡述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是框架為我們提供的一個便利,不讓我們迷失在框架的源碼中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM