mini-spring|基于JDK和Cglib动态代理,实现AOP核心功能

AOP 的核心技术实现主要是动态代理的使用

那么我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。

除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 "execution(* cn.bugstack.springframework.test.bean.IUserService.*(...))",有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。

工程结构

src

├── main

│ └── java

│ └── cn.bugstack.springframework

│ ├── aop

│ │ ├── aspectj

│ │ │ ├──AspectJExpressionPointcut.java

切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用

│ │ │ ├──AspectJExpressionPointcutAdvisor.java

│ │ ├── framework

│ │ │ ├──adapter

│ │ │ │ └──MethodBeforeAdviceInterceptor.java

│ │ │ ├── autoproxy

│ │ │ │ └── MethodBeforeAdviceInterceptor.java

│ │ │ ├── AopProxy.java

│ │ │ ├── Cglib2AopProxy.java

│ │ │ ├──JdkDynamicAopProxy.java

│ │ │ ├──ProxyFactory.java

│ │ │ ├── ReflectiveMethodInvocation.java

│ │ ├── AdvisedSupport.java

AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理

│ │ ├── Advisor.java

│ │ ├── BeforeAdvice.java

│ │ ├── ClassFilter.java

定义类匹配类,用于切点找到给定的接口和目标类。

│ │ ├── MethodBeforeAdvice.java

│ │ ├── MethodMatcher.java

方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())

│ │ ├── Pointcut.java

切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类

│ │ ├── PointcutAdvisor.java

│ │ ├── TargetSource.java

│ ├── beans

│ │ ├── factory

│ │ │ ├── factory

│ │ │ │ ├── AutowireCapableBeanFactory.java

│ │ │ │ ├── BeanDefinition.java(实体类)

初始化和销毁

│ │ │ │ ├── BeanFactoryPostProcessor.java

│ │ │ │ ├── BeanPostProcessor.java

│ │ │ │ ├── BeanReference.java

│ │ │ │ ├── ConfigurableBeanFactory.java(接口) 定义了 destroySingletons 销毁方法

│ │ │ │ └── SingletonBeanRegistry.java

│ │ │ ├── support

│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java(抽象类)

主要作用:

继承关系:继承AbstractBeanFactory

实现AutowireCapableBeanFactory接口

主要方法:

CreateBean():创建Bean 调用registerDisposableBeanIfNecessary

initializeBean():初始化Bean,调用PostProcessor Before 处理,执行初始化方法invokeInitMethods,执行 BeanPostProcessor After 处理

│ │ │ │ ├── AbstractBeanDefinitionReader.java

│ │ │ │ ├── AbstractBeanFactory.java

│ │ │ │ ├── BeanDefinitionReader.java

│ │ │ │ ├── BeanDefinitionRegistry.java

│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java

│ │ │ │ ├── DefaultListableBeanFactory.java

│ │ │ │ ├── DefaultSingletonBeanRegistry.java

实现destroySingletons 销毁方法( AbstractBeanFactory.java的父类)

│ │ │ │ ├── DisposableBeanAdapter.java

描述:销毁方法适配器

继承关系: 实现DisposableBean接口

│ │ │ │ ├── FactoryBeanRegistrySupport.java(继承 DefaultSingletonBeanRegistry)

作用:实现一个 FactoryBean 注册服务

维护一个存放FactoryBean对象的缓存 factoryBeanObjectCache

处理的就是关于 FactoryBean 此类对象的注册操作

GetObjectFromFactoryBean() 从FactoryBean通过getObject()获取对象,先判断缓存中是否有 如果有直接获取,没有则加入缓存

│ │ │ │ ├── InstantiationStrategy.java

│ │ │ │ └── SimpleInstantiationStrategy.java

│ │ │ ├── support

│ │ │ │ └── XmlBeanDefinitionReader.java

│ │ │ ├── Aware.java(接口)

│ │ │ ├── BeanClassLoaderAware.java(实现Aware接口)

│ │ │ ├── BeanFactory.java

│ │ │ ├──BeanFactoryAware.java(实现Aware接口)

│ │ │ ├── BeanNameAware.java

│ │ │ ├── ConfigurableListableBeanFactory.java

│ │ │ ├── DisposableBean.java

│ │ │ ├──FactoryBean.java(实现FactoryBean)

主要方法:

getObject()获取对象

getObjectType()对象类型

isSingleton()是否是单例对象 如果是单例对象会被放到内存中

│ │ │ ├── HierarchicalBeanFactory.java

│ │ │ ├── InitializingBean.java(接口) 定义初始化方法

│ │ │ └── ListableBeanFactory.java

│ │ ├── BeansException.java

│ │ ├── PropertyValue.java

│ │ └── PropertyValues.java

│ ├── context

│ │ ├── support

│ │ │ ├── AbstractApplicationEventMulticaster.java

继承 ApplicationEventMulticaster的抽象类

维护一个事件监听器的Set,一个BeanFactory的属性

addApplicationListener() removeApplicationListener 添加和删除事件监听器

getApplicationListeners 摘取符合广播事件中的监听处理器 使用supportEvent判断

supportsEvent(applicationListener, ApplicationEvent event)

│ │ │ ├── ApplicationContextEvent.java (继承Application Event)

定义事件的抽象类

│ │ │ ├── ApplicationEventMulticaster.java

作用:添加监听和删除监听

接口

│ │ │ ├── ContextClosedEvent.java

定义关闭事件的抽象类(对象是ApplicationContent)

│ │ │ ├── ContextRefreshedEvent.java

定义刷新事件的抽象类(对象是ApplicationContent)

│ │ │ ├── SimpleApplicationEventMulticaster.java

│ │ ├── support

│ │ │ ├── AbstractApplicationContext.java(抽象类)

继承关系:实现 ConfigurableApplicationContext接口 继承DefaultResourceLoader类

│ │ │ ├── AbstractRefreshableApplicationContext.java

│ │ │ ├── AbstractXmlApplicationContext.java

│ │ │ ├── ApplicationContextAwareProcessor.java(实现BeanPostProcessor接口)

│ │ │ └── ClassPathXmlApplicationContext.java

│ │ ├── ApplicationContext.java

│ │ ├──ApplicationContextAware.java

│ │ ├──ApplicationEvent.java

定义事件的抽象类

包含事件源对象

│ │ ├── ApplicationEventPublisher.java

事件发布接口

│ │ ├── ApplicationListener.java

│ │ └── ConfigurableApplicationContext.java(接口)

主要描述:虚拟机关闭钩子注册调用销毁,定义刷新容器,关闭应用上下文

继承关系:继承ApplicationContext

主要方法:

refresh():

registerShutdownHook():注册虚拟机钩子的方法

close():手动执行关闭虚拟机钩子的方法

│ ├── core.io

│ │ ├── ClassPathResource.java

│ │ ├── DefaultResourceLoader.java(实体类)

作用:资源处理器

│ │ ├── FileSystemResource.java

│ │ ├── Resource.java

│ │ ├── ResourceLoader.java

│ │ └── UrlResource.java

│ └── utils

│ └── ClassUtils.java

└── test
结构关系

1.整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。

2.AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。

3.AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。

实现

代理方法案例
java 复制代码
@Test
public void test_proxy_method() {
    // 目标对象(可以替换成任何的目标对象)
    Object targetObj = new UserService();
    // AOP 代理->代理对象
    IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
        // 方法匹配器
        MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (methodMatcher.matches(method, targetObj.getClass())) {//方法匹配
                // 方法拦截器->加入操作
                MethodInterceptor methodInterceptor = invocation -> {
                    long start = System.currentTimeMillis();
                    try {
                        return invocation.proceed();
                    } finally {
                        System.out.println("监控 - Begin By AOP");
                        System.out.println("方法名称:" + invocation.getMethod().getName());
                        System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
                        System.out.println("监控 - End\r\n");
                    }
                };
                // 反射调用
                return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
            }
            return method.invoke(targetObj, args);
        }
    });
    String result = proxy.queryUserInfo();
    System.out.println("测试结果:" + result);
}

**目标:**整个案例的目标是给一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。

切点表达式

Pointcut

java 复制代码
public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never <code>null</code>)
     */
    ClassFilter getClassFilter();

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never <code>null</code>)
     */
    MethodMatcher getMethodMatcher();

}

切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。

ClassFilter

java 复制代码
public interface ClassFilter {
    boolean matches(Class<?> clazz);
}

定义类匹配类,用于切点找到给定的接口和目标类。
MethodMatcher

java 复制代码
public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);
    
}

方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
实现切点表达式类

java 复制代码
public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

    static {
    //EXECUTION是常用的一种切点函数
        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<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }

    @Override
    public ClassFilter getClassFilter() {
        return this;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }

}

切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用
包装切面通知信息
AdvisedSupport

java 复制代码
public class AdvisedSupport {

    // 被代理的目标对象
    private TargetSource targetSource;
    // 方法拦截器
    private MethodInterceptor methodInterceptor;
    // 方法匹配器(检查目标方法是否符合通知条件)
    private MethodMatcher methodMatcher;
    
    // ...get/set
}

AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理

TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。

MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。像我们本文的案例中是做方法监控处理

MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务。

代理抽象实现(JDK&Cglib)
java 复制代码
public interface AopProxy {

    Object getProxy();

}

获取代理类的接口,接口包括JDK和Cglib类型

在invoke方法中定义拦截器操作完成代理
JdkDynamicAopProxy

java 复制代码
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
            MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args));
        }
        return method.invoke(advised.getTargetSource().getTarget(), args);
    }

}

基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。

getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了 invoke 方法。

invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。methodInterceptor.invoke 是什么

methodInterceptor是方法对象,方法对象通过调用invoke方法来调用对应方法

这里还有一个 ReflectiveMethodInvocation (着重再看),其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。
Cglib2AopProxy

java 复制代码
public class Cglib2AopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public Cglib2AopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
        enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
            //if else的判断逻辑是为了什么
            if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.getMethodInterceptor().invoke(methodInvocation);
            }
            return methodInvocation.proceed();
        }
    }

    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        @Override
        public Object proceed() throws Throwable {
            return this.methodProxy.invoke(this.target, this.arguments);
        }

    }

}

基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。

关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

测试

java 复制代码
public class UserService implements IUserService {

    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "小傅哥,100001,深圳";
    }

    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "注册用户:" + userName + " success!";
    }

}

自定义拦截方法

java 复制代码
public class UserServiceInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            System.out.println("监控 - Begin By AOP");
            System.out.println("方法名称:" + invocation.getMethod());
            System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
            System.out.println("监控 - End\r\n");
        }
    }

}

用户自定义的拦截方法需要实现 MethodInterceptor 接口的 invoke 方法,使用方式与 Spring AOP 非常相似,也是包装 invocation.proceed() 调用被拦截的方法,并在 finally 中添加监控信息。

单元测试

java 复制代码
public void test_dynamic() {
    // 目标对象
    IUserService userService = new UserService();     

    // 组装代理信息
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTargetSource(new TargetSource(userService));
    advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
    advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));
    
    // 代理对象(JdkDynamicAopProxy)
    IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_jdk.queryUserInfo());
    
    // 代理对象(Cglib2AopProxy)
    IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
    // 测试调用
    System.out.println("测试结果:" + proxy_cglib.register("花花"));
}

相关面试题

1.AOP的基础概念

Aop把业务和日志隔离开

相关概念

切面(Aspect):包含多个切入点表达式和通知的类

连接点(joinpoint):程序中可能被织入增强的点

增强(Advice):定义了在连接点执行的具体逻辑

切点(pointcut):定义了增强逻辑要发生的地方,通过其确定哪些连接点被织入增强

切面表达式:需要拦截的方法(使用execution)

2.JDK和Cglib的实现原理与不同

详见博客
JDK动态代理
JDK的使用流程

jdk动态代理机制使用proxy的newProxyInstance() 方法生成代理对象

该方法有三个参数:

1)loader :目标类的类加载器

2)interfaces : 代理类需要实现的一些接口;

3)h : 实现了 InvocationHandler 接口的类对象(xx implements InvocationHandler 属性包含目标对象);

InvocationHandler(方法对象)的invoke()方法实现自定义的处理逻辑,使用InvocationHandler.invoke()进行调用

invoke()参数:

proxy :动态生成的代理类

method : 与被代理类对象调用的方法相对应

args : 当前 method 方法的参数
注: 注意InvocationHandler的invoke与方法对象method的invoke的不同

方法对象method的invoke方法调用被代理对象的方法

InvocationHandler的invoke方法包含加入其他操作的逻辑

当执行方法时,会调用InvocationHandler的invoke方法
JDK为什么只能代理实现了接口的类

当你使用Proxy类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型。由于代理对象的类型是由接口列表决定的,因此只有实现了接口的类才能被代理
CGLIB 动态代理

自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

1.)接口MethodInterceptor继承被代理类并定义intercept方法

2)实现 MethodInterceptor接口 并重写 intercept 方法,intercept方法参数:被代理对象,被拦截的方法,用于调用原始方法的MethodProxy(用spuerinvoke调用)

3)获取代理类:

参数:被代理对象类加载器,被代理类,自定义方法拦截器

3.利用JDK和Cglib的实现AOP的过程

1).实现切点表达式类AspectJExpressionPointcut,用于方法和类的筛选

2).AdvisedSupport包装切面通知信息 目标对象,方法拦截器,方法匹配器

3).动态代理实现

JDK

JdkDynamicAopProxy继承InvocationHandler,属性是切面信息

如果切面信息可以处理目标方法

就调用methodInterceptor的invoke方法,后调用目标方法。否则只调用目标方法

Cglib

Cglib2AopProxy属性是切面信息

同样的判断逻辑

相关推荐
hweiyu004 分钟前
Maven 私库
java·maven
Boilermaker199211 分钟前
【Java EE】SpringIoC
前端·数据库·spring
Super Rookie12 分钟前
Spring Boot 企业项目技术选型
java·spring boot·后端
写不出来就跑路18 分钟前
Spring Security架构与实战全解析
java·spring·架构
电商数据girl27 分钟前
有哪些常用的自动化工具可以帮助处理电商API接口返回的异常数据?【知识分享】
大数据·分布式·爬虫·python·系统架构
CoooLuckly30 分钟前
numpy数据分析知识总结
python·numpy
超龄超能程序猿1 小时前
(六)PS识别:源数据分析- 挖掘图像的 “元语言”技术实现
python·组合模式
ZeroNews内网穿透1 小时前
服装零售企业跨区域运营难题破解方案
java·大数据·运维·服务器·数据库·tcp/ip·零售
sleepcattt1 小时前
Spring中Bean的实例化(xml)
xml·java·spring
amazinging1 小时前
北京-4年功能测试2年空窗-报培训班学测开-第四十四天
python·学习·appium