zoukankan      html  css  js  c++  java
  • SpringAOP概述(六)

    OOP 面向对象编程,AOP(Aspect-Oriented Programming) 面向切面编程。

    官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

    1. AOP简介

    主要有以下AOP技术:

    1》AspectJ: 源代码和字节码级别的编织器,用户需要使用不同于Java的新语言

    2》AspectWerkz:AOP框架,使用字节码动态编织器和XML配置

    3》JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上,以及在AOP中用到的一些相关的技术实现

    4》BCEL(Byte-Code Engineering Library): Java字节码操作类 库

    5》Javassist:Java字节码操作类型,JBOSS的一个子项目

    对应于现有的AOP实现方法,AOP联盟对它们进行了一定程度的抽象,从而定义出AOP体系结构。AOP联盟定义的AOP体系结构如图:

     AOP联盟定义的体系结构把与AOP相关的概念大致分为由高到低、从使用到实现的三个层次。从上往下,最高层是语言和开发环境,在这个环境中可以看到几个重要的概念:

    (1) 基础(base) 可以视为待增强对象或者说目标对象

    (2) 切面(aspect) 通常包含对于基础的增强应用

    (3) 配置(configuration)可以看成是一种编织,通过在AOP体系中提供这个配置环境,可以把基础和切面结合起来,从而完成切面对目标对象的编织实现

      在SpringAOP的实现中,使用Java语言来实现增强对象与切面增强应用,并为这两者的结合提供了配置环境。对于编织配置,毫无疑问,可以使用IoC容器了完成;对于POJO对象的配置,本身就是Spring核心IoC容器的强项。因此,对于使用SpringAOP开发而言,使用POJO就能完成AOP任务。但是,对于其他AOP实现方案,可能需要使用特定的实现语言、配置环境甚至是特定的编译环境。例如在AspectJ中,尽管切面增强的是Java对象,但却需要使用特定的Aspect语言和Aspect编译器。

      AOP体系的第二个层次是为语言和开发环境提供支持的,在这个层次中可以看到AOP框架的高层实现,主要包括配置和编织实现两部分内容。

      最底层是编织的具体实现模块,比如反射、程序预处理、拦截器框架、类装载器框架、元数据处理等。

    2. Spring AOP简介

       AOP与IoC容器的结合使用,为应用开发或Spring自身功能的扩展都提供了许多便利。Spring AOP的实现和其他特性的实现一样,除了可以Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方法AspectJ来供应用使用。这里主要眼界Spring自身的AOP实现原理。Spring充分利用了IoC容器Proxy代理对象以及AOP拦截器的功能特性,通过这些对AOP基本功能的封装机制,为用户提供了AOP的实现框架。所以Java的Proxy机制是Spring AOP的重点。

    1. Advice通知

    Advice(通知) 定义在连接点做什么,为切面增强提供织入接口。在SpringAOP中,它主要描述SpringAOP围绕方法调用而注入的切面行为。Advice是AOP联盟定义的一个接口,具体的接口在 org.aopalliance.aop 包下。在SpringAOP实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的植入功能做了更多的细化和扩展,衍生出很多接口,如下:

     以 BeforeAdvice 接口为例进行分析。Advice和BeforeAdvice 都是一个空接口。BeforeAdvice继承关系如下:

     MethodBeforeAdvice 接口定义了为待增强的目标方法设置的前置增强接口,接口包含一个方法如下:

    package org.springframework.aop;
    
    import java.lang.reflect.Method;
    
    import org.springframework.lang.Nullable;
    
    public interface MethodBeforeAdvice extends BeforeAdvice {
    
        void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
    
    }

       作为回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法前被回调。具体的调用参数有:Method对象,这个参数是目标方法的反射对象;Object[] 对象数组,这个对象数组包含目标方法的输入参数。以CountingBeforeAdvice为例子说明其用法。CountingBeforeAdvice  完成的工作是统计被调用的方法次数。作为切面增强实效,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对存入一个map中。源码如下:

    package org.springframework.tests.aop.advice;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    @SuppressWarnings("serial")
    public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {
    
        @Override
        public void before(Method m, Object[] args, Object target) throws Throwable {
            count(m);
        }
    
    }

     MethodCounter源码如下:

    package org.springframework.tests.aop.advice;
    
    import java.io.Serializable;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    
    /**
     * Abstract superclass for counting advices etc.
     *
     * @author Rod Johnson
     * @author Chris Beams
     */
    @SuppressWarnings("serial")
    public class MethodCounter implements Serializable {
    
        /** Method name --> count, does not understand overloading */
        private HashMap<String, Integer> map = new HashMap<>();
    
        private int allCount;
    
        protected void count(Method m) {
            count(m.getName());
        }
    
        protected void count(String methodName) {
            Integer i = map.get(methodName);
            i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);
            map.put(methodName, i);
            ++allCount;
        }
    
        public int getCalls(String methodName) {
            Integer i = map.get(methodName);
            return (i != null ? i.intValue() : 0);
        }
    
        public int getCalls() {
            return allCount;
        }
    
        /**
         * A bit simplistic: just wants the same class.
         * Doesn't worry about counts.
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object other) {
            return (other != null && other.getClass() == this.getClass());
        }
    
        @Override
        public int hashCode() {
            return getClass().hashCode();
        }
    
    }

    Spring提供了与BeforeAdvice 对等的AfterAdvice,类图如下:

     以AfterReturningAdvice 为例进行分析:

    package org.springframework.aop;
    
    import java.lang.reflect.Method;
    
    import org.springframework.lang.Nullable;
    
    public interface AfterReturningAdvice extends AfterAdvice {
        void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
    
    }

     afterReturning 方法在目标方法调用结束并成功返回时调用。对于回调参数有 目标方法的返回结果、反射对象、以及调用参数等。有一个实现类CountingAfterReturningAdvice,它的实现与上面CountingBeforeAdvice  基本一样:

    package org.springframework.tests.aop.advice;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    @SuppressWarnings("serial")
    public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {
    
        @Override
        public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {
            count(m);
        }
    
    }

    AfterAdvice还有一个子接口ThrowsAdvice,它在抛出异常时回调,这个回调是AOP使用反射机制完成的。可以通过查看CountingThrowsAdvice,如下:

        public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {
    
            public void afterThrowing(IOException ex) throws Throwable {
                count(IOException.class.getName());
            }
    
            public void afterThrowing(UncheckedException ex) throws Throwable {
                count(UncheckedException.class.getName());
            }
    
        }

      在afterThrowing 方法中,从输入的异常对象中得到异常的名字并进行统计。这个count方法同样是继承MethodCounter 的方法。只是前面两个Advice统计的是方法调用次数,这里count方法统计的是抛出异常的次数。

    补充:Spring AbstractAspectJAdvice类下面的五个接口代表了常见的五种通知

     对应五种通知类型:

    @Before 前置通知

    @AfterReturning 后置通知

    @Around 环绕通知

    @After 最终通知

    @AfterThrowing 异常抛出通知  

     2. Pointcut 切点

      Pointcut切点决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如:这些需要增强的地方可以由某个正则表达式进行标识,或者根据某个方法名称进行匹配。

      为了方便用户使用,Spring AOP提供了具体的切点供用户使用,切点在Spring AOP的类继承体系如下:

     Pointcut接口如下:

    package org.springframework.aop;
    
    public interface Pointcut {
    
        ClassFilter getClassFilter();
    
        MethodMatcher getMethodMatcher();
    
        Pointcut TRUE = TruePointcut.INSTANCE;
    
    }

      可以看到需要返回一个Methodmatcher。对于Point的匹配判断功能,具体是由这个MethodMatcher 来完成的,也就是说这个MethodMatcher需要判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。

      以JdkRegexpMethodPointcut 为例说明Pointcut的原理。其源码如下:

    public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {
    
        /**
         * Compiled form of the patterns.
         */
        private Pattern[] compiledPatterns = new Pattern[0];
    
        /**
         * Compiled form of the exclusion patterns.
         */
        private Pattern[] compiledExclusionPatterns = new Pattern[0];
    
    
        /**
         * Initialize {@link Pattern Patterns} from the supplied {@code String[]}.
         */
        @Override
        protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
            this.compiledPatterns = compilePatterns(patterns);
        }
    
        /**
         * Initialize exclusion {@link Pattern Patterns} from the supplied {@code String[]}.
         */
        @Override
        protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
            this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
        }
    
        /**
         * Returns {@code true} if the {@link Pattern} at index {@code patternIndex}
         * matches the supplied candidate {@code String}.
         */
        @Override
        protected boolean matches(String pattern, int patternIndex) {
            Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
            return matcher.matches();
        }
    
        /**
         * Returns {@code true} if the exclusion {@link Pattern} at index {@code patternIndex}
         * matches the supplied candidate {@code String}.
         */
        @Override
        protected boolean matchesExclusion(String candidate, int patternIndex) {
            Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);
            return matcher.matches();
        }
    
    
        /**
         * Compiles the supplied {@code String[]} into an array of
         * {@link Pattern} objects and returns that array.
         */
        private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
            Pattern[] destination = new Pattern[source.length];
            for (int i = 0; i < source.length; i++) {
                destination[i] = Pattern.compile(source[i]);
            }
            return destination;
        }
    
    }

      matches 方法使用正则表达式来对方法名进行匹配。关于AOP框架中对matches 方法的调用会在下面介绍。

      在SpringAOP中,还提供了其他的MethodPointcut,比如通过方法名称进行Advice匹配的NameMatchMethodPointcut, 它的匹配是方法名相同或者相匹配。如下:

        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            for (String mappedName : this.mappedNames) {
                if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
                    return true;
                }
            }
            return false;
        }
    
        protected boolean isMatch(String methodName, String mappedName) {
            return PatternMatchUtils.simpleMatch(mappedName, methodName);
        }

    3. Advisor 通知器

       完成对目标方法的切面增强设计(Advice)和关注点(Pointcut)以后,需要一个对象把他们结合起来,完成这个作用的就是Advisor(通知器)。通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在SpringAOP中,以一个Advisor的实现DefaultPointcutAdvisor 为例,了解Advisor的工作原理,其源码如下:

    package org.springframework.aop.support;
    
    import java.io.Serializable;
    
    import org.aopalliance.aop.Advice;
    
    import org.springframework.aop.Pointcut;
    import org.springframework.lang.Nullable;
    
    @SuppressWarnings("serial")
    public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
    
        private Pointcut pointcut = Pointcut.TRUE;
    
        public DefaultPointcutAdvisor() {
        }
    
        public DefaultPointcutAdvisor(Advice advice) {
            this(Pointcut.TRUE, advice);
        }
    
        public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
            this.pointcut = pointcut;
            setAdvice(advice);
        }
    
        public void setPointcut(@Nullable Pointcut pointcut) {
            this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
        }
    
        @Override
        public Pointcut getPointcut() {
            return this.pointcut;
        }
    
    
        @Override
        public String toString() {
            return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
        }
    
    }

     在DefaultPointcutAdvisor 中,pointcut默认被设置为Pointcut.TRUE,这个Pointcut.TRUE 在Pointcut接口被定义为:

    Pointcut TRUE = TruePointcut.INSTANCE;

    TruePointcut.INSTANCE 是一个单例模式的对象,看名字就知道这个匹配器匹配任何方法。在TruePointcut 的MethodMatcher实现中,用TrueMethodMatcher 作为匹配器,同样是一个单例对象。

    final class TruePointcut implements Pointcut, Serializable {
    
        public static final TruePointcut INSTANCE = new TruePointcut();
    
        /**
         * Enforce Singleton pattern.
         */
        private TruePointcut() {
        }
    
        @Override
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }
    
        @Override
        public MethodMatcher getMethodMatcher() {
            return MethodMatcher.TRUE;
        }
    
        /**
         * Required to support serialization. Replaces with canonical
         * instance on deserialization, protecting Singleton pattern.
         * Alternative to overriding {@code equals()}.
         */
        private Object readResolve() {
            return INSTANCE;
        }
    
        @Override
        public String toString() {
            return "Pointcut.TRUE";
        }
    
    }

    org.springframework.aop.MethodMatcher#TRUE如下:

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

     TrueMethodMatcher 源码如下:

    final class TrueMethodMatcher implements MethodMatcher, Serializable {
    
        public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
    
        private TrueMethodMatcher() {
        }
    
    
        @Override
        public boolean isRuntime() {
            return false;
        }
    
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return true;
        }
    
        @Override
        public boolean matches(Method method, Class<?> targetClass, Object... args) {
            // Should never be invoked as isRuntime returns false.
            throw new UnsupportedOperationException();
        }
    
    
        @Override
        public String toString() {
            return "MethodMatcher.TRUE";
        }
    
        private Object readResolve() {
            return INSTANCE;
        }
    
    }

     补充: SpringAOP和AspectJ的关系

      aop是一种思想而不是一种技术。所以说,如果抛开spring,动态代理甚至静态代理都可以算是一种aop。

      spring中的aop实现分为两种,基于动态代理的aop和基于AspectJ的aop。AspectJ是完全独立于Spring存在的一个Eclipse发起的项目。AspectJ甚至可以说是一门独立的语言,在java文件编译期间,织入字节码,改变原有的类。

      我们常看到的在spring中用的@Aspect注解只不过是Spring2.0以后使用了AspectJ的风格而已,本质上还是Spring的原生实现。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    Shell – Wget 克隆网站
    Tools
    Tools
    Tools
    Ubuntu
    android studio中配置X5 webview时的一个坑
    android studio中Fragment使用webview返回上一页的问题
    android studio中退出时弹出对话框
    android studio中使用x5 webview来读写cookies的问题
    flask blueprint出现的坑
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14432120.html
Copyright © 2011-2022 走看看