• 手写Spring-第十一章-用动态代理实现AOP核心功能


    前言

    前面经过了十章的漫长练习,我们终于把Spring的IOC部分完善的差不多了。那么从这一章开始,我们就要步入Spring的另外半壁江山:AOP了。AOP,也就是面向切面编程。它可以实现对业务逻辑的各个部分进行隔离,从而降低各个模块之间的耦合度,提高代码可复用性。

    我们平时对Spring AOP的应用,最常见的情况可能就是对方法执行的监控了。比如在方法执行前后输出一些日志之类的。那么Spring是如何实现这个功能的呢?答案就是动态代理。我们最终使用的类,实际上已经不是我们最原始的类了,而是被Spring代理过的类。而一旦这个类被代理了,那就意味着Spring可以在执行前后做任何自己想做的事情了。就像租房子时候的黑中介一样,给你加上各种条款还有高额的中介费(泣。

    工程结构

    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─com
    │  │  │      └─akitsuki
    │  │  │          └─springframework
    │  │  │              ├─aop
    │  │  │              │  │  AdvisedSupport.java
    │  │  │              │  │  ClassFilter.java
    │  │  │              │  │  MethodMatcher.java
    │  │  │              │  │  PointCut.java
    │  │  │              │  │  TargetSource.java
    │  │  │              │  │  
    │  │  │              │  ├─aspect
    │  │  │              │  │      AspectJExpressionPointcut.java
    │  │  │              │  │    
    │  │  │              │  └─framework
    │  │  │              │          AopProxy.java
    │  │  │              │          Cglib2AopProxy.java
    │  │  │              │          JdkDynamicAopProxy.java
    │  │  │              │          ReflectiveMethodInvocation.java
    │  │  │              │        
    │  │  │              ├─beans
    │  │  │              │  ├─exception
    │  │  │              │  │      BeanException.java
    │  │  │              │  │    
    │  │  │              │  └─factory
    │  │  │              │      │  Aware.java
    │  │  │              │      │  BeanClassLoaderAware.java
    │  │  │              │      │  BeanFactory.java
    │  │  │              │      │  BeanFactoryAware.java
    │  │  │              │      │  BeanNameAware.java
    │  │  │              │      │  ConfigurableListableBeanFactory.java
    │  │  │              │      │  DisposableBean.java
    │  │  │              │      │  FactoryBean.java
    │  │  │              │      │  HierarchicalBeanFactory.java
    │  │  │              │      │  InitializingBean.java
    │  │  │              │      │  ListableBeanFactory.java
    │  │  │              │      │  
    │  │  │              │      ├─config
    │  │  │              │      │      AutowireCapableBeanFactory.java
    │  │  │              │      │      BeanDefinition.java
    │  │  │              │      │      BeanDefinitionRegistryPostProcessor.java
    │  │  │              │      │      BeanFactoryPostProcessor.java
    │  │  │              │      │      BeanPostProcessor.java
    │  │  │              │      │      BeanReference.java
    │  │  │              │      │      ConfigurableBeanFactory.java
    │  │  │              │      │      DefaultSingletonBeanRegistry.java
    │  │  │              │      │      PropertyValue.java
    │  │  │              │      │      PropertyValues.java
    │  │  │              │      │      SingletonBeanRegistry.java
    │  │  │              │      │    
    │  │  │              │      ├─support
    │  │  │              │      │      AbstractAutowireCapableBeanFactory.java
    │  │  │              │      │      AbstractBeanDefinitionReader.java
    │  │  │              │      │      AbstractBeanFactory.java
    │  │  │              │      │      BeanDefinitionReader.java
    │  │  │              │      │      BeanDefinitionRegistry.java
    │  │  │              │      │      CglibSubclassingInstantiationStrategy.java
    │  │  │              │      │      DefaultListableBeanFactory.java
    │  │  │              │      │      DisposableBeanAdapter.java
    │  │  │              │      │      FactoryBeanRegistrySupport.java
    │  │  │              │      │      InstantiationStrategy.java
    │  │  │              │      │      SimpleInstantiationStrategy.java
    │  │  │              │      │    
    │  │  │              │      └─xml
    │  │  │              │              XmlBeanDefinitionReader.java
    │  │  │              │            
    │  │  │              ├─context
    │  │  │              │  │  ApplicationContext.java
    │  │  │              │  │  ApplicationContextAware.java
    │  │  │              │  │  ApplicationEvent.java
    │  │  │              │  │  ApplicationEventPublisher.java
    │  │  │              │  │  ApplicationListener.java
    │  │  │              │  │  ConfigurableApplicationContext.java
    │  │  │              │  │  
    │  │  │              │  ├─event
    │  │  │              │  │      AbstractApplicationEventMulticaster.java
    │  │  │              │  │      ApplicationContextEvent.java
    │  │  │              │  │      ApplicationEventMulticaster.java
    │  │  │              │  │      ContextClosedEvent.java
    │  │  │              │  │      ContextRefreshEvent.java
    │  │  │              │  │      SimpleApplicationEventMulticaster.java
    │  │  │              │  │    
    │  │  │              │  └─support
    │  │  │              │          AbstractApplicationContext.java
    │  │  │              │          AbstractRefreshableApplicationContext.java
    │  │  │              │          AbstractXmlApplicationContext.java
    │  │  │              │          ApplicationContextAwareProcessor.java
    │  │  │              │          ClasspathXmlApplicationContext.java
    │  │  │              │        
    │  │  │              ├─core
    │  │  │              │  └─io
    │  │  │              │          ClasspathResource.java
    │  │  │              │          DefaultResourceLoader.java
    │  │  │              │          FileSystemResource.java
    │  │  │              │          Resource.java
    │  │  │              │          ResourceLoader.java
    │  │  │              │          UrlResource.java
    │  │  │              │        
    │  │  │              └─util
    │  │  │                      ClassUtils.java
    │  │  │                    
    │  │  └─resources
    │  └─test
    │      └─java
    │          └─com
    │              └─akitsuki
    │                  └─springframework
    │                      │  ApiTest.java
    │                      │  
    │                      ├─aop
    │                      │  └─aspect
    │                      │          AspectJExpressionPointcutTest.java
    │                      │        
    │                      └─bean
    │                              IUserDao.java
    │                              UserDao.java
    │                              UserDaoInterceptor.java
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119

    这次的内容,主要新增了一个aop包,经历了前面的练习,这些已经是小场面了。让我们开始这一次的内容吧。

    事前准备

    这次我们需要引入两个新的依赖,来帮助我们解析表达式,以及实现代理

            <dependency>
                <groupId>org.aspectjgroupId>
                <artifactId>aspectjweaverartifactId>
                <version>1.9.7version>
            dependency>
            <dependency>
                <groupId>aopalliancegroupId>
                <artifactId>aopallianceartifactId>
                <version>1.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    切点表达式

    回忆一下,我们在Spring中使用AOP的时候,一般都是用一个表达式来表示我们需要切入的方法或者类。那么我们首先就要来实现这一功能。

    首先,我们需要一个切入点

    package com.akitsuki.springframework.aop;
    
    /**
     * 切入点接口
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 9:51
     */
    public interface PointCut {
    
        /**
         * 获取classFilter
         * @return
         */
        ClassFilter getClassFilter();
    
        /**
         * 获取method匹配器
         * @return
         */
        MethodMatcher getMethodMatcher();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里的 ClassFilterMethodMatcher,我们会在下面进行介绍。这两个类,分别是用来匹配类与方法的。

    我们来看看ClassFilter

    package com.akitsuki.springframework.aop;
    
    /**
     * 类匹配接口,用于切点找到给定的接口和目标类
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 9:54
     */
    public interface ClassFilter {
    
        /**
         * 是否匹配
         *
         * @param clazz 要匹配的类
         * @return result
         */
        boolean matches(Class<?> clazz);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    简单易懂的一个方法,就是用来判断传入的类是否匹配的

    我们再来看MethodMatcher

    package com.akitsuki.springframework.aop;
    
    import java.lang.reflect.Method;
    
    /**
     * 方法匹配接口,找到表达式范围内匹配下的目标类和方法
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 9:55
     */
    public interface MethodMatcher {
    
        /**
         * 是否匹配
         *
         * @param method 要匹配的方法
         * @param clazz  要匹配的类
         * @return result
         */
        boolean matches(Method method, Class<?> clazz);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    和上面类似,这次是判断方法是否匹配。

    有了这些接口作为框架,我们就可以实现我们的表达式匹配类了

    package com.akitsuki.springframework.aop.aspect;
    
    import com.akitsuki.springframework.aop.ClassFilter;
    import com.akitsuki.springframework.aop.MethodMatcher;
    import com.akitsuki.springframework.aop.PointCut;
    import org.aspectj.weaver.tools.PointcutExpression;
    import org.aspectj.weaver.tools.PointcutParser;
    import org.aspectj.weaver.tools.PointcutPrimitive;
    
    import java.lang.reflect.Method;
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 9:58
     */
    public class AspectJExpressionPointcut implements PointCut, ClassFilter, MethodMatcher {
    
        private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
    
        static {
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        }
    
        private final PointcutExpression pointcutExpression;
    
        public AspectJExpressionPointcut(String expression) {
            PointcutParser pointcutParser = PointcutParser
                    .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                            SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
            pointcutExpression = pointcutParser.parsePointcutExpression(expression);
        }
    
        @Override
        public boolean matches(Class<?> clazz) {
            return pointcutExpression.couldMatchJoinPointsInType(clazz);
        }
    
        @Override
        public boolean matches(Method method, Class<?> clazz) {
            return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
        }
    
        @Override
        public ClassFilter getClassFilter() {
            return this;
        }
    
        @Override
        public MethodMatcher getMethodMatcher() {
            return this;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    我们来分析一下。首先,我们维护了一个Set,用来表示我们支持的匹配方式。然后在静态代码块中,加入了execution表达式的支持。然后我们看构造方法,主要内容是通过传入的表达式字符串,初始化我们的表达式解析器。最后我们来看看对方法内容的实现。可以看到主要都是通过表达式解析器来进行判断传入的类或者方法,是否满足当前的表达式。

    对切面信息进行包装

    我们既然要对一个对象进行代理,首先我们需要一个类来表示它。怎么理解呢,有些像我们前面的Bean定义一样,需要一个类来存放一些被代理类的信息。

    package com.akitsuki.springframework.aop;
    
    /**
     * 被代理的目标对象
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 11:11
     */
    public class TargetSource {
    
        private final Object target;
    
        public TargetSource(Object target) {
            this.target = target;
        }
    
        public Class<?>[] getTargetClass() {
            return target.getClass().getInterfaces();
        }
    
        public Object getTarget() {
            return target;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    可以看到,我们用TargetSource类,来表示被代理的对象。类中维护了一个Object属性来存储被代理对象,同时提供了getTargetClass方法,来获取对象所实现的接口。这一点很重要,并不是获取对象本身,而是获取对象的接口。因为我们用JDK的动态代理来进行实现的话,被代理的对象必须要实现接口,所以我们也需要一个方法来提供这些接口。

    接下来,我们还需要一个类,用来包装上面的这些信息。它的作用有些类似于我们平时使用的DTO,主要是对需要用到的信息起到一个包装汇总作用。

    package com.akitsuki.springframework.aop;
    
    import lombok.Getter;
    import lombok.Setter;
    import org.aopalliance.intercept.MethodInterceptor;
    
    /**
     * 切面通知属性包装类,主要是方便管理
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 11:08
     */
    @Getter
    @Setter
    public class AdvisedSupport {
    
        /**
         * 被代理的目标对象
         */
        private TargetSource targetSource;
    
        /**
         * 方法拦截器
         */
        private MethodInterceptor methodInterceptor;
    
        /**
         * 方法匹配器
         */
        private MethodMatcher methodMatcher;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    可以看到,它包含了被代理的对象、方法拦截器和方法匹配器三个内容。

    开始代理!

    上面铺垫了这么多,我们终于要开始今天的重头戏了。我们知道常用的代理,有JDK的动态代理,还有Cglib提供的代理。所以我们首先需要一个接口,用来抽象我们的代理方式。

    package com.akitsuki.springframework.aop.framework;
    
    /**
     * 获取代理类,因为代理的方式可能有多种实现
     *
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 11:29
     */
    public interface AopProxy {
    
        /**
         * 获取代理类
         *
         * @return resuLt
         */
        Object getProxy();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们这次准备提供JDK和Cglib两种代理的实现,首先来看JDK的实现方式

    package com.akitsuki.springframework.aop.framework;
    
    import com.akitsuki.springframework.aop.AdvisedSupport;
    import org.aopalliance.intercept.MethodInterceptor;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 11:30
     */
    public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    
        private final AdvisedSupport advisedSupport;
    
        public JdkDynamicAopProxy(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }
    
        @Override
        public Object getProxy() {
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    advisedSupport.getTargetSource().getTargetClass(),
                    this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getTarget().getClass())) {
                MethodInterceptor methodInterceptor = advisedSupport.getMethodInterceptor();
                return methodInterceptor.invoke(new ReflectiveMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, args));
            }
            return method.invoke(advisedSupport.getTargetSource().getTarget(), args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    可以看到,首先这个类实现了 AopProxyInvocationHandler接口。实现了 InvocationHandler接口,就意味着当前这个类可以作为JDK动态代理类来使用了。getProxy方法中,通过 Proxy.newProxyInstance方法,来动态生成代理对象。接下来我们看 invoke方法。这个方法是代理类在调用方法时,会被拦截执行的一个方法。可以看到这里实现的内容是,通过方法匹配器来判断当前方法是否需要被拦截,如果需要被拦截,则通过方法拦截器来进行调用。否则,直接通过反射进行调用方法。

    这里还可以看到有一个 ReflectiveMethodInvocation,它其实也是起到一个包装作用,实现了 MethodInvocation接口。主要内容的实现 proceed方法,实际上还是对方法进行反射调用。

    package com.akitsuki.springframework.aop.framework;
    
    import org.aopalliance.intercept.MethodInvocation;
    
    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Method;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 15:39
     */
    public class ReflectiveMethodInvocation implements MethodInvocation {
    
        protected final Object target;
    
        protected final Method method;
    
        protected final Object[] arguments;
    
        public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) {
            this.target = target;
            this.method = method;
            this.arguments = arguments;
        }
    
        @Override
        public Method getMethod() {
            return method;
        }
    
        @Override
        public Object[] getArguments() {
            return arguments;
        }
    
        @Override
        public Object proceed() throws Throwable {
            return method.invoke(target, arguments);
        }
    
        @Override
        public Object getThis() {
            return target;
        }
    
        @Override
        public AccessibleObject getStaticPart() {
            return method;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    接下来我们看Cglib的实现方式

    package com.akitsuki.springframework.aop.framework;
    
    import com.akitsuki.springframework.aop.AdvisedSupport;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 15:45
     */
    public class Cglib2AopProxy implements AopProxy {
    
        private final AdvisedSupport advisedSupport;
    
        public Cglib2AopProxy(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }
    
        @Override
        public Object getProxy() {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(advisedSupport.getTargetSource().getTarget().getClass());
            enhancer.setInterfaces(advisedSupport.getTargetSource().getTargetClass());
            enhancer.setCallback(new DynamicAdvisedInterceptor(advisedSupport));
            return enhancer.create();
        }
    
        private static class DynamicAdvisedInterceptor implements MethodInterceptor {
    
            private final AdvisedSupport advisedSupport;
    
            public DynamicAdvisedInterceptor(AdvisedSupport advisedSupport) {
                this.advisedSupport = advisedSupport;
            }
    
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                CglibMethodInvocation methodInvocation =
                        new CglibMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, objects, methodProxy);
                if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getTarget().getClass())) {
                    return advisedSupport.getMethodInterceptor().invoke(methodInvocation);
                }
                return methodInvocation.proceed();
            }
        }
    
        private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
    
            private final MethodProxy methodProxy;
    
            public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy) {
                super(target, method, arguments);
                this.methodProxy = methodProxy;
            }
    
            @Override
            public Object proceed() throws Throwable {
                return this.methodProxy.invoke(this.target, this.arguments);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    看起来很复杂,还有两个静态的内部类。但其实仔细分析一下的话,会发现也不是很难。我们先来看 getProxy的内容,可以看到,这里是通过Enhancer创建了代理对象,重点在于Callback的内容。这里的Callback,实际上和上面JDK动态代理中的invoke功能是一样的,都是代理后拦截执行的逻辑。这里需要传入一个Callback类型的对象,Callback实际上是一个标记接口,我们之前也接触过很多次标记接口了,有很多类都实现了Callback接口,我们一般常用的是 MethodInterceptor,也就是方法拦截器。往下看,我们的第一个静态内部类,实际上就是一个方法拦截器,然后我们看具体内容,实际上还是通过方法匹配器来进行判断,如果匹配就调用方法拦截器来执行,和JDK动态代理是一样的玩法,如果不匹配,则调用 methodInvocation来执行。这里的 MethodInvocation,就是我们下面的静态内部类,可以看到它继承了我们上面提到的 ReflectiveMethodInvocation,所以其实本质上它们没什么区别。主要就是这里的执行,是通过方法代理 MethodProxy来进行的,而 ReflectiveMethodInvocation则是通过反射直接执行的,就这么一点区别。

    测试!

    好了,终于又到了测试环节。总觉得这次虽然新加了不少类,但是实际上内容却不是很多。这次的测试,我们和前面的Spring IOC内容决裂了!已经没有什么Context了!也没有什么Bean定义了!回归最初的美好吧!

    不过我们还是得有个bean(笑)

    由于JDK动态代理要求类必须实现接口,所以我们先得给整一个接口

    package com.akitsuki.springframework.bean;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 17:26
     */
    public interface IUserDao {
    
        String queryUserName(Long id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    嗯,简单易懂,接下来是我们永远跑不了的bean

    package com.akitsuki.springframework.bean;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/11/8 14:42
     */
    public class UserDao implements IUserDao {
    
        private static final Map<Long, String> userMap = new HashMap<>();
    
        static {
            userMap.put(1L, "akitsuki");
            userMap.put(2L, "toyosaki");
            userMap.put(3L, "kugimiya");
            userMap.put(4L, "hanazawa");
            userMap.put(5L, "momonogi");
        }
    
        @Override
        public String queryUserName(Long id) {
            return userMap.get(id);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    没啥大变化,就是实现了上面的接口

    下面是重点了,我们要实现自定义的方法拦截器,也就是我们切面的具体内容,需要实现 MethodInerceptor接口。

    package com.akitsuki.springframework.bean;
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 16:51
     */
    public class UserDaoInterceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                return methodInvocation.proceed();
            } finally {
                System.out.println("+++++ AOP 方法执行监控 +++++");
                System.out.println("方法名称:" + methodInvocation.getMethod().getName());
                System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
                System.out.println("----- AOP 方法监控结束 -----");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打印一下方法执行的信息,很简单。

    下面,是真正的测试类

    package com.akitsuki.springframework;
    
    import com.akitsuki.springframework.aop.AdvisedSupport;
    import com.akitsuki.springframework.aop.TargetSource;
    import com.akitsuki.springframework.aop.aspect.AspectJExpressionPointcut;
    import com.akitsuki.springframework.aop.framework.Cglib2AopProxy;
    import com.akitsuki.springframework.aop.framework.JdkDynamicAopProxy;
    import com.akitsuki.springframework.bean.IUserDao;
    import com.akitsuki.springframework.bean.UserDao;
    import com.akitsuki.springframework.bean.UserDaoInterceptor;
    import org.junit.Test;
    
    /**
     * @author ziling.wang@hand-china.com
     * @date 2022/12/5 16:46
     */
    public class ApiTest {
    
        @Test
        public void test() {
            IUserDao userDao = new UserDao();
    
            AdvisedSupport advisedSupport = new AdvisedSupport();
            advisedSupport.setTargetSource(new TargetSource(userDao));
            advisedSupport.setMethodInterceptor(new UserDaoInterceptor());
            advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* com.akitsuki.springframework.bean.IUserDao.*(..))"));
    
            IUserDao proxyJdk = (IUserDao) new JdkDynamicAopProxy(advisedSupport).getProxy();
    
            assert proxyJdk != null;
            System.out.println("测试结果:" + proxyJdk.queryUserName(1L));
    
            IUserDao proxyCglib = (IUserDao) new Cglib2AopProxy(advisedSupport).getProxy();
    
            System.out.println("测试结果:" + proxyCglib.queryUserName(2L));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    测试结果

    +++++ AOP 方法执行监控 +++++
    方法名称:queryUserName
    方法耗时:0ms
    ----- AOP 方法监控结束 -----
    测试结果:akitsuki
    +++++ AOP 方法执行监控 +++++
    方法名称:queryUserName
    方法耗时:12ms
    ----- AOP 方法监控结束 -----
    测试结果:toyosaki
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,我们切面中的内容已经正常打印出来了。这次我们的表达式拦截的是IUserDao接口的所有方法,分别创建了JDK动态代理和Cglib动态代理两种方式来进行测试,都能够正常进行工作。

    不过我们也可以看到,这种方式终究还是有些繁琐,而且没有和Spring很好的结合起来,显得有些割裂。那么就留到下一章再进行完善吧。

    相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring,这里对应的代码是mini-spring-11

  • 相关阅读:
    【Linux】动静态库
    数据动态验证的简单应用
    算法与设计分析 | 汉诺塔问题
    SpringBoot+MinIO8.0开箱即用的启动器
    观影笔记 |独行月球
    java-net-php-python-46jspm制衣厂后整管理系统计算机毕业设计程序
    写个计算器
    Android系统之SurfaceFlinger
    JSR303参数校验
    web前端期末大作业 html+css家乡旅游主题网页设计 湖北武汉家乡介绍网页设计实例
  • 原文地址:https://blog.csdn.net/Forget_Re/article/details/128201082