手写Spring:第12章-基于JDK、Cglib实现AOP切面

news2024/9/27 23:25:47

文章目录

  • 一、目标:基于JDK、Cglib实现AOP切面
  • 二、设计:基于JDK、Cglib实现AOP切面
  • 三、实现:基于JDK、Cglib实现AOP切面
    • 3.0 引入依赖
    • 3.1 工程结构
    • 3.2 AOP切点表达式和使用以及基于JDK和CGLIB的动态代理类图
    • 3.3 切点表达式
      • 3.3.1 类匹配接口
      • 3.3.2 匹配方法接口
      • 3.3.3 切点表达式接口
      • 3.3.4 实现切点表达式类
    • 3.4 包装切面通知信息
      • 3.4.1 被代理的目标对象
      • 3.4.2 包装切面通知消息
    • 3.5 代理抽象实现(JDK&Cglib)
      • 3.5.1 反射方法调用
      • 3.5.2 AOP代理接口
      • 3.5.3 JDK 动态代理
      • 3.5.4 Cglib代理
  • 四、测试:基于JDK、Cglib实现AOP切面
    • 4.1 添加测试配置
      • 4.1.1 用户接口
      • 4.1.2 用户接口实现类
      • 4.1.3 自定义用户拦截方法
    • 4.2 单元测试
      • 4.2.1 代理方法测试
      • 4.2.2 匹配方法测试
      • 4.2.3 单元测试
  • 五、总结:基于JDK、Cglib实现AOP切面

一、目标:基于JDK、Cglib实现AOP切面

💡 AOP是什么?如何实现AOP?

  • AOP:面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能的统一维护。
    • 其实 AOP 也是 OOP 的延续,在 Spring 框架中是一个非常重要的内容。
    • 使用 AOP 可以对业务逻辑的各个部分进行隔离,从而使各个模块间的业务逻辑耦合度降低,提高代码的可复用性,同时也能提高开发效率。
  • AOP 的核心技术实现主要是:动态代理的使用,就像你可以给一个接口的实现类,使用代理的方式替换掉这个代理类,使用代理类来处理你需要的逻辑。

动态代理

@Test
public void test_proxy_class() {
    IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!");
    String result = userService.queryUserInfo();
    System.out.println("测试结果:" + result);
}

测试结果


测试结果:你被代理了!
  • 代理类的实现就是使用 Proxy.newProxyInstance 方法。
  • 那么有了一个基本的思路后,解析来就考虑怎么给方法做代理,而不是代理类?另外怎么去代理所有符合某些规则的所有类中方法?
  • 如果可以代理掉所有类的方法,就可以做一个方法拦截器,给所有被代理的方法添加一些自定义处理。如:打印日志、记录耗时、监控异常等。

二、设计:基于JDK、Cglib实现AOP切面

💡 如何给符合规则的方法做代理?怎么做完代理方法的案例后,把类的职责拆分出来?

  • 这两个功能点的实现,都是以切面的思想进行设计和开发。

在这里插入图片描述

  • 就像你在使用 Spring AOP 一样,只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。
  • 需要先实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args)
  • 除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 execution(* com.lino.springframework.test.bean.IUserService.*(..)),有了方法代理和处理拦截,就可以设计出一个 AOP 的雏形了。

三、实现:基于JDK、Cglib实现AOP切面

3.0 引入依赖

<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

3.1 工程结构

spring-step-11
|-src
	|-main
	|	|-java
	|		|-com.lino.springframework
	|			|-aop
	|			|	|-aspectj
	|			|	|	|-AspectJExpressionPointcut.java
	|			|	|-framework
	|			|	|	|-AopProxy.java
	|			|	|	|-Cglib2AopProxy.java
	|			|	|	|-JdkDynamicAopProxy.java
	|			|	|	|-ReflectiveMethodInvocation.java
	|			|	|-AdvisedSupport.java
	|			|	|-ClassFilter.java
	|			|	|-MethodMatcher.java
	|			|	|-Pointcut.java
	|			|	|-TargetSource.java
	|			|-beans
	|			|	|-factory
	|			|	|	|-config
	|			|	|	|	|-AutowireCapableBeanFactory.java
	|			|	|	|	|-BeanDefinition.java
	|			|	|	|	|-BeanFactoryPostProcessor.java
	|			|	|	|	|-BeanPostProcessor.java
	|			|	|	|	|-BeanReference.java
	|			|	|	|	|-ConfigurableBeanFactory.java
	|			|	|	|	|-SingletonBeanRegistry.java
	|			|	|	|-support
	|			|	|	|	|-AbstractAutowireCapableBeanFactory.java
	|			|	|	|	|-AbstractBeabDefinitionReader.java
	|			|	|	|	|-AbstractBeabFactory.java
	|			|	|	|	|-BeabDefinitionReader.java
	|			|	|	|	|-BeanDefinitionRegistry.java
	|			|	|	|	|-CglibSubclassingInstantiationStrategy.java
	|			|	|	|	|-DefaultListableBeanFactory.java
	|			|	|	|	|-DefaultSingletonBeanRegistry.java
	|			|	|	|	|-DisposableBeanAdapter.java
	|			|	|	|	|-FactoryBeanRegistrySupport.java
	|			|	|	|	|-InstantiationStrategy.java
	|			|	|	|	|-SimpleInstantiationStrategy.java
	|			|	|	|-support
	|			|	|	|	|-XMLBeanDefinitionReader.java
	|			|	|	|-Aware.java
	|			|	|	|-BeanClassLoaderAware.java
	|			|	|	|-BeanFactory.java
	|			|	|	|-BeanFactoryAware.java
	|			|	|	|-BeanNameAware.java
	|			|	|	|-ConfigurableListableBeanFactory.java
	|			|	|	|-DisposableBean.java
	|			|	|	|-FactoryBean.java
	|			|	|	|-HierarcgicalBeanFactory.java
	|			|	|	|-InitializingBean.java
	|			|	|	|-ListableBeanFactory.java
	|			|	|-BeansException.java
	|			|	|-PropertyValue.java
	|			|	|-PropertyValues.java
	|			|-context
	|			|	|-event
	|			|	|	|-AbstractApplicationEventMulticaster.java
	|			|	|	|-ApplicationContextEvent.java
	|			|	|	|-ApplicationEventMulticaster.java
	|			|	|	|-ContextclosedEvent.java
	|			|	|	|-ContextRefreshedEvent.java
	|			|	|	|-SimpleApplicationEventMulticaster.java
	|			|	|-support
	|			|	|	|-AbstractApplicationContext.java
	|			|	|	|-AbstractRefreshableApplicationContext.java
	|			|	|	|-AbstractXmlApplicationContext.java
	|			|	|	|-ApplicationContextAwareProcessor.java
	|			|	|	|-ClassPathXmlApplicationContext.java
	|			|	|-ApplicationContext.java
	|			|	|-ApplicationContextAware.java
	|			|	|-ApplicationEvent.java
	|			|	|-ApplicationEventPublisher.java
	|			|	|-ApplicationListener.java
	|			|	|-ConfigurableApplicationContext.java
	|			|-core.io
	|			|	|-ClassPathResource.java
	|			|	|-DefaultResourceLoader.java
	|			|	|-FileSystemResource.java
	|			|	|-Resource.java
	|			|	|-ResourceLoader.java
	|			|	|-UrlResource.java
	|			|-util
	|			|	|-ClassUtils.java
	|-test
		|-java
			|-com.lino.springframework.test
                |-bean
                |	|-IUserService.java
                |	|-UserService.java
                |	|-UserServiceInterceptor.java
                |-ApiTest.java
		|-resources
			|-spring.xml

3.2 AOP切点表达式和使用以及基于JDK和CGLIB的动态代理类图

在这里插入图片描述

  • 整个类图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
  • AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。
  • AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。

3.3 切点表达式

3.3.1 类匹配接口

ClassFilter.java

package com.lino.springframework.aop;

/**
 * @description: 类匹配接口
 */
public interface ClassFilter {
    /**
     * 匹配类
     *
     * @param clazz 类类型
     * @return 是否匹配类
     */
    boolean matches(Class<?> clazz);
}
  • 定义类匹配类,用于切点好到给定的接口和目标类

3.3.2 匹配方法接口

MethodMatcher.java

package com.lino.springframework.aop;

import java.lang.reflect.Method;

/**
 * @description: 匹配方法接口
 */
public interface MethodMatcher {
    /**
     * 匹配方法
     *
     * @param method      匹配方法
     * @param targetClass 类
     * @return 是否匹配方法
     */
    boolean matches(Method method, Class<?> targetClass);
}
  • 方法匹配,找到表达式范围内匹配下的目标类和方法。

3.3.3 切点表达式接口

Pointcut.java

package com.lino.springframework.aop;

/**
 * @description: 切点表达式接口
 */
public interface Pointcut {

    /**
     * 获取切点中类匹配类
     *
     * @return 类匹配类
     */
    ClassFilter getClassFilter();

    /**
     * 获取切点中匹配方法
     *
     * @return 匹配方法
     */
    MethodMatcher getMethodMatcher();
}
  • 切入点接口,定义用于获取 ClassFilter、MethodMatcher 两个类,这两个接口获取都是切点表达式提供的内容。

3.3.4 实现切点表达式类

AspectJExpressionPointcut.java

package com.lino.springframework.aop.aspectj;

import com.lino.springframework.aop.ClassFilter;
import com.lino.springframework.aop.MethodMatcher;
import com.lino.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;

/**
 * @description: 切点表达式实现类
 */
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<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }

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

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }
}
  • 切点表达式实现了 Pointcut, ClassFilter, MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
  • 匹配 matches
    • pointcutExpression.couldMatchJoinPointsInType(clazz)
    • pointcutExpression.matchesMethodExecution(method).alwaysMatches()

3.4 包装切面通知信息

3.4.1 被代理的目标对象

TargetSource.java

package com.lino.springframework.aop;

/**
 * @description: 被代理的目标对象
 */
public class TargetSource {

    private final Object target;

    public TargetSource(Object target) {
        this.target = target;
    }

    /**
     * 获取目标对象列表
     *
     * @return 目标对象列表
     */
    public Class<?>[] getTargetClass() {
        return this.target.getClass().getInterfaces();
    }

    /**
     * 获取目标对象
     *
     * @return 目标对象
     */
    public Object getTarget() {
        return this.target;
    }
}

3.4.2 包装切面通知消息

AdvisedSupport.java

package com.lino.springframework.aop;

import org.aopalliance.intercept.MethodInterceptor;

/**
 * @description: 包装切面通知信息
 */
public class AdvisedSupport {
    /**
     * 被代理的目标对象
     */
    private TargetSource targetSource;
    /**
     * 方法拦截器
     */
    private MethodInterceptor methodInterceptor;
    /**
     * 方法匹配器(检查目标方法是否符合通知条件)
     */
    private MethodMatcher methodMatcher;

    public TargetSource getTargetSource() {
        return targetSource;
    }

    public void setTargetSource(TargetSource targetSource) {
        this.targetSource = targetSource;
    }

    public MethodInterceptor getMethodInterceptor() {
        return methodInterceptor;
    }

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    public MethodMatcher getMethodMatcher() {
        return methodMatcher;
    }

    public void setMethodMatcher(MethodMatcher methodMatcher) {
        this.methodMatcher = methodMatcher;
    }
}
  • AdvisedSupport:主要用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。
  • TargetSource:是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
  • MethodInterceptor:是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的实现。
  • MethodMatcher:是一个匹配方法,这个对象由 AspectJExpressionPointcut 提供服务。

3.5 代理抽象实现(JDK&Cglib)

3.5.1 反射方法调用

ReflectiveMethodInvocation.java

package com.lino.springframework.aop.framework;

import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;

/**
 * @description: 反射方法调用
 * @author: lingjian
 * @createDate: 2022/12/2 9:44
 */
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;
    }
}

3.5.2 AOP代理接口

AopProxy.java

package com.lino.springframework.aop.framework;

/**
 * @description: AOP代理接口
 */
public interface AopProxy {

    /**
     * 获取代理对象
     *
     * @return 代理对象
     */
    Object getProxy();
}
  • 定义一个标准接口,用于获取代理类。因为具体实现代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定义接口会更加方便管理。

3.5.3 JDK 动态代理

JdkDynamicAopProxy.java

package com.lino.springframework.aop.framework;

import com.lino.springframework.aop.AdvisedSupport;
import org.aopalliance.intercept.MethodInterceptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @description: JDK 动态代理
 */
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 实现的代理类,需要实现 AopProxyInvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。
  • getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoaderAdvisedSupport 和当前类 this,因为这个类提供了 invoke 方法。
  • invoke 方法中主要处理匹配的方法后,使得用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke
  • 这里还有一个 ReflectiveMethodInvocation,其实它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。

3.5.4 Cglib代理

Cglib2AopProxy.java

package com.lino.springframework.aop.framework;

import com.lino.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;

/**
 * @description: Cglib代理
 */
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 {

        private final AdvisedSupport advised;

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

        @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 (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
                return advised.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);
        }
    }
}
  • 基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
  • 关于扩展用户拦截方法,主要在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。
    • 这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

四、测试:基于JDK、Cglib实现AOP切面

4.1 添加测试配置

4.1.1 用户接口

IUserService.java

package com.lino.springframework.test.bean;

/**
 * @description: 用户接口
 */
public interface IUserService {

    /**
     * 查询用户信息
     *
     * @return 用户信息
     */
    String queryUserInfo();

    /**
     * 注册用户
     *
     * @param userName 用户名
     * @return 用户信息
     */
    String register(String userName);
}

4.1.2 用户接口实现类

UserService.java

package com.lino.springframework.test.bean;

import java.util.Random;

/**
 * @description: 用户接口实现类
 */
public class UserService implements IUserService {

    @Override
    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "张三,100001,杭州";
    }

    @Override
    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "注册用户:" + userName + " success!";
    }
}
  • UserService 中提供了2个不同方法。查询和注册。

4.1.3 自定义用户拦截方法

UserServiceInterceptor.java

package com.lino.springframework.test.bean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * @description: 用户拦截器
 */
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 中添加监控信息。

4.2 单元测试

4.2.1 代理方法测试

ApiTest.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(* com.lino.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());
                        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 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。
  • 从案例中可以看到有代理的实现 Proxy.newProxyInstance,有方法的匹配 MethodMatcher,有反射的调用 invoke(Object proxy, Method method, Object[] args),也有用户自己拦截方法后的操作。
  • 这样看其实就和我们使用的 AOP 非常类似。

测试结果

监控 - Begin By AOP
方法名称:public abstract java.lang.String com.lino.springframework.test.bean.IUserService.queryUserInfo()
方法耗时:86ms
监控 - End

测试结果:张三,100001,杭州
  • 从测试结果看,我们已经对 UserService#queryUserInfo 方法进行了拦截监控操作。

拆解案例

在这里插入图片描述

  • 拆解过程参考上图,把代理对象拆解出来,因为它可以是 JDK 的实现也可以是 Cglib 的处理。
  • 方法匹配器操作其实已经是一个单独的实现类了,不过我们还需要把传入的目标对象、方法匹配、拦截方法,都进行统一的包装,方便外部调用时进行一个入参透传。
  • 最后其实就是 ReflectiveMethodInvocation 的使用,它目前已经是实现 MethodInvocation 接口的一个包装后的类。
    • 参数信息包括:调用的对象、调用的方法、调用的入参。

4.2.2 匹配方法测试

ApiTest.java

@Test
public void test_aop() throws NoSuchMethodException {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* com.lino.springframework.test.bean.UserService.*(..))");

    Class<UserService> clazz = UserService.class;
    Method method = clazz.getDeclaredMethod("queryUserInfo");

    System.out.println(pointcut.matches(clazz));
    System.out.println(pointcut.matches(method, clazz));
}

测试结果

true
true
  • 这是一个匹配方法的测试,可以看看拦截的方法与对应的对象是否匹配。

4.2.3 单元测试

ApiTest.java

@Test
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(* com.lino.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("小零"));
}
  • 整个案例测试了 AOP 在与 Spring 结合前的核心代码,包括什么是目标对象、怎么组装代理信息、如何调用代理对象等。
  • AdvisedSupport:包装了目标对象、用户自己实现的拦截方法以及方法匹配表达式。
  • 之后就是分别调用 JdkDynamicAopProxyCglib2AopProxy,两个不同方式实现的代理类,看看是否可以成功拦截方法。

测试结果

监控 - Begin By AOP
方法名称:public abstract java.lang.String com.lino.springframework.test.bean.IUserService.queryUserInfo()
方法耗时:90ms
监控 - End

测试结果:张三,100001,杭州

监控 - Begin By AOP
方法名称:public java.lang.String com.lino.springframework.test.bean.UserService.register(java.lang.String)
方法耗时:99ms
监控 - End

测试结果:注册用户:小零 success!
  • AOP 功能定义一样,我们可以通过这样的代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印。

五、总结:基于JDK、Cglib实现AOP切面

  • 本文对 Proxy#newProxyInstanceMethodInterceptor#invoke 的使用验证切面核心原理以及再把功能拆解到 Spring 框架实现中。
    • 可以看到一个貌似复杂的技术其实核心内容往往没有太多,但因为需要为了满足后续更多的扩展就需要进行职责解耦和包装,通过这样设计模式的使用,以此让调用方能更加简化,自身也可以不断按需扩展。
  • AOP 的功能实现目前还没有与 Spring 结合,只是对切面技术的一个具体实现,可以先学到:如何处理代理对象、过滤方法、拦截方法,以及使用 JDKCglib 代理的区别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/985005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一生一芯10——verilator v5.008环境搭建

搜索 verilator 官网&#xff0c;得到网址如下&#xff1a; https://www.veripool.org/verilator/ 点击download 找到 git quick install 可以看到git快捷安装所需命令行 可以看到&#xff0c;需要预先安装下面的包文件&#xff0c;去掉前面的#注释符号进行安装 直接进行下面…

全力助推徐工集团转型升级,迅镭激光智能装备展现硬核实力!

在江苏省徐州市&#xff0c;工程机械产业集群在成功入选首批国家先进制造业集群后&#xff0c;正加快向世界级先进制造业集群跃升。徐工集团作为徐州市“343”创新产业集群“一号产业”链主企业&#xff0c;正发挥着“领头雁”作用。 为了把徐州市“全球工程机械之都”名片擦得…

WebGIS外包开发流程

WebGIS开发流程需要综合考虑前端和后端开发、地理信息数据处理、用户需求和安全性等多个方面。成功的WebGIS应用程序需要不断地进行更新和维护&#xff0c;以适应变化的需求和技术。WebGIS开发是一个复杂的过程&#xff0c;通常包括以下主要步骤。北京木奇移动技术有限公司&…

jmeter 接口快速创建

通过cURL命令创建测试计划 从浏览器获取接口 从postman获取接口

2000-2021年上市公司全要素生产率数据(OLS法、OP法、LP法、GMM法、FE法)(含原始数据+计算代码+结果)

2000-2021年上市公司全要素生产率数据&#xff08;OLS法、OP法、LP法、GMM法、FE法&#xff09;&#xff08;含原始数据计算代码结果&#xff09; 1、时间&#xff1a;2000-2021年 2、指标&#xff1a;股票代码、年份、证券代码、固定资产净额、营业总收入、营业收入、营业成…

[PyTorch][chapter 54][GAN- 1]

前言&#xff1a; GAN playground: Experiment with Generative Adversarial Networks in your browser 生成对抗网络&#xff08;Generative Adversarial Nets&#xff0c;GAN&#xff09;是一种基于对抗学习的深度生成模型&#xff0c;最早由Ian Goodfellow于2014年在《Gener…

Android 性能优化--内存优化分析总结

一、内存优化概念 1.1 为什么要做内存优化&#xff1f; 内存优化一直是一个很重要但却缺乏关注的点&#xff0c;内存作为程序运行最重要的资源之一&#xff0c;需要运行过程中做到合理的资源分配与回收&#xff0c;不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏&…

maven(总)

maven maven的简介 maven主要服务于基于java平台的项目构建&#xff0c;依赖管理和项目信息管理 主要体现在项目和管理 瀑布式开发&#xff1a;在做项目的时候要求有明确的需求&#xff0c;必须按照需求一步一步去做好规划&#xff0c;在项目的运行过程中严格的产出一些文档 …

gdal求矢量图形的形心

gdal求矢量图形的形心 #include "gdal_priv.h" #include "ogrsf_frmts.h"int main() {OGRRegisterAll();OGRPolygon* square_1 new OGRPolygon();OGRLinearRing* ring_1 new OGRLinearRing();// 添加 square_1 的点ring_1->addPoint(0, 0);ring_1-&g…

js--15----闭包是什么?说说闭包的使用场景

1、闭包是什么&#xff1f; 一个函数和对其周围状态&#xff08;词法环境&#xff09;的引用绑定在一起&#xff08;或者说函数被引用包围&#xff09;&#xff0c;这样的组合就是是闭包&#xff08;closure&#xff09;&#xff0c;也就是说&#xff0c;闭包让你在一个内层函数…

win环境安装SuperMap iserver和配置许可

SuperMap iServer是我国北京超图公司研发的基于跨平台GIS内核的云GIS应用服务器产品&#xff0c;通过服务的方式&#xff0c;面向网络客户端提供与专业GIS桌面产品相同功能的GIS服务&#xff0c;能够管理、发布多源服务&#xff0c;包括REST服务、OGC服务等。 SuperMap iserve…

23062C++QT day2

封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1a;void…

AI 时代的向量数据库、关系型数据库与 Serverless 技术丨TiDB Hackathon 2023 随想

TiDB Hackathon 2023 刚刚结束&#xff0c;我仔细地审阅了所有的项目。 在并未强调项目必须使用人工智能&#xff08;AI&#xff09;相关技术的情况下&#xff0c;引人注目的项目几乎一致地都使用了 AI 来构建自己的应用。 大规模语言模型&#xff08;LLM&#xff09;的问世使得…

监听Helm release资源

监听Helm release资源 基于helm做部署管理工具时&#xff0c;可能想要管理用户已有环境&#xff0c;这时需要将已有环境中的release信息上报到业务系统中。当用户在环境中部署新的release时&#xff0c;也需要实时监听并上报回来。下面将讲解如何去监听release资源 helm rele…

天津web前端培训班 前端是否适合零基础学?

随着HTML 5和ECMAScript 6的正式发布&#xff0c;大量的前端业务逻辑&#xff0c;极大地增加了前端的代码量&#xff0c;前端代码的模块化、按需加载和依赖管理势在必行&#xff0c;因此Web前端越来越被人们重视。 Web前端的就业前景 Web前端开发工程师薪资持续走高&#xff…

LoadRunner参数化详解

安装打开loadrunner时&#xff0c;发现虽然自己的思想还在&#xff0c;但已经非常生疏了&#xff0c;好多设置都找不到了具体的位置。下面说参数化参数化是性能测试中时最常用的一种技巧吧&#xff01;这里需要说明的是&#xff0c;不是只有loadrunner才可以设置参数化&#xf…

《热题100》字符串、双指针、贪心算法篇

思路&#xff1a;对于输入的的字符串&#xff0c;只有三种可能&#xff0c;ipv4,ipv6,和neither ipv4:四位&#xff0c;十进制&#xff0c;无前导0&#xff0c;小于256 ipv6:八位&#xff0c;十六进制&#xff0c;无多余0&#xff08;00情况不允许&#xff09;&#xff0c;不…

JAVA设计模式第十讲:SPI - 业务差异解决方案

JAVA设计模式第十讲&#xff1a;SPI - 业务差异解决方案 我们需要在不修改源代码的情况下&#xff0c;动态为程序提供一系列额外的特性。首先想到的是Spring的AOP技术来构建应用插件&#xff0c;但是在Java自带的插件中&#xff0c;就有完整的实现。SPI&#xff08;Service Pro…

OJ练习第165题——修车的最少时间

修车的最少时间 力扣链接&#xff1a;2594. 修车的最少时间 题目描述 给你一个整数数组 ranks &#xff0c;表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你一个整数 cars &#xff0c;表示总…

pyspark 系统找不到指定的路径; \Java\jdk1.8.0_172\bin\java

使用用具PyCharm 2023.2.1 1&#xff1a;pyspark 系统找不到指定的路径&#xff0c; Java not found and JAVA_HOME environment variable is not set. Install Java and set JAVA_HOME to point to the Java installation directory. 解决方法&#xff1a;配置正确环境变量…