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属性是切面信息

同样的判断逻辑

相关推荐
2401_8574396914 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66616 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索17 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
CSXB9919 分钟前
三十四、Python基础语法(文件操作-上)
开发语言·python·功能测试·测试工具
芒果披萨23 分钟前
Filter和Listener
java·filter
qq_49244844627 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国36 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
亚图跨际1 小时前
MATLAB和Python及R潜变量模型和降维
python·matlab·r语言·生物学·潜变量模型
IT古董1 小时前
【机器学习】决定系数(R²:Coefficient of Determination)
人工智能·python·机器学习
德育处主任Pro1 小时前
『Django』APIView基于类的用法
后端·python·django